PHP 支付宝支付、订阅支付(周期扣款)整理汇总

最近项目中需要使用支付宝的周期扣款,整理一下各种封装方法

APP支付(服务端)

    /******************************************************
     * 调用方法
     ******************************************************/
    function test_pay()
    {
    
    
        $isSubscribe = 1;

        $price = 0.01;
        $detail = $body = "会员充值";
        $orderSn = date("mdHis") . mt_rand(2000, 8000);
        $hostApi = config('host_api');
        if (!$isSubscribe) {
    
     // 一次性支付
            $bizSontent = [
                "timeout_express" => "30m",
                "product_code" => "QUICK_MSECURITY_PAY",
                "total_amount" => $price,
                "subject" => $detail,
                "body" => $body,
                "out_trade_no" => $orderSn,
            ];
        } else {
    
     // 订阅
            // 参见下文sign_scene参数说明 https://opendocs.alipay.com/open/08bg92?pathHash=b655de17
            $bizSontent = [
                "out_trade_no" => $orderSn,
                "total_amount" => $price, //订单总金额,首次支付的金额,不算在周期扣总金额里。
                "subject" => $detail,
                "body" => $body,
                "product_code" => "CYCLE_PAY_AUTH",  // CYCLE_PAY_AUTH
                "timeout_express" => "90m",
                //商家扣款协议信息
                "agreement_sign_params" => [
                    "product_code" => "GENERAL_WITHHOLDING",//收单产品码固定为GENERAL_WITHHOLDING
                    "personal_product_code" => "CYCLE_PAY_AUTH_P", //个人签约产品码固定为CYCLE_PAY_AUTH_P
                    "sign_scene" => "INDUSTRY|DEFAULT_SCENE",//协议签约场景,参见下文sign_scene参数说明 数字传媒行业
                    "external_agreement_no" => $orderSn,//商户签约号,代扣协议中用户的唯一签约号
//                    "sign_notify_url" => $hostApi . "/v1/notify/alipay_sub",//签约成功异步通知地址
                    "access_params" => [ //签约接入的方式
                        "channel" => "ALIPAYAPP"
                    ],
                    // 签约规则
                    "period_rule_params" => [
                        "period_type" => "DAY",//周期类型枚举值为 DAY 和 MONTH
                        "period" => 9999,//周期数,与 period_type 组合使用确定扣款周期 // 扣款周期类型period_type参数为DAY时,扣款周期period参数不得小于7。
                        "execute_time" => date('Y-m-d'),//用户签约后,下一次使用代扣支付扣款的时间
                        "single_amount" => $price,//单次扣款最大金额
//                        "total_amount" => "0.02",//周期内扣允许扣款的总金额,单位为元
//                        "total_payments" => "2"//总扣款次数。
                    ]
                ],
            ];
        }

        $notiyUrl = $hostApi . '/notify/alipay';
        list($result, $responseNode) = PayUtil::pay($bizSontent, $notiyUrl);
        dump($result);

    }

