Unity调用iOS原生内购
我项目中主要是对消耗品进行内购,非消耗品没进行测试,对iOS商店后台的构建,我这边不说了,下面主要是对Unity怎么实现iOS原生内购功能进行讲解。
一 在Xcode中编写原生内购代码给Unity调用
1 IAPManager.h
#import <Foundation/Foundation.h> #import <StoreKit/StoreKit.h> @interface IAPManager : NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>{ SKProduct *proUpgradeProduct; SKProductsRequest *productsRequest; NSString *productIndentify; } -(void)attachObserver; -(BOOL)CanMakePayment; -(void)requestProductData:(NSString *)productIdentifiers; -(void)buyRequest:(NSString *)productIdentifier;//保存Unity传递的商品ID @end
2 IAPManager.m
#import "IAPManager.h" @implementation IAPManager -(void) attachObserver{ NSLog(@"AttachObserver"); [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; } -(BOOL) CanMakePayment{ return [SKPaymentQueue canMakePayments]; } -(void) requestProductData:(NSString *)productIdentifiers{ NSArray *idArray = [productIdentifiers componentsSeparatedByString:@"\t"]; NSSet *idSet = [NSSet setWithArray:idArray]; [self sendRequest:idSet]; } -(void)sendRequest:(NSSet *)idSet{ SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:idSet]; request.delegate = self; [request start]; } -(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{ NSLog(@"-----------收到产品反馈信息--------------"); NSArray *products = response.products; NSLog(@"产品Product ID:%@",response.invalidProductIdentifiers); NSLog(@"产品付费数量: %d", (int)[products count]); // populate UI for (SKProduct *p in products) { NSLog(@"product info"); NSLog(@"SKProduct 描述信息%@", [products description]); NSLog(@"产品标题 %@" , p.localizedTitle); NSLog(@"产品描述信息: %@" , p.localizedDescription); NSLog(@"价格: %@" , p.price); NSLog(@"Product id: %@" , p.productIdentifier); UnitySendMessage("IOSIAPMgr", "ShowProductList", [[self productInfo:p] UTF8String]); } for(NSString *invalidProductId in response.invalidProductIdentifiers){ NSLog(@"Invalid product id:%@",invalidProductId); } // [request autorelease]; } -(void)buyRequest:(NSString *)productIdentifier{ // NSArray* transactions=[SKPaymentQueue defaultQueue].transactions; // if(transactions.count>0) // { // for(SKPaymentTransaction *tran in transactions) // { // NSLog(@"**************************************************************%@",tran.transactionState); //检查是否有完成的交易 // SKPaymentTransaction* transaction =[transactions firstObject]; // if(tran.transactionState==SKPaymentTransactionStatePurchasing) // { // NSLog(@"----------------------%@",tran.transactionState); // [[SKPaymentQueue defaultQueue] finishTransaction:tran]; // return; // } //} // } productIndentify=productIdentifier; SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier]; [[SKPaymentQueue defaultQueue] addPayment:payment]; } -(NSString *)productInfo:(SKProduct *)product{ NSArray *info = [NSArray arrayWithObjects:product.localizedTitle,product.localizedDescription,product.price,product.productIdentifier, nil]; return [info componentsJoinedByString:@"\t"]; } -(NSString *)transactionInfo:(SKPaymentTransaction *)transaction{ return [self encode:(uint8_t *)transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length]; //return [[NSString alloc] initWithData:transaction.transactionReceipt encoding:NSASCIIStringEncoding]; } -(NSString *)encode:(const uint8_t *)input length:(NSInteger) length{ static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; NSMutableData *data = [NSMutableData dataWithLength:((length+2)/3)*4]; uint8_t *output = (uint8_t *)data.mutableBytes; for(NSInteger i=0; i<length; i+=3){ NSInteger value = 0; for (NSInteger j= i; j<(i+3); j++) { value<<=8; if(j<length){ value |=(0xff & input[j]); } } NSInteger index = (i/3)*4; output[index + 0] = table[(value>>18) & 0x3f]; output[index + 1] = table[(value>>12) & 0x3f]; output[index + 2] = (i+1)<length ? table[(value>>6) & 0x3f] : '='; output[index + 3] = (i+2)<length ? table[(value>>0) & 0x3f] : '='; } return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; } -(void) provideContent:(SKPaymentTransaction *)transaction{ UnitySendMessage("IOSIAPMgr", "ProvideContent", [[self transactionInfo:transaction] UTF8String]); } //沙盒测试环境验证 #define SANDBOX @"https://sandbox.itunes.apple.com/verifyReceipt" //正式环境验证 #define AppStore @"https://buy.itunes.apple.com/verifyReceipt" /** * 验证购买,避免越狱软件模拟苹果请求达到非法购买问题 * */ -(void)verifyPurchaseWithPaymentTransaction{ //从沙盒中获取交易凭证并且拼接成请求体数据 NSURL *receiptUrl=[[NSBundle mainBundle] appStoreReceiptURL]; NSData *receiptData=[NSData dataWithContentsOfURL:receiptUrl]; NSString *receiptString=[receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//转化为base64字符串 NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", receiptString];//拼接请求数据 NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding]; //测试的时候填写沙盒路径,上APPStore的时候填写正式环境路径 NSURL *url=[NSURL URLWithString:SANDBOX]; NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url]; requestM.HTTPBody=bodyData; requestM.HTTPMethod=@"POST"; //创建连接并发送同步请求 NSError *error=nil; NSData *responseData=[NSURLConnection sendSynchronousRequest:requestM returningResponse:nil error:&error]; if (error) { NSLog(@"验证购买过程中发生错误,错误信息:%@",error.localizedDescription); return; } NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil]; NSLog(@"%@",dic); if([dic[@"status"] intValue]==0){ NSLog(@"购买成功!"); NSDictionary *dicReceipt= dic[@"receipt"]; NSLog(@"--------------%@",dicReceipt); //NSDictionary *dicInApp=[dicReceipt[@"in_app"] firstObject]; for(NSDictionary *tmp in dicReceipt[@"in_app"]) { // NSLog(@"+++++++++++%@",dicInApp); NSString *productIdentifier= tmp[@"product_id"];//读取产品标识 NSLog(@"+++++++++++++++++++++++++++++++++++++%@",productIdentifier); NSLog(@"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx%@",productIndentify); //如果是消耗品则记录购买数量,非消耗品则记录是否购买过 NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; if ([productIdentifier isEqualToString:productIndentify]) { NSInteger purchasedCount=[defaults integerForKey:productIdentifier];//已购买数量 [[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier]; UnitySendMessage("IOSIAPMgr", "BuyProcuctSucessCallBack",productIdentifier.UTF8String); break; }else{ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:productIdentifier]; UnitySendMessage("IOSIAPMgr", "BuyProcuctSucessCallBack",productIdentifier.UTF8String); break; } } }else{ NSLog(@"购买失败,未通过验证!"); } } //监听购买结果 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{ for(SKPaymentTransaction *tran in transaction){ switch (tran.transactionState) { case SKPaymentTransactionStatePurchased:{ NSLog(@"交易完成"); // 发送到苹果服务器验证凭证 [self verifyPurchaseWithPaymentTransaction]; [[SKPaymentQueue defaultQueue] finishTransaction:tran]; } break; case SKPaymentTransactionStatePurchasing: NSLog(@"商品添加进列表"); break; case SKPaymentTransactionStateRestored:{ NSLog(@"已经购买过商品"); [[SKPaymentQueue defaultQueue] finishTransaction:tran]; } break; case SKPaymentTransactionStateFailed:{ NSLog(@"交易失败"); UIAlertView *mBoxView = [[UIAlertView alloc] initWithTitle:@"交易提示" message:@"交易失败" delegate:nil cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil]; [mBoxView show]; [[SKPaymentQueue defaultQueue] finishTransaction:tran]; } break; default: { [[SKPaymentQueue defaultQueue] finishTransaction:tran]; } break; } } } -(void) completeTransaction:(SKPaymentTransaction *)transaction{ NSLog(@"Comblete transaction : %@",transaction.transactionIdentifier); [self provideContent:transaction]; [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } -(void) failedTransaction:(SKPaymentTransaction *)transaction{ NSLog(@"Failed transaction : %@",transaction.transactionIdentifier); if (transaction.error.code != SKErrorPaymentCancelled) { NSLog(@"!Cancelled"); } [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } -(void) restoreTransaction:(SKPaymentTransaction *)transaction{ NSLog(@"Restore transaction : %@",transaction.transactionIdentifier); [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } @end
3 IAPInterface.h
#import <Foundation/Foundation.h>
@interface IAPInterface : NSObject
@end
4 IAPInterface.mm Untiy主要是调用这个接口去调用IAPMananger
#import "IAPInterface.h" #import "IAPManager.h" @implementation IAPInterface #if defined (__cplusplus) extern "C" { #endif IAPManager *iapManager = nil; //初始化商品信息 void InitIAPManager(){ iapManager = [[IAPManager alloc] init]; [iapManager attachObserver]; } //判断是否可以购买 bool IsProductAvailable(){ return [iapManager CanMakePayment]; } //获取商品信息 void RequstProductInfo(char *p){ NSString *list = [NSString stringWithUTF8String:p]; NSLog(@"商品列表:%@",list); [iapManager requestProductData:list]; } //购买商品 void BuyProduct(char *p){ [iapManager buyRequest:[NSString stringWithUTF8String:p]]; } #if defined (__cplusplus) } #endif @end
二 Unity调用Xcode主要是通过 [DllImport("__Internal")]去调用,代码如下:
using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; public class IOSIAPMgr : MonoBehaviour { public List<string> productInfo = new List<string>(); private static IOSIAPMgr _instance; public static IOSIAPMgr Instance{ get{ // if(_instance==null) // { // GameObject go=new GameObject("IOSIAPMgr"); // DontDestroyOnLoad (go); // _instance=go.AddComponent<IOSIAPMgr>(); // } return _instance; } } private string _goodsID = ""; void Awake() { _instance = this; Init (); StartCoroutine (InitProductInfo ()); DontDestroyOnLoad (this.gameObject); } private IEnumerator InitProductInfo() { yield return new WaitForSeconds(5f); IOSIAPMgr.Instance.RequstALLProductInfo(); } [DllImport("__Internal")] private static extern void InitIAPManager();//初始化 [DllImport("__Internal")] private static extern bool IsProductAvailable();//判断是否可以购买 [DllImport("__Internal")] private static extern void RequstProductInfo(string s);//获取商品信息 [DllImport("__Internal")] private static extern void BuyProduct(string s);//购买商品 //测试从xcode接收到的字符串 void IOSToU(string s) { Debug.Log("[MsgFrom ios]"+s); } //获取product列表 void ShowProductList(string s){ productInfo.Add(s); } //获取商品回执 void ProvideContent(string s) { Debug.Log("[MsgFrom ios]proivideContent : "+s); } public void Init () { Debug.Log ("初始化InitIAPManager"); InitIAPManager(); } public bool IsProductVailable() { return IsProductAvailable(); } public void RequstALLProductInfo() { if(IsProductAvailable()) { RequstProductInfo("com.smallMBag"); RequstProductInfo("com.MidMBag"); RequstProductInfo("com.BigMBag"); RequstProductInfo("com.SpecialM"); RequstProductInfo("com.SmallFBag"); RequstProductInfo("com.Weapon"); //RequstProductInfo("com.SingleFuHuoSHi"); RequstProductInfo("com.BigFBag"); } } /// <summary> /// 购买商品成功的回调 /// </summary> /// <param name="str">String.</param> public void BuyProcuctSucessCallBack(string str) { } /// <summary> /// 购买商品失败调回调 /// </summary> /// <param name="str">String.</param> public void BuyProcuctFailedCallBack(string str) { } }
三 注意事项
1 iOS 11的系统对沙盒测试有bug,第一次会让你输入沙盒账号和密码,第二次也会再让你输入一次,最后购买的时候,直接购买失败(仅对沙盒测试,正式上线的时候没有这个问题)。
2 iOS内购的时候非消耗品的交易一直会存在账单里面而且不会被清除。
3 使用沙盒账号测试的时候要退出登录苹果手机的账号,不然也对导致购买失败。
4 在内购商品之前也调用请求商品信息。