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

推荐订阅源

The Cloudflare Blog
Microsoft Security Blog
Microsoft Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
L
LangChain Blog
W
WeLiveSecurity
P
Proofpoint News Feed
月光博客
月光博客
NISL@THU
NISL@THU
L
LINUX DO - 最新话题
Webroot Blog
Webroot Blog
T
Threatpost
Y
Y Combinator Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Threat Research - Cisco Blogs
Vercel News
Vercel News
Jina AI
Jina AI
阮一峰的网络日志
阮一峰的网络日志
S
Schneier on Security
J
Java Code Geeks
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
小众软件
小众软件
MyScale Blog
MyScale Blog
N
News and Events Feed by Topic
Stack Overflow Blog
Stack Overflow Blog
有赞技术团队
有赞技术团队
The Hacker News
The Hacker News
Schneier on Security
Schneier on Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Help Net Security
Help Net Security
Recent Announcements
Recent Announcements
S
Security @ Cisco Blogs
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Securelist
T
The Exploit Database - CXSecurity.com
云风的 BLOG
云风的 BLOG
C
Cisco Blogs
雷峰网
雷峰网
量子位
Google DeepMind News
Google DeepMind News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Spread Privacy
Spread Privacy
L
Lohrmann on Cybersecurity
I
Intezer
T
The Blog of Author Tim Ferriss
G
GRAHAM CLULEY
D
DataBreaches.Net
V
Vulnerabilities – Threatpost
P
Privacy & Cybersecurity Law Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
罗磊的独立博客

希仁之拥

