一、IAP
In-App Purchase,应用内购买。注意,IAP与ApplePay不是同一回事,ApplePay纯粹是一种支付方式,同微信支付、支付宝类似;而IAP是一种App内部购买的方式,购买时付款的方式是需要提前在苹果帐户中设置的,可以选择微信支付、支付宝、ApplePay、银行卡等。
二、4种商品类型
(1)消耗性商品:使用后就被消耗掉,需要再购买的商品。比如,钓鱼App中的鱼食,App中的金币;
(2)非消耗性商品:购买后可永久使用的商品。比如,读书App中的书籍,购买后可永久阅读;游戏App中的赛道;
(3)自动续期订阅商品:允许在固定时间段内购买动态内容的商品,如果购买后不手动取消,将自动续期自动付费。比如,每月订阅提供流媒体服务的App;
(4)非自动续期订阅商品:有时限性服务的商品,不自动续期。比如,App提供的VIP特权服务;
三、开发步骤:
(1)在iTunesConnect中填写协议、税务、银行等信息,创建配置内购商品,将商品添加到App中;
(2)Xcode中配置内购
(3)编写代码
(4)沙箱测试
(5)提审上线
四、内购流程:
(1)从本地或服务器获取可购买的商品ID列表;
(2)从App Store获取商品ID对应的商品信息;
(3)用户选择某一商品点击购买按钮后,请求服务端生成订单
(4)应用获取订单号后,向App Store发起交易请求,并监听交易状态;
(5)App Store处理交易请求,从苹果账户扣款;
(6)App Store交易成功,应用监听到交易成功状态;交易失败,则移除交易并提示购买失败;
(7)应用获取到交易票据;
(8)应用将票据和订单号提交到服务器,由服务器向App Store请求验证票据。注意对于沙盒环境和正式环境,App Store验证票据的URL是不同的;
(9)服务器判断订单号不存在,或票据中的交易号已存在,则通知应用移除交易,购买失败;
(9)验证成功后,服务器保存用户购买记录(订单号、交易号、用户账号等),并向用户发放相应商品,即修改服务器上用户的数据,并通知应用购买成功更新界面;如果验证失败,也通知应用;
(10)应用收到票据验证成功的反馈后,标记该交易完成状态,提示用户购买成功,并更新界面,并删除本地票据;
(11)应用收到票据验证失败的反馈后,如果错误码为21007,说明当前票据为沙盒测试票据,但却连接App Store正式环境进行了票据验证,故需要连接App Store沙盒环境再次验证票据;
(12)应用收到票据验证失败的反馈,如果确定票据无效,可提示购买失败,并标记交易完成,以便将交易移出交易队列;对于需要重新验证的票据,可以隔一段时间或者App重启时重新请求验证,直到验证成功才标记交易完成。
10票据验证:
(1)两种票据内容。一种是付费下载的App的票据;另一种是App内部购买商品的票据;
(2)从App Store安装App后,会自动在App的沙盒的StoreKit目录下创建票据文件receipt;
(3)票据文件receipt中,内购项目可能为空,也可能同时存在多个内购项目,只要数据正确,验证票据都会成功;
(4)对于购买的订阅类商品、非消耗性商品,购买信息会永久保存在票据文件中;
(5)对于消耗性商品,购买信息在交易付款后保存到票据中,一旦交易被标识为完成,该信息会在请求刷新票据,或者下次交易更新票据时被清除掉;
(6)iOS7以后,票据数据可以从沙盒中读取;iOS7以前,票据数据可以从交易对象的transactionReceipt属性中获取;如果本地没有票据数据,可以向App Store请求票据数据;
App Store票据验证接口参数:
(1)"receipt-data":base64编码的票据数据data;
(2)"password": 共享密码,仅在购买自动续期订阅商品时需要;
App Store票据验证返回JSON格式:
(1)"status": 0为验证成功;
(2)"receipt": 提交的票据原数据;
Receipt票据数据格式:
{
"adam_id" = 0;
"app_item_id" = 0;
"application_version" = 1;
"bundle_id" = "com.test.xxx";
"download_id" = 0;
"in_app" = (
{
"is_trial_period" = false;
"original_purchase_date" = "2017-12-14 07:18:56 Etc/GMT";
"original_purchase_date_ms" = 1513235936000;
"original_purchase_date_pst" = "2017-12-13 23:18:56 America/Los_Angeles";
"original_transaction_id" = 1000000359369424;
"product_id" = "com.test.product1";
"purchase_date" = "2017-12-14 07:18:56 Etc/GMT";
"purchase_date_ms" = 1513235936000;
"purchase_date_pst" = "2017-12-13 23:18:56 America/Los_Angeles";
quantity = 1;
"transaction_id" = 1000000359369424;
},
...
);
"original_application_version" = "1.0";
"original_purchase_date" = "2013-08-01 07:00:00 Etc/GMT";
"original_purchase_date_ms" = 1375340400000;
"original_purchase_date_pst" = "2013-08-01 00:00:00 America/Los_Angeles";
"receipt_creation_date" = "2017-12-14 07:18:56 Etc/GMT";
"receipt_creation_date_ms" = 1513235936000;
"receipt_creation_date_pst" = "2017-12-13 23:18:56 America/Los_Angeles";
"receipt_type" = ProductionSandbox;
"request_date" = "2017-12-14 07:19:23 Etc/GMT";
"request_date_ms" = 1513235963829;
"request_date_pst" = "2017-12-13 23:19:23 America/Los_Angeles";
"version_external_identifier" = 0;
};
服务端验证接口参数:
(1)base64编码的票据数据字符串;
(2)服务端订单号;
(3)验证环境标识(沙盒环境或正式环境);
(4)用户ID;
(5)所有接口参数应该加密后传输;
服务端验证逻辑:
(1)解密接口参数,提取出票据数据、订单号、验证环境、用户ID;
(2)如果票据数据为空,或验证环境参数错误,返回参数错误的错误码;否则进行下一步;
(3)查询订单是否存在:
(3.1)如果订单存在且已完成(订单对应交易ID已存在),则无需再验证票据数据。如果用户ID与订单所属用户相同,则返回用户的所有商品信息和重复验证票据的返回码,否则只返回重复验证票据的返回码而不返回用户所有商品信息;
(3.2)如果订单存在且未完成(订单对应交易ID不存在),则进入下一步验证票据数据;
(3.3)如果订单不存在,则查询票据中交易ID是否已有对应订单。如果没有订单,则返回订单不存在的错误码;如果有订单,则进入下一步验证票据数据;
(4)验证票据数据。调用苹果提供的票据验证接口进行验证。沙盒环境验证接口:https://sandbox.itunes.apple.com/verifyReceipt,正式环境验证接口:https://buy.itunes.apple.com/verifyReceipt。如果验证失败,将苹果返回的状态码作为错误码返回给客户端,如果验证成功,则向订单对应用户发放商品;如果验证成功(状态码为0)且用户ID与订单所属用户相同,则同时返回用户所有商品信息;
服务端验证接口返回数据:
(1)用户当前已购买的所有商品信息;
(2)接口错误信息;
(3)错误码:一类是服务器自身的报错;另一类是App Store返回的错误码。
漏单处理:
漏单,即用户付款成功,但没有收到购买的商品的情况。如果交易在App Store中尚未完成,则应用定时或重启后会继续处理该交易。如果交易已完成,则用户只能向客服反馈,提供苹果账号和单号,确认漏单后补发商品。
注意点:
(1)内购必须使用真机测试,测试时先退出真机上的AppleID,在App内购买时填写沙箱测试账号;
(2)游客模式下,App中不要出现商品相关信息;
(3)等待请求交易的时候,如果App重启,则重启后App Store仍会向监听对象发送交易状态反馈;如果交易成功后没有被标识为finished,App Store仍会向监听对象发送交易状态;