异步回调

 // 支付宝异步通知
    function alipay()
    {
    
    
        $verify = PayUtil::notifyVerify($_POST);
        $orderSn = addslashes($_POST['out_trade_no'] ?? '');    //商户订单号
        $trade_no = addslashes($_POST['trade_no'] ?? '');       //支付宝交易号
        $trade_status = trim(addslashes($_POST['trade_status'] ?? ''));

        if (!empty($trade_status)) {
    
     // 支付回调
            LogHelperUtil::outLog('alipay_notify_' . $trade_status, json_encode(['post' => $_POST, 'verify' => $verify]), 'alipay_notify');
            if (empty($orderSn)) {
    
    
                return "fail";
            }
            Db::name('order_log_alipay')->insert([
                'order_sn' => $orderSn, 'trans_id' => $trade_no, 'create_time' => date('Y-m-d H:i:s'),
                'content' => json_encode($_POST)
            ]);
            $orderInfo = OrderModel::get_info(['order_sn' => $orderSn, 'status' => 0, 'pay_type' => 1]);
            if (!empty($orderInfo) && $trade_status == 'TRADE_SUCCESS') {
    
    
                $this->paySuccess($orderInfo, $trade_no);
                return "success";
            }
            if ($trade_status == 'TRADE_CLOSED') {
    
     // 退款
                return "success";
            }
            return "fail";
        }

        /***************************************************************
         * 订阅回调
         * 重要参数说明
         * status:协议状态,枚举支持。 NORMAL:正常  UNSIGN:解约。
         * external_agreement_no:标示用户的唯一签约协议号,商家自定义。仅签约接口传入时返回
         * agreement_no:支付宝系统中用以唯一标识用户签约记录的编号。
         * notify_type:异步通知类型,枚举支持。 dut_user_sign:当 status = NORMAL 表示签约成功。  dut_user_unsign:当 status = UNSIGN 表示解约成功。
         * sign_scene:签约协议场景。
         * personal_product_code:协议产品码。
         * alipay_user_id:用户的支付宝账号对应的支付宝唯一用户号。
         **********************************************************************/
        $status = $_POST['status'] ?? '';  // 协议状态,枚举支持。 NORMAL:正常  UNSIGN:解约。
        $notifyType = $_POST['notify_type'] ?? '';  // 异步通知类型,枚举支持。 dut_user_sign:当 status = NORMAL 表示签约成功。  dut_user_unsign:当 status = UNSIGN 表示解约成功。
        $orderSn = $_POST['external_agreement_no'] ?? ''; // 自定义
        $agreementNo = $_POST['agreement_no'] ?? '';
        Db::name('order_log_alipay_sub')->insert([
            'order_sn' => $orderSn, 'trans_id' => $agreementNo, 'create_time' => date('Y-m-d H:i:s'),
            'content' => json_encode($_POST)
        ]);

        LogHelperUtil::outLog('alipay_notify_sub_' . $status, json_encode(['post' => $_POST, 'verify' => $verify]), 'alipay_notify_sub');
        if (empty($agreementNo)) {
    
    
            return "fail";
        }

        $orderInfo = OrderSubscribeModel::get_info(['order_sn' => $orderSn, 'pay_type' => 1]);
        $oid = intval($orderInfo['id'] ?? 0);    //  订单ID
        if ($status == 'UNSIGN' && $notifyType == 'dut_user_unsign') {
    
     // 解约
            $response = OrderSubscribeModel::update_data(['id' => $oid], [
                'status' => 2, // 0-签约中 1-已订阅 2-已退订
                'contract_del_date' => date('Y-m-d H:i:s'), // 解约时间
            ]);
            return "success";
        }
        // 0-签约中 1-已订阅 2-已退订
        if (!empty($orderInfo) && $status == 'NORMAL' && $notifyType == 'dut_user_sign') {
    
     // 签约成功
            // 记录签约成功的订单
            $response = OrderSubscribeModel::update_data(['id' => $oid], [
                'status' => 1, // 0-签约中 1-已订阅 2-已退订
                'agreement_no' => $agreementNo,  // 签约号
                'contract_date' => date('Y-m-d H:i:s'), // 签约时间
            ]);
            if ($response) {
    
    
                $toDay = date('Y-m-d');
                $kontDay = date('Y-m-d', strtotime("+1 day"));
                $nextPay = $orderInfo['next_pay'] ?? '';
                if ($nextPay == $kontDay || $toDay == $nextPay) {
    
    
                    $notiyUrl = config('host_api') . '/notify/alipay_sub_knot?order_sn=' . $orderSn;
                    send_socket_time_task($notiyUrl, 300);
                    LogHelperUtil::outLog('alipay_notify_sub_' . $status, "已加入队列" . $notiyUrl, 'alipay_notify_sub');
                }
            }
            return "success";
        }

        return "fail";
    }

