PHP 苹果内购订阅验单函数,及其订阅回调处理案例

支付时拿到票据:

<?php
/**
     * POST验单curl
     * @param $post_data 请求参数['key'=>'value','keys'=>'values']
     * @param $url 请求地址
     * @return mixed
     */
    public function post_receipt_data($post_data,$url){

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($curl, CURLOPT_POST, 1);
        $post_data = $post_data ? json_encode($post_data) : '';
        curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
        if( !empty($aHeader) ){
            curl_setopt($curl, CURLOPT_HTTPHEADER, $aHeader);
        }
        curl_setopt($curl, CURLOPT_TIMEOUT, 120);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        $result = curl_exec($curl);
        $info = curl_getinfo($curl);
        $error_no = curl_errno($curl);
        $error_str = curl_error($curl);

        curl_close($curl);
        $result_array = json_decode($result,true);
        return $result_array;
    }

    /**
     * 苹果验单转发方法
     *
     * apple地址:
     * https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html
     *
     * @param $receipt 苹果票据
     * @param int $type 1:普通充值验单 2:支持传共享秘钥验单(不传则默认取订阅配置的秘钥)
     * @param null $url 请求地址
     * @param null $pwd 共享秘钥
     * @return mixed
     */
    public function apple_validation_url($receipt,$type=1, $url=null, $pwd = null)
    {
        $post_data['receipt-data'] = $receipt; //验单票据

        //增加共享秘钥参数
        if($type === 2){
            $post_data['password'] = !empty($pwd) ? $pwd :$this->my_config['apple_shared_secret_key'];
            $post_data['exclude-old-transactions'] = true; //true仅返回最新订阅信息
        }

        $url             = !empty($url) ? $url : "https://buy.itunes.apple.com/verifyReceipt"; //正式地址
        $test_verify_url = "https://sandbox.itunes.apple.com/verifyReceipt"; //测试环境地址
        $prod_verify_url = "https://buy.itunes.apple.com/verifyReceipt"; //生产环境地址

        $result_validation_info = $this->post_receipt_data($post_data, $url);
        switch ($result_validation_info['status']) {
            case 0: //正常
                break;

            case 21006: //订阅到期返回过期类票据
                break;

            case 21007: //沙盒模式的票据发送给了生产环境-重新转发给到测试地址
                $result_validation_info = $this->post_receipt_data($post_data, $test_verify_url);
                break;

            case 21008: //生产模式的票据发送给了测试环境-重新转发给到正式地址
                $result_validation_info = $this->post_receipt_data($post_data, $prod_verify_url);
                break;

            default: //记录错误日志
                //todo 写log弹出错误;
                \Common::response_error_header(403,"verify fail");
        }

        return $result_validation_info;
    }

    /**
     * 苹果验单数据解析(订阅类型返回结构体)
     * @param array $data 数据
     * @param int $type 结构体类型 1普通支付验单结构体 2订阅首次支付验单票据返回结构体 3apple订阅回调票据返回结构体
     * @return array
     */
    public function apple_data_parsing(array $data = [],$type = 2)
    {
        if (empty($data)) return $data; //没有数据

        if ($data['status'] != 0 && $data['status'] != 21006) return $data; //status不等于0

        $k= []; //返回结构体

        switch ($type){
            case 1: //普通验单结构
                $ks['status']  = $data['status'];
                $ks['receipt'] = $data['receipt'];

                unset($ks['receipt']['in_app']);
                $ks['receipt']['in_app'][0] = $data['receipt']['in_app'][0];

                //取in_app信息
                $k = array_merge($ks,$data['receipt']['in_app'][0]);
                break;

            case 2:
                $k['status']  = $data['status'];
                $k['receipt'] = $data['receipt'];

                unset($k['receipt']['in_app']);
                $k['pending_renewal_info'] = $data['pending_renewal_info'];

                //票据
                if(isset($data['latest_receipt'])){
                    $k['latest_receipt'] = $data['latest_receipt'];
                }
                //如果存在值-降序取最新的数据-兼容返回全部订阅信息
                $latest = !empty($data['latest_receipt_info']) ? array_reverse($data['latest_receipt_info']) : [];

                //产品id
                if (isset($latest[0]['product_id'])) {
                    $k['product_id'] = $latest[0]['product_id'];
                }
                //事件id
                if (isset($latest[0]['transaction_id'])) {
                    $k['transaction_id'] = $latest[0]['transaction_id'];
                }
                //原始事件id
                if (isset($latest[0]['original_transaction_id'])) {
                    $k['original_transaction_id'] = $latest[0]['original_transaction_id'];
                }
                //到期时间
                if (isset($latest[0]['expires_date_ms'])) {
                    $k['expires_date'] = floor($latest[0]['expires_date_ms'] / 1000); //转换成时间戳 - 到期时间
                }
                //订阅状态
                if (isset($data['pending_renewal_info'][0]['auto_renew_status'])) {
                    $k['auto_renew_status'] = $data['pending_renewal_info'][0]['auto_renew_status'];
                }
                break;

            case 3:
                $k['status']  = $data['status'];
                $k['receipt'] = $data['receipt'];

                //21006 apple返回到期票据返回结构体
                if($data['status'] == 21006 && !empty($data['latest_expired_receipt_info'])){
                    $latest = $data['latest_expired_receipt_info'];
                }else{
                    $latest = $data['latest_receipt_info'];
                }

                //票据
                if(isset($data['latest_receipt'])){
                    $k['latest_receipt'] = $data['latest_receipt'];
                }
                //产品id
                if (isset($data['auto_renew_product_id']['product_id'])) {
                    $k['product_id'] = $data['auto_renew_product_id']['product_id'];
                }
                //事件id
                if (isset($latest['transaction_id'])) {
                    $k['transaction_id'] = $latest['transaction_id'];
                }
                //原始事件id
                if (isset($latest['original_transaction_id'])) {
                    $k['original_transaction_id'] = $latest['original_transaction_id'];
                }
                //到期时间
                if (isset($latest['expires_date'])) {
                    $k['expires_date'] = floor($latest['expires_date'] / 1000); //转换成时间戳 - 到期时间
                }
                //订阅状态
                if (isset($data['auto_renew_status'])) {
                    $k['auto_renew_status'] = $data['auto_renew_status'];
                }
                break;

            default:
                return $data;
        }

        return $k;
    }

