本文介绍: 苹果支付流程以及服务端php验证

苹果支付和常规国内的支付流程完全不一样,流程如下:

步骤如下:

1.上架产品

首先需要在苹果网站上架对应的app产品,有对应的id和价格,名称等数据

2.前端拉起商品列表

用户登录app后,进入商品购买页面,前端拉起app在苹果网站上的商品列表信息

3.下单

用户点击’购买’操作,发送给服务端一条当前商品信息,服务端保存该商品的相关订单数据,并返回给前端一个内部业务订单号order_id

4. 购买

用户接收到服务端返回的内部订单号后发起苹果支付,支付完成后苹果会返回一个购买凭证信息,{“receipt-data” : “MIIT9wYJKoSL2JSDFsd……”},大概有8000+的长度,发送到客户端

5.发送凭证信息给服务器

前端拿到苹果返回的购买凭证信息后,将购买凭证、内部业务订单号order_id等信息传至服务端进行购买校验

6. 购买凭证校验以及后续业务逻辑处理

服务端向苹果发送校验购买凭证的请求,根据返回的结果,进行业务处理(改变订单状态等操作),最后返回校验结果给前端

校验结果状态码如下:

0 正常

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

7.返回客户端

前端拿到服务端返回的结果,视结果处理商品,比如: 返回校验成功的结果,前端需向苹果发送一条修改对应产品支付状态的消息,这样才能进行金额的结算操作

8.客服端代码

php以yii2框架为参考

    /**
     * 苹果手机支付回调处理: 用于处理商家订单状态等逻辑操作
     */
    public function actionNotify()
    {
        //$input_data = '{"buddleId":"recharge_01","receiptData":"MIIULgYJKoZIhvcNAQcCoIIUHzCCFBsCAQExC...bV3L15A JAbVFEk7CLOJ7 oQr8WqvpIGu8v5rUkzEzXglAPwkg9BQZdfsew==","transactionId":"10423434320297269213"}';
        //获取回调通知数据
        $input_data = post();
        //判断数据是否json数据
        if (!isJsonString($input_data)) {
            jsonFail();
        }

        //转换通知的JSON文本消息为PHP Array数组
        $input_data = ArrayHelper::toArray(json_decode($input_data));
        //获取购买凭证
        $receiptDate = trim($input_data['receiptData']);

        if (!$receiptDate) {
            jsonFail('购买凭证不存在');
        }
        if (strlen($receiptDate) < 20) {
            jsonFail('购买凭证长度错误');
        }

        $trans = Yii::$app->db->beginTransaction();
        try {
            //验证购买凭证
            $config = Yii::$app->params['applet']; // 获取配置
            $url = $config['is_sandbox'] ? $config['url_sandbox'] : $config['url_buy']; //获取验证请求地址
            //todo 注意:返回的购买凭证里面如果有空格,把空格转换成+,不然校验不通过
            $receiptDate = str_replace(" ", "+", $receiptDate);
            //拼接校验信息
            $data = [
                "receipt-data" => $receiptDate
            ];
            //转换成json字符串格式
            $data = json_encode($data);
            //发送验证请求
            $result = curl_post($url, $data); 
            if (!$result) {
                throw new Exception('验证失败');
            }
            $result = json_decode($result, true);
            /**
             * 0 正常
             * 21000: "App Store 的请求不是使用 HTTP POST 请求方法发出的。", //·21000 对 App Store 的请求不是使用 HTTP POST 请求方法发出的。
             * 21001: "App Store 不再发送此状态代码。",                 //·21001 App Store 不再发送此状态代码。
             * 21002: "属性中的数据receipt-data格式不正确或服务遇到临时问题。",    //·21002 属性中的数据receipt-data格式不正确或服务遇到临时问题。再试一次。
             * 21003: "无法验证收据。",                              //·21003 无法验证收据。
             * 21004: "您提供的共享密钥与您帐户的文件共享密钥不匹配。",              //·21004 您提供的共享密钥与您帐户的文件共享密钥不匹配。
             * 21005: "收据服务器暂时无法提供收据。",                       //·21005 收据服务器暂时无法提供收据。再试一次
             * 21006: "此收据有效,但订阅已过期。",                        //·21006 此收据有效,但订阅已过期。当此状态代码返回到您的服务器时,收据数据也会被解码并作为响应的一部分返回。仅针对自动续订订阅的 iOS 6 样式交易收据返回。
             * 21007: "这条回执是来自测试环境,但它是发送到生产环境进行验证的。",         //·21007 这条回执是来自测试环境,但它是发送到生产环境进行验证的。
             * 21008: "这条回执来自生产环境,但它被发送到测试环境进行验证。",           //·21008 这条回执来自生产环境,但它被发送到测试环境进行验证。
             * 21009: "内部数据访问错误。",                            //·21009 内部数据访问错误。稍后再试。
             * 21010: "用户帐户找不到或已被删除。",                        //·21010 用户帐户找不到或已被删除。
             */
            // 如果是沙盒数据 则验证沙盒模式
            if ($result['status'] == '21007') {
                // 请求验证
                $url = $config['url_sandbox']; //沙盒购买地址
                $result = curl_post($url, $data);
                $result = json_decode($result, true);
            }

            if ($result['status'] != 0) {
                throw new Exception("验证失败,订单购买凭证状态为" . $result['status']);
            }

            //获取内部订单号
            $orderId = $input_data['orderId'];
            // 处理业务逻辑
            $platform_order_number = $input_data["transactionId"];  //获取苹果订单号
            // 订单状态
            $status = UserOrder::STATUS_BUY;
            //支付时间
            $paid_at = $result['receipt']['in_app']['purchase_date'];
            //判断订单是否存在
            $user_order = UserOrder::findOne(['order_id' => $orderId]);
            if (!$user_order) {
                throw new Exception("订单不存在");
            }
            if ($user_order->status == $status) {
                throw new Exception("商品已支付");
            }

            // 变更订单属性,修改订单状态以及支付时间等数据
            UserOrder::changeAttribute(
                ['order_id' => $orderId],
                [
                    'status' => $status,
                    'paid_at' => strtotime($paid_at),
                    'platform_order_number' => $platform_order_number,
                ]
            );
          
            $trans->commit();
            $msg = '支付成功后回调: ' . $ono . '订单更新成功';
            jsonSuccess($msg);
        } catch (Exception $e) {
            $msg = 'error: ' . $e->getMessage() . ' (file: ' . $e->getFile() . ' [' . $e->getLine() . '])';     
            $trans->rollBack();
            jsonFail($msg);
        }
    }

