最近项目中需要使用支付宝的周期扣款,整理一下各种封装方法
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); // 订单IDif ($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 封装方法文件
<?phpnamespace 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