文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

支付宝周期扣款(支付后签约)业务功能总结(php+golang)

2023-09-10 21:09

关注

文档

周期扣款支付后签约场景文档:https://opendocs.alipay.com/open/041bxs

业务流程

处理签约成功回调,添加到订阅表

定时任务自行请求订阅表,把达到扣款日期的订阅,然后请求支付宝扣款,并本地开通权限给用户,再计算下次扣款时间

处理签约解除回调,删除订阅表数据。(需要去设置网关回调地址,有退款的话支付宝会回调告诉我们)

包文件

golang包:https://github.com/phpgolangdeveloper/smartwalle 
phpsdk包:https://opendocs.alipay.com/open/02np96

注意:上面的golang的包我是做过改进的,在 https://github.com/smartwalle/alipay 基础上改了源码

把改进的包下载下来之后,放在gopath路径中:/Users/twj/Documents/go_www/src/github.com/smartwalle/alipay,我这里的go是gopath环境

备注

因为我们支付系统是用golang写的,业务系统是用php写的,所以下面会有两个系统的代码,但go和php都大同小异。

接周期扣款要注意的点

支付宝的周期扣款,后续的扣款是商家自行请求扣款接口的,支付宝是不会帮你们做定时器然后回调接口提示你已经扣款的。需要你自己写定时任务计算好扣款日期,再去请求支付宝的,然后支付宝可以提前5天扣款。

周期扣款日期不能是28号到月底最后一天的,假设下次扣款日是9月28日,那么建议你设置扣款日期是下个月的1~3号,也就是这个字段:execute_time

周期扣款的后续,商家自行请求支付宝时候,每笔扣款是100元内,也就是你接入周期扣款的时候,后续的每笔自动扣款都必须是100元内,没得提升,想要提升额度就是要用商家代扣,具体问问alipay客服。

代码层

golang代码

结构体

