20200504-IAP订阅功能梳理-applepay


title: IAP订阅功能梳理
date: 2020-05-04 08:10:08
tags: [Applepay, iap, 业务功能, 交易系统]


会员功能需要接入 IAP 订阅功能, SKU -> 连续包年(首月免费);连续包月(前3天免费);连续包季(首周免费)

前提: 系统已经有交易系统, 下面的描述只针对 连续订阅产品

需求

  • 用户订阅后,获得会员资格, 且每次扣费会有对应的订单(包含免费试用阶段的订单)

苹果支付背景

  • 苹果支付的核心是 收据(receipt)

    • 连续订阅产品的话: 第一次产生收据是一直可用的 (can verify) 每次续订/恢复购买的记录都「保存」在收据里, 业务逻辑核心就是理解 receipt 里的字段
  • 涉及到的人员

    • 设计: 设计购买页面
    • 产品: 产品交互/辅助开发 在 app_store 创建产品
    • IOS 开发: 初始化购买
    • 服务器开发: 验证客户端上传收据,并管理用户续费订单

开发流程简述

  • 产品将 产品列表创建好
App product_id 服务器后台 sku 价格 优惠说明 标题
product_year_xxx xxxx 100 首月免费 连续包年
product_quarter_xxx xxx 50 首周免费 连续包季
product_month_xxx xxxx 30 3天免费 连续包月
  • IOS 客户端 可以从苹果拿到 product_id 列表开始测试

    • 目的: 产生 receipt 交给服务器设计表结构
    • 走具体流程
image-20200504170848061.png
  • 服务器具体要做的事
    • 产品列表接口: 提供可购买的 product_id, 以及当前用户的订阅状态; 如果已订阅那么禁止用户继续订阅
    • 验证收据接口: 服务器收到 receipt 先在本地通过单元测试解析并结合文档(https://developer.apple.com/documentation/appstorereceipts)梳理
    • 定时任务: 检测 payment_applepay 数据 按过期了一个月/将要过期的 订单每隔6h 轮询一次 业务逻辑同 (验证收据接口)

服务器表结构设计

  • 验证动作表
    • 记录每次验证 receipt 的 request_data 以及 response_data 方便测试和复盘
/******************************************/
/*   DatabaseName = trade   */
/*   TableName = trade_payment_applepay_verify   */
/******************************************/
CREATE TABLE `trade_payment_applepay_verify` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created` datetime(6) NOT NULL,
  `modified` datetime(6) NOT NULL,
  `is_removed` tinyint(1) NOT NULL,
  `user_id` int(11) NOT NULL,
  `action_type` varchar(32) NOT NULL DEFAULT 'client' COMMENT '验证类型,客户端验证(client);服务器轮询验证(server_crontab)',
  `order_no` varchar(64) NOT NULL,
  `receipt_data` longtext NOT NULL COMMENT '待验证的收据',
  `status` int(11) NOT NULL COMMENT '验证状态 成功/失败',
  `environment` varchar(16) NOT NULL COMMENT 'sandbox/production',
  `is_retryable` tinyint(1) NOT NULL,
  `latest_receipt` longtext NOT NULL COMMENT 'response: latest_receipt',
  `latest_receipt_info` longtext NOT NULL COMMENT ' response: latest_receiptjson对象',
  `pending_renewal_info` longtext NOT NULL COMMENT 'response: pending_renewal_info',
  `receipt` longtext NOT NULL COMMENT 'response: receipt',
  PRIMARY KEY (`id`),
  KEY `trade_payment_applepay_verify_user_id_4c0bc485_idx` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
;
  • 对应 receipt 的 in-app 列表 就是对应的每个 transcation_id
    • 记录每次购买 、 续费、恢复购买数据
/******************************************/
/*   DatabaseName = trade   */
/*   TableName = trade_payment_applepay   */
/******************************************/
CREATE TABLE `trade_payment_applepay` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created` datetime(6) NOT NULL,
  `modified` datetime(6) NOT NULL,
  `is_removed` tinyint(1) NOT NULL,
  `user_id` int(11) NOT NULL,
  `action_type` varchar(32) NOT NULL DEFAULT 'client',
  `order_no` varchar(64) NOT NULL  DEFAULT '' COMMENT '对应订单 订单创建成功后赋值',
  `paid_time` bigint(20) DEFAULT NULL,
  `status` varchar(100) NOT NULL,
  `status_changed` datetime(6) NOT NULL,
  `bundle_id` varchar(32) NOT NULL,
  `application_version` varchar(16) NOT NULL,
  `original_application_version` varchar(16) NOT NULL,
  `version_external_identifier` varchar(16) NOT NULL,
  `app_item_id` varchar(16) NOT NULL,
  `product_id` varchar(64) NOT NULL DEFAULT '',
  `quantity` int(10) unsigned NOT NULL,
  `environment` varchar(16) NOT NULL,
  `transaction_id` varchar(32) NOT NULL COMMENT '每次交易 id 注意恢复购买时不需要',
  `original_transaction_id` varchar(32) NOT NULL COMMENT '原交易 id',
  `expires_date` datetime(6) DEFAULT NULL COMMENT '过期时间',
  `expires_date_ms` bigint(20) NOT NULL COMMENT '过期时间 ms',
  `purchase_date_ms` bigint(20) NOT NULL COMMENT '购买时间 ms', 
  `purchase_date` varchar(64) NOT NULL  COMMENT '购买时间',
  `auto_renew_status` tinyint(1) NOT NULL COMMENT '续费状态', 
  `is_in_intro_offer_period` tinyint(1) NOT NULL COMMENT 'An indicator of whether an auto-renewable subscription is in the introductory price period',
  `is_trial_period` tinyint(1) NOT NULL COMMENT '是否是使用期间',
  `latest_receipt` longtext NOT NULL COMMENT '最后一次的收据',
  `web_order_line_item_id` varchar(64) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `trade_payment_applepay_transaction_id_product_i_7f940417_uniq` (`original_transaction_id`,`product_id`,`expires_date_ms`) USING BTREE,
  KEY `trade_payment_applepay_user_id_9e9b627e_idx` (`user_id`),
  KEY `trade_payment_applepay_transaction_id_f8abbb58_idx` (`transaction_id`),
  KEY `trade_payment_applepay_order_no_0ad1fa70_idx` (`order_no`),
  KEY `trade_payment_applepay_original_transaction_id_7746fd50_idx` (`original_transaction_id`)
) ENGINE=InnoDB AUTO_INCREMENT=337 DEFAULT CHARSET=utf8
;

字段设计以及 Why

  • original_transaction_id,product_id,expires_date_ms 组合 unique_key 唯一索引

    对于连续订阅产品 orginal_transaction_id 只有一个, 区分每次订阅/续期的就是 expires_date_ms(每次续费时间会不一样) 和 product_id(更换产品比如从 连续包年->连续包月 )

  • payment_applepay 为什么需要 order_no

    关联 order 才知道这单处理过 防止丢单

  • 怎么判断需不需要创建订单?

    解析in-app里的数据,

    1. 如果此时是插入数据 且 expires_date_ms > now 就表明用户续费了或者刚购买 需要创建订单
    2. 如果此时payment 数据已存在, 且 expires_date_ms > now 且 order_no 没赋值, 那么说明漏单了, 创建订单
    3. 如果 is_trial_period or is_in_intro_offer_period 就要注意创建试用订单 此时用户还没真正付钱所以权益之只分配是用权限 比如 首月免费 那就创建首月免费订单 , 等苹果扣费时, 验证苹果 receipt 时 会新增一个 transaction_id 表明付款了

测试流程

  • 了解 in-app-purchase sandbox 背景 如下图


    image-20200504163524179.png

补充

  • 苹果还有订阅 subscribe 接口, 订阅文档 当订阅发生改动时会向你的服务器发送通知. 这个为了订阅时效性可以选择接入。 我们这边只是写了接口 把订阅的数据存到database 没有做具体逻辑; 数据结构如下
/******************************************/
/*   DatabaseName = trade   */
/*   TableName = trade_payment_applepay_subscribe   */
/******************************************/
CREATE TABLE `trade_payment_applepay_subscribe` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created` datetime(6) NOT NULL,
  `modified` datetime(6) NOT NULL,
  `is_removed` tinyint(1) NOT NULL,
  `user_id` int(11) NOT NULL DEFAULT 0,
  `order_no` varchar(64) NOT NULL DEFAULT '',
  `auto_renew_adam_id` varchar(64) NOT NULL,
  `auto_renew_product_id` varchar(64) NOT NULL,
  `auto_renew_status` tinyint(1) NOT NULL,
  `auto_renew_status_change_date` datetime(6) NOT NULL,
  `auto_renew_status_change_date_ms` bigint(20) NOT NULL,
  `environment` varchar(16) NOT NULL,
  `expiration_intent` smallint(6) NOT NULL,
  `latest_expired_receipt` longtext NOT NULL,
  `latest_expired_receipt_info` longtext NOT NULL,
  `latest_receipt` longtext NOT NULL,
  `latest_receipt_info` longtext NOT NULL,
  `notification_type` varchar(64) NOT NULL,
  `unified_receipt` longtext NOT NULL,
  `password` varchar(128) NOT NULL,
  `transaction_id` varchar(32) NOT NULL,
  `original_transaction_id` varchar(32) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `trade_payment_applepay_subscribe_user_id_0a63f52c_idx` (`user_id`),
  KEY `trade_payment_applepay_subscribe_OTI_0a63f52c_idx` (`original_transaction_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=190 DEFAULT CHARSET=utf8
;

你可能感兴趣的:(20200504-IAP订阅功能梳理-applepay)