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 在內購商品之前也調用請求商品信息。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM