惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
有赞技术团队
有赞技术团队
H
Help Net Security
V
Visual Studio Blog
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 司徒正美
G
Google Developers Blog
Google DeepMind News
Google DeepMind News
腾讯CDC
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Stack Overflow Blog
Stack Overflow Blog
I
InfoQ
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
L
LangChain Blog
N
Netflix TechBlog - Medium
罗磊的独立博客
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
Hugging Face - Blog
Hugging Face - Blog
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
Recent Announcements
Recent Announcements

Jiajun的技术笔记

你好,2026! TiDB 源码阅读(六):TiDB Coprocessor 源码解析 性能优化的核心思想 TiDB 源码阅读(五):索引 TiDB 源码阅读(四):AST、逻辑计划、物理计划 CockroachDB Serverless Architecture podman 无故退出 Cursor Control-L (CTRL-L) Keyboard Shortcuts in Terminal Replace docker with podman Using xmonad with xfce4 A RC script for freebsd frpc 自己动手写一个k8s controller AI 会取代你的(编程)岗位吗? 自建DERP服务器提升Tailscale连接速度(使用Nginx转发) 自动升级Docker容器 再读《程序员修炼之道-从小工到专家》 让浏览器下载文件 再读《软件随想录》/《黑客与画家》/《软技能》 HTTP 压力测试中的 Coordinated Omission 2的补码 编程语言中的 context 是什么? flutter macOS 构建出错 Flatpak 使用小记 Golang CAS 操作是怎么实现的 PostgreSQL 当MQ来使用 Clash 结合 工作VPN 的网络设计 使用 PostgreSQL 搭建 JuiceFS PostgreSQL 配置优化和日志分析 有GitHub Copilot?那就可以搭建你的ChatGPT4服务 窗口函数的使用(以PG为例)
应用内购的坑
Jiajun Huang · 2020-03-27 · via Jiajun的技术笔记

应用内购的坑

iOS应用内购,那真不是一般的坑。

最近了解了一下iOS和Android的应用内购,也就是在App里进行购买。

一般来说,购买分为两种,一种是一次性的,比如购买金币,这种处理很简单,就不多说了;另外一种就是订阅,也就是每个月或者 每一定周期,就会付费一次,比如VIP会员,这是我们今天的主角。

来看一下常见的流程:

  • 首先在App内显示可以购买的产品(product),可能是一个,也可能是多个(比如会员、超级会员等)
  • 然后App调用SDK进行购买,此时App会与Apple的App Store或者Google 的Play Store进行通信,进行购买,成功之后,App会获得一个 receipt,这是此次购买的凭证
  • 与此同时,服务端会收到一个通知,表示初次购买成功
  • App获得receipt之后,将receipt发给服务端
  • 服务端对receipt进行校验(可以选择本地校验,也可以选择联网校验),确认receipt的真实性之后,将商品或者服务发放给用户

IAP 流程

对于订阅来说,很重要的一个环节是处理通知,比如当用户取消、续订、升降级等事件发生时,我们的服务端都会收到一个通知。

Google Play的过期处理

对于Google的通知,处理起来很简单,直接获取 expiryTimeMillis 即可知道什么时候过期。

When checking the developer API, you should always do the following:

- If expiryTimeMillis is in the future, always grant entitlement.
- If autoRenewing = false, try to get the user to resignup because the subscription will end at expiry time.
- If paymentState = 0,send the user to the subscriptions center using the subscriptions center deep-link to
fix their payment issue. 

其中还包括宽限期,也会体现到 expiryTimeMillis

Google 的通知坑比较少,我踩到的一个就是,如果没有正确处理通知(返回200),那么Google就会开启无限循环通知,短短几个 小时内,就会收到数万次请求。

Apple App Store的过期处理

Apple的可就坑爹了:

  • 首先 receipt 校验返回的结构特别奇葩:

    {
    "latest_receipt": "MIIbngYJKoZIhvcNAQcCoIIbj...",
    "status": 0,
    "receipt": {
        "download_id": 0,
        "receipt_creation_date_ms": "1486371475000",
        "application_version": "2",
        "app_item_id": 0,
        "receipt_creation_date": "2017-02-06 08:57:55 Etc/GMT",
        "original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
        "request_date_pst": "2017-02-06 04:41:09 America/Los_Angeles",
        "original_application_version": "1.0",
        "original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
        "request_date_ms": "1486384869996",
        "bundle_id": "com.yourcompany.yourapp",
        "request_date": "2017-02-06 12:41:09 Etc/GMT",
        "original_purchase_date_ms": "1375340400000",
        "in_app": [{
            "purchase_date_ms": "1486371474000",
            "web_order_line_item_id": "1000000034281189",
            "original_purchase_date_ms": "1486371475000",
            "original_purchase_date": "2017-02-06 08:57:55 Etc/GMT",
            "expires_date_pst": "2017-02-06 01:00:54 America/Los_Angeles",
            "original_purchase_date_pst": "2017-02-06 00:57:55 America/Los_Angeles",
            "purchase_date_pst": "2017-02-06 00:57:54 America/Los_Angeles",
            "expires_date_ms": "1486371654000",
            "expires_date": "2017-02-06 09:00:54 Etc/GMT",
            "original_transaction_id": "1000000271014363",
            "purchase_date": "2017-02-06 08:57:54 Etc/GMT",
            "quantity": "1",
            "is_trial_period": "false",
            "product_id": "com.yourcompany.yourapp",
            "transaction_id": "1000000271014363"
        }],
        "version_external_identifier": 0,
        "receipt_creation_date_pst": "2017-02-06 00:57:55 America/Los_Angeles",
        "adam_id": 0,
        "receipt_type": "ProductionSandbox"
    },
    "latest_receipt_info": [{
            "purchase_date_ms": "1486371474000",
            "web_order_line_item_id": "1000000034281189",
            "original_purchase_date_ms": "1486371475000",
            "original_purchase_date": "2017-02-06 08:57:55 Etc/GMT",
            "expires_date_pst": "2017-02-06 01:00:54 America/Los_Angeles",
            "original_purchase_date_pst": "2017-02-06 00:57:55 America/Los_Angeles",
            "purchase_date_pst": "2017-02-06 00:57:54 America/Los_Angeles",
            "expires_date_ms": "1486371654000",
            "expires_date": "2017-02-06 09:00:54 Etc/GMT",
            "original_transaction_id": "1000000271014363",
            "purchase_date": "2017-02-06 08:57:54 Etc/GMT",
            "quantity": "1",
            "is_trial_period": "true",
            "product_id": "com.yourcompany.yourapp",
            "transaction_id": "1000000271014363"
        }, {
            "purchase_date_ms": "1486371719000",
            "web_order_line_item_id": "1000000034281190",
            "original_purchase_date_ms": "1486371720000",
            "original_purchase_date": "2017-02-06 09:02:00 Etc/GMT",
            "expires_date_pst": "2017-02-06 01:06:59 America/Los_Angeles",
            "original_purchase_date_pst": "2017-02-06 01:02:00 America/Los_Angeles",
            "purchase_date_pst": "2017-02-06 01:01:59 America/Los_Angeles",
            "expires_date_ms": "1486372019000",
            "expires_date": "2017-02-06 09:06:59 Etc/GMT",
            "original_transaction_id": "1000000271014363",
            "purchase_date": "2017-02-06 09:01:59 Etc/GMT",
            "quantity": "1",
            "is_trial_period": "false",
            "product_id": "com.yourcompany.yourapp",
            "transaction_id": "1000000271016119"
        }],
    "environment": "Sandbox"
    }
    
  • 其次字段结构特别坑爹

    • validate结果中,latest_receipt_info 是一个列表,但是服务端通知中,是一个结构体
    • 没有过期发生时,通知体现在 latest_receipt_info 里,而当过期发生时,通知体现在 latest_expired_receipt_info
    • 对于宽限期的receipt来说,validate的结果 体现在 pending_renewal_infograce_period_expires_date_ms
    • 最坑的是,这些毫秒时间戳,都是字符串
  • Apple还有一个坑爹之处是,Sandbox环境特别不稳定

这就是本文想记录的IAP坑爹之处。


参考资料: