本文使用是基于thinkPHP 实现相关的功能,Python、java等实现的网上都有相关的实现连接。不说废话,直接开始。
一、安装jwt.
composer 安装【强烈推荐】本文用这种方式:
composer require firebase/php-jwt
自己上传下载的安装包,下载地址在下面,如果是用这种方式,请独自引入相关的load,如【require_once '/xxx/xxx/xxx/xxxx/autoload.php';】:
https://github.com/firebase/php-jwt
二、找好相关参数及文件
$this->private_key = file_get_contents('/xxx/xxxx/xxxx/xxxx/teset.p8'); //密钥
//公钥 官网可以下载,但要将cer转化为pem PHP只能读取pem格式
$this->pub_key = file_get_contents('/xxx/xxx/xxx/xxxx/xxx/testappleRootCA-G3.pem');
$this->kid = 'test'; //kid
$this->bundle_id = 'com.test.com.package'; //工程包名
$this->iss = '123445-23456-1111-0000-123456789111';
苹果公钥下载链接,需转换为转换为pem格式:https://www.apple.com/certificateauthority/AppleRootCA-G3.cer
不知道的具体看看这篇文章,里面说明了参数该在那里找:WWDC21 - App Store Server API 实践总结 - 腾讯云开发者社区-腾讯云
三、正式写代码
private_key = file_get_contents('/xxx/xxxx/xxxx/xxxx/teset.p8'); //密钥 //公钥 官网可以下载,但要将cer转化为pem PHP只能读取pem格式 $this->pub_key = file_get_contents('/xxx/xxx/xxx/xxxx/xxx/testappleRootCA-G3.pem'); $this->kid = 'test'; //kid $this->bundle_id = 'com.test.com.package'; //工程包名 $this->iss = '123445-23456-1111-0000-123456789111'; } // 请求苹果的订单查询接口 public function getapple() { try { $orderid = input('orderid');//接收输入的订单号查询 $url = "https://api.storekit.itunes.apple.com/inApps/v1/lookup/" . $orderid; $res = $this->getres($url); if (!empty($res['signedTransactions'])) { $data = $res['signedTransactions']; $resl = []; foreach ($data as $k => $v) { $payload = $this->decodePayNotifyV2($v); $resl[] = $payload; } dump($resl); } } catch (\Throwable $th) { echo $th->getMessage(); } } // 查询苹果用户历史收据【消耗型商品只返回退款\失效订单数据】 function gethis() { $orderid = input('orderid'); $url = 'https://api.storekit.itunes.apple.com/inApps/v1/history/' . $orderid; $res = $this->getres($url); $resl = []; dump($res); if (!empty($res['signedTransactions'])) { foreach ($res['signedTransactions'] as $k => $v) { $payload = $this->decodePayNotifyV2($v); $resl[] = $payload; } } dump($resl); } // 查询订单退款记录 function getrefund() { $orderid = input('orderid'); $url = 'https://api.storekit.itunes.apple.com/inApps/v1/refund/lookup/' . $orderid; $res = $this->getres($url); $resl = []; dump($res); if (!empty($res['signedTransactions'])) { foreach ($res['signedTransactions'] as $k => $v) { $payload = $this->decodePayNotifyV2($v); $resl[] = $payload; } } dump($resl); } //获取Authorization: Bearer private function authorbeaer() { $this->payload = [ 'iss' => $this->iss, //iss值 'iat' => intval(time()), 'exp' => intval(time() + 3600), 'aud' => 'https://appleid.apple.com', //固定值 'bid' => $this->bundle_id, //应用bundle_id ]; $this->header = [ "alg" => "ES256", "kid" => $this->kid, "typ" => "JWT" ]; $token = JWT::encode($this->payload, $this->private_key, $this->algorithm, $this->kid, $this->header); return $token; } // 校验签名并解码返回数据 public function decodePayNotifyV2($jwt) { list($header, $payload, $sign) = explode('.', $jwt); $header_decode = base64_decode($header); $header_json = json_decode($header_decode, true); if (!isset($header_json['x5c'])) { // 解析失败 return false; } //这一步是将公钥转成对应的格式 $pubkey = "-----BEGIN CERTIFICATE-----\n" . $header_json['x5c'][0] . "\n-----END CERTIFICATE-----"; try { $decoded = JWT::decode($jwt, new Key($pubkey, $header_json['alg'])); // 判断返回的证书链是否等于苹果公钥证书 $pem = $this->pub_key; $str = str_replace("\r\n", "", $pem); $pubPem = "-----BEGIN CERTIFICATE-----" . $header_json['x5c'][2] . "-----END CERTIFICATE-----"; if ($str != $pubPem) { // 返回的公钥证书链错误 } } catch (\Exception $e) { // 签名校验失败 return false; } return json_decode(json_encode($decoded), true); } //发起请求 获取token private function getres($url, $token = null) { $jwt_str = $token ?? $this->authorbeaer(); $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 0, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => 'GET', CURLOPT_HTTPHEADER => array( 'Authorization: Bearer ' . $jwt_str ), )); $response = curl_exec($curl); $errno = curl_errno($curl); if (!empty($errno)) { echo 'err:' . $errno; } curl_close($curl); $res = json_decode($response, true); return $res; }}
三、小结
代码应该都是挺简单的,大概的流程就是:将下面三部分转码变成JWT然后放在get请求的header中的Authorization 中,形式就是:Bearer AJDasdfaSFJFJFGFL【‘JWT’】,
- header:主要声明了 JWT 的签名算法;
- payload:主要承载了各种声明并传递明文数据;
- signture:拥有该部分的 JWT 被称为 JWS,也就是签了名的 JWS
然后如果成功会返回相对应的结果,这里提供一些坑:
查询订单号时,要用用户端的订单号,大致形式是这样子的:MVL678M6AL;如果用其他订单号查询会返回:{status:1}这是订单错误的提示;
校验并返回结果的时候,decode函数参数要注意一下,假如返回的结果如下图,
$jwt:就是【signedTransactions[0]的值】,$header_json['alg']就是刚开始转码前的那个,这里的是ES256;$pubkey这里不是那个官网下载的公钥,jwt用小数点(.)分割后,base64_decode后再json_decode后得到一个key:【x5c】的数组,里面有3个证书链,取用下标为0的那个证书链作为这里的值。而开头准备的那个公钥是用来比对X5C下标为2的那个证书链
JWT::decode($jwt, new Key($pubkey, $header_json['alg']));
至于其他的,欢迎大家补充,有啥疑问的,可以在评论区留言,大家探讨探讨。
下面有大牛写的文章,详细看看,写得非常好:
WWDC21 - App Store Server API 实践总结 - 腾讯云开发者社区-腾讯云
WWDC21 - App Store Server API 实践总结 - 掘金
下面的这个大佬的校验是有点的问题的,仅供参考
苹果商店退款API PHP验签 signedPayload - 简书
来源地址:https://blog.csdn.net/weixin_45143733/article/details/129714412