• 齐桓公ios内购对接之源码篇详细


    在开篇之前首先提供一个调试工具,对ios开发人员来说微不足道,但对新手来说可谓如获至宝,mac工具,图标如下控制台

    打开之后

    有时候我们的xcode并不能抛出这些错误,只有通过控制台连接手机才可才看所有相关的信息日志,报错信息等

    对接ios内购

    前提条件:ios内购有一个库storeKit.framework,这个库封装好了ios内购相关的代码,我们只需要调用即可,一般情况下工程会自动添加这个库,同时我们要保证自己的app开启了内购,在开发者平台的appid里可以开启,现在都是默认开启了

    那么第一步我们要先了解ios内购的流程,看过很多教程,流程图都一大堆,但桓公只做最简单的流水线流程

    通过productid向苹果服务器请求商品 --- 拿到之后即掉支付进入支付队列 --- 支付完成后回调支付状态 ----  支付完成之后会有一个receipt字段是一个加密字符串  --- 把这个receipt给到自己的服务端 ----  自己的服务端对receipt进行解码并向苹果服务器验证  ----  验证成功  通知客户端发放奖励   

    基本流程如此,就相当于  去书店(appstore)买书 --- 买黄帝内经素问(productid) ---  书店查有没有(请求商品)  ---- 有素问(查询成功) ----  支付成功店里银行账户到账了(苹果支付了)  给了一个发票 ----  拿着发票去服务人员那领书素问 (向服务器请求receipt 并验证成功 发放奖励) --- 完成

    流程清楚了,接下来就好办了,需要一步步来,不要越步  ,做任何事只要你知道了流程就如同你预知了未来一样,只要按照步骤流程走,预言就一定会实现

    此以laya为例,由于laya生产的ios项目是需要H5这边来掉的,虽然laya自带了一个conchMarket,但这里不用他的,因为有坑,这里会自己封装JSBridge,JSBridge是客户端与H5的通信桥梁,皆以字符串的形式完成掉用和回调

    在jsbridge里添加方法

    +(void)callBackToJs:(NSString*)strFunc param:(NSString*)param
    {
        [[conchRuntime GetIOSConchRuntime] callbackToJSWithClassName:NSStringFromClass(self.class) methodName:strFunc ret:param];
    }
    +(void)callRunJs:(NSString*)strCode
    {
        [[conchRuntime GetIOSConchRuntime] runJS:strCode];
    }

    callBackToJs是回调js的方法,其中的methodName既是js端掉用的方法名,注意这里的回调是异步的

    向jsbridge添加内购及回调的方法

    //苹果内购
    +(void)doIosZF:(NSString*)jsonParam
    {
        NSData* data = [jsonParam dataUsingEncoding:NSUTF8StringEncoding];
        NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
        if (json == nil) return [self iosPayCallBack:-1 reason:@"订单信息错误!" receipt:@"" orderid:@""];
        NSString *productId = json[@"productID"];
        NSString *orderid = json[@"orderID"];
        if (productId == nil || orderid == nil) return [self iosPayCallBack:-1 reason:@"订单信息缺失!" receipt:@"" orderid:@""];
        [[IAPManager getInstance] buy:productId orderid:orderid];
    }
    
    +(void)iosPayCallBack:(NSInteger)p_iRet reason:(NSString *)p_pReason receipt:(NSString *)p_pReceipt orderid:(NSString *)p_pOrderid
    {
        NSString* pJsonString = @"{  \"act\" : \"0\", \"tip\" : \"error\", \"order\" : \"error\", \"receipt\" : \"error\"}";
        NSString* sNum = [NSString stringWithFormat:@"%ld",p_iRet];
        NSDictionary* pDictionary = [NSDictionary dictionaryWithObjectsAndKeys:sNum,@"act",p_pReason?p_pReason:@"error",@"tip",p_pOrderid?p_pOrderid:@"error",@"order",p_pReceipt?p_pReceipt:@"error",@"receipt",nil];
        NSError* pError = nil;
        NSData* pJsonData = [NSJSONSerialization dataWithJSONObject:pDictionary options:NSJSONWritingPrettyPrinted error:&pError];
        if( !pError )
        {
            pJsonString = [[NSString alloc] initWithData:pJsonData encoding:NSUTF8StringEncoding];
        }
        [self callBackToJs:@"doIosZF:" param:pJsonString];
    }

    js端则如下

    private static doIosZF(productId, cb_completed){
            let func = (res)=>{
                let code = res["act"];
                log("---ios iap back act:", code, "data:", res);
                if (code == 0) {
                    let param =  {
                        token:userData.token,
                        uid:userData.uid,
                        receipt:res.receipt,
                        product_id:productId
                    };
                    WebAPI.post('shop/applePay',param,(obj)=>{
                        let son = JSON.parse(obj);
                        dealPayEnd(son.code, cb_completed);
                    })
                }
                else{
                    app.showAlert('支付失败');
                }
            }
            let params = {
                "orderID":`OrderID_${new Date().getTime()}_${productId}`,
                "productID":this.marketCommedities[productId],
            }
            let json = JSON.stringify(params);
            log('----reqest buy ios ',json)
            Native.callWithBack(func,'doIosZF',json);
        }

    Native.ts 初始化jsbridge及调用如下

    public static initBridge() {
            if (this.isAndroidApp()) {
                this.bridge = Laya.PlatformClass.createClass("app.JSBridge");
            }
            if (this.isIOSApp()) {
                this.bridge = Laya.PlatformClass.createClass("JSBridge")
            }
        }
    
    public static callWithBack(callback: Function, methodName: string, ...args: any[]): void {
            if (!this.bridge) { this.initBridge(); }
            if (this.isIOSApp() && args.length == 1) {
                if (args.length == 1) {
                    methodName += ":";
                } else {
                    log("ios call native error!!!");
                }
            }
            this.bridge.callWithBack(function (msg) { callback(JsonParseSafe(msg)); }, methodName, ...args);
        }

    注意:

    1、ios的方法要加 : 这个字符串, oc端回调的时候亦是如此,如下

    2、这里的receipt的校验是在js端,非oc端,oc端仅做内购功能及回调

    OK 这里我们H5端js调用了oc端的   doIosZF  方法,接下来就是oc端去实现

    IAPManager的逻辑

    桓公本对此一窍不通,如何对接?我们看到JSBridge里的doIosZF方法里掉了IAPManager的buy方法,并传了productid和orderid两个参数,那么我们来看下IAPManager的buy方法

    -(void)buy:(NSString*)productId orderid:(NSString*) orderid
    {
        bool canBuy = [SKPaymentQueue canMakePayments];
        
        if (canBuy) {
            NSLog(@"允许程序内付费购买");
            //直接购买
            //SKPayment *payment = [SKPayment paymentWithProductIdentifier:productId];
            
            SKMutablePayment *payment = [SKMutablePayment paymentWithProductIdentifier:productId];
            payment.applicationUsername = orderid;
            
            NSLog(@"---------发送购买请求------------");
            [[SKPaymentQueue defaultQueue] addPayment:payment];
        }
        else
        {
            [self.delegate onCallBack:-1 reason:@"You can‘t purchase in app store(没允许应用程序内购买)" retry:false receipt:@"" orderid:@""];
        }
    }

    这里自己去研究,oc代码:函数的执行皆是[],其实也很容易懂

    IAPManager首先要初始化,初始化方法,这个初始化方法其实在MarketAppStore里初始化了

    - (id)initWithGameID:(NSString*)gameID andDelegate:(id<JCIapProcessCtrlDelegate>)delegate{
        if ((self = [super init])) {
            NSAssert(gameID != nil,@"[IAP] gameID can not be nil");
            NSAssert(gameID.length <= __MAX_GAME_ID_SIZE,@"gameID is too long");
            self.gameAppID = [NSString stringWithString:gameID];
            NSAssert(delegate != nil, @"[IAP] delegate can not be nil");
            self.delegate = delegate;
            self.strPlateform = [NSString stringWithFormat:@"%@,%@,%@", [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemName],[[UIDevice currentDevice] systemVersion]];
            NSLog(@"[IAP]plateform info: %@\n", self.strPlateform);
            [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        }
        return self;
    }

    注意 这里不使用MarketAppStore作为回调,而是使用另外封装的SDKManager作为回调对象代理,因此这里初始化的代码就变成了

    -(void)	LP_Init
    {
        SDKManager *sdkmgr = [SDKManager getInstance];
        [[IAPManager getInstance] initWithGameID:[conchConfig GetInstance]->m_sGameID andDelegate:sdkmgr];
    }

    SDKManager.mm类里添加这个回调,这里有个retry不用管,完成后他会去掉JSBridge的iosPayCallBack,这里就会回调给js的doIosZF的回调方法了,同时把参数也传过去了,注意传的是json字符串,注意解析

    - (void)onCallBack:(NSInteger)p_iRet reason:(NSString *)p_pReason retry:(Boolean)bRetry receipt:(NSString *)p_pReceipt orderid:(NSString *)p_pOrderid
    {
        if(bRetry)
        {
            NSString* sJs = [NSString stringWithFormat:@"if(window.Laya)window.Laya.Browser.window.onRetryIosZF(%ld,'%@','%@','%@');",(long)p_iRet,p_pReason,p_pReceipt,p_pOrderid];
            [JSBridge callRunJs:[NSString stringWithCString:[sJs UTF8String] encoding:NSUTF8StringEncoding]];
        }
        else
        {
            [JSBridge iosPayCallBack:p_iRet reason:(p_pReason?p_pReason:@"") receipt:(p_pReceipt?p_pReceipt:@"") orderid:(p_pOrderid?p_pOrderid:@"")];
        }
    }

    然后我们看IAPManager里内购完成的回调方法

    - (void) completeTransaction: (SKPaymentTransaction *)transaction retry:(Boolean)bRetry
    {
        NSLog(@"-----completeTransaction--------");
        NSString* pszBase64 = [transaction.transactionReceipt base64Encoding];
        [self.delegate onCallBack:0 reason:@"购买成功!" retry:bRetry receipt:pszBase64 orderid:transaction.payment.applicationUsername];
    }
    
    - (void) failedTransaction: (SKPaymentTransaction *)transaction retry:(Boolean)bRetry
    {
        NSLog(@"失败,%ld,%@",(long)transaction.error.code,[transaction.error localizedDescription]);
        
        [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
        //用户取消
        if (transaction.error.code == SKErrorPaymentCancelled)
            [self.delegate onCallBack:-1 reason:@"您取消支付!" retry:bRetry receipt:@"" orderid:@""];
        else
            [self.delegate onCallBack:-1 reason:[transaction.error localizedDescription] retry:bRetry receipt:@"" orderid:@""];
    }

    都会有这个代理方法  self.delegate onCallBack:  这个就会回调到SDKManager的onCallBack方法

    注意:productid必须是与app内购里配置的id一致,否则找不到

    而且重中之重的是:productid  一定是字符串,绝不可传number型,否则你会看到请求没任何反应,也没报错,这时候就要用到开头说的控制台了,这个让我查了足足三天之多,可见阴阳变化莫测,虽极微之道,亦可倾天盖地也。

    这里奉上IAPManger 源码,望世间无如吾之愚人

    #import "IAPManager.h"
    #define __MAX_GAME_ID_SIZE     32
    
    @interface IAPManager()<NSURLConnectionDataDelegate>
    
    @property (nonatomic,assign) id<JCIapProcessCtrlDelegate> delegate;
    @property (nonatomic,strong) NSString* gameAppID;
    @property (nonatomic,strong) NSString *strPlateform;
    @end
    
    @implementation IAPManager
    
    + (IAPManager*) getInstance{
        static IAPManager* iap = nil;
        if (iap == nil){
            iap = [IAPManager alloc];
        }
        return iap;
    }
    
    - (id)initWithGameID:(NSString*)gameID andDelegate:(id<JCIapProcessCtrlDelegate>)delegate{
        if ((self = [super init])) {
            NSAssert(gameID != nil,@"[IAP] gameID can not be nil");
            NSAssert(gameID.length <= __MAX_GAME_ID_SIZE,@"gameID is too long");
            self.gameAppID = [NSString stringWithString:gameID];
            NSAssert(delegate != nil, @"[IAP] delegate can not be nil");
            self.delegate = delegate;
            self.strPlateform = [NSString stringWithFormat:@"%@,%@,%@", [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemName],[[UIDevice currentDevice] systemVersion]];
            NSLog(@"[IAP]plateform info: %@\n", self.strPlateform);
            [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
            
            // [self.delegate onCallBack:1 reason:@"" retry:true receipt:@"" orderid:@""];
        }
        return self;
    }
    
    - (void)dealloc
    {
        [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
    }
    
    -(void)buy:(NSString*)productId orderid:(NSString*) orderid
    {
        bool canBuy = [SKPaymentQueue canMakePayments];
        
        if (canBuy) {
            NSLog(@"允许程序内付费购买");
            //直接购买
            //SKPayment *payment = [SKPayment paymentWithProductIdentifier:productId];
            
            SKMutablePayment *payment = [SKMutablePayment paymentWithProductIdentifier:productId];
            payment.applicationUsername = orderid;
            
            NSLog(@"---------发送购买请求------------");
            [[SKPaymentQueue defaultQueue] addPayment:payment];
        }
        else
        {
            [self.delegate onCallBack:-1 reason:@"You can‘t purchase in app store(没允许应用程序内购买)" retry:false receipt:@"" orderid:@""];
        }
    }
    
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions//交易结果
    {
        //完成购买
        NSLog(@"-----paymentQueue--------");
        for (SKPaymentTransaction *transaction in transactions)
        {
            switch (transaction.transactionState)
            {
                case SKPaymentTransactionStatePurchased://交易完成
                    [self completeTransaction:transaction retry:false];
                    NSLog(@"-----交易完成 --------");
                    break;
                case SKPaymentTransactionStateFailed://交易失败
                    [self failedTransaction:transaction retry:false];
                    break;
                case SKPaymentTransactionStateRestored://已经购买过该商品
                    //[self restoreTransaction:transaction];
                    //NSLog(@"-----已经购买过该商品 --------");
                    break;
                case SKPaymentTransactionStatePurchasing:      //商品添加进列表
                    NSLog(@"-----商品添加进列表 --------");
                    break;
                default:
                    break;
            }
        }
    }
    
    - (void) completeTransaction: (SKPaymentTransaction *)transaction retry:(Boolean)bRetry
    {
        NSLog(@"-----completeTransaction--------");
        NSString* pszBase64 = [transaction.transactionReceipt base64Encoding];
        [self.delegate onCallBack:0 reason:@"购买成功!" retry:bRetry receipt:pszBase64 orderid:transaction.payment.applicationUsername];
    }
    
    - (void) failedTransaction: (SKPaymentTransaction *)transaction retry:(Boolean)bRetry
    {
        NSLog(@"失败,%ld,%@",(long)transaction.error.code,[transaction.error localizedDescription]);
        
        [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
        //用户取消
        if (transaction.error.code == SKErrorPaymentCancelled)
            [self.delegate onCallBack:-1 reason:@"您取消支付!" retry:bRetry receipt:@"" orderid:@""];
        else
            [self.delegate onCallBack:-1 reason:[transaction.error localizedDescription] retry:bRetry receipt:@"" orderid:@""];
    }
    
    -(void)retryProvideProduct
    {
        bool bTraslate = false;
        NSLog(@"-----paymentQueue--------");
        
        for (SKPaymentTransaction *transaction in [[SKPaymentQueue defaultQueue]transactions])
        {
            switch (transaction.transactionState)
            {
                case SKPaymentTransactionStatePurchased://交易完成
                {
                    [self completeTransaction:transaction retry:true];
                    bTraslate = true;
                    NSLog(@"-----交易完成 --------");
                }
                    break;
                case SKPaymentTransactionStateFailed://交易失败
                {
                    [self failedTransaction:transaction retry:true];
                    bTraslate = true;
                }
                    break;
                default:
                    break;
            }
        }
        if(!bTraslate){
            [self.delegate onCallBack:1 reason:@"" retry:true receipt:@"" orderid:@""];
        }
    }
    
    -(void)finishTransaction
    {
        NSLog(@"-----finishTransaction --------");
        for (SKPaymentTransaction *transaction in [[SKPaymentQueue defaultQueue]transactions])
        {
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        }
    }
    
    @end

    至此ios内购可谓告一段落,对ios专业开发人员来说,似乎是鸟之距媲美鹏之高也,然

    天下难事,必作于易;天下大事,必作于细 

    望楼顶之人不小泥潭之蛙,此吾生之愿也!

  • 相关阅读:
    Spring Boot中实现logback多环境日志配置
    阿里云ECSLinux系统下挂载磁盘(转)
    解决 No qualifying bean of type 问题
    通过rpm离线安装Oracle 19C
    ADFS配置踩坑记
    .NET Core 2.0下载和文档
    .NET Core 2.0和ASP.NET Core 2.0正式版抢先体验
    .NET 微服务和Docker容器
    DocFX生成PDF文档
    ASP.NET Core 开源论坛项目 NETCoreBBS
  • 原文地址:https://www.cnblogs.com/wangzisheng/p/16333826.html
Copyright © 2020-2023  润新知