微信小程序请求支付实现思路

先给一个请求支付接口的实际例子

  1. 用户通过小程序服务查询商品
  2. 小程序服务调用售货机服务查询商品信息返回
  3. 小程序服务将商品信息返回给用户
  4. 用户支付下单
  5. 订单服务执行支付完成逻辑,向售货机下达出货指令
  6. 售货机出货,并响应出货结果给订单服务
  7. 订单更新订单状态

其中订单服务执行支付完成逻辑是我们现阶段所需要重点关注的,这里的逻辑较为复杂,需要我们具体分析,我们先针对小程序请求支付这一环节深入剖析

我们先来再看一张图

从上面的交互图可以看出微信小程序端需要:

  1. 调用后台接口请求下单支付
  2. 后台在调用微信统一下单接口之前先要获取openId
  3. 获取openId时需要客户端调用微信登录接口获取微信端返回的jsCode,并将该code传入后台,后台将该code传入微信端接口从而才能得到openId
  4. 后台调用微信统一下单接口将相关数据传入到微信接口,此时微信平台会返回相关数据(其中包括prepay_id,也叫预付单信息)
  5. 后台将得到的数据再次签名之后返回到前端
  6. 前端根据后台返回的数据向微信端调起鉴权支付请求,如果通过则在小程序内部呼起微信支付

前面3步,不做赘述。

从第4步开始,我们需要传参与微信接口进行交互,所以我们从第四步仔细分析:

分析一:后台调用微信统一下单接口将相关数据传入到微信接口

这里我们要明确几件事:

一是后台调用的微信统一下单接口具体是什么?

二是相关数据是什么?

从官方文档中,我们能找到答案:

小程序支付接口链接

URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder

请求参数非常之多,这里就不CV过来了(详情见官方文档),太占篇幅,我们挑点必要的(必填的),捋一捋

字段名

变量名

必填

类型

示例值

描述

公众账号ID

appid

String(32)

wxd678efh567hg6787

微信支付分配的公众账号ID(企业号corpid即为此appid)

商户号

mch_id

String(32)

1230000109

微信支付分配的商户号

随机字符串

nonce_str

String(32)

5K8264ILTKCH16CQ2502SI8ZNMTM67VS

随机字符串,长度要求在32位以内。推荐随机数生成算法

签名

sign

String(32)

C380BEC2BFD727A4B6845133519F3AD6

通过签名算法计算得出的签名值,详见签名生成算法

商品描述

body

String(127)

腾讯充值中心-QQ会员充值

商品简单描述,该字段请按照规范传递,具体请见参数规定

商户订单号

out_trade_no

String(32)

20150806125346

商户系统内部订单号,要求32个字符内(最少6个字符),只能是数字、大小写字母_-|*且在同一个商户号下唯一。详见商户订单号

标价金额

total_fee

int

88

订单总金额,单位为分,详见支付金额

终端IP

spbill_create_ip

String(64)

123.12.12.123

支持IPV4和IPV6两种格式的IP地址。用户的客户端IP

通知地址

notify_url

String(256)

https://www.weixin.qq.com/wxpay/pay.php

body 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http

交易类型

trade_type

String(16)

JSAPI

JSAPI -JSAPI支付

NATIVE -Native支付

APP -APP支付

说明详见参数规定

商品ID

product_id

String(32)

12235413214070356458058

trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义。

用户标识

openid

String(128)

oUpF8uMuAJO_M2pxb1Q9zNjWeS6o

