cocos creator 发布IOS版本(六)内购

您所在的位置:网站首页 搞笑日记笑死人400字图片高清 cocos creator 发布IOS版本(六)内购

cocos creator 发布IOS版本(六)内购

2024-05-27 15:31| 来源: 网络整理| 查看: 265

cocos creator 版本3.3

参考文档:

iOS 内购(In-App Purchase)总结 | 笑忘书店

iOS的应用内支付In-app purchase的开发 - 胡东东博客

App内购项目的App Store推广 - 胡东东博客

Unity调用iOS原生内购 - 围墙外的世界 - 博客园

以非消耗型为例:非消耗型和订阅类型,存在app升级或者重装后恢复内购的需求

1、配置产品ID

在对应项目中找到-》管理点击+

非消耗型可以调用恢复接口恢复: 

消耗品(Consumable products):比如游戏内金币等。非消耗品(Non-consumable products):简单来说就是一次购买,终身可用(用户可随时从App Store restore)。自动更新订阅品(Auto-renewable subscriptions):和不可消耗品的不同点是有失效时间。比如一整年的付费周刊。在这种模式下,开发者定期投递内容,用户在订阅期内随时可以访问这些内容。订阅快要过期时,系统将自动更新订阅(如果用户同意)。非自动更新订阅品(Non-renewable subscriptions):一般使用场景是从用户从IAP购买后,购买信息存放在自己的开发者服务器上。失效日期/可用是由开发者服务器自行控制的,而非由App Store控制,这一点与自动更新订阅品有差异。免费订阅品(Free subscriptions):在Newsstand中放置免费订阅的一种方式。免费订阅永不过期。只能用于Newsstand-enabled apps。

 

 填写好相对于的所有信息,其中产品id,就是传递的接口

 正式上线完的状态是已批准,测试状态是:审核或者准备中,元数据丢失状态是内容没填全,可能会影响测试

2、Xcode代码调用(如何发布ios可参考前面文章)

a、添加内购的Lib库:StoreKit.framework

 b、Xcode接口,供cocos调用及回掉cocos

IAPInterface.h

#import NS_ASSUME_NONNULL_BEGIN @interface IAPInterface : NSObject + (instancetype)sharedSingleton; + (void) InitIAPManager; + (bool) IsProductAvailable; + (void) RequstProductInfo :(NSString *)p; + (void) BuyProduct :(NSString *)p; + (void) RestoreBuyProduct :(NSString *)p; - (void) BuyProcuctSucessCallBack:(NSString *)str; - (void) BuyProcuctFailedCallBack:(NSString *)str; - (void) ShowProductList:(NSString *)str; - (void) RestoreBuyProductSucessCallBack:(NSString *)str; - (void) test1; @end NS_ASSUME_NONNULL_END

IAPInterface.mm

