InAppPurchase前往AppStore支付校验-Python后端开发记录

文章目录

    • 非自动订阅式购买
      • 首先理流程
      • 校验所需的参数
      • 请求 AppStore
      • 解析响应
    • 恢复购买的校验
    • 自动订阅式购买的校验
      • 首次购买
      • 续订结果查询
      • 取消订阅的查询

Python 版本是 3.5.2 ,后端架构是 Django == 1.11.1 和 MySQL 。

非自动订阅式购买

首先理流程

  • iOS 端发起支付请求
  • 支付成功后,携校验所需数据向服务端发起校验请求
  • 服务端将参数进行包装,向 AppStore 发起校验请求
  • AppStore 返回响应,包含着用户在 App 中所有交易的收据
  • 服务端解析响应,用 iOS 传来的数据与 AppStore 返回的数据进行比对,数据一致即为校验成功
  • 校验完成后,服务端走业务逻辑,给 iOS 端返回响应

校验所需的参数

  • receiptData:base64 编码的收据信息 - 请求 AppStore 的主数据
  • transactionId:交易单号 - 与 AppStore 返回的数据进行比对的键
  • productId:购买的产品标识 - 与 AppStore 返回的数据进行比对的值

请求 AppStore

  • 服务端准备参数 {'receipt-data': receiptData, 'password': 'xxxxxxxxxxxx'},password 为非自动订阅式购买必传
  • post 请求至 https://buy.itunes.apple.com/verifyReceipt/ (沙盒环境:https://sandbox.itunes.apple.com/verifyReceipt/)

解析响应

响应示例:

{
	'receipt': 
		{
			'receipt_creation_date_ms': '1562909318000', 
			'adam_id': 0, 
			'receipt_creation_date': '2019-07-12 05:28:38 Etc/GMT', 
			'version_external_identifier': 0,
			'original_purchase_date_pst': '2013-08-01 00:00:00  America/Los_Angeles', 
			'original_purchase_date_ms': '1375340400000',
			'bundle_id': 'xxxxxxx', 
			'receipt_creation_date_pst': '2019-07-11 22:28:38 America/Los_Angeles', 			
			'request_datea_ms': '1562909325495', 
			'app_item_id': 0, 
			'original_purchase_date': '2013-08-01 07:00:00 Etc/GMT', 
			'request_date_pst': '2019-07-11 22:28:45 America/Los_Angeles', 
			'original_application_version': '1.0', 
			'application_version': '1', 
			'receipt_type': 'ProductionSandbox', 
			'download_id': 0, 
			'request_date': '2019-07-12 05:28:45 Etc/GMT', 
			'in_app': [
				{
					'is_trial_period': 'false', 
					'original_purchase_date_ms': '1562908866000', 
					'quantity': '1', 
					'purchase_date_ms': '1562908866000', 
					 'purchase_date_pst': '2019-07-11 22:21:06 America/Los_Angeles', 
					 'product_id': 'xxxxxx', 
					 'original_purchase_date': '2019-07-12 05:21:06 Etc/GMT', 
					 'transaction_id': '100000xxxxx826', 
					 'original_transaction_id': '10000005xxxx826', 
					 'original_purchase_date_pst': '2019-07-11 22:21:06 America/Los_Angeles', 
					 'purchase_date': '2019-07-12 05:21:06 Etc/GMT'}, 
				{
					'is_trial_period': 'false', 
					'original_purchase_date_ms': '1562909318000', 
					'quantity': '1', 
					'purchase_date_ms': '1562909318000', 
					'purchase_date_pst': '2019-07-11 22:28:38 America/Los_Angeles', 
					'product_id': 'xxxxxx', 
					'original_purchase_date': '2019-07-12 05:28:38 Etc/GMT', 
					'transaction_id': '1000000xxxxx664', 
					'original_transaction_id': '1000000xxxxx664', 
					'original_purchase_date_pst': '2019-07-11 22:28:38 America/Los_Angeles', 
					'purchase_date': '2019-07-12 05:28:38 Etc/GMT'
				}
			]
		}, 
	'status': 0, 
	'environment': 'Sandbox'
}

