我项目中主要是对消耗品进行内购,非消耗品没进行测试,对iOS商店后台的构建,我这边不说了,下面主要是对Unity怎么实现iOS原生内购功能进行讲解。
一 在Xcode中编写原生内购代码给Unity调用
1 IAPManager.h
#import#import @interface IAPManager : NSObject { 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>18) & 0x3f]; output[index + 1] = table[(value>>12) & 0x3f]; output[index + 2] = (i+1) >6) & 0x3f] : '='; output[index + 3] = (i+2) >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@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 ListproductInfo = new List (); private static IOSIAPMgr _instance; public static IOSIAPMgr Instance{ get{ // if(_instance==null) // { // GameObject go=new GameObject("IOSIAPMgr"); // DontDestroyOnLoad (go); // _instance=go.AddComponent (); // } 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"); } } /// /// 购买商品成功的回调 /// /// String. public void BuyProcuctSucessCallBack(string str) { } ////// 购买商品失败调回调 /// /// String. public void BuyProcuctFailedCallBack(string str) { } }
三 注意事项
1 iOS 11的系统对沙盒测试有bug,第一次会让你输入沙盒账号和密码,第二次也会再让你输入一次,最后购买的时候,直接购买失败(仅对沙盒测试,正式上线的时候没有这个问题)。2 iOS内购的时候非消耗品的交易一直会存在账单里面而且不会被清除。
3 使用沙盒账号测试的时候要退出登录苹果手机的账号,不然也对导致购买失败。
4 在内购商品之前也调用请求商品信息。