支付宝周期付款
兩種模式
1.簽約扣款
2.扣款后簽約
依據業務需求使用了 扣款后簽約??
1.每次扣款不能超過100元 每期每個簽約只能扣款一次
2.應用審核必須通過審核才能走通
審核過程中會有sign錯誤問題出現? 應用通過就沒問題了(耽誤了基本一天排查該問題)
應用私鑰和證書一定要弄對 不然很麻煩
想看支付寶接口情況?https://opensupport.alipay.com/support/tools/cloudparse/interface?ant_source=antsupport
支付寶文檔:周期扣款 | 網頁&移動應用
composer包:支付寶更多方便的插件 | Pay
PHP框架 laravel?
主要邏輯
1. 支付接口 - 返回str字符串 客戶端利用sdk拉起
2.支付回調接口? - 支付成功會回調地址
3.簽約通知接口? - 支付接口sign_notify_url參數填寫地址 簽約成功會回調該地址
4.解約通知接口? - 商戶主動解約-沒有用到? 用戶主動解約 會請求應用網關 設置支付寶應用網關為該地址
腳本
后續扣款腳本? 時間為下次扣款的時間 最小維度為7天 提前5天可以扣款
重試腳本? 建議重試兩次
更新時間重試腳本? 超過扣款時間 請求更改簽約日期接口
1.app支付?
支付接口 返回加密str給客戶端客戶端使用sdk拉起支付寶 、統一支付接口新增agreement_sign_params 參數 alipay/payment```
/*** 支付寶支付** @return \Illuminate\Http\JsonResponse* @throws \Throwable*/ public function payment() {$user = getApiUser();$type = request("type", 'setmeal');//setmeal 包時套餐 eachcost單次套餐$setmeal_id = request("setmeal_id");// 套餐id$setmeal = db('vip_setmeal')->find($setmeal_id);if (!isset($setmeal)) {return json(4001, '請選擇套餐');}if (strpos($setmeal->channel, '1') === false) {return json(4001, '類型不正確');}$price = $setmeal->money;$title = $setmeal->title;$days = 0;switch ($setmeal->date_type) {case 1:$vip = 'week';$days = 7;break;case 2:$vip = 'onemonth';$days = 30;break;case 3:$vip = 'month';$days = 90;break;case 4:$vip = 'year';break;case 5:$vip = 'oneyear';break;case 6:$vip = 'perpetual';break;default:$vip = '';break;}switch ($type) {case 'setmeal':$title1 = "購買會員時長" . $title;$title = $user['name'] . "購買會員時長" . $title;break;case 'eachcost':$title1 = "購買次數" . $title;$title = $user['name'] . "購買次數" . $title;break;default:$title1 = "購買會員時長";$title = $user['name'] . "購買會員時長";break;}// 將返回字符串,供后續 APP 調用,調用方式不在本文檔討論范圍內,請參考官方文檔。$orderno = Order::getOrderNum();$other['num'] = $setmeal->num;$other['price'] = $price;$other['type'] = $type;$other['date'] = $vip;$other['is_new'] = 1;$other['setmeal_id'] = $setmeal_id;// 生成支付寶支付參數$params = ['subject' => $title1,'out_trade_no' => $orderno,'total_amount' => $price,'agreement_sign_params' => ['personal_product_code' => 'CYCLE_PAY_AUTH_P','sign_scene' => 'INDUSTRY|DIGITAL_MEDIA','external_agreement_no' => $orderno,'access_params' => ['channel' => 'ALIPAYAPP'],'period_rule_params' => ['period_type' => 'DAY','period' => $days,'execute_time' => Carbon::now()->addDays($days)->toDateString(),'single_amount' => $price,],'sign_notify_url' => config('app.url') . '/api/alipay/agreement'],];Log::channel('orders')->info($orderno . '-拉起支付-data:' . json_encode($params, JSON_UNESCAPED_UNICODE));try {DB::beginTransaction();// 獲取支付寶支付信息Pay::config(config('ypay.alipay_config'));$order_str = Pay::alipay()->app($params)->getBody()->getContents();Log::channel('orders')->info($orderno . '-拉起成功-data:' . $order_str);// 保存訂單信息$order = Order::query()->create(["user_id" => $user['id'],"title" => $title,"ordernum" => $orderno,"prepay_id" => '',"remark" => request('remark'),"money" => $price,"channel" => 1,"status" => 1,"other" => $other,]);// 創建簽約OrderAliPayAgreement::query()->create(['user_id' => $user['id'],'order_id' => $order->id,'ordernum' => $orderno,'order_price' => $price,'vip_setmeal_id' => $setmeal_id,'period_price' => $price,'period_day' => $days,'agreement_execute_time' => Carbon::now()->addDays($days)->toDateTimeString(),]);DB::commit();// 創建簽約} catch (\Throwable $t) {Log::channel('orders')->error($orderno . '-拉起失敗-' . $t->getMessage());DB::rollBack();return json(4001, '支付拉起異常');}return json(1001, '請求成功', ['order_str' => $order_str, 'orderId' => $orderno]); }2. 支付回調接口
/*** 支付回調* @throws \EasyWeChat\Kernel\Exceptions\Exception*/ public function notify() {$alipay = Pay::alipay(config('ypay.alipay_config'));$res = request()->all();$orderno = $res['out_trade_no'] ?? 0;Log::channel('orders')->info('alipay_notify:' . $orderno . ':data:' . json_encode($res, JSON_UNESCAPED_UNICODE));try {$data = $alipay->callback(); // 是的,驗簽就這么簡單!$data = $data->all();// 請自行對 trade_status 進行判斷及其它邏輯進行判斷,在支付寶的業務通知中,只有交易通知狀態為 TRADE_SUCCESS 或 TRADE_FINISHED 時,支付寶才會認定為買家付款成功。// 1、商戶需要驗證該通知數據中的out_trade_no是否為商戶系統中創建的訂單號;// 2、判斷total_amount是否確實為該訂單的實際金額(即商戶訂單創建時的金額);// 3、校驗通知中的seller_id(或者seller_email) 是否為out_trade_no這筆單據的對應的操作方(有的時候,一個商戶可能有多個seller_id/seller_email);// 4、驗證app_id是否為該商戶本身。// 5、其它業務邏輯情況if (in_array($data['trade_status'], ['TRADE_CLOSED', 'TRADE_FINISHED'])) {return $alipay->success();}$order = Order::query()->with('user')->where('ordernum', $data['out_trade_no'])->first();if (!$order || $order->status == 2) { // 如果訂單不存在 或者 訂單已經支付過了return $alipay->success();}if ($data['trade_status'] == 'TRADE_SUCCESS') {$order->pay_time = now()->toDateTimeString(); // 更新支付時間為當前時間$order->transaction_id = $data['trade_no'];$order->status = 2;$order->save(); // 保存訂單if (isset($order->other['is_new']) && $order->other['is_new'] == 2) {//更新用戶到期時間,剩余次數User::saveNewVip($order);} else {//更新用戶到期時間,剩余次數User::saveVip($order);}Log::channel('orders')->info('alipay_notify:' . $orderno . ':success');}return $alipay->success();} catch (\Exception $e) {Log::channel('orders')->error('alipay_notify:' . $orderno . ':error:' . $e->getMessage());$error_msg = "【支付寶-回調異常】\n\n【訂單】:{$orderno}\n【原因】:{$e->getMessage()}\n";DingTalk::ding(1, $error_msg, 'text', []);} }3.簽約回調接口
/*** 簽約回調** @return \Psr\Http\Message\ResponseInterface*/ public function agreement() {$alipay = Pay::alipay(config('ypay.alipay_config'));$res = request()->all();$agreement_no = $res['external_agreement_no'] ?? 0;Log::channel('orders')->info('alipay_agreement_notify:' . $agreement_no . ':data:' . json_encode($res, JSON_UNESCAPED_UNICODE));try {$data = $alipay->callback();$data = $data->all();$agreement = OrderAliPayAgreement::query()->where('ordernum', $data['external_agreement_no'])->first();if (!$agreement || $agreement->agreement_status == 2) { // 如果簽約不存在 或者 已經簽約過了return $alipay->success();}if ($data['status'] == 'NORMAL') {$agreement->agreement_success_time = now()->toDateTimeString(); // 更新簽約時間為當前時間$agreement->agreement_no = $data['agreement_no'];$agreement->agreement_status = 2;$agreement->save();Log::channel('orders')->info('alipay_agreement_notify:' . $agreement_no . ':success');}return $alipay->success();} catch (\Exception $e) {Log::channel('orders')->error('alipay_agreement_notify:' . $agreement_no . ':error:' . $e->getMessage());$error_msg = "【支付寶簽約-回調異常】\n\n【訂單】:{$agreement_no}\n【原因】:{$e->getMessage()}\n";DingTalk::ding(1, $error_msg, 'text', []);} }4.解約回調接口
/*** 網關通知*/ public function gateway() {$alipay = Pay::alipay(config('ypay.alipay_config'));$res = request()->all();Log::channel('orders')->info('alipay_gateway_notify:data:' . json_encode($res, JSON_UNESCAPED_UNICODE));try {$data = $alipay->callback();$data = $data->all();switch ($data['notify_type']) {// 簽約case 'dut_user_sign':break;// 解約case 'dut_user_unsign':if ($data['status'] == 'UNSIGN') {OrderAliPayAgreement::query()->where('agreement_no', '=', $data['agreement_no'])->update(['agreement_close_time' => now()->toDateTimeString(),'agreement_status' => 3]);}break;default:break;}return $alipay->success();} catch (\Exception $e) {Log::channel('orders')->error('alipay_gateway_notify:error:' . $e->getMessage());// todo dingding} }腳本
public function handle() {switch ($this->argument('operation')) {// 扣款case 'alipay_check':$this->alipayCheck();break;// 重試case 'alipay_retry' :$this->alipayRetry();break;// 延期case 'alipay_change' :$this->alipayChange();break;default:break;} }/**** 周期扣款**/ public function alipayCheck() {// https://pay.yansongda.cn/docs/v3/alipay/more.html// 當前時間處于扣款時間段內(提前 5 天+扣款日當天)則直接發起重試,如:約定扣款日為 20 號,支持商家在 15 至 20 號內可直接重試。// 當前時間即將超過扣款時間段,可以通過 alipay.user.agreement.executionplan.modify(周期性扣款協議執行計劃修改接口)推遲下一次扣款時間繼續重試。$now = now()->addDays(5)->toDateString();$to_pay_agreement = OrderAliPayAgreement::query()->where(['agreement_status' => 2])->whereBetween('agreement_execute_time', [$now . ' 00:00:00', $now . ' 23:59:59'])->get()->toArray();if (!$to_pay_agreement) {return true;}$config = config('ypay.alipay_config');foreach ($to_pay_agreement as $item) {// 是否存在關聯訂單$is_exist = OrderAliPayAgreementRelation::query()->where('agreement_id', '=', $item['agreement_id'])->where('agreement_execute_time', '=', $item['agreement_execute_time'])->first();// 獲取原始訂單數據$order_info = Order::query()->where('id', '=', $item['order_id'])->first();if (empty($is_exist['id'])) {$orderno = Order::getOrderNum();$order = Order::query()->create(["user_id" => $item['user_id'],"title" => $order_info->title,"ordernum" => $orderno,"prepay_id" => '',"remark" => '簽約續費',"money" => $item['period_price'],"channel" => 1,"status" => 1,"other" => $order_info->other,]);// 創建訂單OrderAliPayAgreementRelation::query()->create(['agreement_id' => $item['agreement_id'],'order_id' => $order->id,'ordernum' => $orderno,'agreement_execute_time' => $item['agreement_execute_time'],]);$order_id = $order->id;} else {$order_id = $is_exist->order_id;$orderno = $is_exist->ordernum;}$agreement_params = ['period_now_order_id' => $order_id,'period_now_status' => 2,'period_now_time' => now()->toDateTimeString(),'period_execute_time' => $item['agreement_execute_time'],'period_now_log' => '',];// 開始執行扣款$params = ['subject' => $order_info->title,'out_trade_no' => $orderno,'total_amount' => $item['period_price'],'product_code' => 'CYCLE_PAY_AUTH','agreement_params' => ['agreement_no' => $item['agreement_no'],],];$this->_doPeriod($config, $params, $item, $agreement_params, $order_id, $orderno);} }/*** 周期重試** @return bool*/ public function alipayRetry() {$today = now()->toDateString();$retry_agreement = OrderAliPayAgreement::query()->where(['agreement_status' => 2, 'period_now_status' => 2])->where('period_retry', '<', 3)->where('period_execute_time', '>=', $today . ' 00:00:00')->get()->toArray();if (!$retry_agreement) {return true;}$config = config('ypay.alipay_config');foreach ($retry_agreement as $item) {// 獲取扣款訂單數據$order_info = Order::query()->where('id', '=', $item['period_now_order_id'])->first();$agreement_params = ['period_now_status' => 2,'period_now_time' => now()->toDateTimeString(),'period_now_log' => '','period_retry' => $item['period_retry'] + 1,];// 開始執行扣款$params = ['subject' => $order_info->title,'out_trade_no' => $order_info->ordernum,'total_amount' => $item['period_price'],'product_code' => 'CYCLE_PAY_AUTH','agreement_params' => ['agreement_no' => $item['agreement_no'],],];$this->_doPeriod($config, $params, $item, $agreement_params, $order_info['id'], $order_info['ordernum']);} }/*** 扣款** @param $config* @param $params* @param $agreement_info* @param $agreement_params* @param $order_id* @param $orderno*/ private function _doPeriod($config, $params, $agreement_info, $agreement_params, $order_id, $orderno) {try {// 開始執行扣款pay::config($config);$allPlugins = Pay::alipay()->mergeCommonPlugins([PayPlugin::class]);$result = Pay::alipay()->pay($allPlugins, $params)->toArray();$agreement_params['period_now_log'] = $result;if ($result['code'] == '10000' && !empty($result['trade_no'])) {$agreement_params['period_now_status'] = 1;$agreement_params['period_success'] = $agreement_info['period_success'] + 1;// 更新下次扣款時間$agreement_params['agreement_execute_time'] = Carbon::parse($agreement_info['agreement_execute_time'])->addDays($agreement_info['period_day'])->toDateTimeString();}} catch (Exception $exception) {$agreement_params['period_now_log'] = $exception->getMessage();}OrderAliPayAgreement::query()->where('agreement_id', '=', $agreement_info['agreement_id'])->update($agreement_params);// 日志入庫OrderAliPayAgreementLog::query()->create(['agreement_id' => $agreement_info['agreement_id'],'order_id' => $order_id,'ordernum' => $orderno,'agreement_execute_time' => $agreement_info['agreement_execute_time'],'params' => json_encode($params),'response' => json_encode($agreement_params['period_now_log']),]); }/*** 周期改簽*** @return bool*/ public function alipayChange() {$change_agreement = OrderAliPayAgreement::query()->where(['agreement_status' => 2, 'period_now_status' => 2])->where('period_retry', '=', 3)->where('agreement_execute_time', '<', now()->toDateString() . ' 00:00:00')->whereNull('period_change')->get()->toArray();if (!$change_agreement) {return true;}$config = config('ypay.alipay_config');foreach ($change_agreement as $item) {$agreement_params = ['period_now_order_id' => 0,'period_now_status' => 0,'period_execute_time' => null,'period_now_time' => null,'period_change' => 1,'period_retry' => 0,'period_now_log' => null,];try {$params = ['agreement_no' => $item['agreement_no'],'deduct_time' => Carbon::parse($item['agreement_execute_time'])->addDays($item['period_day'])->toDateString(),'memo' => '失敗重試-延期',];// 開始執行改簽pay::config($config);$allPlugins = Pay::alipay()->mergeCommonPlugins([AgreementExecutionPlanModifyPlugin::class]);$result = Pay::alipay()->pay($allPlugins, $params)->toArray();$agreement_params['period_now_log'] = $result;if ($result['code'] == '10000' && !empty($result['agreement_no'])) {// 更新下次扣款時間$agreement_params['agreement_execute_time'] = Carbon::parse($item['agreement_execute_time'])->addDays($item['period_day'])->toDateTimeString();}} catch (Exception $exception) {$agreement_params['period_now_log'] = $exception->getMessage();}OrderAliPayAgreement::query()->where('agreement_id', '=', $item['agreement_id'])->update($agreement_params);} }支付寶要開通 周期設置好 證書 以及應用私鑰? !!!!!!
mysql
1.
CREATE TABLE `order_alipay_agreement` (
? `agreement_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
? `user_id` int(11) NOT NULL DEFAULT '0' COMMENT '用戶id',
? `order_id` int(11) NOT NULL DEFAULT '0' COMMENT '簽約時訂單ID',
? `ordernum` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '訂單編號',
? `order_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '簽約時價格',
? `vip_setmeal_id` int(11) NOT NULL DEFAULT '0' COMMENT '套餐ID',
? `agreement_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '支付寶協議號',
? `agreement_status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1待簽約 2已簽約 3關閉簽約',
? `agreement_success_time` datetime DEFAULT NULL COMMENT '簽約時間',
? `agreement_close_time` datetime DEFAULT NULL COMMENT '關閉簽約時間',
? `agreement_execute_time` datetime DEFAULT NULL COMMENT '下次扣款時間',
? `period_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '周期扣款價格',
? `period_day` int(11) NOT NULL DEFAULT '0' COMMENT '周期天數',
? `period_success` int(11) NOT NULL DEFAULT '0' COMMENT '成功扣款次數',
? `period_now_order_id` int(11) DEFAULT '0' COMMENT '周期-最新扣款訂單ID',
? `period_now_status` tinyint(4) DEFAULT NULL COMMENT '周期-最新執行狀態 ?1-扣款成功 2-扣款失敗',
? `period_now_time` datetime DEFAULT NULL COMMENT '周期-最新執行時間',
? `period_execute_time` datetime DEFAULT NULL COMMENT '周期-扣款時間',
? `period_now_log` text COLLATE utf8mb4_unicode_ci COMMENT '周期-最新執行日志',
? `period_retry` int(11) NOT NULL DEFAULT '0' COMMENT '周期-重試次數',
? `period_change` tinyint(1) DEFAULT NULL COMMENT '周期-是否延期 1-是 ',
? `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
? `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間',
? PRIMARY KEY (`agreement_id`) USING BTREE,
? UNIQUE KEY `unidx_order_num` (`ordernum`) USING BTREE,
? KEY `idx_status_channel` (`agreement_status`),
? KEY `idx_user_id_status` (`user_id`,`agreement_status`) USING BTREE,
? KEY `idx_time` (`agreement_execute_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPACT COMMENT='支付寶簽約表'
2.
CREATE TABLE `order_alipay_agreement_relation` (
? `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
? `agreement_id` int(11) NOT NULL DEFAULT '0' COMMENT '簽約ID',
? `order_id` int(11) NOT NULL DEFAULT '0' COMMENT '后續扣款訂單ID',
? `ordernum` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '后續扣款訂單號',
? `agreement_execute_time` datetime DEFAULT NULL COMMENT '計劃扣款時間',
? `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
? `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間',
? PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='訂單后續扣款關系表'
3.
CREATE TABLE `order_alipay_agreement_log` (
? `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
? `agreement_id` int(11) NOT NULL DEFAULT '0' COMMENT '簽約ID',
? `order_id` int(11) NOT NULL DEFAULT '0' COMMENT '后續扣款訂單ID',
? `ordernum` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '后續扣款訂單號',
? `agreement_execute_time` datetime DEFAULT NULL COMMENT '計劃扣款時間',
? `params` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '請求參數',
? `response` text COLLATE utf8mb4_unicode_ci COMMENT '返回值',
? `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
? `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間',
? PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='訂單后續扣款請求記錄表'
總結
- 上一篇: cuda安装-01
- 下一篇: 《鬼谷子本经阴符七术》4分威法伏熊