Unity iOS内购

简介: 前言:最近项目需要切换到iOS平台做一些提交审核和支付对接相关的工作,上一篇刚分享了最新的iOS10提交审核的一些坑,这篇分享一些内购相关的流程。Unity iOS内购思路:Unity调用iOS内购代码实现效果图:重要提示...

前言:最近项目需要切换到iOS平台做一些提交审核和支付对接相关的工作,上一篇刚分享了最新的iOS10提交审核的一些坑,这篇分享一些内购相关的流程。

Unity iOS内购

思路:

Unity调用iOS内购代码实现

效果图:

购买弹框
购买结果

重要提示:

测试一定要用沙盒账号,否则无效!

流程

这里就不重复写了,直接上截图
这里写图片描述

OC代码:

IAPInterface(主要是实现Unity跟OC的IAP代码的一个交互作用,等于是一个中间桥梁)

#import <Foundation/Foundation.h>

@interface IAPInterface : NSObject

@end
#import "IAPInterface.h"
#import "IAPManager.h"

@implementation IAPInterface

void TestMsg(){
    NSLog(@"Msg received");

}

void TestSendString(void *p){
    NSString *list = [NSString stringWithUTF8String:p];
    NSArray *listItems = [list componentsSeparatedByString:@"\t"];

    for (int i =0; i<listItems.count; i++) {
        NSLog(@"msg %d : %@",i,listItems[i]);
    }

}

void TestGetString(){
    NSArray *test = [NSArray arrayWithObjects:@"t1",@"t2",@"t3", nil];
    NSString *join = [test componentsJoinedByString:@"\n"];


    UnitySendMessage("Main", "IOSToU", [join UTF8String]);
}

IAPManager *iapManager = nil;

void InitIAPManager(){
    iapManager = [[IAPManager alloc] init];
    [iapManager attachObserver];

}

bool IsProductAvailable(){
    return [iapManager CanMakePayment];
}

void RequstProductInfo(void *p){
    NSString *list = [NSString stringWithUTF8String:p];
    NSLog(@"productKey:%@",list);
    [iapManager requestProductData:list];
}

void BuyProduct(void *p){
    [iapManager buyRequest:[NSString stringWithUTF8String:p]];
}

@end

IAPManager(真真的iOS的购买功能)

#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>

@interface IAPManager : NSObject<SKProductsRequestDelegate, SKPaymentTransactionObserver>{
    SKProduct *proUpgradeProduct;
    SKProductsRequest *productsRequest;
}

-(void)attachObserver;
-(BOOL)CanMakePayment;
-(void)requestProductData:(NSString *)productIdentifiers;
-(void)buyRequest:(NSString *)productIdentifier;

@end
#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{
    NSArray *products = response.products;

    for (SKProduct *p in products) {
        UnitySendMessage("Main", "ShowProductList", [[self productInfo:p] UTF8String]);
    }

    for(NSString *invalidProductId in response.invalidProductIdentifiers){
        NSLog(@"Invalid product id:%@",invalidProductId);
    }

    [request autorelease];
}

-(void)buyRequest:(NSString *)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("Main", "ProvideContent", [[self transactionInfo:transaction] UTF8String]);
}

-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:
                [self completeTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
                break;
            default:
                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

Unity中调用的C#代码

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;

public class IAPExample : MonoBehaviour {

    public List<string> productInfo = new List<string>();

    [DllImport("__Internal")]
    private static extern void TestMsg();//测试信息发送

    [DllImport("__Internal")]
    private static extern void TestSendString(string s);//测试发送字符串

    [DllImport("__Internal")]
    private static extern void TestGetString();//测试接收字符串

    [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);
    }
    bool back = false;
    //获取商品回执
    void ProvideContent(string s){
        Debug.Log ("[MsgFrom ios]proivideContent : "+s);
        back = true;
    }


    // Use this for initialization
    void Start () {
        InitIAPManager();
    }

    void OnGUI(){

        if(Btn ("GetProducts")){
            if(!IsProductAvailable())
                throw new System.Exception("IAP not enabled");
            productInfo = new List<string>();
            RequstProductInfo("com.aladdin.fishpocker1\tcom.aladdin.fishpocker2");
        }

        GUILayout.Space(40);

        if (back)
            GUI.Label (new Rect (10, 150, 100, 100), "Message back");

        for(int i=0; i<productInfo.Count; i++){
            if(GUILayout.Button (productInfo[i],GUILayout.Height (100), GUILayout.MinWidth (200))){
                string[] cell = productInfo[i].Split('\t');
                Debug.Log ("[Buy]"+cell[cell.Length-1]);
                BuyProduct(cell[cell.Length-1]);
                GUI.Label(new Rect (10, 10, 100, 200), string.Format("[Buy]{0}" ,cell[cell.Length-1]));
            }  
        }
    }

    bool Btn(string msg){
        GUILayout.Space (100);
        return  GUILayout.Button (msg,GUILayout.Width (200),GUILayout.Height(100));
    }
}

