博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
内购开发指南
阅读量:6591 次
发布时间:2019-06-24

本文共 9897 字,大约阅读时间需要 32 分钟。

hot3.png

关于内购

内购的收益是三七分成。

关于内购产品

内购产品可以是内容(比如数字内容)、功能(比如去除广告)或服务(比如语音编辑),不能用于真实物品与服务、不合适的内容(比如色情作品等)。

内购产品类型有:自动续期订阅、非续期订阅、非消耗型项目、消耗型项目。

区别 自动续期订阅 非续期订阅 非消耗型项目 消耗型项目
用户可购买次数 多次 多次 多次 一次
凭据中购买记录信息 一直 一直 一直 一次
跨设备同步 系统控制 App控制 系统控制 不存在
恢复 系统控制 App控制 系统控制 不存在

关于自动续期订阅

Apple会在订阅到期前10天检查续订可行性,会在自动续期订阅到期前24小时开始尝试续订,如果续订失败,AppStore可能还会继续尝试,连续续订多次后就不会再次尝试,从时间上最多尝试60天。

在测试环境中自动续期订阅会比正常的要快,而且每天最多续订6次。

内购产品UI设计

  • 只有用户能购买的时候才展示商店
  • 很自然的过渡到产品界面
  • 产品有组织化能方便用户找到
  • 向用户传达你的产品的价值
  • 产品价格清楚显示,根据AppStore返回的地区和价格单位来显示

关于凭据

iTunes Connect 配置

配置协议、税务银行业务

配置用户和职能,在其中配置测试账号

  • 测试账号不要在正式环境登陆,已登陆就会永久失效

内购产品的配置

  • 内购产品可以删除,但是删除后的内购产品ID依然不可再次使用
  • 内购产品不完整会显示元数据丢失
  • 内购产品跨平台不能使用,比如iOS上买的,在macOS不可用

场景

在应用中购买内购产品

  • 根据服务器或本地存储的内购产品ID构造SKProductsRequest请求,使用SKProductsRequestDelegate获取产品的信息SKProduct,比如产品的描述、产品的价格
    • 以Apple返回的产品列表为准,因为有的产品可能会变成无效
  • 使用[SKPaymentQueue canMakePayments]判断用户是否启用程序内购买
  • 根据SKProduct构造SKPayment,使用-[SKPaymentQueue addPayment:]开始购买,使用SKPaymentTransactionObserver监听交易结果
    • 如果购买成功,那么记录交易情况,发送产品(比如下载附件),然后结束交易,结束监听
    • 如果购买失败,结束交易,结束监听
    • 如果已购买,那么需要做 恢复购买 处理,然后结束监听

在AppStore购买内购产品

  • 使用SKProductStorePromotionController控制 Promoted Products 的可见性和显示顺序(本地存储)
    • 使用-[SKProductStorePromotionController fetchStorePromotionVisibilityForProduct:completionHandler:]读取可见性
    • 使用-[SKProductStorePromotionController updateStorePromotionVisibility:forProduct:]设置可见性
    • 使用-[SKProductStorePromotionController fetchStorePromotionOrderCompletionHandler:]读取显示顺序
    • 使用-[SKProductStorePromotionController updateStorePromotionOrder:completionHandler:]设置显示顺序
      • 传进来的数组中的产品显示在列表的开头,然后才是剩余的内购产品
      • 如果传进来的是空数组,那么就表示默认显示顺序
  • 使用-[SKPaymentTransactionObserver paymentQueue:shouldAddStorePayment:forProduct:]延迟或取消购买
  • 使用-[SKPaymentQueue addPayment:]开始购买,使用SKPaymentTransactionObserver监听交易结果,后续操作参考 在应用中购买内购产品
  • 使用itms-services://?action=purchaseIntent&bundleId=<com.example.app>&productIdentifier=<product_name>来测试

从AppStore下载内购产品的附件

  • 使用-[SKPaymentQueue startDownloads:]下载附件
  • 使用-[SKPaymentTransactionObserver paymentQueue:updatedDownloads:]监听下载过程
  • 使用pauseDownloads:resumeDownloads:cancelDownloads:来操作下载
  • 使用SKDownloadcontentURLcontentURLForProductID:deleteContentForProductID:来读取或删除下载文件

自动续订订阅的服务器到服务器通知

要接收状态更新通知,请在App Store Connect中为您的应用配置通知URL。App Store将通过HTTP POST将JSON对象传送到您的服务器。

