|
问题背景最近接入微信支付,微信官方并没有提供Python版的服务端SDK,因而只能根据文档手动实现一版,这里记录一下微信支付的整体流程、踩坑过程与最终具体实现。微信支付APP下单流程根据微信官方文档: 开发指引-APP支付|微信支付商户平台文档中心开发指引-APP支付|微信支付商户平台文档中心下单流程:和支付宝不同,微信多了一个预付单的概念,这里把APP下单实际分为四大部分,其中包含请求微信后端需要的首次签名和需要返回给APP的二次支付信息签名--这里踩一个小坑,流程图中并没把第二次签名支付信息需要返回给APP的步骤画出来(即下面的步骤6.5),因而一开始误以为只需要返回prepay_id给客户端,导致校验失败。一.对应步骤1~4,APP请求业务后端,业务后台进行V3签名后,请求微信后端生成预付单prepay_id二.对应步骤5~6.5,业务后端收到微信后端返回prepay_id,将支付相关参数打包进行二次签名后返回给APP,这里相比流程图多了一个6.5--即业务后端返回签名支付信息到APP三.对应步骤7~18,APP收到业务后端返回签名支付信息后调起SDK发起支付请求,收到同步消息结果通知实现方法:这是我实际的商品购买的业务代码,用户需要登录授权之后,在下单是把用户的openid和商品价格商品描述发送给后端。需要安装wechatpayv3pipinstallwechatpayv3我封装好的支付方法:Pay.pyimportjsonfromwechatpayv3importWeChatPay,WeChatPayTypefromutils.pay.configimport*classPay():def__init__(self):""":paramwechatpay_type:微信支付类型,示例值:WeChatPayType.MINIPROG:parammchid:直连商户号,示例值:'1230000109':paramprivate_key:商户证书私钥,示例值:'MIIEvwIBADANBgkqhkiG9w0BAQE...':paramcert_serial_no:商户证书序列号,示例值:'444F4864EA9B34415...':paramappid:应用ID,示例值:'wxd678efh567hg6787':paramapiv3_key:商户APIv3密钥,示例值:'a12d3924fd499edac8a5efc...':paramnotify_url:通知地址,示例值:'https://www.weixin.qq.com/wxpay/pay.php':paramcert_dir:平台证书存放目录,示例值:'/server/cert':parampartner_mode:接入模式,默认False为直连商户模式,True为服务商模式:paramproxy:代理设置,示例值:{"https":"http://10.10.1.10:1080"},没有就是None:paramtimeout:超时时间,示例值:(10,30),10为建立连接的最大超时时间,30为读取响应的最大超时实践"""self.wxpay=WeChatPay(wechatpay_type=WeChatPayType.MINIPROG,mchid=MCHID,private_key=PRIVATE_KEY,cert_serial_no=CERT_SERIAL_NO,apiv3_key=APIV3_KEY,appid=APPID,notify_url=NOTIFY_URL,cert_dir=CERT_DIR,partner_mode=PARTNER_MODE,proxy=PROXY,timeout=TIMEOUT,)defpay(self,openid,price,nonce_str,description):""":paramopenid:微信用户的唯一标识:paramprice:该订单的价格:paramnonce_str:请求随机串nonce_str,与签名使用的随机字符串值仙童:paramdescription:商户证书序列号,示例值:'444F4864EA9B34415...'returnprepay_id:【预支付交易会话标识】预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时"""code,message=self.wxpay.pay(description=description,out_trade_no=nonce_str,amount={'total':price},payer={'openid'penid})#print('code:%s,message:%s'%(code,message))message=json.loads(message)print(message)returnmessage["prepay_id"]defsign(self,prepay_id,timeStamp,nonce_str):""":paramprepay_id:预支付交易会话标识:paramprice:该订单的价格:paramnonce_str:请求随机串nonce_str,与签名使用的随机字符串值仙童:paramdescription:商户证书序列号,示例值:'444F4864EA9B34415...'returnprepay_id:【预支付交易会话标识】预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时"""#:微信支付订单采用RSAwithSHA256算法时,示例值:['wx888','1414561699','5K8264ILTKCH16CQ2502S....','prepay_id=wx201410272009395522657....']sign=[APPID,timeStamp,nonce_str,f'prepay_id={prepay_id}']#print(self.wxpay.sign(sign))paySign=self.wxpay.sign(sign)return{"timeStamp":timeStamp,"nonceStr":nonce_str,"package":f"prepay_id={prepay_id}","signType":"RSA","paySign":paySign}defdecrypt_callback(self,headers,body):""":paramheaders::parambody:returnprepay_id"""returnself.wxpay.decrypt_callback(headers,body)在pay方法中需要构造对微信官方的请求:JSAPI下单(可以看里面官方需要的必须数据:)sign方法中是对应答prepay_id做签名处理:如何生成请求签名(官方)并返回前端需要的对接官方接口:wx.requestPayment(Objectobject)的请求数据:decrypt_callback方法是对回调的通知接口,用于接收微信官方的响应,判断支付是否成功django框架下实现代码:config.py配置文件代码:#微信支付商户号(直连模式)或服务商商户号(服务商模式,即sp_mchid)MCHID='***************'#商户证书私钥withopen('utils/zhi_cert/apiclient_key.pem')asfRIVATE_KEY=f.read()#商户证书序列号CERT_SERIAL_NO='***************'#APIv3密钥,https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtmlAPIV3_KEY='***************'#APPID,应用ID或服务商模式下的sp_appidAPPID='***************'#写自己小程序的密钥AppSecret="***************"#回调地址,也可以在调用接口的时候覆盖NOTIFY_URL='http://63m4gj.natappfree.cc/front-lp/wechatnotify'#微信支付平台证书缓存目录,初始调试的时候可以设为None,首次使用确保此目录为空目录。CERT_DIR='./zhijiao/cert'#接入模式:False=直连商户模式,True=服务商模式。PARTNER_MODE=False#代理设置,None或者{"https":"http://10.10.1.10:1080"},详细格式参见[https://requests.readthedocs.io/en/latest/user/advanced/#proxies](https://requests.readthedocs.io/en/latest/user/advanced/#proxies)PROXY=None#请求超时时间配置TIMEOUT=(10,30)#建立连接最大超时时间是10s,读取响应的最大超时时间是30s然后接口代码实现与前端请求响应代码:url.pyfromdjango.urlsimportpathfromappFrontLP.viewsimport*fromdjango.conf.urls.staticimportstaticurlpatterns=[path("wechatnotify",WeChatNotifyView.as_view()),#微信支付回调通知path('wechatpay',WeChatPayView.as_view()),#支付]视图函数view.py方法classWeChatPayView(APIView):defpost(self,request)penid=request.data.get('openid')price=int(request.data.get('price'))description=request.data.get('description')nonce_str=''.join(random.choices(string.ascii_letters+string.digits,k=32))current_time=datetime.now()current_date=int(datetime.timestamp(current_time))pay=Pay()#下单prepay_id=pay.pay(openid,price,nonce_str,description)#吊起支付order=pay.sign(prepay_id,f"{current_date}",nonce_str)returnResponse(order)classWeChatNotifyView(APIView,Cursor):defpost(self,request):headers={'Wechatpay-Signature':request.META.get('HTTP_WECHATPAY_SIGNATURE'),'Wechatpay-Timestamp':request.META.get('HTTP_WECHATPAY_TIMESTAMP'),'Wechatpay-Nonce':request.META.get('HTTP_WECHATPAY_NONCE'),'Wechatpay-Serial':request.META.get('HTTP_WECHATPAY_SERIAL')}result=Pay().decrypt_callback(headers=headers,body=request.body).................returnResponse({"status":200})下面是前端uniapp的代码: //表单提交 constformSub=async()=>{ uni.showLoading({ title:'支付中', icon:'none' }) //实际支付等于总价(all_cost)-折扣金额(deduction)=实际支付(actual_pay) orderData.value.actual_pay=orderData.value.all_cost-orderData.value.deduction console.log('订单表单',orderData.value); constres=awaitpostApi.api('/order',orderData.value); id.value=res.data.order_id if(res.data.status==200){ //表单 constForm={ openidrderData.value.openid, pricerderData.value.actual_pay, description:'测试', recharge_type:'1' } console.log('支付表单',Form); constpayRes=awaitpostApi.api('/wechatpay',Form) console.log('支付参数',payRes); uni.hideLoading() if(payRes.statusCode==200){ uni.requestPayment({ provider:'wxpay', //orderInfo:'1', timeStamp:String(payRes.data.timeStamp),//时间戳 nonceStr:payRes.data.nonceStr,//随机字符串 package:payRes.data.package, signType:payRes.data.signType,//签名算法 paySign:payRes.data.paySign,//签名 success:async(res)=>{ uni.showLoading({ title:'订单提交中', icon:'none' }) console.log(res); constwechatnotifyForm={ order_id:id.value, countrderData.value.count } console.log('回调表单',wechatnotifyForm); constpayRes=awaitpostApi.api('/wechatnotify',wechatnotifyForm) console.log('回调成功',payRes); //成功带参跳转ok uni.hideLoading() uni.navigateTo({ url:'/pages/myself/sonMoreGoods?orderId='+id.value }) }, failerr)=>{ console.log(err); //失败带参跳转failcancel uni.showToast({ title:'支付失败', icon:'none' }) uni.navigateTo({ url:'/pages/myself/sonMoreGoods?orderId='+id.value }) }, }) }else{ uni.showToast({ title:'订单异常', icon:'none' }) } }else{ uni.showToast({ title:'订单异常', icon:'none' }) } };
|
|