本文使用是基于thinkPHP 实现相关的功能,Python、java等实现的网上都有相关的实现连接。不说废话,直接开始。
一、安装jwt.
1、composer 安装【强烈推荐】本文用这种方式:
composer require firebase/php-jwt
2、自己上传下载的安装包,下载地址在下面,如果是用这种方式,请独自引入相关的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 实践总结 - 腾讯云开发者社区-腾讯云
三、正式写代码
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
use think\Db;
use \Firebase\JWT\JWT;
use \Firebase\JWT\Key;
/**
*
*测试
* @icon fa fa-circle-o
*/
class Testapple extends Backend
{
private $private_key = null;
private $header = null;
private $algorithm = "ES256";
private $payload = null;
private $kid = null;
private $bundle_id=null;
private $pub_key=null;
private $iss=null;
public function _initialize()
{
parent::_initialize();
$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';
}
// 请求苹果的订单查询接口
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);
//这个值可以存一下缓存,过期了再去重新生成,下次再获取的时候校验一下过期时间exp
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
然后如果成功会返回相对应的结果,这里提供一些坑:
1、查询订单号时,要用用户端的订单号,大致形式是这样子的:MVL678M6AL;如果用其他订单号查询会返回:{status:1}这是订单错误的提示;
2、校验并返回结果的时候,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 实践总结 - 掘金
下面的这个大佬的校验是有点的问题的,仅供参考