编辑:[db:作者] 时间:2024-08-25 05:53:19
简而言之,苹果规定:适用范围内的虚拟商品或做事,必须利用 IAP 进行购买支付,不许可利用支付宝、微信支付等其它第三方支付办法(包括Apple Pay),也不许可以任何办法(包括跳出App、提示文案等)勾引用户通过运用外部渠道购买
iOS 内购的准备App 内集成内购代码之前须要先去开拓账号的 Itunes Connect 后台填写银行账户信息、配置内购商品(包括产品ID、价格等),还须要配置沙盒账号用于 IAP 测试
银行账户信息填写关于如何去 Itunes Connect 后台填写账户信息,本文不做谈论,可以参考以下文章:
iOS内购一条龙—账户信息填写
配置内购商品IAP 是一套商品交易系统,而非大略的支付系统,每一个购买项目都须要在开拓者后台的Itunes Connect后台为 App 创建一个对应的商品,提交给苹果审核通过后,购买项目才会生效
Itunes Connect 后台选择 我的APP,选择须要内购 App,选择 功能,可以进入到配置内购商品的页面
新建内购商品有四种选择分别是:
花费型项目:只可利用一次的产品,利用之后即失落效,必须再次购买,如:游戏币、一次性虚拟道具等非花费型项目:只需购买一次,不会过期或随着利用而减少的产品。如:电子书自动续期订阅:许可用户在固定时间段内购买动态内容的产品。除非用户选择取消,否则此类订阅会自动续期,如:Apple Music这类按月订阅的商品非续期订阅:许可用户购买有时限性做事的产品,此 App 内购买项目的内容可以是静态的。此类订阅不会自动续期一样平常情形下用的最多的是花费型商品,根据 App 类型也会利用到非花费型和自动续期订阅,以花费型商品举例
这里须要把稳的是产品 ID 具有唯一性,建议利用项目的 Bundle Identidier 作为前缀后面拼接自定义的唯一的商品名或者 ID(字母、数字),这里有个坑:一旦新建一个内购商品,它的产品ID将永久被占用,纵然该商品已经被删除,已创建的内购商品除了产品 ID 之外的所有信息都可以修正,如果删除了一个内购商品,将无法再创建一个相同产品 ID 的商品,也意味着该产品 ID 永久失落效,一样平常来说产品ID有特定的命名规则,如果命名规则下有某个产品 ID 永久失落效,可能会导致全体产品ID命名规则都要修正,这里千万要把稳!
其余内购商品的定价只能从苹果供应的价格等级去选择,这个价格等级是固定的,同一价格等级会对应各个国家的货币,也便是说内购商品的价格是根据 Apple ID 所在区域的货币进行结算的,比如:一个内购商品你选择等级1,那么这个商品在美区是 0.66 美元,在中区是 6 元公民币,在喷鼻香港去是 8 港币,这些价格一样平常是固定的,除非某些货币涌现大的变动(印象中有过一次卢布大跌,苹果调度过俄区的价格),价格等级表可以点击上图右边的 所有价格和货币 查看
其余要把稳:苹果内购是须要抽取30%的分成,实际结算是分成之前须要先扣除交易税,不同地区交易税不同,详细分成数额参看价格表
iOS 11 用户可以在 App Store 内 App 的下载页面内直接购买运用的内购商品,这项功能苹果称作做 Promoting In-App Purchases,如果你的 App 须要在 App Store 推广自己的内购商品,则须要在上图的 App Store 推广 里上传推广用的图像,其余苹果也在 iOS11 SDK 里面新增了从 App Store 购买内购项目跳转到 App 的新方法
配置沙箱测试账号内购也是须要测试的,但是内购涉及到钱,以是苹果为内购测试供应了 沙箱测试账号 的功能,Apple Pay 推出之后 沙箱测试账号 也可以用于 Apple Pay 支付的测试,沙箱测试账号 大略理解便是:只能用于内购和 Apple Pay 测试功能的 Apple ID,它并不是真实的 Apple ID,下面看如何创建 沙箱测试账号
Itunes Connect 后台选择 用户和职能,选择 + 添加测试账号
填写沙箱测试账号信息须要把稳以下几点:
电子邮件不能是别人已经注册过 AppleID 的邮箱电子邮箱可以不是真实的邮箱,但是必须符合邮箱格式App Store 地区的选择,测试的时候弹出的提示框以及结算的价格会按照沙箱账号选择的地区来,建议测试的时候新建几个不同地区的账号进行测试配置好测试账号之后,看一下沙箱账号测试的时候如何利用:
首先沙箱测试账号必须在真机环境下进行测试,并且是 adhoc 证书或者 develop 证书署名的安装包,沙盒账号不支持直接从 App Store 下载的安装包去真机的 App Store 退出真实的 Apple ID 账号,退出之后并不须要在App Store 里面登录沙箱测试账号然后去 App 里面测试购买商品,会弹出登录框,选择 利用现有的 Apple ID,然后登录沙箱测试账号,登录成功之后会弹出购买提示框,点击 购买,然后会弹出提示框完成购买上面说了这么多只是集成内购的一些准备,真正主要的是下面的内购代码的实现
iOS 内购详细代码写代码之前先来理解比拟一下 IPA 和支付宝支付,首先看支付宝的支付流程:
App 发起一笔支付交易,然后做事端根据支付宝的哀求把订单信息进行加密署名做事端把加密的交易信息返回给 App,App 拿到交易信息调用支付宝的 SDK,把支付信息给到支付宝的做事端验证验证通过后,App 跳转到支付宝 App 或者网页版支付宝,用户利用支付宝进行支付支付成功后从支付宝 App 跳转回到我们自己 APP,我们在 App 里处理回调结果刷新UI等同时支付宝的做事器也会回调我们自己做事器,把收据传给做事器,支付宝做事器会一贯回调我们的做事器直到我们的做事器确认收到收据我们的做事器收到回调确认之后,确认订单支付成功为了以防万一,App 上回调返回成功之后我们还须要去自己做事器验证是否真的支付成功(统统以做事器为准)微信支付和支付宝支付的流程是类似的,来看看 IAP 的支付流程:
App 发起一笔内购支付,然后做事端天生一个订单号并且返回给 AppApp 拿到交易订单之后调用 IPA 创建一个 IPA 交易,并且添加到支付行列步队然后 IAP 会调用 Apple ID 支付页面等待用户确认支付,IPA 和苹果自己的 IPA 做事器通讯,回调购买成功,并且把收据写入 App 沙盒然后 App 去沙盒获取收据并且上传到自己的做事器做事器去 IAP 做事器查询收据的有效性并且对应到某个订单号,如果有效就关照 App,并且发放该内购商品,App 调用IAP 支付行列步队去结束该 IPA 交易比拟来看两者差异彷佛也不大,支付宝或者微信支付,一旦App 端支付成功,之后的验证事情就完备是我们的做事器和支付宝做事器之前的通讯了,做事端之间的通讯就担保了交易的可靠性,但是看看 IAP,同样的交易,做事真个验证却须要 App 端去驱动,由于 App 的网络环境比做事端繁芜、用户操作的不愿定性可能会导致 APP 无法精确的驱动做事端验证交易,另一方面 IAP 的做事器在美国,验证查询交易的延迟也很严重
苹果 IAP 的那些坑后面再讲,先按照上面的流程,我们把 IPA 代码的逻辑理一理:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
import UIKitimport StoreKitclass ViewController: UIViewController, SKProductsRequestDelegate,SKPaymentTransactionObserver { override func viewDidLoad() { super.viewDidLoad() // 监听支付行列步队 SKPaymentQueue.default().add(self as SKPaymentTransactionObserver) } deinit { SKPaymentQueue.default().remove(self as SKPaymentTransactionObserver) } // 点击购买 @IBAction func buy(_ sender: UIButton) { if SKPaymentQueue.canMakePayments() { requestProductInfo("com.xiaovv.IAPDemo.vip1") }else { print("用户禁止购买") } } // 要求查询 iTunes Connect 后台配置的内购商品 fileprivate func requestProductInfo (_ productId: String) { let identifiers: Set<String> = [productId] let request = SKProductsRequest(productIdentifiers: identifiers) request.delegate = self request.start() } fileprivate func transcationPurchasing(_ transcation: SKPaymentTransaction) { print("交易中...") } fileprivate func transcationPurchased(_ transcation: SKPaymentTransaction) { print("交易成功...") // 持久化订单信息 if let receiptUrl = Bundle.main.appStoreReceiptURL {//获取收据地址 let receipt = NSData(contentsOf: receiptUrl) let receiptStr = receipt?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) DispatchQueue.main.asyncAfter(deadline: DispatchTime(uptimeNanoseconds: 3)) {// 仿照上传收据到做事端 print("receiptStr:\(String(describing: receiptStr))") print("applicationUsername:\(String(describing: transcation.payment.applicationUsername))") // 收据发送到做事器 // 收据验证成功之后结束交易 SKPaymentQueue.default().finishTransaction(transcation) // 删除保存的订单信息 } } } fileprivate func transcationFailed(_ transcation: SKPaymentTransaction) { print("交易失落败...") } fileprivate func transcationRrestored(_ transcation: SKPaymentTransaction) { print("已经购买该商品...") } fileprivate func transcationDeferred(_ transcation: SKPaymentTransaction) { print("交易延期...") } //MARK: - SKPaymentTransactionObserver // 购买内购商品之后会调用的代理方法 func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transcation in transactions { switch (transcation.transactionState) { case .purchasing: transcationPurchasing(transcation) case .purchased: transcationPurchased(transcation) case .failed: transcationFailed(transcation) case .restored: transcationRrestored(transcation) case .deferred: transcationDeferred(transcation) } } } //MARK: - SKProductsRequestDelegate // 查询iTunes Connect 后台内购商品的回调代理方法 func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { if response.products.count > 0 { let payment = SKMutablePayment(product: response.products.first!) // applicationUsername 可以把我们的自己订单和IAP的交易订单绑定 payment.applicationUsername = "orderid,userid and so on" }else { print("没有可以购买的商品") } }}
IAP 的代码看起来并不多流程也比较清晰,紧张是下面几步:
根据内购商品的产品 ID 初始化一个 SKProductsRequest 工具,调用该工具的 start() 方法进行内购商品的要求把商品要求中获取到的 SKProduct 工具天生一个 SKPayment 工具,并把它压入到 SKPaymentQueue 支付行列步队中然后从支付行列步队的代理方法 func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) 里面获取到交易(transaction)的状态,交易完成后调用支付行列步队的 finishTransaction()完成内购支付iOS 内购减少丢单处理上面说到 IAP 的坑,下面就列举一下 IAP 留给开拓者的 “坑”:
用户输入完 Apple ID 密码或者验证完指纹支付成功之后,网络溘然中断导致 IAP 没有收到支付成功的关照,App 就无法在支付行列步队的代理方法中获取支付成功的关照,后续的发放内购商品也就不可能了App 在代理方法里收到了支付成功的关照,但是 App 上传交易收据到我们做事器去查询的时候如果查询失落败,那么做事器就无法发放内购商品,由于这个行为是 App 驱动做事器的行为,这里有个坑便是支付行列步队的代理方法 func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) 须要下次 App 重新启动才会重新调用,这个时候我们 App 才能重新去驱动做事器查询交易,由于用户操作的不愿定性,不知道什么时候用户才会重新打开App,发放内购商品的周期自然也不愿定之前有开拓者反应,IAP 关照代理方法交易成功,但是沙盒里面取收据的时候创造为空,或者当前支付成功的订单并没有写入沙盒的收据,导致上传到做事器的收据查询不到结果如果用户支付成功,收据也上传做事器成功,但是在做事器验证阶段用户删除了App,导致App 无法去处理这些没有被验证完的订单如何处理越狱iOS手机内购的问题以上问题都可能会导致用户支付成功了,却收不到我们发放的内购商品,统一起来称为:内购丢单
接下来谈论一下如何处理 IAP 支付的一些细节问题战胜上述 IAP 的坑
越狱问题我本人也折腾过 iOS 越狱,安装实用插件、美化主题等,自从iOS 9 之后越狱社区越来越不生动了,能完美越狱的基本都是iPhone 5 以及更老的 32位设备了,这也侧面也解释了 iOS 在功能方面已经非常完善了,导致折腾越狱的人越来越少了,当前 iOS 版本是否越狱可以去这个网站查看:Can I jailbreak
回归正题,由于越狱之后会导致系统不愿定的问题很多,我以为可以采取宁肯错杀一千也不放过一个原则,对付已越狱的iOS 设备全部不许可内购行为,大略粗暴有效,可以通过下面临象的方法判断iOS设备是否越狱
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
.h 文件#import <Foundation/Foundation.h>@interface JailbreakDetectTool : NSObject/ 检讨当前设备是否已经越狱。 /+ (BOOL)detectCurrentDeviceIsJailbroken;@end.m 文件#import "JailbreakDetectTool.h"#define ARRAY_SIZE(a) sizeof(a)/sizeof(a[0])@implementation JailbreakDetectTool// 四种检讨是否越狱的方法, 只要命中一个, 就解释已经越狱.+ (BOOL)detectCurrentDeviceIsJailbroken { BOOL result = NO; result = [self detectJailBreakByJailBreakFileExisted]; if (!result) { result = [self detectJailBreakByAppPathExisted]; } if (!result) { result = [self detectJailBreakByEnvironmentExisted]; } if (!result) { result = [self detectJailBreakByCydiaPathExisted]; } return result;}/ 剖断常见的越狱文件 /Applications/Cydia.app /Library/MobileSubstrate/MobileSubstrate.dylib /bin/bash /usr/sbin/sshd /etc/apt 这个表可以尽可能的列出来,然后剖断是否存在,只要有存在的就可以认为机器是越狱了。 /const char jailbreak_tool_pathes[] = { "/Applications/Cydia.app", "/Library/MobileSubstrate/MobileSubstrate.dylib", "/bin/bash", "/usr/sbin/sshd", "/etc/apt"};+ (BOOL)detectJailBreakByJailBreakFileExisted { for (int i = 0; i<ARRAY_SIZE(jailbreak_tool_pathes); i++) { if ([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithUTF8String:jailbreak_tool_pathes[i]]]) { NSLog(@"The device is jail broken!"); return YES; } } NSLog(@"The device is NOT jail broken!"); return NO;}/ 判断cydia的URL scheme. /+ (BOOL)detectJailBreakByCydiaPathExisted { if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://"]]) { NSLog(@"The device is jail broken!"); return YES; } NSLog(@"The device is NOT jail broken!"); return NO;}/ 读取系统所有运用的名称. 这个是利用不越狱的机器没有这个权限来剖断的。 /#define USER_APP_PATH @"/User/Applications/"+ (BOOL)detectJailBreakByAppPathExisted { if ([[NSFileManager defaultManager] fileExistsAtPath:USER_APP_PATH]) { NSLog(@"The device is jail broken!"); NSArray applist = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:USER_APP_PATH error:nil]; NSLog(@"applist = %@", applist); return YES; } NSLog(@"The device is NOT jail broken!"); return NO;}/ 这个DYLD_INSERT_LIBRARIES环境变量,在非越狱的机器上该当是空,越狱的机器上基本都会有Library/MobileSubstrate/MobileSubstrate.dylib. /char printEnv(void) { char env = getenv("DYLD_INSERT_LIBRARIES"); return env;}+ (BOOL)detectJailBreakByEnvironmentExisted { if (printEnv()) { NSLog(@"The device is jail broken!"); return YES; } NSLog(@"The device is NOT jail broken!"); return NO;}@end
储存交易订单
上述第1、2点坑都是由于交易订单支付成功了,但是没有完成做事器验证收据,以是我们非常有必要持久化我们的订单信息,IAP 支付成功的交易(transaction)只要没有调用 finishTransaction(),下次启动 App 重新监听支付行列步队的时候会重新调用 func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) 这个时候就可以重新获取到未完成的该交易(transaction)
支付成功后,我们持久化自己订单信息的目的便是和 IAP的交易(transaction)绑定起来,这样如果在App驱动做事器验证订单的时候涌现了非常,App重新启动之后我们获取到未完成的 IAP的交易(transaction)之后依然可以和我们自己的订单联系起来,然后可以连续发送给后台做事器去去验证,直到验证成功完成 IAP 交易(transaction),购买结束
这里我的做法是:用户支付成功之后用 UserDefault 把该交易的交易标识(transactionIdentifier)和我们自己的交易订单作为字典的键值保存在沙盒内,然后把我们自己的订单号和支付收据发送给做事器去验证,做事器返回验证结果之后,删除 UserDefault 中的订单信息,并且完成该笔IAP 交易(transaction),如果在做事器返回验证结果之前涌现非常(用户杀掉App、手机关机了,App崩溃等)订单信息便不会从UserDefault 中删除,该笔IAP 交易也不会被完成,App重新启动之后,重新监听支付行列步队的时候,可以重新获取到该笔交易(transaction),然后根据该笔交易(transaction)的交易标识(transactionIdentifier)找到 UserDefault 中对应的我们自己的订单号,重新把订单号和交易收据发送给后台做事器验证
我们自己的订单是后台天生并且和用户ID进行了绑定的,以是这里不担心用户支付成功之后,切换 App 账号会把内购商品发放给另一个用户
我的做法比较大略,实际上用户如果删除 App,UserDefault 的订单信息就会丢失,以是订单信息储存在钥匙串 keychain 才是相对最稳妥的,特殊是如果你的项目以内购为主并且支付非常频繁,这里可以参看贝聊的做法 贝聊 IAP 实战之见坑填坑
applicationUsername由于 IAP 支付成功之后我们只能从 IAP 的代理方法里面获取到支付成功的交易(transaction),以是我们就必须把自己的订单号和对应的 IAP 交易(transaction)绑定在一起,IAP 创建 SKPayment 工具,并把它压入到SKPaymentQueue 支付行列步队的时候,我们可以利用可变的 SKMutablePayment,它有一个属性叫做 applicationUsername,这个属性就可以用来储存我们自己天生的订单号信息,用户支付成功之后可以成功的交易(transaction)的 payment 中获取到绑定的订单号信息,这样就可以完美的把 IAP交易(transaction)和我们自己的订单号绑定在一起了
但是,但是!
苹果在这个属性上面有个巨坑,前面说到如果某个IAP交易(transaction)支付成功但是并没有调用 finishTransaction()去完成这个交易的时候,下次启动 App 重新监听支付行列步队的时候会重新调用 func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) 这个时候就可以重新获取到这个未完成的交易(transaction),苹果的坑便是这个时候获取 transaction 的 payment 的 applicationUsername 会涌现 nil 的情形,以是实际开拓中不能完备去相信这个 applicationUsername 属性,这也是为什么上面我要把自己的订单号和 IAP 交易(transaction)的交易标识持久化下来的缘故原由
第3点坑,用户支付成功之后从沙盒获取收据为空,IAP 供应了一个SKReceiptRefreshRequest类,通过这个类可以在收据无效或者丢失的时候申请到新的收据,SKReceiptRefreshRequest继续自 SKRequest 和 SKProductsRequest 都遵照 SKRequestDelegate
第4点坑,如果我们的订单信息是持久化到钥匙串中,那么纵然 App 被删除,下次重新安装的时候依然可以拿到持久化的订单信息
实际上纵然把上面的问题全部考虑到了依然还可能出问题,比如用户切换了 Apple ID 这个时候钥匙串里持久化的订单信息也会发生改变、或者验证成功之前用户换新的手机登录或者用户其他的不可预知的操作行为依然会导致错漏单的问题,但是我以为内购大部分时候是用户一种强意识的行为,上面这些操作只能因此为用户在阻挡自己购买成功,以是我们终极是无法百分之百避免错漏单的,手动去处理这些订单是非常有必要的
IAP 做事真个处理前面说到用户支付成功之后,我们拿到沙盒的收据信息去苹果的 IAP 做事器去验证,这里既可以直接在 App 端验证也可以让做事器去验证,实际上根据我的测试,App 端直接去IAP做事器验证比较快,毕竟中间少了很多步骤,但是考虑到越狱的 iOS 设备完备可以在系统层面跳过或者假造收据,早期的 iOS 开拓很多公司都是采取都是这种本地验证,但是现在基本都是通过后台验证的办法,详细的后台验证步骤如下:
App端拿到沙盒的收据(receipt-data),进行一次base64编码,上传给做事器做事器拿到收据之后发到 IAP 做事器去验证,验证成功之后收据须要和自己的订单号进行映射并且记录在数据库,之后每次验证之前都须要先判断收据是否存在,防止 App 端重复上传相同的收据,重复发放内购商品做事器发放内购商品,推送关照给用户等由于 App 上线 App Store 之前我们是利用沙盒账号测试的,沙盒测试的收据验证也是要去沙盒收据的做事器验证
沙盒环境验证做事器:https://sandbox.itunes.apple.com/verifyReceipt正式环境验证做事器:https://buy.itunes.apple.com/verifyReceipt而且苹果在上线审核的时候也是利用沙盒账号测试的,那如何识别App端发过来的收据是沙盒测试还是正式环境用户的购买呢?这里做事端就要采取双重验证,即先把收据拿到正式环境的验证地址去验证,如果苹果的正式环境验证做事器返回的状态码 status 为 21007,则解释当前收据是沙盒环境产生,则再连接一次沙盒环境做事器进行验证,这样不管是我们自己采取沙盒账号测试还是苹果审核职员采取沙盒账号进行审核、或者用户购买都可以担保收据正常的验证成功
沙盒测试碰着的其他问题在进行沙盒测试的时候,碰着过 IAP 沙盒做事器涌现非常,购买过程非常的慢,而且总是超时,返回购买失落败,谷歌到有同行也涌现过沙盒购买缓慢的问题(iTunes Sandbox Extremely Slow),以是如果确定代码没有问题,多数便是 IAP 的沙盒做事器涌现了问题,建议换个韶光再测试,可以通过 System Status - Apple Developer 查看苹果公司开拓者干系的做事的做事状态如果内购的时候提示 您已购买此 App 内购买项目。此项目将免费规复。,解释有一个购买了同一商品 IAP 交易(transaction)没有调用 finishTransaction()去结束这个交易Promoting In-App Purchases前面提到iOS 11 之后,开拓者可以在 App Store 自己App的下载页面推广自己的内购商品,用户可以直接在App下载页面购买内购商品,这就涉及到从App Store跳转到自己App,以是苹果在 SKPaymentTransactionObserver 新增了一个代理方法:
12345
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool { return false}
用户如果在 App下载页面点击购买你推广的内购商品,如果用户已经安装过你的 App 则会直接跳转你的App并调用上述代理方法;如果用户还没有安装你的 App 那么就会去下载你的 App,下载完成之后系统会推送一个关照,如果用户点击该关照就会跳转到你的App并且调用上面的代理方法
上面的代理方法返回 true 则表示跳转到你的 App,IAP 连续完成交易,如果返回 false 则表示推迟或者取消购买,实际开拓中由于可能还须要用户登录自己的账号、天生订单等,一样平常都是返回 false,之后自己手动把代理方法里面返回的 SKPayment 加入支付行列步队,然后在按照自己的支付、验证逻辑完成支付
以上便是我做内购的时候从网上的资料以及官方文档总结的一些东西
参考和推举阅读:
iOS内购一条龙——账户信息填写
苹果运用内购买(IAP)—从入门到放弃
iOS 贝聊 IAP 实战之满地是坑
苹果IAP开拓中的那些坑和掉单问题
Receipt Validation Programming Guide
In-App Purchase Programming Guide
收据验证编程指南
iOS内购充值 做事器端处理
iOS 内购做事器验票(漏单处理)
作者:Hsiaovv
出处:https://xiaovv.me/2018/05/03/My-iOS-In-App-Purchase-Summarize/
本站所发布的文字与图片素材为非商业目的改编或整理,版权归原作者所有,如侵权或涉及违法,请联系我们删除,如需转载请保留原文地址:http://www.baanla.com/xyj/145396.html
下一篇:返回列表
Copyright 2005-20203 www.baidu.com 版权所有 | 琼ICP备2023011765号-4 | 统计代码
声明:本站所有内容均只可用于学习参考,信息与图片素材来源于互联网,如内容侵权与违规,请与本站联系,将在三个工作日内处理,联系邮箱:123456789@qq.com