PHP App Store Server API 苹果API退款 查询订单 历史订单 PHP校验签名解码

本文使用是基于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);
        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 实践总结 - 掘金

下面的这个大佬的校验是有点的问题的,仅供参考

苹果商店退款API PHP验签 signedPayload - 简书

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
API接口签名验证可以确保请求的来源是可信的,并且请求参数没有被篡改。以下是一个PHP实现API接口签名验证的示例: 1. 客户端发送请求时,将参数按照参数名进行字典排序,并将参数名和参数值用等号连接起来,每个键值对之间用&符号连接起来,得到待签名字符串。 2. 将待签名字符串和应用程序的appsecret进行拼接,然后通过哈希算法(如SHA1或MD5)计算得到签名值。 3. 将签名值作为参数(通常是sign或signature)添加到原有的请求参数中,然后发送请求。 4. 服务器接收到请求后,按照同样的方式计算签名,将计算出的签名值与请求中的签名值进行比对,如果一致则认为请求合法,否则认为请求不合法。 以下是一个简单的示例代码: ```php <?php // 定义应用程序的appsecret $appsecret = "your_app_secret"; // 获取请求参数 $params = $_POST; // 按照参数名进行字典排序 ksort($params); // 将参数名和参数值用等号连接起来,每个键值对之间用&符号连接起来,得到待签名字符串 $sign_str = ""; foreach ($params as $key => $value) { $sign_str .= $key . "=" . $value . "&"; } $sign_str .= "appsecret=" . $appsecret; // 计算签名值 $sign = md5($sign_str); // 验证签名 if ($sign != $params['sign']) { // 签名验证失败 echo "Invalid Signature"; } else { // 签名验证成功 // 执行业务逻辑 } ?> ``` 在实际应用中,为了增加安全性,可以使用更加复杂的签名算法,如RSA数字签名等。另外,还可以限制请求的时间戳和nonce值,防止重放攻击。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值