Ios苹果支付流程:

  1. 客户端先从苹果获取内购Id。
  2. 客户端将内购id,金额、用户id等传给服务端获取一个自己服务端生成的订单号。
  3. 客户端向苹果发起支付。
  4. 支付成功后,客户端从本地拿支付凭证、将支付凭证和订单号、用户id等参数传给服务端;服务端拿支付凭证向苹果发起验证订单是否有效,然后将结果反馈给客户端。
  5. 客户端刷新用户个人信息。

代码

/**
 * 服务器二次验证代码
 * @param $receipt
 * @param bool $isSandbox
 * @return array|void
 * @throws \Exception
 */
function getReceiptData($receipt, $isSandbox = false) {
    if ($isSandbox) {
        $endpoint = 'https://sandbox.itunes.apple.com/verifyReceipt';
    } else {
        $endpoint = 'https://buy.itunes.apple.com/verifyReceipt';
    }

    //$receipt = str_replace(' ',"+", $receipt);
    $postData = json_encode(["receipt-data" => $receipt]);
    //$postData = '{"receipt-data":"'. $receipt .'"}';
    BLog::pay("苹果支付:postData:$postData");

    $ch = curl_init($endpoint);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);  //这两行一定要加,不加会报SSL 错误
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);


    $response = curl_exec($ch);
    $errno    = curl_errno($ch);
    $errmsg   = curl_error($ch);
    curl_close($ch);
    
    //判断时候出错,抛出异常
    if ($errno != 0) {
        return [
            'result'     => false,
            'txtMessage' => $errmsg,
        ];
    }

    $data = json_decode($response);

    BLog::pay("苹果支付:response:$response");

    //此处是看到先人们的指导,又看到apple的官方说法改的。否则会审核不过貌似是审核也会走沙盒测试者,
    //此处先判断一次返回的status是否=21007 这数据是从测试环境,但它发送到生产环境中进行验证。它发送到测试环境来代替。
    if ($data->status == 21007) {
        $this->getReceiptData($receipt, true);
        return;
    }
    //判断返回的数据是否是对象
    if (!is_object($data)) {
        throw new \Exception('Invalid response data');
    }
    //判断购买时候成功
    if (!isset($data->status) || $data->status != 0) {
        throw new \Exception('Invalid receipt');
    }

    //返回产品的信息
    return (array)$data->receipt;
}

/**
 * ios 支付验证
 * @param $allData
 * @return array|string
 */
public function iosIAPPay($allData) {
    $receiptData = $allData['receiptData'];
    //获取 App 发送过来的数据,设置时候是沙盒状态
    $receipt   = $receiptData;
    $isSandbox = true;
    if ('server' == APP_ENV) {
        $isSandbox = false;
    }
    //开始执行验证
    try {
        $info = $this->getReceiptData($receipt, $isSandbox);

        $package_name = $info['bid'] ?? "";
        if (!$package_name) {
            $package_name = $info['bundle_id'] ?? "";
        }
        if (!$package_name) {
            return [
                'result'     => false,
                'txtMessage' => 'bid或bundle_id缺失',
            ];
        }

        $product_id = $info['product_id'] ?? "";
        if (!$product_id) {
            $product_id = $info['in_app'][0]->product_id ?? "";
        }
        if (!$product_id) {
            return [
                'result'     => false,
                'txtMessage' => 'product_id缺失',
            ];
        }

        $transaction_id = $info['transaction_id'] ?? "";
        if (!$transaction_id) {
            $transaction_id = $info['in_app'][0]->transaction_id ?? "";
        }
        if (!$transaction_id) {
            return [
                'result'     => false,
                'txtMessage' => 'transaction_id缺失',
            ];
        }

        $productInfo = DB::table('ios_payment_config')
                            ->where('package_name', $package_name)
                            ->where('product_id', $product_id)
                            ->first();
        if (!$productInfo) {
            BLog::pay("苹果支付:无该产品:" . json_encode($info));
            return [
                'result'     => false,
                'txtMessage' => '无该产品',
            ];
        }
        $user_open_id = $info['download_id'] ?? "";
        $res          = $this->doPay($allData['userId'], $allData['orderId'], $transaction_id, $user_open_id);
        BLog::pay("苹果支付:支付结果:" . json_encode($info) . json_encode($res));
        return $res;

    } catch (\Exception $e) {
        return [
            'result'     => false,
            'txtMessage' => $e->getMessage(),
        ];
    }
}

常见错误码

/**
* 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环境的验证服务
*/

遇到的坑

回包结构的修改

新结构:


{
    "receipt": {
        "receipt_type": "ProductionSandbox",
        "adam_id": 0,
        "app_item_id": 0,
        "bundle_id": "申请苹果支付时的串号 固定的值",
        "application_version": "24",
        "download_id": 0,
        "version_external_identifier": 0,
        "receipt_creation_date": "2022-************Etc/GMT",
        "receipt_creation_date_ms": "1643************000",
        "receipt_creation_date_pst": "2022-0a************/Los_Angeles",
        "request_date": "2022-02-2************Etc/GMT",
        "request_date_ms": "164************7",
        "request_date_pst": "2022-************ngeles",
        "original_purchase_date": "201************tc/GMT",
        "original_purchase_date_ms": "13************00",
        "original_purchase_date_pst": "2013************geles",
        "original_application_version": "1.0",
        "in_app": [ //变成了数组
            {
                "quantity": "1",
                "product_id": "10000",#产品ID
                "transaction_id": "", ##交易单号
                "original_transaction_id": "",
                "purchase_date": "2022************Etc/GMT",
                "purchase_date_ms": "164************0",
                "purchase_date_pst": "2022-************les",
                "original_purchase_date": "2022************GMT",
                "original_purchase_date_ms": "164************0",
                "original_purchase_date_pst": "202************les",
                "is_trial_period": "false",
                "in_app_ownership_type": "PURCHASED"#交易状态
            },
            {
                "quantity": "1",
                "product_id": "10000",#产品ID
                "transaction_id": "", ##交易单号
                "original_transaction_id": "",
                "purchase_date": "2022************Etc/GMT",
                "purchase_date_ms": "164************0",
                "purchase_date_pst": "2022-************les",
                "original_purchase_date": "2022************GMT",
                "original_purchase_date_ms": "164************0",
                "original_purchase_date_pst": "202************les",
                "is_trial_period": "false",
                "in_app_ownership_type": "PURCHASED"#交易状态
            },
        ]
    },
    "environment": "Sandbox", //环境
    "status": 0
}

老版本是一个一维数组,没有in_app

post请求的坑,报21003

  • 第一种,有空格,使用字符串替换
$receipt = str_replace(' ',"+", $receipt);
  • 第二种,json不对,直接拼字符串,不要json_encode
$postData = '{"receipt-data":"'. $receipt .'"}';
  • 第三种,苹果那边的问题,什么都没改,第一天好的,第二天报错(沙盒报错,线上好的),第三天好了。