收据在 receipt 中的 in_app 字段里,这是数组包对象的形式,里面的每一个对象都是一次交易

  • 先获取 in_app 这个字段中的数据
  • 遍历其中的每一个对象,将其中的 transaction_id 与 iOS 端传来的 transactionId 进行匹配
  • 匹配成功的话,就说明这单交易是存在的,但是为了进一步校验交易的准确性,我们再将 product_id 与 iOS 端传来的 productId 做比较,如果相等则校验成功,若不相等,则有可能是伪造的虚假数据。
  • 还有值得注意的一个字段是 original_transaction_id ,这个字段是自动订阅式购买时,当前用户在你的App内首次购买的交易单号,可以利用这个字段在某些情况下进行用户关联。而在本例中可以看到,在 in_app 的两个对象中,original_transaction_id 是不一样的,因为本次操作的是非自动订阅式购买。

恢复购买的校验

恢复购买的校验,由 IOS 端将产品和用户的 AppleID 联系起来,可生成唯一的 uuid,通过校验此 uuid 直接获得用户的鉴权结果和订阅状态,也可单独作为用户身份的校验。

自动订阅式购买的校验

收据示例:

{
'status': 0, 
'environment': 'Sandbox', 
'receipt': {
	'receipt_type': 'ProductionSandbox', 
	'adam_id': 0, 
	'app_item_id': 0, 
	'bundle_id': ‘…’, 
	'application_version': '1', 
	'download_id': 0, 
	'version_external_identifier': 0, 
	'receipt_creation_date': '2019-09-11 10:12:57 Etc/GMT', 
	'receipt_creation_date_ms': '1568196777000', 
	'receipt_creation_date_pst': '2019-09-11 03:12:57 America/Los_Angeles', 
	'request_date': '2019-09-11 11:38:36 Etc/GMT', 
	'request_date_ms': '1568201916879', 
	'request_date_pst': '2019-09-11 04:38:36 America/Los_Angeles', 
	'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', 
	'original_application_version': '1.0', 
	'in_app': [
		{
			'quantity': '1', 
			'product_id': ‘…’, 
			'transaction_id': '1000000567124099', 
			'original_transaction_id': '1000000567124099', 
			'purchase_date': '2019-09-11 10:06:34 Etc/GMT', 
			'purchase_date_ms': '1568196394000',
			 'purchase_date_pst': '2019-09-11 03:06:34 America/Los_Angeles', 
			 'original_purchase_date': '2019-09-11 10:06:35 Etc/GMT', 
			 'original_purchase_date_ms': '1568196395000', 
			 'original_purchase_date_pst': '2019-09-11 03:06:35 America/Los_Angeles',
			 'expires_date': '2019-09-11 11:06:34 Etc/GMT',
			 'expires_date_ms': '1568199994000', 
			 'expires_date_pst': '2019-09-11 04:06:34 America/Los_Angeles',
			 'web_order_line_item_id': '1000000046836911',
			 'is_trial_period':'false', 
			 'is_in_intro_offer_period': 'false'
	}
	]
}, 
'latest_receipt_info': [
	{
		'quantity': '1', 
		'product_id': ‘…’, 
		'transaction_id': '1000000567124099', 
		'original_transaction_id': '1000000567124099', 
		'purchase_date': '2019-09-11 10:06:34 Etc/GMT', 
		'purchase_date_ms': '1568196394000', 
		'purchase_date_pst': '2019-09-11 03:06:34 America/Los_Angeles', 
		'original_purchase_date': '2019-09-11 10:06:35 Etc/GMT', 
		'original_purchase_date_ms': '1568196395000', 
		'original_purchase_date_pst': '2019-09-11 03:06:35 America/Los_Angeles', 
		'expires_date': '2019-09-11 11:06:34 Etc/GMT', 
		'expires_date_ms': '1568199994000', 
		'expires_date_pst': '2019-09-11 04:06:34 America/Los_Angeles', 
		'web_order_line_item_id': '1000000046836911', 
		'is_trial_period': 'false', 'is_in_intro_offer_period': 'false'
	},
    {
		'quantity': '1',
		'product_id': ‘…’, 
		'transaction_id': '1000000567154188', 
		'original_transaction_id': '1000000567124099', 
		'purchase_date': '2019-09-11 11:06:34 Etc/GMT',
		'purchase_date_ms': '1568199994000', 
		'purchase_date_pst': '2019-09-11 04:06:34 America/Los_Angeles', 
		'original_purchase_date': '2019-09-11 10:06:35 Etc/GMT', 
		'original_purchase_date_ms': '1568196395000', 
		'original_purchase_date_pst': '2019-09-11 03:06:35 America/Los_Angeles', 
		'expires_date': '2019-09-11 12:06:34 Etc/GMT', 
		'expires_date_ms': '1568203594000', 
		'expires_date_pst': '2019-09-11 05:06:34 America/Los_Angeles',
		'web_order_line_item_id': '1000000046836912', 
		'is_trial_period': 'false', 
		'is_in_intro_offer_period': 'false'
	}
], 
'latest_receipt': 'MIIVYwYJKoZIhvcNAQ…..cCoIBsnXUDnzo8pPMuV2A==', 
'pending_renewal_info': [
	{
		'auto_renew_product_id': ‘…’, 
		'original_transaction_id': '1000000567124099', 
		'product_id': ‘…’, 
		'auto_renew_status': '1'
	}
]
}

