2023接入小程序微信支付【JAVA】

2,079 阅读5分钟

必读信息

产品介绍-小程序支付 | 微信支付商户平台文档中心

小程序支付开发指引

1.对接信息

对接场景用户在小程序下单,使用微信支付付款
对接商户类型直连商户
对接支付版本v3
后端架构Java+SpringBoot
对接时间2023年9月

2.对接流程

共分为4个大流程,主要开发工作量在后端。

image.png

2.1 流程1:注册微信小程序、微信支付商户号

步骤略

2.2 流程2:登录微信商户、小程序后台,进行配置

登录微信商户,设置v3秘钥(此步骤可得到:v3秘钥开发需要,记下来)
申请证书(此步骤可得到:证书序列号,查看证书序列号查看 myssl.com/cert_decode…
关联,微信商户发起关联;小程序平台确认授权。
已申请状态

2.3 流程3:后端接口开发

根据调用流程和业务需求,后端需提供接口

下单接口向微信平台请求下单参数,再给到小程序前端,然后前端拉起微信支付弹窗,用户付款;参考 JSAPI下单
支付通知回调用于更新自己业务的订单状态。参考支付通知;支付回调需要提供https域名,本地调试可使用cpolar实现内网穿透进行测试(免费)
查询订单接口(非必须)场景:当用户主动查单时

后端开发流程

image.png

集成官方SDK wechatpay-java

1、maven添加配置

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-java</artifactId>
    <version>0.2.11</version>
</dependency>

2、创建配置类


import lombok.Data;

/**
 * 配置
 */
@Data
public class WxV3PayConfig {
   
    /**
     * 微信回调通知地址
     */
    public static String PAY_BACK_URL = "";

    //平台证书序列号
    public static String MCH_SERIAL_NO = "";

    //appID
    public static String APP_ID = "";

    //商户id
    public static String MCH_ID = "";

    // API V3密钥
    public static String API_V3_KEY = "";

    // 商户私钥 打开apiclient_key.pem拷贝出来的字符串
    public static String PRIVATE_KEY = "";

}
/**
 * 动态下单参数
 */
@Data
public class PreOrderDynamicParam {
    /**
     * 订单号(业务)
     */
    String outTradeNo;
    /**
     * 用户openId
     */
    String openId;
    /**
     * 订单描述
     */
    String description;
    /**
     * 订单总金额,单位为分
     */
    int total;

    public PreOrderDynamicParam(String outTradeNo, String openId, String description, int total) {
        this.outTradeNo = outTradeNo;
        this.openId = openId;
        this.description = description;
        this.total = total;
    }
}
/**
 * 微信支付签名对象
 */
@Data
public class WechatPaySign {
    /**
     * 预支付编号
     */
    private String prepayId;

    /**
     * 时间戳
     */
    private String timeStamp;

    /**
     * 随机字符串
     */
    private String nonceStr;

    private String packageStr;

    /**
     * 支付签名
     */
    private String sign;
}

3、创建服务类和实现类


import com.dxda.basic.entity.PreOrderDynamicParam;
import com.wechat.pay.java.service.payments.model.Transaction;
import org.springframework.http.ResponseEntity;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.SortedMap;

/**
 * 微信支付
 */
public interface IWxPayService {
    /**
     * JSAPI下单
     *
     * @return
     */
    SortedMap<String, String> jsApiOrder(PreOrderDynamicParam preOrderDynamicParam) throws Exception;



    /**
     * 支付回调
     *
     * @param request
     * @return
     * @throws IOException
     */
    ResponseEntity callback(HttpServletRequest request) throws IOException;

    /**
     * 查询订单,根据商户订单号
     *
     * @return
     */
    Transaction queryWxPayOrderOutTradeNo(String transNo);

    /**
     * 关闭订单
     *
     * @return
     */
    void closePay(String outTradeNo);
}


import com.alibaba.fastjson.JSON;
import com.dxda.basic.entity.PreOrderDynamicParam;
import com.dxda.basic.service.UdiOrderService;
import com.dxda.common.utils.uuid.RandomUtils;
import com.dxda.wx.service.IWxPayService;
import com.dxda.wx.service.config.WechatPaySign;
import com.dxda.wx.service.config.WxV3PayConfig;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.SortedMap;
import java.util.TreeMap;


/**
 * @author lws
 * @since 2023-9-20
 */
@Slf4j
@Service
public class WxPayServiceImpl implements IWxPayService {

    @Resource
    UdiOrderService udiOrderService;

    /**
     * 微信支付
     */
    @Override
    @Transactional
    public SortedMap<String, String> jsApiOrder(PreOrderDynamicParam preOrderDynamicParam) throws Exception {
        log.info("微信支付 >>>>>>>>>>>>>>>>> 金额:{}", preOrderDynamicParam.getTotal());
        PrepayRequest request = new PrepayRequest();
        SortedMap<String, String> params = new TreeMap<>();
        Amount amount = new Amount();
        amount.setTotal(preOrderDynamicParam.getTotal());
        Payer payer = new Payer();

        payer.setOpenid(preOrderDynamicParam.getOpenId());
        request.setAmount(amount);
        request.setPayer(payer);
        request.setAppid(WxV3PayConfig.APP_ID);
        request.setMchid(WxV3PayConfig.MCH_ID);
        request.setDescription(preOrderDynamicParam.getDescription());
      
        request.setNotifyUrl(WxV3PayConfig.PAY_BACK_URL);
        request.setOutTradeNo(preOrderDynamicParam.getOutTradeNo());

        PrepayResponse response = getJsapiService().prepay(request);


        WechatPaySign sign = sign(response.getPrepayId());
        params.put("trans_no", preOrderDynamicParam.getOutTradeNo());// 订单号(业务需要)
        params.put("appId", WxV3PayConfig.APP_ID);
        params.put("nonceStr", sign.getNonceStr());
        params.put("package", "prepay_id=" + sign.getPrepayId());
        params.put("signType", "RSA");
        params.put("timeStamp", sign.getTimeStamp());
        params.put("paySign", sign.getSign());

        return params;
    }


    String getBodyUTF8(HttpServletRequest request) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line);
            }
            return stringBuilder.toString();
        } catch (Exception e) {
            e.printStackTrace();

        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return "";
    }

    /**
     * 微信回调
     *
     * @param request
     * @return
     * @throws IOException
     */
    @Transactional
    public ResponseEntity callback(HttpServletRequest request) throws IOException {
        log.info("微信回调v3 >>>>>>>>>>>>>>>>> ");
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(request.getHeader("Wechatpay-Serial"))
                .nonce(request.getHeader("Wechatpay-Nonce"))
                .timestamp(request.getHeader("Wechatpay-Timestamp"))
                .signature(request.getHeader("Wechatpay-Signature"))
                .body(getBodyUTF8(request))
                .build();

        NotificationConfig config = new RSAAutoCertificateConfig.Builder()
                .merchantId(WxV3PayConfig.MCH_ID)
                .privateKey(WxV3PayConfig.PRIVATE_KEY)
                .merchantSerialNumber(WxV3PayConfig.MCH_SERIAL_NO)
                .apiV3Key(WxV3PayConfig.API_V3_KEY)
                .build();

        NotificationParser parser = new NotificationParser(config);

        try {
            // 验签、解密并转换成 Transaction(返回参数对象)
            Transaction transaction = parser.parse(requestParam, Transaction.class);
            log.info("微信支付回调 成功,解析" + JSON.toJSONString(transaction));
              // TODO 处理你的业务逻辑
            // 处理成功,返回 200 OK 状态码
            return ResponseEntity.status(HttpStatus.OK).build();
        } catch (ValidationException e) {
            log.error("sign verification failed", e);
            log.error("微信支付回调v3java失败=" + e.getMessage(), e);
        }
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }

    /**
     * 关闭微信支付
     */
    @Override
    public void closePay(String outTradeNo) {
        CloseOrderRequest closeRequest = new CloseOrderRequest();
        closeRequest.setMchid(WxV3PayConfig.MCH_ID);
        closeRequest.setOutTradeNo(outTradeNo);
        getJsapiService().closeOrder(closeRequest);
    }

    @Override
    public Transaction queryWxPayOrderOutTradeNo(String transNo) {
        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
        request.setMchid(WxV3PayConfig.MCH_ID);
        request.setOutTradeNo(transNo);
        Transaction transaction = getJsapiService().queryOrderByOutTradeNo(request);
        // TODO 处理你的业务逻辑
        return transaction;
    }


    /**
     * 创建小程序支付服务
     *
     * @return
     */
    protected JsapiService getJsapiService() {
        Config config =
                new RSAAutoCertificateConfig.Builder()
                        .merchantId(WxV3PayConfig.MCH_ID)
                        .privateKey(WxV3PayConfig.PRIVATE_KEY)
                        .merchantSerialNumber(WxV3PayConfig.MCH_SERIAL_NO)
                        .apiV3Key(WxV3PayConfig.API_V3_KEY)
                        .build();
        config.createSigner().getAlgorithm();
        return new JsapiService.Builder().config(config).build();
    }



    /**
     * 获取支付签名
     *
     * @param prepayId
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws SignatureException
     */
    public static WechatPaySign sign(String prepayId) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
        String nonceStr = RandomUtils.randomEles(30);//随机字符串
        String packageStr = "prepay_id=" + prepayId;

        // 不能去除'.append("\n")',否则失败
        String signStr = WxV3PayConfig.APP_ID + "\n" +
                timeStamp + "\n" +
                nonceStr + "\n" +
                packageStr + "\n";

        byte[] message = signStr.getBytes(StandardCharsets.UTF_8);

        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(PemUtil.loadPrivateKeyFromString(WxV3PayConfig.PRIVATE_KEY));
        sign.update(message);
        String signStrBase64 = Base64.getEncoder().encodeToString(sign.sign());

        WechatPaySign wechatPaySign = new WechatPaySign();
        wechatPaySign.setPrepayId(prepayId);
        wechatPaySign.setTimeStamp(timeStamp);
        wechatPaySign.setNonceStr(nonceStr);
        wechatPaySign.setPackageStr(packageStr);
        wechatPaySign.setSign(signStrBase64);
        return wechatPaySign;
    }
}

5、创建Controller

由于大家业务不同,此处省略示例。

2.4 流程4 前端小程序对接

前端调用后台接口jsApiOrder拿到返回参数,前端再调用wx.requestPayment即可拉起微信支付弹窗组件。参考

wx.requestPayment
(
        {
                "timeStamp": "1414561699",
                "nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
                "package": "prepay_id=wx201410272009395522657a690389285100",
                "signType": "RSA",
                "paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq/xDg==",
                "success":function(res){},
                "fail":function(res){},
                "complete":function(res){}
        }
)

3.其他问题

3.1 验签异常排查

检查body顺序和编码;因素很多,可以参考下面

微信回调验签总结 - ahguo - 博客园

3.2 人民币单位

如果前端传递单位是元,需要转分去下单

3.3其他坑

《完》