周期扣款操作(定时任务)


    // 支付宝订阅自动扣款
    function alipay_sub_knot()
    {
    
    
        $orderSnSub = trim(addslashes($_GET['order_sn'] ?? ''));
        if (empty($orderSnSub)) {
    
    
            return 'fail-sn';
        }
        // 0-签约中 1-已订阅 2-已退订
        $orderInfo = OrderSubscribeModel::get_info(['order_sn' => $orderSnSub, 'status' => 1]);
        if (empty($orderInfo)) {
    
    
            return 'fail-order';
        }
        $type = $orderInfo['type'] ?? 0;   
        $uid = $orderInfo['uid'] ?? 0;
        $priceRenew = $orderInfo['price_renew'] ?? 0;  // 续订价格
        $agreementNo = $orderInfo['agreement_no'] ?? '';  // 签约号
        if (empty($agreementNo)) {
    
    
            return 'fail-order';
        }
        LogHelperUtil::outLog('alipay_sub_knot', json_encode(['orderSnSub' => $orderSnSub, 'order' => $orderInfo]), 'alipay_sub_knot');
 
        $detail = '周期扣款';
        $orderSn = "DYK{
      
      $type}U" . $uid . date("mdHis") . mt_rand(2000, 8000);
        list($result) = PayUtil::pay_sub_knot($orderSn, $detail, $priceRenew, $agreementNo);
        $resultCode = $result['code'] ?? 0;
        if ($resultCode != 10000) {
    
    
            LogHelperUtil::outLog('alipay_sub_knot_fail', json_encode(['orderSnSub' => $orderSnSub, 'order' => $orderInfo, 'result' => $result]), 'alipay_sub_knot');
            return "fail-result";
        }
        // 扣款成功,写入订单逻辑
       
        return "success";
    }

其他一下方法

    function alipay_test()
    {
    
    

        $agreementNo = '20235529965404462663';
        list($res,$code) = PayUtil::agreementQuery($agreementNo); // 签约查询
        dump($res);

        // 周期性扣款协议执行计划修改
        list($res,$code) = PayUtil::agreementModify($agreementNo,'2023-11-01','购买了半年包月');
        dump($res);

        // 解约
        list($res,$code) = PayUtil::agreementUnsign($agreementNo);
        dump($res);
        return '';
    }

PayUtil 封装方法文件

<?php

namespace pay\alipay;

class PayUtil
{
    
    

    /**
     * 发起支付
     * 支付后签约场景:https://opendocs.alipay.com/pre-open/08bpuc?pathHash=a572b7a7
     * @param $bizSontent
     * @param $notiyUrl
     * @return array|string[]
     * @author wzb
     * @date 2023/7/22 9:50
     */

    static function pay($bizSontent = [], $notiyUrl = '')
    {
    
    
        $alipayConfig = config('alipay_config'); // 配置
        if (empty($bizSontent) || empty($alipayConfig)) {
    
    
            return ['', ''];
        }
        $aop = new AopClient();
        $aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
        $aop->appId = $alipayConfig['appid'] ?? '';
        $aop->rsaPrivateKey = $alipayConfig['rsaPrivateKey'] ?? '';
        $aop->alipayrsaPublicKey = $alipayConfig['alipayrsaPublicKey'] ?? '';
        $aop->apiVersion = '1.0';
        $aop->signType = 'RSA2';
        $aop->postCharset = 'utf-8';
        $aop->format = 'json';
        $request = new \AlipayTradeAppPayRequest();
        $request->setNotifyUrl($notiyUrl);
        $request->setBizContent(json_encode($bizSontent));
        $result = $aop->sdkExecute($request);
        $responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
        return [$result, $responseNode];
    }

    /**
     * 查询交易信息
     *
     * @param $outTradeNo
     * @return string[]|void
     * @author wzb
     * @date 2023/7/22 10:03
     */
    static function queryOrder($outTradeNo = '')
    {
    
    
        $bizSontent = [
            "out_trade_no" => $outTradeNo,
//            "trade_no"=>"DJ4U2407211930124801",
//            "query_options"=>[
//                "trade_settle_info", // 交易结算信息
//            ]
        ];
        $alipayConfig = config('alipay_config'); // 配置
        if (empty($alipayConfig)) {
    
    
            return ['', ''];
        }
        $aop = new AopClient();
        $aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
        $aop->appId = $alipayConfig['appid'] ?? '';
        $aop->rsaPrivateKey = $alipayConfig['rsaPrivateKey'] ?? '';
        $aop->alipayrsaPublicKey = $alipayConfig['alipayrsaPublicKey'] ?? '';
        $aop->apiVersion = '1.0';
        $aop->signType = 'RSA2';
        $aop->postCharset = 'utf-8';
        $aop->format = 'json';
        $request = new \AlipayTradeQueryRequest();
        $request->setBizContent(json_encode($bizSontent));
        $result = $aop->execute($request);
        $responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
        $response = $result->$responseNode;
        $response = json_decode(json_encode($response), true);
        return [$response, $responseNode];
    }