苹果凭证校验返回结果案例展示如下:

{
    "receipt": {
        "receipt_type": "ProductionSandbox",
        "adam_id": 0,
        "app_item_id": 0,
        "bundle_id": "xxx",
        "application_version": "0",
        "download_id": 0,
        "version_external_identifier": 0,
        "receipt_creation_date": "2023-03-16 06:49:00 Etc/GMT",
        "receipt_creation_date_ms": "1678949340000",
        "receipt_creation_date_pst": "2023-03-15 23:49:00 xxx",
        "request_date": "2023-03-17 09:57:55 xxx",
        "request_date_ms": "xxx",
        "request_date_pst": "2023-03-17 02:57:55 xxx",
        "original_purchase_date": "2013-08-01 07:00:00 xxx",
        "original_purchase_date_ms": "xxx",
        "original_purchase_date_pst": "2013-08-01 00:00:00 xxx",
        "original_application_version": "1.0",
        "in_app": [
            {
                "quantity": "1",
                "product_id": "recharge_01",
                "transaction_id": "1424324229453534",
                "original_transaction_id": "1424324229453534",
                "purchase_date": "2023-03-16 06:49:00 xxx",
                "purchase_date_ms": "xxx",
                "purchase_date_pst": "2023-03-15 23:49:00 xxx",
                "original_purchase_date": "2023-03-16 06:49:00 xxx",
                "original_purchase_date_ms": "xxx",
                "original_purchase_date_pst": "2023-03-15 23:49:00 xxx",
                "is_trial_period": "false",
                "in_app_ownership_type": "PURCHASED"
            }
        ]
    },
    "environment": "Sandbox",
    "status": 0
}

上面需要使用到的公共方法

/**
 * 校验一个字符串是不是json字符串
 * @param string $data
 * @param bool $assoc
 * @return bool
 */
function isJsonString($data = '', $assoc = true) {
    $data = json_decode($data, $assoc);
    if(($data && is_object($data)) || (is_array($data) && !empty($data))){
        return true;
    }
    return false;
}

//post请求
function curl_post($url, $data, $headers = array())
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
    //设置header头
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    // POST数据
    curl_setopt($ch, CURLOPT_POST, 1);
    // 把post的变量加上
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    $output = curl_exec($ch);
    curl_close($ch);
    return $output;
}

/**
 * 操作成功
 * @example1 jsonSuccess();
 */
function jsonSuccess($data = NULL, $message = '操作成功')
{
    header("Content-Type:application/json");
    $json = array();
    $json['code'] = 0;
    $json['data'] = $data;
    $json['message'] = $message;
    $json['request_time'] = now();
    echo Json::encode($json);
    exit();
}

/**
 * 错误信息
 * @example1 jsonFail();
 * @example1 jsonFail("删除失败!");
 * @example2 jsonFail($model->getErrors());
 */
function jsonFail($message = '请求失败', $code = 1, $data = null)
{
    header("Content-Type:application/json");
    $json = array();
    $json['code'] = $code;
    $json['data'] = $data;
    $json['message'] = $message;
    $json['request_time'] = now();
    echo Json::encode($json);
    exit();
}

Yii2苹果相关配置:params-local.php

<?php

return [
    //applet配置
    'applet' => [
        'app_id' => '', //应用ID
        'secret' => '', //支付密钥
        //沙箱,现网
        'is_sandbox' => 0,
        'url_buy'     => "https://buy.itunes.apple.com/verifyReceipt", // 正式购买地址
        'url_sandbox' => "https://sandbox.itunes.apple.com/verifyReceipt",// 沙盒购买地址
        'pay_test' => 1,  //是否支付测试
        'is_screen_pay' => 0, //是否屏蔽支付
    ],
];

原文地址:https://blog.csdn.net/zhoupenghui168/article/details/129624667

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_50599.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注