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 在内购商品之前也调用请求商品信息。

 

posted @ 2018-05-29 09:22  围墙外的世界  阅读(2442)  评论(0编辑  收藏  举报