    /**
     * @param string $orderSn 订单号
     * @param string $detail 说明
     * @param int $totalAmount 扣款金额
     * @param string $agreement_no 签约号
     * @return array|string[]
     * @author wzb
     * @date 2023/7/29 10:50
     */
    static function pay_sub_knot($orderSn, $detail, $totalAmount, $agreement_no)
    {
    
    
        $bizSontent = [
            "out_trade_no" => $orderSn,  //订单号
            "total_amount" => $totalAmount,
            "subject" => $detail,
            "product_code" => "GENERAL_WITHHOLDING",
            // 代扣信息。
            'agreement_params' => [
                "agreement_no" => $agreement_no,
            ],
        ];

        $alipayConfig = config('alipay_config'); // 配置
        if (empty($bizSontent) || empty($alipayConfig)) {
    
    
            return ['', ''];
        }
        $aop = new AopClient();
        $aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
        $aop->appId = $alipayConfig['appid'] ?? '';
        $aop->rsaPrivateKey = $alipayConfig['rsaPrivateKey'] ?? '';
        $aop->alipayrsaPublicKey = $alipayConfig['alipayrsaPublicKey'] ?? '';
        $aop->apiVersion = '1.0';
        $aop->signType = 'RSA2';
        $aop->postCharset = 'utf-8';
        $aop->format = 'json';
        $request = new \AlipayTradePayRequest();
        $request->setBizContent(json_encode($bizSontent));
        $result = $aop->execute($request);
        $responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
        $response = $result->$responseNode;
        $response = json_decode(json_encode($response), true);
        return [$response, $responseNode];
    }

    /**
     * 退款
     * @param $outTradeNo
     * @param $tradeNo
     * @param $refundAmount
     * @return array
     * @author wzb
     * @date 2023/7/25 17:14
     */
    static function refundOrder($outTradeNo = '', $tradeNo = '', $refundAmount = 0)
    {
    
    
        $bizSontent = [
            "out_trade_no" => $outTradeNo,
            "trade_no" => $tradeNo,
            "refund_amount" => $refundAmount,
        ];

        $alipayConfig = config('alipay_config'); // 配置
        if (empty($alipayConfig)) {
    
    
            return ['', ''];
        }
        $aop = new AopClient();
        $aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
        $aop->appId = $alipayConfig['appid'] ?? '';
        $aop->rsaPrivateKey = $alipayConfig['rsaPrivateKey'] ?? '';
        $aop->alipayrsaPublicKey = $alipayConfig['alipayrsaPublicKey'] ?? '';
        $aop->apiVersion = '1.0';
        $aop->signType = 'RSA2';
        $aop->postCharset = 'utf-8';
        $aop->format = 'json';
        $request = new \AlipayTradeRefundRequest();
        $request->setBizContent(json_encode($bizSontent));
        $result = $aop->execute($request);
        $responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
        $response = $result->$responseNode;
        $response = json_decode(json_encode($response), true);
        return [$response, $responseNode];
    }


    /**
     * 查询签约接口
     * https://opendocs.alipay.com/open/3dab71bc_alipay.user.agreement.query?scene=8837b4183390497f84bb53783b488ecc&pathHash=9a0c5949
     * @return array|string[]
     * @author wzb
     * @date 2023/7/29 13:35
     */
    static function agreementQuery($agreementNo)
    {
    
    
        $bizSontent = [
            "agreement_no" => $agreementNo,
        ];

        $alipayConfig = config('alipay_config'); // 配置
        if (empty($alipayConfig)) {
    
    
            return ['', ''];
        }
        $aop = new AopClient();
        $aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
        $aop->appId = $alipayConfig['appid'] ?? '';
        $aop->rsaPrivateKey = $alipayConfig['rsaPrivateKey'] ?? '';
        $aop->alipayrsaPublicKey = $alipayConfig['alipayrsaPublicKey'] ?? '';
        $aop->apiVersion = '1.0';
        $aop->signType = 'RSA2';
        $aop->postCharset = 'utf-8';
        $aop->format = 'json';
        $request = new \AlipayUserAgreementQueryRequest();
        $request->setBizContent(json_encode($bizSontent));
        $result = $aop->execute($request);
        $responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
        $response = $result->$responseNode;
        $response = json_decode(json_encode($response), true);
        return [$response, $responseNode];
    }