type TradePay struct {Channel            string  `json:"channel"`TBody              string  `json:"t_body"`TotalFee           float64 `json:"amount"`OutTradeNo         string  `json:"out_trade_no"`ProductName        string  `json:"product_name"`ProductDesc        string  `json:"product_desc"`OpenId             string  `json:"open_id"`TradeType          string  `json:"trade_type"`FrontUrl           string  `json:"front_url"`JsonStr            stringAppId              string     `json:"app_id"`MchId              string     `json:"mch_id"`ApiKey             string     `json:"-"`NotifyUrl          string     `json:"notify_url"`NotifyType         string     `json:"notify_type"`ReturnUrl          string     `json:"return_url"`RsaPrivateKey      string     `json:"-"`AlipayrsaPublicKey string     `json:"-"`PassbackParams     url.Values `json:"body"`PayIp              string     `json:"bill_ip"`Tid int64 `json:"tid"`Charset string `json:"charset"`Returl  string `json:"returl"`//add for UnionPay<通联支付专用>MerchantId    int64  `json:"merchant_id"`UnionPayAppId string `json:"-"`CusId         string `json:"-"`SignType      string `json:"-"`ValidTime     int    `json:"-"`// benefitdetail 优惠信息Benefitdetail       Benefitdetail `json:"benefitdetail"`ProductCode         string        `json:"product_code"`PeriodType          string        `json:"period_type,omitempty"`Period              int           `json:"period,omitempty"`ExternalAgreementNo string        `json:"external_agreement_no,omitempty"`}

业务代码:

package paymentimport ("golang_payment/pkg/logging""golang_payment/pkg/setting""strconv""time")func demo(periodType string, period int)  {var client, clientErr = alipay.New("appid", "AlipayrsaPublicKey","RsaPrivateKey", false)if clientErr != nil {logging.Info("Alipay UnifiedOrder New Client Error")panic(clientErr)}var p = alipay.TradeAppPay{}var signNotifyUrl = ""if setting.SanBox {p.NotifyURL = "沙盒支付成功回调地址"signNotifyUrl = "沙盒签约成功回调地址"} else {p.NotifyURL = "正式环境支付成功回调地址"signNotifyUrl = "正式环境签约成功回调地址"}p.ReturnURL = "同步回调地址"p.Body = "商品名字"p.Subject = "商品名字"p.OutTradeNo = "商家订单号"p.TotalAmount = strconv.FormatFloat(1, 'f', 2, 64)// 订单总价p.ProductCode = "QUICK_MSECURITY_PAY"// 请确定是周期扣款还是普通支付扣款if p.ProductCode == "CYCLE_PAY_AUTH" { // 周期购// 默认是按月var ExecuteTime = time.Now().AddDate(0, period, 0).Format("2006-01-02")if periodType == "DAY" {// 哪找天ExecuteTime = time.Now().AddDate(0, period, 1).Format("2006-01-02")}// TODO::注意:支付宝周期扣不能大于 28号, 如果周期扣款当天计算是大于本月28号的,建议设置到下个月的1~3号if time.Now().Format("2006-01-02") > time.Now().Format("2006-01")+"-28" {now := time.Now()currentYear, currentMonth, _ := now.Date()currentLocation := now.Location()//本月6号截止//fmt.Println(time.Date(currentYear, currentMonth, 1, 0, 0, 0, 0, currentLocation).Unix()+6*24*3600-1)//下个月6号截止//fmt.Println(time.Date(currentYear, currentMonth, 1, 0, 0, 0, 0, currentLocation).AddDate(0, 1, 0).Unix()+6*24*3600-1)tm := time.Unix(time.Date(currentYear, currentMonth, 1, 0, 0, 0, 0, currentLocation).AddDate(0, 1, 0).Unix()+1*24*3600-1, 0)ExecuteTime = tm.Format("2006-01-02")}// 签约参数var agreementSign = alipay.Trade{AgreementSignParams: &alipay.AgreementSignParams{PersonalProductCode: "CYCLE_PAY_AUTH_P",SignScene:           "INDUSTRY|EDU",AccessParams:        &alipay.Channel{Channel: "ALIPAYAPP"},ExternalAgreementNo: "随机生成的商家签约码,作用是商家数据库做唯一",PeriodRuleParams: &alipay.PeriodRuleParams{PeriodType:   periodType,Period:       period,ExecuteTime:  ExecuteTime,SingleAmount: 100,// 周期扣款每笔限制扣款最大金额,目前支付宝最大是100元上限},SignNotifyUrl: signNotifyUrl,},}p.AgreementSignParams = agreementSign.AgreementSignParams}p.TimeoutExpress = "30m"var url, payErr = client.TradeAppPay(p)if payErr != nil {logging.Info(payErr)panic(payErr)} else {logging.Info("支付宝-tradePay:订单号支付宝支付串:" + url)}logging.Info(url)}

签约成功回调逻辑

      public function alipayPayCallback(Request $request)    {        try {            if (!CallbackService::alipayPayCallback($request->all())) {                throw new \Exception('周期购回调有误');            }            echo 'success';        }catch (\Exception $e){            Log::error('alipayPayCallback 订阅商品核查失败', [                'msg'  => $e->getMessage(),                'line' => $e->getLine(),                'file' => $e->getFile(),            ]);            echo 'error';        }    }
    public static function alipayPayCallback($all)    {        Log::info('周期扣签约回调', ['all' => $all]);        $all = collect($all)->toArray();        if (!AlipaySigningCallbackModel::where('external_agreement_no', $all['external_agreement_no'])            ->where('callback_status', $all['status'])            ->first()) {            // 添加回调表            AlipaySigningCallbackModel::insertGetId([                'external_agreement_no' => $all['external_agreement_no'],                'info_data'             => json_encode($all, true),                'callback_status'       => $all['status'],            ]);        }        if ($all['status'] != 'NORMAL') {            Log::error('周期扣签约回调 支付宝周期扣异步回调status', ['all' => $all]);            throw new \Exception('签约没成功');        }        $aop = new AopClient();        //编码格式        $aop->postCharset = "UTF-8";        //支付宝公钥赋值        $aop->alipayrsaPublicKey = self::publicKey;        $urlString               = urldecode(http_build_query($all));        $data                    = explode('&', $urlString);        $params                  = [];        foreach ($data as $param) {            $item             = explode('=', $param, "2");            $params[$item[0]] = $item[1];        }        $flag = $aop->rsaCheckV1($params, null, 'RSA2');        if (!$flag) {            Log::error('周期扣签约回调 支付宝周期扣签约回调验证签名不通过', ['all' => $all]);            throw new \Exception('支付宝周期扣签约回调验证签名不通过');        }        $signingData = UserGoodSubscribeModel::from('m_user_good_subscribe as s')            ->leftJoin('m_orders as o', 'o.transfer_no', '=', 's.out_trade_no')            ->leftJoin('m_goods as g', 'o.sku', '=', 'g.sku')            ->where('s.contract_no', $all['external_agreement_no'])            ->select(['s.id', 'o.user_id', 'o.sku', 's.contract_no', 's.status', 's.out_trade_no', 'g.options'])            ->first();        if (!$signingData) {            throw new \Exception('该签约号有误');        }        if ($signingData->status == 2) {            throw new \Exception('该首次订单订单已经签约');        }        try {            DB::beginTransaction();            if (!AlipaySigningCallbackModel::where('external_agreement_no', $all['external_agreement_no'])->update([                'status' => 1,            ])) {                throw new \Exception('回调表修改失败');            }            $period_type = 'months';            $options     = json_decode($signingData->options, true);            switch ($options['unit']) {                case 1:                    $period_type = 'day';                    $period      = (int)$options['unit_value'];                    break;                case 2:                    $period = (int)$options['unit_value'];                    break;                case 3:                    $period = 12 * (int)$options['unit_value'];                    break;                default:                    throw new \Exception('签约回调商品有误');            }            // 下次扣款时间            $next_pay = date('Y-m-d', strtotime("+$period $period_type", strtotime($all['sign_time'])));            if (!UserGoodSubscribeModel::where('id', $signingData->id)->update([                'status'        => 2,                'contract_time' => date('Y-m-d H:i:s'),                'next_pay'      => $next_pay,                'agreement_no'  => $all['agreement_no'],            ])) {                throw new \Exception('回调添加订阅表失败');            }            if (!OrdersModel::where('external_order', $all['external_agreement_no'])                ->update(['agreement_no' => $all['agreement_no']])) {                throw new \Exception('更新订单表失败');            }            DB::commit();        } catch (\Exception $e) {            Log::error('周期扣签约回调 周期购签约回调逻辑处理失败', [                'msg'  => $e->getMessage(),                'line' => $e->getLine(),                'file' => $e->getFile(),            ]);            DB::rollBack();            throw new \Exception($e->getMessage());        }        return true;    }

后续扣款逻辑

php使用的是阿里云提供的sdk包,上面有链接

写一个定时任务,每次扣款、首次签约,都计算好扣款时间,定时任务去查数据库,查当天是否达到了扣款时间,然后进行扣款逻辑。

     public function aliPayCycle($agreement_no, $amount, $out_trade_no, $sku, $user_id, $contract_no)    {        $userInfo = User::where('id', $user_id)->where('status', 1)->select(['mobile', 'id'])->first();        $good     = Goods::where('sku', $sku)->where('status', 1)->select(['name', 'sku', 'price', 'options'])->first();        $aop                      = new AopClient();        $aop->gatewayUrl          = 'https://openapi.alipay.com/gateway.do';        $aop->appId               = '你的appid';        $aop->rsaPrivateKey       = "私钥";        $aop->alipayrsaPublicKey  = "公钥";        $aop->apiVersion          = '1.0';        $aop->signType            = 'RSA2';        $aop->postCharset         = 'utf-8';        $aop->format              = 'json';        $object                   = new \stdClass();        $object->out_trade_no     = "订单号";        $object->total_amount     = "订单价格";        $object->subject          = "商品名字";        $object->product_code     = 'CYCLE_PAY_AUTH';        $agreementParams          = [            'agreement_no' => $agreement_no,        ];        $object->agreement_params = $agreementParams;        $json                     = json_encode($object);        $request                  = new AlipayTradePayRequest();        $request->setBizContent($json);        $result = $aop->execute($request);//dd($result);        $responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response";        $resultCode   = $result->$responseNode->code;        $resultSubMsg = $result->$responseNode->sub_msg;        if (empty($resultCode) && $resultCode != 10000) {            Log::error('支付宝周期扣失败', ['result' => $result]);            throw new \Exception('支付宝周期扣失败' . $resultSubMsg);        }        // 下面是我们系统中的逻辑和计算下次周期扣的时间,不用管    $addPer = OrderService::onlineOrder(            $sku,            $user_id,            $good->price,            $amount,            0,            $userInfo->mobile,            date('Y-m-d H:i:s'),            $contract_no,            2,            $agreement_no        );        //更新下次划扣时间        $period_type = 'months';        $options     = json_decode($good->options, true);        switch ($options['unit']) {            case 1:                $period_type = 'day';                $period      = (int)$options['unit_value'];                break;            case 2:                $period = (int)$options['unit_value'];                break;            case 3:                $period = 12 * (int)$options['unit_value'];                break;            default:                throw new \Exception('签约回调商品有误');        }        // 下次扣款时间        $next_pay = date('Y-m-d', strtotime("+$period $period_type", strtotime(date('Y-m-d H:i:s'))));        if (isset($addPer['makeResultID']) && $addPer['makeResultID']) {            // 下次扣款时间            UserGoodSubscribe::where('agreement_no', $agreement_no)->update(['next_pay' => $next_pay]);        }        Log::info('支付宝周期扣扣款', ['addPer' => $addPer, 'agreement_no' => $agreement_no, 'user_id' => $user_id]);        return true;    }

解除签约

去支付宝的开放后台设置设置应用网关。用户解除签约的时候,是会回调到这个地址的

解除签约回调地址的逻辑

        public function alipayGatewayCallback(Request $request) {        try {            if (!CallbackService::alipayGatewayCallback($request->all())) {                throw new \Exception('周期购回调有误');            }            echo 'success';        }catch (\Exception $e){            Log::error('订阅商品核查失败', [                'msg'  => $e->getMessage(),                'line' => $e->getLine(),                'file' => $e->getFile(),            ]);            echo 'error';        }    }
     public static function alipayGatewayCallback($all)    {        Log::info('支付宝网关回调', ['all' => $all]);        $all = collect($all)->toArray();        // 添加回调表        $notifiId = AlipaySigningCallbackModel::insertGetId([            'external_agreement_no' => $all['external_agreement_no'],            'info_data'             => json_encode($all, true),            'callback_status'       => $all['status'],        ]);        $aop      = new AopClient();        //编码格式        $aop->postCharset = "UTF-8";        //支付宝公钥赋值        $aop->alipayrsaPublicKey = "公钥";        $urlString               = urldecode(http_build_query($all));        $data                    = explode('&', $urlString);        $params                  = [];        foreach ($data as $param) {            $item             = explode('=', $param, "2");            $params[$item[0]] = $item[1];        }        $flag = $aop->rsaCheckV1($params, null, 'RSA2');        if (!$flag) {            Log::error('支付宝周期扣签约回调验证签名不通过', ['all' => $all]);            throw new \Exception('支付宝周期扣签约回调验证签名不通过');        }        // 状态        switch ($all['status']) {            case 'UNSIGN':// 支付宝周期扣解约操作                break;            default:                break;        }        return true;    }

数据表

CREATE TABLE `m_alipay_signing_callback` (  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,  `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '回调情况 0未处理 1处理已完成',  `info_data` text NOT NULL COMMENT '整个订单数据序列化,后续需要再拿出来使用',  `external_agreement_no` varchar(150) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '支付宝签约号',  `callback_status` varchar(20) NOT NULL DEFAULT '' COMMENT '回调状态;正常:NORMAL,解约:UNSIGN,暂存,协议未生效过:TEMP,暂停:STOP',  PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='支付宝周期购签约回调表';CREATE TABLE `m_user_good_subscribe` (  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,  `user_id` int(11) unsigned NOT NULL DEFAULT '0',  `type` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '1:支付宝扣款 2:苹果扣款 3:微信扣款',  `out_trade_no` varchar(50) NOT NULL DEFAULT '' COMMENT '首次商户订单号',  `status` tinyint(1) DEFAULT '0' COMMENT '0 未订阅 1签约中 2已订阅 -1已退订',  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',  `deleted_at` timestamp NULL DEFAULT NULL,  `contract_no` varchar(255) NOT NULL DEFAULT '' COMMENT '支付宝商家本地唯一签约号/苹果transaction_id',  `contract_time` timestamp NULL DEFAULT NULL COMMENT '签约成功时间',  `sku` varchar(32) NOT NULL COMMENT '商品唯一ID',  `next_pay` timestamp NULL DEFAULT NULL COMMENT '下次扣款时间',  `amount` decimal(10,2) NOT NULL COMMENT '签约价格',  `agreement_no` varchar(255) NOT NULL DEFAULT '' COMMENT '支付宝平台签约成功返回签约号/苹果原始original_transaction_id',  PRIMARY KEY (`id`) USING BTREE,  UNIQUE KEY `contract_no` (`contract_no`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=76 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户商品签约表';


 

来源地址:https://blog.csdn.net/qq_41672878/article/details/126300858

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