苹果内购退款IAP内购中发现transactionReceipt被弃用了 用什么替换

一、In App Purchase概览
Store Kit代表App和App Store之间进行通信。程序将从App Store接收那些你想要提供的产品的信息,并将它们显示出来供用户购买。
当用户需要购买某件产品时,程序调用StoreKit来收集购买信息。下图即为基本的store kit 模型:
Store Kit的API只是为程序添加In App Purchase功能的一小部分。你需要决定如何去记录那些你想要提交的产品,如何在程序中将商店功能展现给用户,
还要考虑如何将用户购买的产品提交。本章的剩余部分会展示整个流程。
产品可以是任意一项你想要出售的特性。产品在iTunes Connect中被组织,这和你添加一个新的App是一样的。支持的产品种类共有四种:
1. 内容型。包括电子书,电子杂志,照片,插图,游戏关卡,游戏角色,和其他的数字内容。
2. 扩展功能。这些功能已经包含在App内部。在未购买之前被锁定。例如,你可以在一个游戏程序中包含若干个小游戏,用户可以分别来购买这些游戏。
3. 服务。允许程序对单次服务收费。比如录音服务。
4. 订阅。支持对内容或服务的扩展访问。例如,你的程序可以每周提供财务信息或游戏门户网站的信息。应该设定一个合理的更新周期,以避免过于频繁的
提示困扰用户。要记住:你将负责跟踪订阅的过期信息,并且管理续费。App Store不会替你监视订阅的周期,也不提供自动收费的机制。
In App Purchase为创建产品提供了一种通用的机制,如何操作将由你负责。当你设计程序的时候,有以下几点需要注意:
1. 你必须提供电子类产品和服务。不要使用In App Purchase 去出售实物和实际服务。
2. 不能提供代表中介货币的物品,因为让用户知晓他们购买的商品和服务是很重要的。
2. 服务器类型
使用这终方式,要提供另外的服务器将产品发送给程序。 服务器交付适用于订阅、内容类商品和服务,因为商品可以作为数据发送,而不需改动程序束。 例如,一个游戏提供的新的内容(关卡等)。 Store Kit不会对服务器端的设计和交互做出定义,这方面工作需要你来完成。 而且,Store Kit不提供验证用户身份的机制,你需要来设计。 如果你的程序需要以上功能,例如,纪录特定用户的订阅计划, 你需要自己来设计和实现。
图1-3 展示了服务器类型的购买过程。
1. 程序向服务器发送请求,获得一份产品列表。
2. 服务器返回包含产品标识符的列表。
3. 程序向App Store发送请求,得到产品的信息。
4. App Store返回产品信息。
5. 程序把返回的产品信息显示给用户(App的store界面)
6. 用户选择某个产品
7. 程序向App Store发送支付请求
8. App Store处理支付请求并返回交易完成信息。
9. 程序从信息中获得数据,并发送至服务器。
10. 服务器纪录数据,并进行审(我们的)查。
11. 服务器将数据发给App Store来验证该交易的有效性。
12. App Store对收到的数据进行解析,返回该数据和说明其是否有效的标识。
13. 服务器读取返回的数据,确定用户购买的内容。
14. 服务器将购买的内容传递给程序。
Apple建议在服务器端存储产品标识,而不要将其存储在plist中。 这样就可以在不升级程序的前提下添加新的产品。
在服务器模式下, 你的程序将获得交易(transaction)相关的信息,并将它发送给服务器。服务器可以验证收到的数据,并将其解码以确定需要交付的内容。 这个流程将在“验证store收据”一节讨论。
对于服务器模式,我们有安全性和可靠性方面的顾虑。 你应该测试整个环境来避免威胁。《Secure Coding Guide》文档中有相关的提示说明。
虽然非消耗性商品可以用内置模式来恢复,订阅类商品必须通过服务器来恢复。你要负责纪录订阅信息、恢复数据。&
消耗类商品也可以通过服务器方式来纪录。例如,由服务器提供的一项服务, 你可能需要用户在多个设备上重新获得结果。
(这段翻译的比较生硬,因为我个人也没有机会把各种类型的服务跑一遍,后续会检查并修改。希望大家一起来看看,欢迎补充。)
取得产品信息
要在程序内部显示“商店”,需要从App Store得到信息来购建界面。 本章详细讲解如何从App Store获取产品信息。
向App Store发送请求
Store Kit提供了从App Store上请求数据的通用机制。 程序可以创建并初始化一个request对象, 为其附加delegate, 然后启动请求过程。请求将被发送到App Store,在那里被处理。 处理完成时, request对象的delegate方法将被异步调用,以获得请求的结果。 图2-1显示了请求的数据模型。
如果程序在请求期间退出,则需要重新发送请求。
下面讲解请求过程中用到的类:
SKRequest为request的抽象根类。
SKRequestDelegate
SKRequestDelegate是一个protocol, 实现用以处理请求结果的方法,比如请求成功,或请求失败。
发送获得产品信息的请求
程序使用products request来获得产品的信息。 要完成这一过程,程序需创建一个request对象,其中会包含一个产品标识的列表。之前提到过,你的程序既可以内置产品列表,又可以通过外部服务器来获得。
当发送请求时,产品标识会传送到App Store,App Store将会返回本地化信息(这些信息事先已经在iTunes Connect中设置好了),你将使用这些信息来购建内置商店的界面(显示商品名,描述,等等)。 图2-2显示了请求的过程。
SKProductsRequest
用来请求商品的信息。 创建时,我们将需要显示的商品列表加入该对象。
SKProductsRequestDelegate
该protocol定义了处理App Store响应的方法。
SKProductsResponse
SKProductsResponse对象为App Store返回的响应信息。里面包含两个列表(当然是NSArray了):一是经过验证有效的商品,
@property(nonatomic, readonly) NSArray *products
另外一个是无法被识别的商品信息:
@property(nonatomic, readonly) NSArray * invalidProductIdentifiers
有几种原因将造成商品标识无法被识别,如拼写错误(当然),被标记为不可出售(unavailable for sale),或是对商品信息的改变没有传送到所有App Store的服务器。(这个原因不是很清楚,再议)。
SKProduct对象包含了在App Store上注册的商品的本地化信息。
当用户准备购买商品时,程序向App Store请求支付信息,然后App Store将会创建持久化的交易信息,并继续处理支付流程,即使用户重启程序,这个过程亦是如此。App Store同步待定交易的列表到程序中,并在交易状态发生改变时向程序发送更新的数据。
收集支付信息
要收集支付信息, 你的程序可以创建一个payment的对象,将它放到支付队列中,如图3-1所示。
1. 一个SKPayment的对象,包含了&Sword&的商品标识,并且制定购买数量为1。
2. 使用addPayment:方法将SKPayment的对象添加到SKPaymentQueue里。
3. SKPaymentmentQueue包含的所有请求商品,
4. 使用SKPaymentTransactionObserver的paymentQueue: updatedTransactions: 方法来检测所有完成的购买,并发送购买的商品。
5. 最后,使用finishTransaction:方法完成交易。
当payment的对象被添加到支付队列中的时候, 会创建一个持久保存的transaction对象来存放它。 当支付被处理后,transaction被更新。 程序中将实现一个观察者(observer)对象来获取transaction更新的消息。 观察者应该为用户提供购买的商品,然后将transaction从队列中移除。
下面介绍在购买过程中用到的几个类:
要收集支付信息,先要了解一下支付对象。 支付对象包含了商品的标识(identifier)和要购买商品的数量(quantity)(数量可选)。你可以把同一个支付对象重复放入支付队列,,每一次这样的动作都相当于一次独立的支付请求。
用户可以在Settings程序中禁用购买的功能。 因此在请求支付之前,程序应该首先检查支付是否可以被处理。 调用SKPaymentQueue的canMakePayments方法来检查。
SKPaymentQueue
支付队列用以和App Store之间进行通信。 当新的支付对象被添加到队列中的时候, Store Kit向App Store发送请求。 Store Kit将会弹出对话框询问用户是否确定购买。 完成的交易将会返回给程序的observer对象。
SKPaymentTransaction
transaction对象在每次添加新的payment到队列中的时候被创建。 transaction对象包含了一些属性,可以让程序确定当前的交易状态。
程序可以从支付队列那里得到一份审核中的交易列表,但更常用的做法还是等待支付队列告知交易状态的更新。
SKPaymentTransactionObserver
在程序中实现SKPaymentTransactionObserver的协议,然后把它作为SKPaymentQueue对象的观察者。该观察者的主要职责是:检查完成的交易,交付购买的内容,和把完成后的交易对象从队列中移除。
在程序一启动,就应该为支付队列指定对应的观察者对象,而不是等到用户想要购买商品的时候。 Transaction对象在程序退出时不会丢失。程序重启时, Store Kit继续执行未完成的交易。 在程序初始化的时候添加观察者对象,可以保证所有的交易都被程序接收(也就时说,如果有未完成的transaction,如果程序重启,就重新开始了,如果稍候再添加观察者,就可能会漏掉部分交易的信息)。
恢复交易信息(Transactions)
当transaction被处理并从队列移除之后,正常情况下,程序就再也看不到它们了。 如果你的程序提供的是非消耗性的或是订阅类的商品,就必须提供restore的功能,使用户可以在其他设备上重新存储购买信息。
Store Kit提供内建的功能来重新存储非消耗商品的交易信息。 调用SKPaymentQueue的restoreCompletedTransactions的方法来重新存储。对于那些之前已经完成交易的非消耗性商品,Apple Store生成新的,用于恢复的交易信息。 它包含了原始的交易信息。你的程序可以拿到这个信息,然后继续为购买的功能解锁。 当之前所有的交易都被恢复时, 就会调用观察者对象的paymentQueueRestoreCompletedTransactionsFinished方法。
如果用户试图购买已经买过的非消耗性商品,程序会收到一个常规的交易信息,而不是恢复的交易信息。但是用户不会被再次收费。程序 应把这类交易和原始的交易同等对待。
订阅类服务和消耗类商品不会被Store Kit自动恢复。 要恢复这些商品,你必须在用户购买这些商品时,在你自己的服务器上记录这些交易信息, 并且为用户的设备提供恢复交易信息的机制。
在程序中添加Store功能
本章为添加购买功能的指导
详细流程:
准备工作当然是添加StoreKit.framework了。
然后是具体的步骤:
1. 决定在程序内出售的商品的类型。
之前提到过,程序内可以出售的新feature类型是有限制的。 Store Kit不允许我们下载新的代码。 你的商品要么可以通过当前的代码工作(bundle类型),要么可以通过服务器下载(当然,这里下载的为数据文件,代码是不可以的)。 如果要修改源代码,就只能老实的升级了。
2. 通过iTunes Connect注册商品
每次添加新商品的时候都需要执行这一步骤。 每个商品都需要一个唯一的商品标识。 App Store通过这个标识来查找商品信息并处理支付流程。 注册商品标识的方法和注册程序的方法类似。
要了解如何创建和注册商品信息,请参考“iTunes Connect Developer Guide”文档。
3. 检测是否可以进行支付
用户可以禁用在程序内部支付的功能。在发送支付请求之前,程序应该检查该功能是否被开启。程序可在显示商店界面之前就检查该设置(没启用就不显示商店界面了),也可以在用户发送支付请求前再检查,这样用户就可以看到可购买的商品列表了。
4. 获得商品的信息
程序创建SKProductsRequest对象,用想要出售的商品的标识来初始化, 然后附加上对应的委托对象。 该请求的响应包含了可用商品的本地化信息。
5. 添加一个展示商品的界面
Store Kit不提供界面的类。 这个界面需要我们自己来设计并实现。
6. 为支付队列(payment queue)注册一个观察者对象
你的程序需要初始化一个transaction observer对象并把它指定为payment queue的观察者。
应该在程序启动的时候就添加好观察者,原因前面说过,重启后程序会继续上次未完的交易,这时就添加观察者对象就不会漏掉之前的交易信息。
7. 在MyStoreObserver类中执行paymentQueue: updatedTransactions: 方法。
这个方法会在有新的交易被创建,或者交易被更新的时候被调用。
上面的函数针对不同的交易返回状态,调用对应的处理函数。
8. 观察者对象在用户成功购买一件商品时,提供相应的内容,以下是在交易成功后调用的方法
交易成功的信息包含transactionIdentifier和transactionReceipt的属性。其中,transactionReceipt记录了支付的详细信息,这个信息可以帮助你跟踪、审(我们的)查交易,如果你的程序是用服务器来交付内容,transactionReceipt可以被传送到服务器,然后通过App Store验证交易。(之前提到的server模式,可以参考以前的图)
9. 如果交易是恢复过来的(restore),我们用这个方法来处理:
这个过程完成购买的过程类似。 恢复的购买内容提供一个新的交易信息,这个信息包含了新的transaction的标识和receipt数据。 如果需要的话,你可以把这些信息单独保存下来,供追溯审(我们的)查之用。但更多的情况下,在交易完成时,你可能需要覆盖原始的transaction数据,并使用其中的商品标识。
10. 交易过程失败的话,我们调用如下的方法:
通常情况下,交易失败的原因是取消购买商品的流程。 程序可以从error中读出交易失败的详细信息。
显示错误信息不是必须的,但在上面的处理方法中,需要将失败的交易从支付队列中移除。 一般来说,我们用一个对话框来显示错误信息,这时就应避免将用户取消购买这个error显示出来。
11. 组织好程序内“商店”的UI。当用户选择一件商品时, 创建一个支付对象,并放到队列中。
如果你的商店支持选择同一件商品的数量,你可以设置支付对象的quantity属性
本章中所示代码可用于内置型商品模式(Built-in)。 如果你的程序要使用服务器来发布商品,你需要负责设计和执行iPhone程序和你的服务器之间的通信。服务器应该验证数据并为程序提供内容。
验证store的收据
使用服务器来交付内容,我们还需要做些额外的工作来验证从Store Kit发送的收据信息。
重要信息:来自Store的收据信息的格式是专用的。 你的程序不应直接解析这类数据。可使用如下的机制来取出其中的信息。
验证App Store返回的收据信息
当交易完成时,Store Kit告知payment observer这个消息,并返回完成的transaction。 SKPaymentTransaction的transactionReceipt属性就包含了一个经过签名的收据信息,其中记录了交易的关键信息。你的服务器要负责提交收据信息来确定其有效性,并保证它未经过篡改。 这个过程中,信息被以JSON数据格式发送给App Store,App Store也以JSON的格式返回数据。
(大家可以先了解一下JSON的格式)
验证收据的过程:
1. 从transaction的transactionReceipt属性中得到收据的数据,并以base64方式编码。
2. 创建JSON对象,字典格式,单键值对,键名为&receipt-data&, 值为上一步编码后的数据。效果为:
3. 发送HTTP POST的请求,将数据发送到App Store,其地址为:
4. App Store的返回值也是一个JSON格式的对象,包含两个键值对, status和receipt:
如果status的值为0, 就说明该receipt为有效的。 否则就是无效的。
App Store的收据
发送给App Store的收据数据是通过对transaction中对应的信息编码而创建的。 当App Store验证收据时, 将从其中解码出数据,并以&receipt&的键返回。 返回的响应信息是JSON格式,被包含在SKPaymentTransaction的对象中(transactionReceipt属性)。Server可通过这些值来了解交易的详细信息。 Apple建议只发送receipt数据到服务器并使用receipt数据验证和获得交易详情。 因为App Store可验证收据信息,返回信息,保证信息不被篡改,这种方式比同时提交receipt和transaction的数据要安全。(这段得再看看)
表5-1为交易信息的所有键,很多的键都对应SKPaymentTransaction的属性。
备注:一些键取决于你的程序是链接到App Store还是测试用的Sandbox环境。更多关于sandbox的信息,请查看&Testing a Store&一章。
Table 5-1 购买信息的键:
购买商品的数量。对应SKPayment对象中的quantity属性
product_id
商品的标识,对应SKPayment对象的productIdentifier属性。
transaction_id
交易的标识,对应SKPaymentTransaction的transactionIdentifier属性
purchase_date
交易的日期,对应SKPaymentTransaction的transactionDate属性
original_-transaction_id
对于恢复的transaction对象,该键对应了原始的transaction标识
original_purchase_-date
对于恢复的transaction对象,该键对应了原始的交易日期
app_item_id
App Store用来标识程序的字符串。一个服务器可能需要支持多个server的支付功能,可以用这个标识来区分程序。链接sandbox用来测试的程序的不到这个值,因此该键不存在。
version_external_-identifier
用来标识程序修订数。该键在sandbox环境下不存在
iPhone程序的bundle标识
iPhone程序的版本号
测试Store功能
开发过程中,我们需要测试支付功能以保证其工作正常。然而,我们不希望在测试时对用户收费。 Apple提供了sandbox的环境供我们测试。
备注:Store Kit在模拟器上无法运行。 当在模拟器上运行Store Kit的时候,访问payment queue的动作会打出一条警告的log。测试store功能必须在真机上进行。
Sandbox环境
使用Sandbox环境的话,Store Kit并没有链接到真实的App Store,而是链接到专门的Sandbox环境。 SandBox的内容和App Store一致,只是它不执行真实的支付动作。 它会返回交易成功的信息。 Sandbox使用专门的iTunes Connect测试 账户。不能使用正式的iTunes Connect账户来测试。
要测试程序,需要创建一个专门的测试账户。你至少需要为程序的每个区域创建至少一个测试账户。详细信息,请查看iTunes Connect Developer Guide文档。
在Sandbox环境中测试
1. 在测试的iPhone上退出iTunes账户
Settings中可能会记录之前登录的账户,进入并退出。
重要信息:不能在Settings 程序中通过测试账户登录。
2. 运行程序
当你在程序的store中购买商品后,Store kit提示你去验证交易。用测试账户登录,并批准支付。 这样虚拟的交易就完成了。
在Sandbox中验证收据
验证的URL不同了:
本文来自http://xiongzhend./blog/static//2015年内购最全图文教程/p/86ac7d3b593a
本文已收录于以下专栏:
相关文章推荐
IAP内购整个流程
IAP,无法连接到iTunes store ,RMStore,SKErrorUnknown,Error Domain=com.alamofire.error.serialization.respons...
最近在研究苹果内购功能,所以,在网上找了一些资料,进行学习。但是,内购功能在实现的过程中,有很多坑,笔者算是真的遇到了好多啊,下面也是自己对内购的一些心得与体会吧!
我这里说的可能不太详尽,所以,我...
iOS 内购( IAP)处理流程, 漏单这边介绍的是服务端验证的内购漏单
漏单:正常玩家购买了却没有收到物品、且自己的服务端没有任何记录iOS的订单。iOS的补单是非常麻烦的,用户提供支付的截图中...
现在手机游戏免费模式+道具购买成为主流,所以iOS开发的游戏需要集成苹果的IAP功能。
当用户点击商店时,App会向App Store发送商品列表请求,并获得...
1. 验证逻辑在客户端实现:
// iapData 用户购成功的transactionReceipt
-(BOOL)putStringToItunes:(NSData*)iapDa...
最近研究了一下接入苹果iap,由于对于obj-c完全不懂,起初想用soomla接入,研究了发现,全是英语根本读不懂啊,然后想自己研究unity和ios交互自己写,先学习obj-c,奈何经理各种催只能想...
拒绝理由1.获取用户信息2.审核人对IAP商店的质疑—————–1.获取用户信息———————苹果认为用户信息涉及隐私,如果未经用户允许获取用户信息,会被reject。
但是,审核人员有可能过度使用...
据外媒消息报道称亚马逊这一世界最大的在线零售商即将在其应用商店中使用应用内置付费功能(注:in-app
purchases,以下简称IAP),目前IAP的获得的利润在应用收入中占了相当大的比例,已...
他的最新文章
讲师:刘文志
讲师:陈伟
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)最近开发一个项目涉及到内购, 也遇到过一些问题. 这里拿出来分享一下, 避免一些人走弯路.
开头先聊一聊最近苹果关于2017年新的审核机制和沸沸扬扬的微信和苹果的撕逼
1. 2017新的审核机制:
ipv6: 使用国内阿里云的app上架, 大都会遇到ipv6被拒的邮件:
方案1. 服务端解决:&
方案2. 客户端解决: 手机端配置ipv6环境测试, 录制APP内的操作视频, 上传到YouTobe,
将网址发送给审核人员即可通过审核 (ps: 录制时候一定要录制APP所在的网络环境: 设置中-&无线网络-&DNS: :aab1: :: 1 ,DNS为这种格式则为ipv6)
说一说这个项目内购有趣的事情:
a. 首先做这个项目的时候, 我们充值虚拟币方案定的是: 后台做一个开关, app在审核期间走苹果内购, 在上线后, 走微信和支付宝支付, 并向低版本兼容. 达到绕过苹果审核的目的. 结果被拒了, 邮件中提到了支付宝, 当时很懵逼, 就留下了老大的联系方式和苹果沟通, 第二天苹果打来电话: 说内购的同时不可以使用第三方支付. 由此看来: 第三方支付的相关相关代码或SDK被扫描到了. 遂移除掉, 只使用内购方式
b. 审核期间, 苹果发来一封邮件大概意思是问:&你们确定内购的最高价格是你们期望的吗? 回复以后才可以继续审核,
这里我的理解是: 我们的内购的最高价格定得很高149美元的那一档, 所以苹果要确认一下, 经过回复邮件说明了一下这个最高价格确定是我们自己定的最高价格,
没有错误, 第二天苹果又恢复了审核, 变成了审核中...
c. app被拒后, 内购项目变成了需要开发人员操作, 盗图一张:&
需要开发人员操作
这时候一般只需要进入需要开发人员操作的内购项目中, 修改一下描述,
重新提交即可, 然后重新提交app. (ps: 一般这里我只是将描述中添加或删除空格, 就可以重新提交了)
d. 关于项目中: app内购商品返回列表为空, 返回的都是无效产品
即: [response.products count]始终为0, [response.invalidProductIdentifiers] 有值
这个的原因是: 协议、税务和银行业务中必须通过才可以(盗图一张):
协议、税务和银行业务
2. 谈一谈微信和苹果的撕逼
新的审核协议将打赏列为了内购
我的观点和这个一样&
3. 闲话扯完了, 看一下怎么做内购并处理掉单问题:
苹果这一文中说明两点:
a. 在appdelegate中添加观察者, 在购买成功后提交给自己的服务器, 由自己服务器提交凭证到苹果服务器验证正确后, 返回给客户端之后, 这笔交易才完成, 这时候再queue.finishTransaction(transaction),
如果这期间苹果的服务器还没返回结果 或者 购买成功了,我们提交凭证给自己服务器的时候网断掉了(钱空了, 但是虚拟物品没有到账, 丢单了), 则这笔交易都没有完成, 方法queue.finishTransaction(transaction)都没有调用,
所以再次打开app的时候, 因为appdelegate中添加了观察者, 就会再次调用
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions
transactions: [SKPaymentTransaction])方法
b. 苹果推荐进入内购项目表单页面的时候先请求appstore,根据返回的可销售商品来进行展示(但是很多app的做法都是调用自己的接口取得商品价格列表进行展示, 但是我们不能确定我们自己的服务器返回的和苹果返回的不同), 这里非常抱歉的说明一下: 我们的app也是按照自己服务器的api返回的数据展示的商品价格列表, 哈哈哈
c. 关于内购和服务端的接口参数, 我们设置为:&
此次交易的用户的唯一标示符(accountID):&交易成功的凭证此次交易的订单号服务端也要处理重复请求该接口的情况(不要每次请求成功都给用户加钱..)
说明: 用户的唯一标示符的作用: 如果用户购买成功, 但是将凭证给自己服务端的时候断掉了, 然后自己切换了账号, 下次打开app的时候检测, 我们需要这个表示符知道谁买的..不要将虚拟货币充错用户
ios7 苹果增加了一个属性applicationusername,SKMutablepayment的属性,所以用户在发起支付的时候可以指定用户的username及自己生成的订单,这样用户再下次得到回调的时候就知道,此交易是哪个订单发起的了进而完成交易。回调中获取username。
上代码: (内购工具类)
import Foundation
import StoreKit
enum InpurchaseError: Error {
case noPermission
case noExist
case failTransactions
case noReceipt
typealias Order = (productIdentifiers: String, applicationUsername: String)
class Inpurchase: NSObject, SKPaymentTransactionObserver, SKProductsRequestDelegate {
static let `default` = Inpurchase()
var unFinishedTransaction: ((String, SKPaymentTransaction, SKPaymentQueue) -& ())?
private var sandBoxURLString = &https://sandbox./verifyReceipt&
private var buyURLString = &https://buy./verifyReceipt&
private var isComplete: Bool = true
private var products: [SKProduct] = []
private var failBlock: ((InpurchaseError) -& ())?
private var receiptBlock: ((String, SKPaymentTransaction, SKPaymentQueue) -& ())?
private var successBlock: (() -& Order)?
private override init() {
super.init()
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().remove(self)
func start(productIdentifiers: Set&String&,
successBlock: (() -& Order)? = nil,
receiptBlock: ((String, SKPaymentTransaction, SKPaymentQueue) -& ())? = nil,
failBlock: ((InpurchaseError) -& ())? = nil) {
guard isComplete else { return }
defer { isComplete = false }
let request = SKProductsRequest(productIdentifiers: productIdentifiers)
request.delegate = self
request.start()
self.successBlock = successBlock
self.receiptBlock = receiptBlock
self.failBlock = failBlock
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
products = response.products
guard let order = successBlock?() else { return }
buy(order)
private func buy(_ order: Order) {
let p = products.first { $0.productIdentifier == order.productIdentifiers }
guard let product = p else { failBlock?(.noExist); return }
guard SKPaymentQueue.canMakePayments() else { failBlock?(.noPermission); return }
let payment = SKMutablePayment(product: product)
payment.applicationUsername = order.applicationUsername
SKPaymentQueue.default().add(payment)
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
guard let receiptUrl = Bundle.main.appStoreReceiptURL,
let receiptData = NSData(contentsOf: receiptUrl) else { failBlock?(.noReceipt);return }
let receiptString = receiptData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
if let receiptBlock = receiptBlock {
receiptBlock(receiptString, transaction, queue)
unFinishedTransaction?(receiptString, transaction, queue)
isComplete = true
case .failed:
failBlock?(.failTransactions)
queue.finishTransaction(transaction)
isComplete = true
case .restored:
queue.finishTransaction(transaction)
isComplete = true
appdelegate中的监听使用方式:&
appdelegate中:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -& Bool {
Inpurchase.default.unFinishedTransaction = {(receipt, transaction, queue) in
let data = InpurchaseAPIData(accountID: transaction.payment.applicationUsername,
transactionID: transaction.transactionIdentifier,
receiptData: receipt)
LPNetworkManager.request(Router.verifyReceipt(data)).showToast().loading(in: self.view).success {[weak self] in
showToast(&恢复购买成功&)
queue.finishTransaction(transaction)
print(&向服务器发送凭证失败&)
return true
点击购买的代码:&
let productIdentifiers: Set&String& = [&a&, &b&, &c&]
Inpurchase.default.start(productIdentifiers: productIdentifiers, successBlock: { () -& Order in
return (productIdentifiers: &a&, applicationUsername: &该用户的id或改用户的唯一标识符&)
}, receiptBlock: { (receipt, transaction, queue) in
let data = InpurchaseAPIData(accountID: transaction.payment.applicationUsername,
transactionID: transaction.transactionIdentifier,
receiptData: receipt)
LPNetworkManager.request(Router.verifyReceipt(data)).showToast().loading(in: self.view).success {[weak self] in
showToast(&购买成功&)
queue.finishTransaction(transaction)
print(&向服务器发送凭证失败&)
}, failBlock: { (error) in
print(error)
&能点个star也是极好的, 打不打赏无所谓, 能帮到你就好
还有一种实践方式, 个人并不推荐, 因为太繁琐了:
思路: 购买成功后在本地将订单的用户, 凭证等信息存储到本地(UserDefaults, 数据库,keyChain等), 将凭证发送给自己服务器成功之后再移除此条交易记录, 每次打开app的时候, 在本地扫描是否有未完成的订单, 循环发送给自己的服务器进行二次验证
错误做法: 上线审核的时候使用沙箱测试地址, 审核通过后, 手动发布上线, 上线后让服务器切换到苹果的正式测试地址&
说明: 这种做法第一次上架可以使用, 但是到第二次迭代审核的时候, 苹果测试员使用的是沙盒环境, 但是我们服务器是正式环境, 会导致报错误码: 21007
正确的做法: 判断苹果正式验证服务器的返回code,如果是21007&表示环境不对,则再一次连接测试服务器进行验证即可..
(这一步骤即: 先判断苹果的环境, 根据苹果环境切换沙盒地址还是正式地址)
关于苹果二次验证返回的参数:
服务端\客户端对苹果发送请求进行验证有时会返回多个交易记录
说明: 苹果验证会返回: 一个未完成交易的数组(一般只有一个, 就是当前操作购买的这个), 如果有多个为完成的交易,就会返回多个 (这种情况一般是代码写的不对造成的), 服务端根据transactionIdentifier找到当前购买的交易或者取最后一个也是当前购买的交易来做判断和验证....经过测试发现如果在当前手机请求发现出现多个未完成的交易,
则换另外一部手机和账号等, 仍然会返回那些未完成的交易, 看来每次对商品进行购买, 苹果会把所有未完成的交易都返回(不管这个商品是其他用户的还是其他手机的)
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:30251次
排名:千里之外
原创:14篇
转载:34篇
(1)(5)(1)(2)(3)(5)(1)(1)(2)(1)(3)(2)(2)(1)(3)(5)(11)
(window.slotbydup = window.slotbydup || []).push({
id: '4740887',
container: s,
size: '250,250',
display: 'inlay-fix'}

我要回帖

更多关于 苹果内购退款流程 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信