内购开发指南

关于内购

内购的收益是三七分成。

关于内购产品

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

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

区别自动续期订阅非续期订阅非消耗型项目消耗型项目
用户可购买次数多次多次多次一次
凭据中购买记录信息一直一直一直一次
跨设备同步系统控制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对象传送到您的服务器。

字段类型备注
environmentstringSandbox-沙盒环境 PROD-正式环境
notification_typestringINITIAL_BUY-订阅初次购买 CANCEL-取消交易 RENEWAL-已过期的订阅续订成功 INTERACTIVE_RENEWAL-续订被延迟的订阅 DID_CHANGE_RENEWAL_PREF-用户修改了订阅计划,会影响下次的订阅
passwordstring内购产品密钥
original_transaction_idstring初次交易的id
cancellation_datestring, interpreted as an RFC 3339 date取消交易的时间,仅当通知类型为CANCEL时返回
web_order_line_item_idstring用于区分订阅内购,仅当通知类型为CANCEL时返回
latest_receiptstring最新的交易凭据,仅当通知类型为RENEWAL或INTERACTIVE_RENEWAL时返回
latest_receipt_info[]最新的交易凭据中的信息,当通知类型为CANCEL时不返回,其他情况会返回
latest_expired_receiptstring最新的过交易凭据,仅当有过期交易凭据时返回
latest_expired_receipt_infostring最新的交易凭据中的信息,仅当通知类型为RENEWAL或CANCEL或订阅过期且续订失败时返回
auto_renew_statusstring是否自动续订 true-是 false-否
auto_renew_adam_idstring内购产品的数字序号
auto_renew_product_idstring内购产品的id
expiration_intentstring过期原因,仅当通知类型为RENEWAL或INTERACTIVE_RENEWAL时返回

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

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

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

管理订阅

通过页面 https://buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/manageSubscriptions 来管理订阅。

监听交易

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

刷新凭据

使用SKReceiptRefreshRequest来刷新凭据

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

恢复已完成的交易

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

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

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

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

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

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

取消交易

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

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

验证凭据

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

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

asn1c

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

计算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 TypeASN.1 Field Value备注
2UTF8STRING应用的BundleID
3UTF8STRING应用的版本号
4A series of bytes计算用于验证的哈希值时会用到
520-byte SHA-1 digest用于验证凭据
17[17_object]一组内购信息
18UTF8STRING首次购买时的应用版本号,沙盒环境下始终是 1.0
12IA5STRING, interpreted as an RFC 3339 date凭据创建时间,用于验证,避免本地时间有问题
21IA5STRING, interpreted as an RFC 3339 date过期时间,只有通过Volume Purchase Program购买的才会有这个字段
17_object
ASN.1 Field TypeASN.1 Field Value备注
1701INTEGER内购的数量,跟 SKPayment 的 quantity 呼应
1702UTF8STRING内购的项目ID,跟 SKPayment 的 productIdentifier 呼应
1703UTF8STRING内购的交易ID,跟 transactionIdentifier 呼应
1704IA5STRING, interpreted as an RFC 3339 date内购的购买时间或更新时间
1705UTF8STRING内购的初始交易ID
1706IA5STRING, interpreted as an RFC 3339 date内购的初始购买时间
1708IA5STRING, interpreted as an RFC 3339 date订阅的过期时间,只有自动续期订阅有
1719INTEGER是否处于介绍价格期,只有自动续期订阅有,1-是 0-否
1712IA5STRING, interpreted as an RFC 3339 date取消交易的时间,由Apple客户支持来取消交易
1711INTEGER用于区分跨设备的购买事件,包括订阅续订事件
验证失败处理
  • 在macOS上验证失败码,需要调用 exit(173) ,系统会自动尝试申请新凭据、提示错误信息(前提是系统版本不小于10.6.6)
  • 在iOS上验证失败,需要调用 SKReceiptRefreshRequest 类来刷新凭据
保护验证过程
  • 内联验证代码,而不是使用系统提供的API
  • 代码强化技术,比如混淆
远程验证
请求
字段类型备注
receipt-datastringbase64编码的字符串
passwordstring内购密钥
exclude-old-transactionsstring,枚举用于自动续期订阅、非续期订阅。true,表示返回数据只是最新交易信息;false,表示返回数据包含旧交易信息
响应
字段类型备注
status整数参考 StatusCode
receiptreceipt凭据信息
latest_receiptstring最新的base64编码的字符串
latest_receipt_info[latest_receipt_info]
latest_expired_receipt_info只有iOS 6交易版本的自动续期订阅会出现
pending_renewal_info只有iOS 7交易版本的自动续期订阅会出现,存放的是尝试续订的结果信息
is-retryable只有状态码为21000-21199时会出现
receipt
字段类型备注
bundle_idstring应用的BundleID
application_versionstring应用的版本号
in_app[in_app]一组内购信息
original_application_versionstring首次购买时的应用版本号,沙盒环境下始终是 1.0
receipt_creation_dateIA5STRING, interpreted as an RFC 3339 date凭据创建时间,用于验证,避免本地时间有问题
expiration_dateIA5STRING, interpreted as an RFC 3339 date过期时间,只有通过Volume Purchase Program购买的才会有这个字段
latest_receipt_info
in_app
字段类型备注
quantitystring内购的数量,跟 SKPayment 的 quantity 呼应
product_idstring内购的项目ID,跟 SKPayment 的 productIdentifier 呼应
transaction_idstring内购的交易ID,跟 transactionIdentifier 呼应
original_transaction_idstring内购的初始交易ID
purchase_datestring, interpreted as an RFC 3339 date内购的购买时间或更新时间
original_purchase_datestring, interpreted as an RFC 3339 date内购的初始购买时间
expires_datestring, interpreted as an RFC 3339 date订阅的过期时间,只有自动续期订阅有
expiration_intentstring过期原因,只有自动续期订阅有,-用户取消 2-账单错误,比如用户支付信息不再有效 3-用户不同意最近的价格上涨 4-续订后产品不可用 5-未知错误
is_in_billing_retry_periodstring只有自动续期订阅有,1-仍在尝试续订 0-停止尝试续订
is_trial_periodstring是否处于试用期,只有自动续期订阅有,true-是 false-否
is_in_intro_offer_periodstring是否处于介绍价格期,只有自动续期订阅有,true-是 false-否
cancellation_datestring, interpreted as an RFC 3339 date取消交易的时间,由Apple客户支持来取消交易
cancellation_reasonstring取消交易的理由,1-app有问题 0-其他原因,比如客户意外购买
app_item_idstring用于区分应用的,沙盒环境不会出现
version_external_identifierstring应用的版本,沙盒环境不会出现
web_order_line_item_idstring用于区分跨设备的购买事件,包括订阅续订事件
auto_renew_statusstring1-将自动续订 0-不会自动续订
auto_renew_product_idstring内购项目id,只有自动续期订阅有
price_consent_statusstring只有自动续期订阅有,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 <CommonCrypto/CommonCrypto.h>
 
// 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];

参考

  1. In-App Purchase FAQ
  2. App 内购买项目配置流程
  3. Receipt Validation Programming Guide
  4. In-App Purchase Programming Guide
  5. App Store Connect Help
  6. iOS内购IAP

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值