自动订阅要处理的情况包括:

  • 首次购买某个产品
  • 续订结果的查询
  • 取消订阅的查询

首次购买

走正常的校验流程,不同之处在于,收据里会多一个字段 latest_receipt_info,这里面存的是同一次订阅的所有交易数据,包括首次订阅,和之后的自动续费记录。简言之,一次订阅,在 in_app 字段里有一条记录,在 latest_receipt_info 字段里,会有多条记录。如图上收据示例,in_app 字段中一条记录,transaction_id 为 1000000567124099,在 latest_receipt_info 中有两条记录,transaction_id 虽然不一样,但是original_transaction_id 都是1000000567124099,证明这两次交易都是属于同一次订阅活动。所以,先取 latest_receipt_info 字段,如果为空,再取 in_app 字段,两者的内容格式是一样的。

续订结果查询

取 latest_receipt_info 字段,直接对比 expires_date_ms。因为每个订阅产品都是经过苹果审核,时长也很明确,一经订阅,苹果既然知道订阅付费的时间,肯定也知道过期时间,这个过期时间就存在 expires_date_ms 字段中,是毫秒级的时间戳。我是每次取 latest_receipt_info 最后一条记录的 expires_date_ms,与用户的过期时间对比,如果比库中存的过期时间长,那就是有新的续费,将过期时间一改,完事。(这样做的基础是得做好完整的、基于订阅和过期时间的用户订阅状态鉴权)

取消订阅的查询

第一,苹果服务器的主动通知,做过的都知道,苹果的 StatusUpdateNotification,会主动向你的服务器通知某个用户的首次订阅、续费、退订、更换订阅产品等状态变更消息。但是我接收到的内容为空,查询无果,所以我做了第二方案。如果你成功采用了这个机制,请联系我,感谢。
第二,在主动像苹果服务器校验的时候,有个字段叫 pending_renewal_info,这里面有用户的自动订阅状态,1 为自动订阅,0 为取消订阅,可以通过这个字段得知用户是否已经取消。如果取消了,由 expiration_intent 字段可得知用户的取消原因,1 为用户手动取消。

pending_renewal_info = result.get('pending_renewal_info')
if pending_renewal_info:
    pending_renewal_info = pending_renewal_info[-1]
    auto_renew_status = pending_renewal_info.get('auto_renew_status')
    if auto_renew_status == '0':
        expiration_intent = pending_renewal_info.get('expiration_intent')
        if expiration_intent == '1':
            ...    # 用户已经手动取消订阅

你可能感兴趣的:(python)