?>

回调方法:

/**
     * ios订阅回调
     * ios_subscribe_callback
     * @annotation 同一个苹果appleID 购买的同一个商品的时 original_transaction_id 从始至终都是相同的
     */
    public function iosSubscribeCallback()
    {
        $row_data = file_get_contents('php://input');

        if($row_data){

            $data = json_decode($row_data,true);

            switch ($data){
                case !empty($data['latest_receipt']): //最新票据
                    $latest_receipt = $data['latest_receipt'];
                    break;

                case !empty($data['latest_expired_receipt']): //最新过期票据
                    $latest_receipt = $data['latest_expired_receipt'];
                    break;
                default:
                    $latest_receipt = $data['latest_receipt'];
            }

            if(!empty($latest_receipt)){

                //使用票据去苹果验单
                $receipt_info = $this->apple_validation_url($latest_receipt,2);
                $paper_a = $this->apple_data_parsing($receipt_info,3);

                //返回状态0 并且到期时间大于当前时间
                if($paper_a['status']==0 || $paper_a['status']==21006){

                    //根据原始事件id查询没有关闭订阅服务的追她用户信息进行延期等操作

                    //到期时间大于存储的到期时间
                    if($paper_a['expires_date'] > $y['end_time'] && $paper_a['auto_renew_status'] == 1){
                        //用户续费成功
                        //todo 逻辑处理

                    }else{
                        //用户取消订阅状态
                        if($paper_a['auto_renew_status']== 0){

                            //todo逻辑处理
                        }
                    }
                }
            }else{
                \Common::response_error_header(403,'Invalid latest_receipt');
            }

        }else{
            //无效数据
        }
        \Common::response_success_header(200);
        exit;
    }

注意:回调时候比较坑的是他放票据的键名不是一样的。要对应取

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
对于Java苹果内购回调signedPayload的解密,可以使用Java代码中的Base64解码和RSA解密方法进行处理。示例如下: ```java import java.security.KeyFactory; import java.security.PublicKey; import java.security.spec.X509EncodedKeySpec; import javax.crypto.Cipher; import org.apache.commons.codec.binary.Base64; public class AppleIAPUtils { // 苹果支付公钥 private static final String APPLE_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhdEEU6HmI6QHyUZ6x+ZDyt9CErA8gVrJ+Lw2yVUjz6uMS7VUgHl1cXZ+lEC28ycg2R+sC/DLKPssI/aD+LbEJ3pT1T/lfsNcX9Zflcjhq3YSNb1YZJ/1JKbd+j/0W0zQztRtCYa5PQm9fH/VeO8a5D46hijwCpBFJlnY4+L77v4z5G2HbX5h5g8RUvS46VPi4Cwz7e36oyY88Yv4/f4/dhJx9Z+SxLge3q3p+rnFJnpvgbOhHEtYl2tRfn/h/TZhNc1ULX0a0oQaR7/SHjkkTpnctoA+ud71NqEMNz1HzxIpylsX9FgO8WyVrJLf6V7ML9Q2hjGcZSdA0wIDAQAB"; /** * 对signedPayload进行解密 * * @param signedPayload 苹果服务器返回的加密字符串 * @return 解密后的字符串 */ public static String decryptSignedPayload(String signedPayload) throws Exception { // 先进行Base64解码 byte[] payloadBytes = Base64.decodeBase64(signedPayload); // 使用RSA算法解密 Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); PublicKey publicKey = getPublicKeyFromX509(); cipher.init(Cipher.DECRYPT_MODE, publicKey); byte[] resultBytes = cipher.doFinal(payloadBytes); // 返回解密后的字符串 return new String(resultBytes); } /** * 获取苹果支付公钥 */ private static PublicKey getPublicKeyFromX509() throws Exception { byte[] keyBytes = Base64.decodeBase64(APPLE_PUBLIC_KEY); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return keyFactory.generatePublic(keySpec); } } ``` 以上代码实现了对苹果内购回调signedPayload进行解密的过程,其中使用了Base64解码和RSA解密两种算法。注意,在实际使用过程中需要替换APPLE_PUBLIC_KEY为自己应用的公钥。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值