领克900半年使用体验 | 希仁之拥的博客 Ubuntu 26.04 Desktop使用体验 | 希仁之拥的博客 【转载】谈谈不受欢迎的博客技术特征 | 希仁之拥的博客 【转载】ClaudeCode 你想知道的所有秘密,源码深度研究报告 | 希仁之拥的博客 2025年年终总结 | 希仁之拥的博客 集成和使用Openclaw后的思考 | 希仁之拥的博客 我买了领克900 | 希仁之拥的博客 服务器性能优化之io拷贝 | 希仁之拥的博客 Go-Sail导航站上线啦 | 希仁之拥的博客 今年国庆的一些感受 [2025] | 希仁之拥的博客 在Deepin 25上配置forticlient | 希仁之拥的博客 分享一些酷酷的站点 [20250908] | 希仁之拥的博客 Go-Sail发布v3.0.6版本了 | 希仁之拥的博客 我对V2EX发布$V2EX讨论的一些感受 | 希仁之拥的博客 如何让Stripe支持支付宝和微信支付 | 希仁之拥的博客 2025上半年里程碑 | 希仁之拥的博客 GitLab+Drone使用体验 | 希仁之拥的博客 四姑娘山之旅 | 希仁之拥的博客 近来帮同事做性能优化的过程回顾 | 希仁之拥的博客 聊聊接口的返回数据结构 | 希仁之拥的博客 由GORM的Updates语法糖 我把 Go-Sail 的文档站更新了 | 希仁之拥的博客 这就是我为什么讨厌拼多多 | 希仁之拥的博客 元旦快乐~ | 希仁之拥的博客 致敬还在写博客的我们 | 希仁之拥的博客 逐步的把图片资源迁移到星光图床上 | 希仁之拥的博客 帮弟弟配了一台mini主机 | 希仁之拥的博客 国庆的一些碎碎念 | 希仁之拥的博客 就这一刻而言,我觉得科技冷冰冰的。 | 希仁之拥的博客 如何使用acme.sh自动续签证书 | 希仁之拥的博客 Go-Sail的文档站点搭建好了 | 希仁之拥的博客 我的第一个谷歌浏览器插件发布成功啦! | 希仁之拥的博客 我开源了一个golang的web框架 | 希仁之拥的博客 流芳 | 希仁之拥的博客 第一次开发微信小程序 | 希仁之拥的博客 春节快乐 | 希仁之拥的博客 我把博客站点重写了 | 希仁之拥的博客 阆中古城 | 希仁之拥的博客 入了把Keydous NJ80 | 希仁之拥的博客 Mac上有用的知识 | 希仁之拥的博客 记一次服务器被攻击 | 希仁之拥的博客 Ubuntu server不停机扩容系统盘 | 希仁之拥的博客 端午安康 | 希仁之拥的博客 搭建hadoop伪分布式集群总结 | 希仁之拥的博客 在goa框架中,如何访问原始请求数据 | 希仁之拥的博客 postgresql相关操作笔记 | 希仁之拥的博客 golang解析带命名空间的xml数据(带冒号的节点或属性名) | 希仁之拥的博客 【转载】解决deepin环境下wine QQ无法接收图片问题 | 希仁之拥的博客 杂项笔记 | 希仁之拥的博客 Mac如何修改通知横幅消失时间 | 希仁之拥的博客 Golang校验结构体是否实现了接口 | 希仁之拥的博客 关于deepin os的大半年使用体验 | 希仁之拥的博客 有态度的翻唱 | 希仁之拥的博客 【转载】Golang 在 Mac、Linux、Windows 下如何交叉编译 | 希仁之拥的博客 和幺幺一起成长的半年时光 | 希仁之拥的博客 Element UI中message消息提示实现原理 | 希仁之拥的博客 从predis组件开始了解如何与redis服务交互 | 希仁之拥的博客 【转载】 尤雨溪谈Vue.js :缔造自由与真我 | 希仁之拥的博客 【转载】百万 Go TCP 连接的思考: epoll方式减少资源占用 | 希仁之拥的博客 【分享】移除git中的commit历史(保留文件,仅移除提交历史信息) | 希仁之拥的博客 shell学习笔记 | 希仁之拥的博客 【分享】Ruby 之父松本行弘:程序员的乐趣、存在感与平衡 | 希仁之拥的博客 安利一下我用过的一些sublime text 3工具、插件及配置 | 希仁之拥的博客 PHP获取XML所有节点名称 | 希仁之拥的博客 Sentinel::check() 在Laravel5.3版本之后总是返回false的解决方案 | 希仁之拥的博客 吃货的美食集,持续更新~ | 希仁之拥的博客 第二十一届成都汽车博览会 | 希仁之拥的博客 马云宣布1年后卸任集团董事局主席 由CEO张勇接任 | 希仁之拥的博客 nginx配置非443端口的https连接 | 希仁之拥的博客 PHP7 异常类型列表 | 希仁之拥的博客 【分享】《燃点》:不被嘲笑的愿望是不值得去实现的|一部属于创业者的电影 | 希仁之拥的博客 【分享】前端资源加载优先级[译] | 希仁之拥的博客 【小技巧】Laravel动态配置多个数据库连接 | 希仁之拥的博客 php使用ftp上传、下载文件 | 希仁之拥的博客 php使用mysqlnd扩展进行mysql异步并行查询 | 希仁之拥的博客 基于以太坊区块链,使用web3.js在geth服务器上执行合约方法以及事件监听 | 希仁之拥的博客 Smartisan OS | 希仁之拥的博客 母校 | 希仁之拥的博客 php后期静态绑定 | 希仁之拥的博客 [mysql优化]子查询与连接查询 | 希仁之拥的博客 PHP常见运行模式及原理 | 希仁之拥的博客 mysql用户与授权 | 希仁之拥的博客 如何在ubuntu14.04上安装composer | 希仁之拥的博客 关于mews/captcha for laravel存在method [validateCaptcha] doesn't exists解决方法 | 希仁之拥的博客
PHP处理苹果内购数据验证 | 希仁之拥的博客
希仁之拥 · 2018-10-16 · via 希仁之拥

写在最前

与支付宝支付和微信支付异步回调不同的是,苹果内购并不存在回调一说,交易是由商户服务器主动向 苹果内购服务发起验证请求。

具体流程

1.iOS客户端向苹果内购服务发起交易请求并拉起交易操作,此时,客户端会等待用户进行支付确认和支付验证(如指纹识别,面部识别等);

2.客户端支付成功后,会收到苹果内购服务返回的票据信息(这个信息将用于客户端异常处理、与商户服务器的交互处理等);

3.客户端收到票据信息后,将票据信息发送至商户服务器;

4.商户服务器收到票据信息后,将票据信息发送至苹果内购服务器进行验证;

5.商户服务器接收到验证结果后进行后续逻辑处理(步骤4、5是同步操作);

6.商户服务器处理后,将处理结果下发给iOS客户端进行最后确认。