#import "IAPInterface.h" #import "IAPManager.h" #import "cocos/bindings/jswrapper/SeApi.h" @interface IAPInterface() @property IAPManager *iapManager; @end @implementation IAPInterface + (instancetype)sharedSingleton { static IAPInterface *_sharedSingleton = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //不能再使用alloc方法 //因为已经重写了allocWithZone方法,所以这里要调用父类的分配空间的方法 if (_sharedSingleton==nil) { _sharedSingleton = [[super allocWithZone:NULL] init]; } //[_sharedSingleton initDelegate]; //[_sharedSingleton checkTransaction]; }); return _sharedSingleton; } /** //初始化商品信息 */ + (void) InitIAPManager{ NSLog(@"InitIAPManagerUser"); [[IAPInterface sharedSingleton] InitIAPManagerUser]; } - (void) InitIAPManagerUser{ self.iapManager=[[IAPManager alloc] init]; [self.iapManager attachObserver]; } //判断用户是否可以付费 + (bool) IsProductAvailable{ return [[IAPInterface sharedSingleton] IsProductAvailableUser]; } - (bool) IsProductAvailableUser{ return [self.iapManager CanMakePayment]; } //获取商品信息 + (void) RequstProductInfo:(NSString *)p{ NSLog(@"商品列表:%@",p); [[IAPInterface sharedSingleton] RequstProductInfoUser:p]; } - (void) RequstProductInfoUser:(NSString *)p{ [self.iapManager requestProductData:p]; } //购买商品 + (void) BuyProduct:(NSString *)p{ NSLog(@"购买:%@",p); [[IAPInterface sharedSingleton] BuyProductUser:p]; //[IAPInterface RestoreBuyProduct:p]; } - (void) BuyProductUser:(NSString *)p{ [self.iapManager buyRequest:p]; } //恢复购买商品 + (void) RestoreBuyProduct:(NSString *)p{ NSLog(@"恢复购买:%@",p); [[IAPInterface sharedSingleton] RestoreBuyProductUser:p]; } - (void) RestoreBuyProductUser:(NSString *)p{ [self.iapManager Restore:p]; } /// /// 购买商品成功的回调 /// /// String. - (void) BuyProcuctSucessCallBack:(NSString *)str { NSLog(@"-----ObjectToJs----BuyProcuctSucessCallBack-------"); NSString *bodyString = [NSString stringWithFormat:@"window.PlatformApi.BuyProcuctSucessCallBack(%@)", str];//拼接请求数据 NSLog(bodyString); std::string param1=[bodyString UTF8String]; //通过evalString执行上面的代码 se::ScriptEngine::getInstance()->evalString(param1.c_str()); } /// /// 购买商品失败的回调 /// /// String. - (void) BuyProcuctFailedCallBack:(NSString *)str { NSLog(@"-----ObjectToJs----BuyProcuctFailedCallBack-------"); NSString *bodyString = [NSString stringWithFormat:@"window.PlatformApi.BuyProcuctFailedCallBack(%@)", str];//拼接请求数据 NSLog(bodyString); std::string param1=[bodyString UTF8String]; //通过evalString执行上面的代码 se::ScriptEngine::getInstance()->evalString(param1.c_str()); } /// /// 产品反馈信息回掉- /// /// String. - (void) ShowProductList:(NSString *)str { NSLog(@"-----ObjectToJs----ShowProductList-------"); NSString *bodyString = [NSString stringWithFormat:@"window.PlatformApi.ShowProductList(%@)", str];//拼接请求数据 std::string param1=[bodyString UTF8String]; //通过evalString执行上面的代码 se::ScriptEngine::getInstance()->evalString(param1.c_str()); } /// /// 恢复购买的商品完成的回调 /// /// String. - (void) RestoreBuyProductSucessCallBack:(NSString *)strId { NSLog(@"-----ObjectToJs----RestoreBuyProductSucessCallBack-------"); NSString *bodyString = [NSString stringWithFormat:@"window.PlatformApi.RestoreBuyProductSucessCallBack(%@)", strId]; //NSLog(bodyString); std::string param1=[bodyString UTF8String]; //通过evalString执行上面的代码 se::ScriptEngine::getInstance()->evalString(param1.c_str()); } - (void) test1 { NSLog(@"-----ObjectToJs----test1-------"); NSString *bodyString = [NSString stringWithFormat:@"window.PlatformApi.showRewardVideoADCallback()"];//拼接请求数据 std::string param1=[bodyString UTF8String]; //通过evalString执行上面的代码 se::ScriptEngine::getInstance()->evalString(param1.c_str()); } @end

c、具体调用内购sdk方法

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;//购买 -(void)Restore:(NSString *)productIdentifier; @end

IAPManager.m

#import "IAPManager.h" #import "IAPInterface.h" @implementation IAPManager //初始化 -(void) attachObserver{ NSLog(@"AttachObserver"); [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; } //判断用户是否可以付费 -(BOOL) CanMakePayment{ return [SKPaymentQueue canMakePayments]; } //已内购的内容,恢复购买 -(void) Restore:(NSString *)productIdentifier{ NSLog(@"Restore"); [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; } //请求商品数据 -(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]); NSString *pps= [self productInfo:p]; [[IAPInterface sharedSingleton] ShowProductList:pps]; } 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; i6) & 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]; [[IAPInterface sharedSingleton] BuyProcuctSucessCallBack:productIdentifier]; break; }else{ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:productIdentifier]; [[IAPInterface sharedSingleton] BuyProcuctSucessCallBack:productIdentifier]; break; } } }else{ NSLog(@"购买失败,未通过验证!"); [[IAPInterface sharedSingleton] BuyProcuctFailedCallBack:productIndentify]; } } //从App Store支付 - (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product { NSLog(@"从App Store支付"); //bool _is = paySuccess; //根据product.productIdentifier去判断是否去直接弹出购买弹窗 if ([product.productIdentifier isEqualToString:@"1000000000"]) { return true; } return false; } //监听购买结果 - (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:@"Transaction reminder" message:@"Transaction failed" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil]; [mBoxView show]; [[SKPaymentQueue defaultQueue] finishTransaction:tran]; [[IAPInterface sharedSingleton] BuyProcuctFailedCallBack:productIndentify]; //[[IAPInterface sharedSingleton] test1]; } 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]; } //已内购的内容,恢复购买时回掉 -(void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { NSLog (@"received restored transactions: %i", queue.transactions.count); //没有购买过 if (queue.transactions.count==0) { } //购买过 for (SKPaymentTransaction *transaction in queue.transactions) { NSString *productID = transaction.payment.productIdentifier; NSLog(@"Restore transaction : %@",transaction.payment.productIdentifier); [[IAPInterface sharedSingleton] RestoreBuyProductSucessCallBack:productID]; } UIAlertView *mBoxView = [[UIAlertView alloc] initWithTitle:@"Transaction reminder" message:@"Restore purchase completed" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil]; [mBoxView show]; } @end

 d、初始化内购

e、cocos调用

PlatformApi.ts

import { _decorator, Component, sys, game, director } from 'cc'; import GameManager from './GameManager'; const { ccclass, property } = _decorator; @ccclass('PlatformApi') export class PlatformApi extends Component { private static _instance: PlatformApi | null = null; public static get Instance() { if (!PlatformApi._instance) { window["PlatformApi"] = new PlatformApi(); PlatformApi._instance = new PlatformApi() } // return PlatformApi._instance || (PlatformApi._instance = new PlatformApi()); return PlatformApi._instance; } //内购请求(目前测试:1000000000) public BuyProduct(eventId: string) { console.log("BuyProduct " + eventId); if (sys.platform == sys.Platform.ANDROID) { } else if (sys.platform == sys.Platform.IOS) { jsb.reflection.callStaticMethod("IAPInterface", "BuyProduct:", eventId); } } //内购商品信息请求 public RequstProductInfo(eventId: string) { console.log("RequstProductInfo " + eventId); if (sys.platform == sys.Platform.ANDROID) { } else if (sys.platform == sys.Platform.IOS) { jsb.reflection.callStaticMethod("IAPInterface", "RequstProductInfo:", eventId); } } //app升级或者重装后,对已内购信息恢复请求 public RestoreBuyProduct(eventId: string) { console.log("RestoreBuyProduct " + eventId); if (sys.platform == sys.Platform.ANDROID) { } else if (sys.platform == sys.Platform.IOS) { jsb.reflection.callStaticMethod("IAPInterface", "RestoreBuyProduct:", eventId); } } //内购成功xcode回掉 public BuyProcuctSucessCallBack(eventId: string) { console.log("BuyProcuctSucessCallBack " + eventId); } //内购失败xcode回掉 public BuyProcuctFailedCallBack(eventId: string) { console.log("BuyProcuctFailedCallBack " + eventId); } //商品信息xcode回掉 public ShowProductList(eventId: string) { console.log("ShowProductList " + eventId); } //对已购买的商品进行恢复xcode回掉 public RestoreBuyProductSucessCallBack(eventId: string) { console.log("RestoreBuyProductSucessCallBack " + eventId); if (eventId=="1000000000") { //恢复已购买的商品 } } }

 F:有两个接口需要特别注意

1、非消耗品,需要恢复接口,不然可能会拒审核,单机项目,app升级或者重装,对已购买的商品,进行恢复

参考:

ios内购——因缺少“恢复购买”功能被拒解决方案_专治各种审核难题的博客-CSDN博客_恢复购买

//已内购的内容,恢复购买 -(void) Restore:(NSString *)productIdentifier{ NSLog(@"Restore"); [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; } //已内购的内容,恢复购买时回掉 -(void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { NSLog (@"received restored transactions: %i", queue.transactions.count); //没有购买过 if (queue.transactions.count==0) { } //购买过 for (SKPaymentTransaction *transaction in queue.transactions) { NSString *productID = transaction.payment.productIdentifier; NSLog(@"Restore transaction : %@",transaction.payment.productIdentifier); [[IAPInterface sharedSingleton] RestoreBuyProductSucessCallBack:productID]; } UIAlertView *mBoxView = [[UIAlertView alloc] initWithTitle:@"Transaction reminder" message:@"Restore purchase completed" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil]; [mBoxView show]; }

 2、appstore推广时,需要重载的接口,否则无法推广

参考:App内购项目的App Store推广 - 胡东东博客

 

//从App Store支付 - (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product { NSLog(@"从App Store支付"); //bool _is = paySuccess; //推广的id对比 //根据product.productIdentifier去判断是否去直接弹出购买弹窗, if ([product.productIdentifier isEqualToString:@"1000000000"]) { return true; } return false; }  3、测试

内购测试

内购也是需要测试的,但是内购涉及到钱,所以苹果为内购测试提供了 沙箱测试账号 的功能,Apple Pay 推出之后 沙箱测试账号 也可以用于 Apple Pay 支付的测试,沙箱测试账号 简单理解就是:只能用于内购和 Apple Pay 测试功能的 Apple ID,它并不是真实的 Apple ID,下面看如何创建 配置沙箱测试账号

 测试结果类似下面截图:

 App store推广测试

修改下面的链接地址,然后在手机safari浏览器打开,就可以测试从App Store发起购买了,进程序就会自动打开购买流程,其中链接中的bundleId修改为你自己应用的bundleId,比如com.hudongdong.blog,productId修改为你创建的商品的id

itms-services://?action=purchaseIntent&bundleId=bundleId&productIdentifier=productId



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3