Git地址点击下载


欢迎关注我的围脖
==================== 迂者 丁小未 CSDN博客专栏=================

MyBlog:http://blog.csdn.net/dingxiaowei2013 MyQQ:1213250243

Unity QQ群:375151422 cocos2dx QQ群:280818155

====================== 相互学习,共同进步 ===================


iOS 内购验证

如果我们不做任何处理的话,越狱机是可以直接绕过支付验证直接获得结果的,这样对于我们辛辛苦苦的开发者来说简直噩耗,所以我们有必要了解一下内购验证相关的知识,以及知道如何去预防这样的事情。

校验文章

http://www.cnblogs.com/zhaoqingqing/p/4597794.html

相关资料

本地验证:

优点:

  • 无需服务器验证

缺点:

  • 项目里需要引入 OpenSSL

链接:

服务器验证:

优点:

  • server-side verification over SSL is the most reliable way to determine the authenticity of purchasing records

缺点:

  • 需要部署服务器,服务器和 App 之间的数据交换可能更容易被破解

链接:

双重验证:

先本地验证一次,后服务器再验证一次(感觉没必要)

其他:

常见的破解方法:

总的来说:

  • 服务器验证更适合有自己账号系统的 App,直接可以对 IAP 破解免疫,否则一样很简单就被破解
  • 本地验证使用下面的方法来增强验证
    • Check that the SSL certificate used to connect to the App Store server is an EV certificate.
    • Check that the information returned from validation matches the information in the SKPayment object.
    • Check that the receipt has a valid signature.
    • Check that new transactions have a unique transaction ID.

unity交流群

QQ群
unity3d unity 游戏开发

相关文章
|
图形学 iOS开发
Unity 之 代码获取IOS设备型号
两种方式获取IOS设备型号,一种可以根据机型进行处理,另外一种则是按照屏幕比例进行分类,一起来了解一下吧~
490 0
Unity 之 代码获取IOS设备型号
|
自然语言处理 Java C#
浅谈 Unity、iOS、Android 闭包的使用方法
浅谈 Unity、iOS、Android 闭包的使用方法
212 0
浅谈 Unity、iOS、Android 闭包的使用方法
|
存储 JSON 图形学
Unity 读取Cocos本地化数据 -- 基于IOS
使用Unity读取Cocos存储的数据的尝试过程和具体实现。
246 0
Unity 读取Cocos本地化数据 -- 基于IOS
|
API 开发工具 图形学
Unity 接入友盟统计过程详解(IOS)
Unity 接入友盟统计过程详解(IOS)
274 0
Unity 接入友盟统计过程详解(IOS)
|
图形学 iOS开发
Unity 之 记录打包IOS首次安装启动弹窗通知权限问题
IOS应该如何去掉首次进程序的获取权限确认框。
656 0
Unity 之 记录打包IOS首次安装启动弹窗通知权限问题
|
存储 图形学 数据安全/隐私保护
零基础教你Unity接入IOS原生本地推送
从新建项目开始保姆级教程,教你Unity接入IOS原生本地推送。
710 0
零基础教你Unity接入IOS原生本地推送
|
图形学 iOS开发 开发者
Unity 之 接入IOS内购过程解析
看完此文章你可以了解IOS内购接入全过程,可以学习到Unity从零接入内购功能
1152 0
Unity 之 接入IOS内购过程解析
|
图形学 iOS开发 API
Unity IOS 录屏
用到的是ios的replaykit,现在使用的unity 2017.1.f3版本已经自己集成了,所以调用相应提供的接口就可以,很简单 但是目前测试会在安装后第一次录屏的时候出现黑屏录屏失败的情况,所以可以采用第一次初始化的时候先录制然后放弃保存避免这个...
1355 0
|
图形学 iOS开发
unity ios 截屏
最近项目需要ios响应的截屏功能,参考网上一些资料,code如下 需要注意的是:IOS的XCODE打包要选择打开相册权限,否则截图会闪退!!! 点击加号添加响应权限的字符串,权限字符串列表如下: NSContactsUsageDescr...
1360 0
|
1月前
|
API 数据安全/隐私保护 iOS开发
利用uni-app 开发的iOS app 发布到App Store全流程
利用uni-app 开发的iOS app 发布到App Store全流程
84 3