示例代码

    /**
     * @desc 苹果内购
     * 21000 App Store不能读取你提供的JSON对象
     * 21002 receipt-data域的数据有问题
     * 21003 receipt无法通过验证
     * 21004 提供的shared secret不匹配你账号中的shared secret
     * 21005 receipt服务器当前不可用
     * 21006 receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送
     * 21007 receipt是Sandbox receipt,但却发送至生产系统的验证服务
     * 21008 receipt是生产receipt,但却发送至Sandbox环境的验证服务
     * @return json
     */
    public function postApplePayValidateCallBack()
    {
        //小票信息
        $post_data        = ["receipt-data" => $_REQUEST["receipt-data"]];
        $post_data_encode = json_encode($post_data);

        //正式购买地址 沙盒购买地址
        $url_buy     = "https://buy.itunes.apple.com/verifyReceipt";
        $url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";
        //根据配置信息决定支付环境是否为沙盒环境
        $url         = env("APPLE_PAY_SANDBOX", true) == true ? $url_sandbox : $url_buy;

        //简单的curl
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data_encode);
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        $apple_response = curl_exec($ch);
        curl_close($ch);
        $apple_response_decode = json_decode($apple_response, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
        	exit(json_encode([
	            "status"     => 1,
	            "msg"        => "[APPLE 001] Invalid request data",
	            "error_code" => "DL_APPLE_PAY_RESPONSE_ERROR",
	            "content"    => []]));
        }

	    //判断是否购买成功
	    if(intval($apple_response_decode['status']) === 0){
	    	//写入充值信息
	    	/**
	    	 **********************************************
	    	 *
			 * 由于客户端存在重新回调机制,因此"in_app"数组中可能存在多组历史数据,
			 * 这里进行遍历操作
			 * 此时操作类似于支付宝类的回调成功 处理逻辑
             *
	    	 **********************************************
	    	 */
	    	if (
	    		count($apple_response_decode["in_app"]) !== 0 && 
	    		strtolower($apple_response_decode["environment"]) != "sandbox"
	    	) {
	    		foreach ($apple_response_decode["in_app"] as $deposit_array) {
                    //商品编号
	    			$product_id = $deposit_array["product_id"];
                    //订单编号
	    			$order_id = $deposit_array["transaction_id"];
                    //成功支付时间戳
	    			$deposit_success_time = intval($deposit_array["purchase_date_ms"] / 1000);
                    //todo something
	    		}
	    	}
			exit(json_encode([
				"status"     => 0,
				"msg"        => "deposit success",
				"error_code" => "",
				"content"    => []]));
	    }
    	exit(json_encode([
			"status"     => 1,
			"msg"        => "[APPLE 001] Invalid request data: (" . $apple_response_decode["status"] . ")",
			"error_code" => "DL_APPLE_PAY_INVALID_FAILED",
			"content"    => []]));   
	    
    }

疑问

1.关于失败重发?

支付宝、微信支付等传统支付流程都会有支付异步回调通知,并且有失败重发机制,苹果内购怎么处理失败重发的问题?