    /**
     * 解约
     * https://opendocs.alipay.com/open/b841da1f_alipay.user.agreement.unsign?scene=90766afb41f74df6ae1676e89625ebac&pathHash=a3599432
     * @param string $agreementNo 签约号(协议号)
     * @param string $orderSn 订单号 代扣协议中标示用户的唯一签约号(确保在商户系统中唯一)。
     * @return array|string[]
     * @author wzb
     * @date 2023/7/29 13:49
     */
    static function agreementUnsign($agreementNo, $orderSn)
    {
    
    
        $bizSontent = [
            "agreement_no" => $agreementNo,
            "external_agreement_no" => $orderSn,
        ];

        $alipayConfig = config('alipay_config'); // 配置
        if (empty($alipayConfig)) {
    
    
            return ['', ''];
        }
        $aop = new AopClient();
        $aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
        $aop->appId = $alipayConfig['appid'] ?? '';
        $aop->rsaPrivateKey = $alipayConfig['rsaPrivateKey'] ?? '';
        $aop->alipayrsaPublicKey = $alipayConfig['alipayrsaPublicKey'] ?? '';
        $aop->apiVersion = '1.0';
        $aop->signType = 'RSA2';
        $aop->postCharset = 'utf-8';
        $aop->format = 'json';
        $request = new \AlipayUserAgreementUnsignRequest();
        $request->setBizContent(json_encode($bizSontent));
        $result = $aop->execute($request);
        $responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
        $response = $result->$responseNode;
        $response = json_decode(json_encode($response), true);
        return [$response, $responseNode];
    }

    /**
     * 周期性扣款协议执行计划修改
     * https://opendocs.alipay.com/open/ed428330_alipay.user.agreement.executionplan.modify?pathHash=e019f106
     * @param string $agreementNo 签约号(协议号)
     * @param string $nextPay 商户下一次扣款时间 2023-01-01
     * @param string $memo 具体修改原因    64个字符
     * @return array|string[]
     * @author wzb
     * @date 2023/7/29 13:45
     */
    static function agreementModify($agreementNo, $nextPay, $memo)
    {
    
    
        $bizSontent = [
            "agreement_no" => $agreementNo,
            "deduct_time" => $nextPay,
            "memo" => $memo,
        ];

        $alipayConfig = config('alipay_config'); // 配置
        if (empty($alipayConfig)) {
    
    
            return ['', ''];
        }
        $aop = new AopClient();
        $aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
        $aop->appId = $alipayConfig['appid'] ?? '';
        $aop->rsaPrivateKey = $alipayConfig['rsaPrivateKey'] ?? '';
        $aop->alipayrsaPublicKey = $alipayConfig['alipayrsaPublicKey'] ?? '';
        $aop->apiVersion = '1.0';
        $aop->signType = 'RSA2';
        $aop->postCharset = 'utf-8';
        $aop->format = 'json';
        $request = new \AlipayUserAgreementExecutionplanModifyRequest();
        $request->setBizContent(json_encode($bizSontent));
        $result = $aop->execute($request);
        $responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";
        $response = $result->$responseNode;
        $response = json_decode(json_encode($response), true);
        return [$response, $responseNode];
    }

    /**
     * 验证
     * @param $arr
     * @return bool|string[]|null
     * @author wzb
     * @date 2023/7/22 10:33
     */
    static function notifyVerify($arr)
    {
    
    
        $alipayConfig = config('alipay_config'); // 配置
        if (empty($alipayConfig)) {
    
    
            return false;
        }
        $aop = new AopClient();
        $aop->alipayrsaPublicKey = $alipayConfig['alipayrsaPublicKey'] ?? '';
        $urlString = urldecode(http_build_query($arr));
        $data = explode('&', $urlString);
        $params = [];
        foreach ($data as $param) {
    
    
            $item = explode('=', $param, "2");
            $params[$item[0]] = $item[1];
        }
        $result = $aop->rsaCheckV1($params, null, 'RSA2');
        return $result;
    }
}

PHP服务端SDK

猜你喜欢

转载自blog.csdn.net/qq_23564667/article/details/131995296