字段 类型 备注
environment string Sandbox-沙盒环境 PROD-正式环境
notification_type string INITIAL_BUY-订阅初次购买 CANCEL-取消交易 RENEWAL-已过期的订阅续订成功 INTERACTIVE_RENEWAL-续订被延迟的订阅 DID_CHANGE_RENEWAL_PREF-用户修改了订阅计划,会影响下次的订阅
password string 内购产品密钥
original_transaction_id string 初次交易的id
cancellation_date string, interpreted as an RFC 3339 date 取消交易的时间,仅当通知类型为CANCEL时返回
web_order_line_item_id string 用于区分订阅内购,仅当通知类型为CANCEL时返回
latest_receipt string 最新的交易凭据,仅当通知类型为RENEWAL或INTERACTIVE_RENEWAL时返回
latest_receipt_info [] 最新的交易凭据中的信息,当通知类型为CANCEL时不返回,其他情况会返回
latest_expired_receipt string 最新的过交易凭据,仅当有过期交易凭据时返回
latest_expired_receipt_info string 最新的交易凭据中的信息,仅当通知类型为RENEWAL或CANCEL或订阅过期且续订失败时返回
auto_renew_status string 是否自动续订 true-是 false-否
auto_renew_adam_id string 内购产品的数字序号
auto_renew_product_id string 内购产品的id
expiration_intent string 过期原因,仅当通知类型为RENEWAL或INTERACTIVE_RENEWAL时返回

从字段备注上来看,这里比较混乱,最好以实际返回的结果为准

我方服务器应返回HTTP状态代码200。如果您的服务器发送50x或40x HTTP代码,App Store将重试该通知。 App Store会在一段时间内多次尝试重试通知,但如果尝试失败次数过多,最终会停止。

AppStore会在自动续期订阅到期前24小时开始尝试续订,如果续订成功,将没有通知;如果续订失败,AppStore可能还会继续尝试,连续续订多次后就不会再次尝试,如果继续尝试成功了,而此时订阅已经过期,那么就会发送续订成功的通知。

管理订阅

通过页面 来管理订阅。

监听交易

添加持久的交易监听器,就能在app启动或从后台唤醒的时候收到续订通知。

刷新凭据

使用SKReceiptRefreshRequest来刷新凭据

  • 成功购买了内购
  • 调用了SKReceiptRefreshRequest
  • 调用了restoreCompletedTransactions

恢复已完成的交易

恢复已完成的交易,是恢复自动续期订阅、免费订阅或恢复非消耗型项目。

需要的恢复购买的场景有:

  • Apple用户在其他的设备上登陆,然后安装应用
  • 内购对应的应用被卸载了,重新安装应用

调用 restoreCompletedTransactions 来进行恢复,恢复结果中没有任何项目的可能原因有:

  • 有未完成的交易
  • 根本就没有购买过自动续期订阅、免费订阅或恢复非消耗型项目
  • 在恢复不具备恢复能力的项目,比如非续期订阅、消耗型项目
  • 版本号 CFBundleVersion 不规范

恢复交易不会创建新的交易。

取消交易

取消交易,是针对自动续期订阅、非续期订阅、非消耗型项目。

cancellation_date字段,目前只有自动续期订阅这种取消交易才会有,而非续期订阅、非消耗型项目取消交易的情况,除非Apple专门操作,否者默认是没有的。

验证凭据

获取凭据

  • 通过 NSBundle 的 appStoreReceiptURL 获取凭据
  • 在老版本macOS,可以通过 /Contents/_MASReceipt/receipt 路径获取凭据
  • 在老版本iOS,可以通过 SKPaymentTransaction 的 transactionReceipt 属性获取凭据

本地验证

可以在 main 方法中,在调用 NSApplicationMain 方法之前进行验证,或者出于一些特别的安全要求,可以在程序运行之前进行验证。

asn1c

可以通过 asn1c 工具生成解析凭据中的信息的程序,此工具可以在 和 下载。下载后通过命令 asn1c -fnative-types filename 生成解析代码。

计算GUID的哈希值
  • 在macOS上,参考
  • 在iOS上,以 UIDevice 的 identifierForVendor 作为GUID

将获得的GUID跟包名、type为4的属性的值联系起来,直接使用凭据的原始字节数据,不做任何UTF-8的字符串转化,然后就算拼接起来的字节数据的 SHA-1 值。

验证步骤
  1. 凭据是否有数据
  2. 凭据是否是Apple签名的
  3. 凭据中的包名是否匹配Info.plist中的 CFBundleIdentifier
  4. 凭据中的版本号是否匹配Info.plist中的 CFBundleShortVersionString(macOS)或 CFBundleVersion(iOS)
  5. 计算GUID的哈希值是否正确

凭据中包含的信息为:

ASN.1 Field Type ASN.1 Field Value 备注
2 UTF8STRING 应用的BundleID
3 UTF8STRING 应用的版本号
4 A series of bytes 计算用于验证的哈希值时会用到
5 20-byte SHA-1 digest 用于验证凭据
17 [17_object] 一组内购信息
18 UTF8STRING 首次购买时的应用版本号,沙盒环境下始终是 1.0
12 IA5STRING, interpreted as an RFC 3339 date 凭据创建时间,用于验证,避免本地时间有问题
21 IA5STRING, interpreted as an RFC 3339 date 过期时间,只有通过Volume Purchase Program购买的才会有这个字段
17_object
ASN.1 Field Type ASN.1 Field Value 备注
1701 INTEGER 内购的数量,跟 SKPayment 的 quantity 呼应
1702 UTF8STRING 内购的项目ID,跟 SKPayment 的 productIdentifier 呼应
1703 UTF8STRING 内购的交易ID,跟 transactionIdentifier 呼应
1704 IA5STRING, interpreted as an RFC 3339 date 内购的购买时间或更新时间
1705 UTF8STRING 内购的初始交易ID
1706 IA5STRING, interpreted as an RFC 3339 date 内购的初始购买时间
1708 IA5STRING, interpreted as an RFC 3339 date 订阅的过期时间,只有自动续期订阅有
1719 INTEGER 是否处于介绍价格期,只有自动续期订阅有,1-是 0-否
1712 IA5STRING, interpreted as an RFC 3339 date 取消交易的时间,由Apple客户支持来取消交易
1711 INTEGER 用于区分跨设备的购买事件,包括订阅续订事件
验证失败处理
  • 在macOS上验证失败码,需要调用 exit(173) ,系统会自动尝试申请新凭据、提示错误信息(前提是系统版本不小于10.6.6)
  • 在iOS上验证失败,需要调用 SKReceiptRefreshRequest 类来刷新凭据
保护验证过程
  • 内联验证代码,而不是使用系统提供的API
  • 代码强化技术,比如混淆

远程验证

请求
  • URL
    • 沙盒环境:测试和审核的时候,用 接口进行验证
    • 正式环境:在AppStore销售的时候,用 接口进行验证
  • Method
    • POST
  • Header
    • JSON
  • Body
字段 类型 备注
receipt-data string base64编码的字符串
password string 内购密钥
exclude-old-transactions string,枚举 用于自动续期订阅、非续期订阅。true,表示返回数据只是最新交易信息;false,表示返回数据包含旧交易信息
响应
字段 类型 备注
status 整数 参考 StatusCode
receipt receipt 凭据信息
latest_receipt string 最新的base64编码的字符串
latest_receipt_info [latest_receipt_info]
latest_expired_receipt_info 只有iOS 6交易版本的自动续期订阅会出现
pending_renewal_info 只有iOS 7交易版本的自动续期订阅会出现,存放的是尝试续订的结果信息
is-retryable 只有状态码为21000-21199时会出现
receipt
字段 类型 备注
bundle_id string 应用的BundleID
application_version string 应用的版本号
in_app [in_app] 一组内购信息
original_application_version string 首次购买时的应用版本号,沙盒环境下始终是 1.0
receipt_creation_date IA5STRING, interpreted as an RFC 3339 date 凭据创建时间,用于验证,避免本地时间有问题
expiration_date IA5STRING, interpreted as an RFC 3339 date 过期时间,只有通过Volume Purchase Program购买的才会有这个字段
latest_receipt_info
in_app
字段 类型 备注
quantity string 内购的数量,跟 SKPayment 的 quantity 呼应
product_id string 内购的项目ID,跟 SKPayment 的 productIdentifier 呼应
transaction_id string 内购的交易ID,跟 transactionIdentifier 呼应
original_transaction_id string 内购的初始交易ID
purchase_date string, interpreted as an RFC 3339 date 内购的购买时间或更新时间
original_purchase_date string, interpreted as an RFC 3339 date 内购的初始购买时间
expires_date string, interpreted as an RFC 3339 date 订阅的过期时间,只有自动续期订阅有
expiration_intent string 过期原因,只有自动续期订阅有,-用户取消 2-账单错误,比如用户支付信息不再有效 3-用户不同意最近的价格上涨 4-续订后产品不可用 5-未知错误
is_in_billing_retry_period string 只有自动续期订阅有,1-仍在尝试续订 0-停止尝试续订
is_trial_period string 是否处于试用期,只有自动续期订阅有,true-是 false-否
is_in_intro_offer_period string 是否处于介绍价格期,只有自动续期订阅有,true-是 false-否
cancellation_date string, interpreted as an RFC 3339 date 取消交易的时间,由Apple客户支持来取消交易
cancellation_reason string 取消交易的理由,1-app有问题 0-其他原因,比如客户意外购买
app_item_id string 用于区分应用的,沙盒环境不会出现
version_external_identifier string 应用的版本,沙盒环境不会出现
web_order_line_item_id string 用于区分跨设备的购买事件,包括订阅续订事件
auto_renew_status string 1-将自动续订 0-不会自动续订
auto_renew_product_id string 内购项目id,只有自动续期订阅有
price_consent_status string 只有自动续期订阅有,1-用户同意价格上涨 0-用户没有做任何动作,那么订阅将不会续订
StatusCode
字段 备注
21000 上传数据无法json解析
21002 上传数据中 receipt-data 无法解析
21003 凭据无法验证
21004 密钥不匹配
21005 凭据服务器暂时不可用
21006 凭据有效,但是订阅过期(iOS 6之前会这样),凭据信息也会返回
21007 凭据来自沙盒环境,而当前是正式环境
21008 凭据来自正式环境,而当前是沙盒环境
21010 当作购买没有发生来处理
21100-21199 其他的数据错误

可以始终用正式环境的接口进行验证,如果接口返回21007状态码,那么就表示你所传的凭据是沙盒环境的凭据,然后再用沙盒环境的接口去进行验证;如果接口返回0状态码,表示验证成功。

如果购买成功,调用接口返回的 in_app 为空数组,那么就需要更新凭据,通过 SKReceiptRefreshRequest 类来更新凭据。

如果是自动续期订阅、非续期订阅、非消耗型项目,那么它们的信息会一直保留在凭据中(之前买的信息都会在);如果是消耗型项目,那么它的信息将一直保留到你完成交易,在完成交易后,信息在下次凭据更新就会被删除掉,比如有新的购买或者强制刷新。

丢失凭据

在有些情况下,凭据会丢失,appStoreReceiptURL 会返回为nil。当这种情况发生的时候,调用SKReceiptRefreshRequest来更新凭据,此更新可能会成功,也可能失败,成功会执行 requestDidFinish ,失败会执行 didFailWithError 。

限制

每个开发者账户可在该账户的所有App中创建至多10000个App内购项目产品。

兼容性

  • 自动续订型订阅:iOS >= 4.2, macOS >= 10.9

安全

反信用欺诈

  • SKPayment类的applicationUsername属性:用自己服务器的账号名哈希后的字符串来填充
#import 
// Custom method to calculate the SHA-256 hash using Common Crypto- (NSString *)hashedValueForAccountName:(NSString*)userAccountName{ const int HASH_SIZE = 32; unsigned char hashedChars[HASH_SIZE]; const char *accountName = [userAccountName UTF8String]; size_t accountNameLen = strlen(accountName); // Confirm that the length of the user name is small enough // to be recast when calling the hash function. if (accountNameLen > UINT32_MAX) { NSLog(@"Account name too long to hash: %@", userAccountName); return nil; } CC_SHA256(accountName, (CC_LONG)accountNameLen, hashedChars); // Convert the array of bytes into a string showing its hex representation. NSMutableString *userAccountHash = [[NSMutableString alloc] init]; for (int i = 0; i < HASH_SIZE; i++) { // Add a dash every four bytes, for readability. if (i != 0 && i%4 == 0) { [userAccountHash appendString:@"-"]; } [userAccountHash appendFormat:@"%02x", hashedChars[i]]; } return userAccountHash;}
// product is an SKProduct object.SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; //Populate applicationUsername with your customer's username on your server.payment.applicationUsername = [self hashedValueForAccountName:@"userNameOnYourServer"]; // Submit payment request.[[SKPaymentQueue defaultQueue] addPayment:payment];

参考

转载于:https://my.oschina.net/swustyc/blog/3021959

你可能感兴趣的文章
lamp 一键安装
查看>>
SQL Server 2008 收缩日志(log)文件
查看>>
UICollectionView基础
查看>>
SSAS中CUBE行权限数据级权限控制
查看>>
android学习记录(三)百度地图错误---只有一个电话显示帧,没有地图内容。
查看>>
BZOJ2794 : [Poi2012]Cloakroom
查看>>
【Eclipse】安装subclipse的Eclipse插件
查看>>
Git查看、删除、重命名远程分支和tag【转】
查看>>
浅谈IM软件业务知识——非对称加密,RSA算法,数字签名,公钥,私钥
查看>>
Oracle中REGEXP_SUBSTR及其它支持正则表达式的内置函数小结
查看>>
正确计算linux系统内存使用率
查看>>
关于MapReduce单词统计的例子:
查看>>
【php】利用php的构造函数与析构函数编写Mysql数据库查询类 (转)
查看>>
导出DLLRegisterServer接口遇到的问题
查看>>
压缩算法
查看>>
ios和android的发展前景比较
查看>>
[转载]SpringMVC的Model参数绑定方式
查看>>
Linux socket多进程服务器框架三
查看>>
Debug.print的用法
查看>>
常用名词
查看>>