首先,我们来观察一组苹果内购服务返回的验证数据(步骤4接收到的结果)

    {
	"receipt": {
		"receipt_type": "ProductionSandbox",
		"adam_id": 0,
		"app_item_id": 0,
		"bundle_id": "com.DD.livePlay",
		"application_version": "3",
		"download_id": 0,
		"version_external_identifier": 0,
		"receipt_creation_date": "2018-10-15 14:30:42 Etc/GMT",
		"receipt_creation_date_ms": "1539613842000",
		"receipt_creation_date_pst": "2018-10-15 07:30:42 America/Los_Angeles",
		"request_date": "2018-10-15 15:02:48 Etc/GMT",
		"request_date_ms": "1539615768080",
		"request_date_pst": "2018-10-15 08:02:48 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": "com.DD.livePlay.coin.6",
				"transaction_id": "1000000457122701",
				"original_transaction_id": "1000000457122701",
				"purchase_date": "2018-10-14 04:02:51 Etc/GMT",
				"purchase_date_ms": "1539489771000",
				"purchase_date_pst": "2018-10-13 21:02:51 America/Los_Angeles",
				"original_purchase_date": "2018-10-14 04:02:51 Etc/GMT",
				"original_purchase_date_ms": "1539489771000",
				"original_purchase_date_pst": "2018-10-13 21:02:51 America/Los_Angeles",
				"is_trial_period": "false"
			},
			{
				"quantity": "1",
				"product_id": "com.DD.livePlay.coin.6",
				"transaction_id": "1000000457173576",
				"original_transaction_id": "1000000457173576",
				"purchase_date": "2018-10-14 14:55:18 Etc/GMT",
				"purchase_date_ms": "1539528918000",
				"purchase_date_pst": "2018-10-14 07:55:18 America/Los_Angeles",
				"original_purchase_date": "2018-10-14 14:55:18 Etc/GMT",
				"original_purchase_date_ms": "1539528918000",
				"original_purchase_date_pst": "2018-10-14 07:55:18 America/Los_Angeles",
				"is_trial_period": "false"
			},
			{
				"quantity": "1",
				"product_id": "com.DD.livePlay.coin.12",
				"transaction_id": "1000000457175666",
				"original_transaction_id": "1000000457175666",
				"purchase_date": "2018-10-14 15:10:48 Etc/GMT",
				"purchase_date_ms": "1539529848000",
				"purchase_date_pst": "2018-10-14 08:10:48 America/Los_Angeles",
				"original_purchase_date": "2018-10-14 15:10:48 Etc/GMT",
				"original_purchase_date_ms": "1539529848000",
				"original_purchase_date_pst": "2018-10-14 08:10:48 America/Los_Angeles",
				"is_trial_period": "false"
			},
			{
				"quantity": "1",
				"product_id": "com.DD.livePlay.coin.12",
				"transaction_id": "1000000457640163",
				"original_transaction_id": "1000000457640163",
				"purchase_date": "2018-10-15 14:10:57 Etc/GMT",
				"purchase_date_ms": "1539612657000",
				"purchase_date_pst": "2018-10-15 07:10:57 America/Los_Angeles",
				"original_purchase_date": "2018-10-15 14:10:57 Etc/GMT",
				"original_purchase_date_ms": "1539612657000",
				"original_purchase_date_pst": "2018-10-15 07:10:57 America/Los_Angeles",
				"is_trial_period": "false"
			},
			{
				"quantity": "1",
				"product_id": "com.DD.livePlay.coin.12",
				"transaction_id": "1000000457640514",
				"original_transaction_id": "1000000457640514",
				"purchase_date": "2018-10-15 14:12:36 Etc/GMT",
				"purchase_date_ms": "1539612756000",
				"purchase_date_pst": "2018-10-15 07:12:36 America/Los_Angeles",
				"original_purchase_date": "2018-10-15 14:12:36 Etc/GMT",
				"original_purchase_date_ms": "1539612756000",
				"original_purchase_date_pst": "2018-10-15 07:12:36 America/Los_Angeles",
				"is_trial_period": "false"
			},
			{
				"quantity": "1",
				"product_id": "com.DD.livePlay.coin.12",
				"transaction_id": "1000000457649073",
				"original_transaction_id": "1000000457649073",
				"purchase_date": "2018-10-15 14:30:42 Etc/GMT",
				"purchase_date_ms": "1539613842000",
				"purchase_date_pst": "2018-10-15 07:30:42 America/Los_Angeles",
				"original_purchase_date": "2018-10-15 14:30:42 Etc/GMT",
				"original_purchase_date_ms": "1539613842000",
				"original_purchase_date_pst": "2018-10-15 07:30:42 America/Los_Angeles",
				"is_trial_period": "false"
			}
		]
	},
	"status": 0,
	"environment": "Sandbox"
}

以上"in_app"数组中的值便是支付成功信息,支付成功一条,便会有一组信息。

啥?那为啥有多组数据?

在上述的流程步骤6中,iOS客户端收到商户服务器下发的消息后,才进行最后的交易确认处理,此时iOS客户端会与苹果内购服务进行交互,当成功处理后,内购服务器才认为这笔交易的逻辑彻底完成。

那么,在上述的流程步骤没有完整走通的情况下,内购服务会认为交易没有彻底完成,因此,每次向内购服务进行验证的时候(步骤4、5),都会收到未正常处理的历史记录("in_app"数组)。

此时,商户服务器应当处理"in_app"数组中的历史数据(到这里有没有似曾相识的感觉:没错,这就是失败重发)。

2.纵观验证返回数据,为什么没有看到商品价格信息?

我们能看到,在"in_app"数组中,每一组数据里面有一个键是"product_id",这键就是记录的商品编号。这个商品编号是商户服务器与iOS客户端约定的商品编号与价格的映射,比如此处的"com.DD.livePlay.coin.6"就是6元档位,"com.DD.livePlay.coin.12"就是12元档位。

咦?为啥是6、12...而不是整数?这就要说到人民币与美元的汇率问题...

3.读到这里,不知道你有没有发现,票据是来源于iOS客户端,商户服务器如何确认数据来源的可信(这是否是我自己的iOS客户端)?

我们可以看到在"receipt"中,有一个键是"bundle_id",这个值便是iOS客户端的包名称,从这一点可以鉴别是否是自家商户可信任的客户端(因为步骤1、2操作会根据一定签名与内购服务器交互,数据安全性可以得到保证)。

感谢 墨墨的小孩 小伙伴的支持。


980_120.png(via stardots.io)

觉得博主写的不错?给他一个赞赏:

keepchen的赞赏码-支付宝 keepchen的赞赏码-微信