trade_type=JSAPI时(即JSAPI支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。openid如何获取,可参考【获取openid

】。企业号请使用【企业号OAuth2.0接口

】获取企业号内成员userid,再调用【企业号userid转openid接口

】进行转换

入参说明:

  • 紫色字体-微信客户端程序会生成配置文件配置
  • 红色字体-代码实现需要赋值

分析二:此时微信平台会返回相关数据(其中包括prepay_id,也叫预付单信息)

首先我们要清楚,这里返回的相关数据是叫预付单信息,返回的内容主要有三个:

  1. 商户身份信息的校验结果
  2. 发放预付款交易会话标识符
  3. 发放支付二维码连接(小程序无)

这里我们主要不清楚的是返回的相关数据是什么?

当然官方文档中也详细说明了

这里我们可以C一下,并简单说明一下

返回结果

A表

字段名

变量名

必填

类型

示例值

描述

返回状态码

return_code

String(16)

SUCCESS

SUCCESS/FAIL

此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断

返回信息

return_msg

String(128)

签名失败

返回信息,如非空,为错误原因

签名失败

参数格式校验错误

以下字段在return_code为SUCCESS的时候有返回

B表

字段名

变量名

必填

类型

示例值

描述

小程序ID

appid

String(32)

wx8888888888888888

调用接口提交的小程序ID

商户号

mch_id

String(32)

1900000109

调用接口提交的商户号

设备号

device_info

String(32)

013467007045764

自定义参数,可以为请求支付的终端设备号等

随机字符串

nonce_str

String(32)

5K8264ILTKCH16CQ2502SI8ZNMTM67VS

微信返回的随机字符串

签名

sign

String(64)

C380BEC2BFD727A4B6845133519F3AD6

微信返回的签名值,详见签名算法

业务结果

result_code

String(16)

SUCCESS

SUCCESS/FAIL

错误代码

err_code

String(32)

SYSTEMERROR

详细参见下文错误列表

错误代码描述

err_code_des

String(128)

系统错误

错误信息描述

以下字段在return_code 和result_code都为SUCCESS的时候有返回

C表

字段名

变量名

必填

类型

示例值

描述

交易类型

trade_type

String(16)

JSAPI

交易类型,取值为:JSAPI,NATIVE,APP等,说明详见参数规定

预支付交易会话标识

prepay_id

String(64)

wx201410272009395522657a690389285100

微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时

二维码链接

code_url

String(64)

weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00

trade_type=NATIVE时有返回,此url用于生成支付二维码,然后提供给用户进行扫码支付。

注意:code_url的值并非固定,使用时按照URL格式转成二维码即可。时效性为2小时

这组参数分为三种情况:

一是只返回A的情况 当A中return_code参数不为SUCCESS的时候,只返回A中参数

二是返回AB的情况 当A中return_code参数为SUCCESS的时候,且B中result_code不为SUCCESS

三是返回ABC的情况 当A中return_code参数为SUCCESS的时候,且B中result_code为SUCCESS

分析三:后台将得到的数据再次签名之后返回到前端

注意此时的返回前端才是本次请求的结束!!!

注意此时的返回前端才是本次请求的结束!!!

注意此时的返回前端才是本次请求的结束!!!

我们得到预付单信息后,进行算法加密(这里我们用的是MD5加密算法),生成签名,然后再次组装数据,返回给前端

下面引用github上大神封装好的微信支付工具,可以仔细分析下面的代码,加深理解

package com.github.wxpay.plus;

import com.github.wxpay.sdk.*;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.util.Map;

/**
 * 微信支付工具
 */
@Component
@Slf4j
public class WxPayTemplate {

    @Autowired
    private WXConfig wxConfig;

    @Autowired
    private WxPaySdkConfig wxPaySdkConfig;

    /**
     * 下单
     * @param wxPayParam 微信支付参数
     * @return
     */
    public Map<String,String> requestPay(  WxPayParam wxPayParam) {
        try{
            String nonce_str = WXPayUtil.generateNonceStr();
            //1.封装请求参数
            Map<String,String> map= Maps.newHashMap();
            map.put("appid",wxPaySdkConfig.getAppID());//公众账号ID
            map.put("mch_id",wxPaySdkConfig.getMchID());//商户号
            map.put("nonce_str", nonce_str);//随机字符串
            map.put("body",wxPayParam.getBody());//商品描述
            map.put("out_trade_no",wxPayParam.getOutTradeNo());//订单号
            map.put("total_fee",wxPayParam.getTotalFee()+"");//金额
            map.put("spbill_create_ip","127.0.0.1");//终端IP
            map.put("notify_url",wxConfig.getNotifyUrl());//回调地址
            if(wxPayParam.getOpenid().equals("")){
                map.put("trade_type","NATIVE");//交易类型  : 本地
            }else{
                map.put("trade_type","JSAPI");//交易类型:  小程序
                map.put("openid",wxPayParam.getOpenid());//openId
            }
            String xmlParam  = WXPayUtil.generateSignedXml(map, wxPaySdkConfig.getKey());
            log.info("封装参数:{}",xmlParam);
            //2.发送请求
            WXPayRequest wxPayRequest=new WXPayRequest(wxPaySdkConfig);
            String xmlResult = wxPayRequest.requestWithCert("/pay/unifiedorder", null, xmlParam, false);
            //3.解析返回结果
            Map<String, String> mapResult = WXPayUtil.xmlToMap(xmlResult);
            log.info("返回结果:{}",mapResult);
            //返回给移动端需要的参数
            Map<String, String>  result = Maps.newHashMap();
            if("SUCCESS".equals(mapResult.get("return_code"))){   //返回状态码成功
                //如果result-code为失败
                if("SUCCESS".equals(mapResult.get("result_code"))){ //如果成功
                    if(wxPayParam.getOpenid().equals("")) {  //本地支付
                        result.put("codeUrl", mapResult.get("code_url"));  //如果本地支付,会显示二维码
                    }else{  //小程序支付
                        result.put("appId",wxPaySdkConfig.getAppID());
                        result.put("package", "prepay_id=" + mapResult.get("prepay_id"));  //预付单信息
                        result.put("signType","MD5");
                        result.put("nonceStr", WXPayUtil.generateNonceStr());
                        Long timeStamp = System.currentTimeMillis() / 1000;
                        result.put("timeStamp", timeStamp + "");//要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
                        //再次签名,这个签名用于小程序端调用wx.requesetPayment方法
                        String sign = WXPayUtil.generateSignature(result,wxConfig.getPartnerKey());
                        result.put("paySign", sign);
                        result.put("appId","");
                    }
                    result.put("orderNo",wxPayParam.getOutTradeNo());
                    result.put("code","SUCCESS");  //成功
                    return result;
                }else {
                    result.put("msg",  mapResult.get("err_code_des" ));  //错误描述
                    result.put("code","RESULT_FAIL");//失败
                    return result;
                }
            }else {
                log.info("调用微信统一下单接口失败",mapResult.get("return_msg"));
                result.put("msg",  mapResult.get("return_msg" ));  //错误描述
                result.put("code","RETURN_FAIL");//返回失败
                return result;
            }
        }catch (Exception ex){
            log.info("调用微信统一下单接口失败",ex);
            return null;
        }
    }


    /**
     * 验证支付结果
     * @param inputStream
     * @return 订单号
     * @throws Exception
     */
    public Map<String,String> validPay(InputStream inputStream) throws Exception {
        String notifyResult = ConvertUtils.convertToString( inputStream);
        Map<String,String> result=Maps.newHashMap();
        //解析
        Map<String, String> map = WXPayUtil.xmlToMap( notifyResult );        //验签
        boolean signatureValid = WXPayUtil.isSignatureValid(map, wxConfig.getPartnerKey());

        if(signatureValid){
            if("SUCCESS".equals(map.get("result_code"))){
                result.put("code","SUCCESS");//成功
                result.put("order_sn", map.get("out_trade_no") );//订单号
                //返回订单号
               return result;
            }else {
                log.info("支付回调出错:"+notifyResult);
                result.put("code","FAIL");//失败
                return null;
            }
        }else {
            log.info("支付回调验签失败:"+notifyResult);
            result.put("code","FAIL");//失败
            return result;
        }
    }





}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值