明天的太阳

导航

使用APIv3 Java SDK接入微信小程序支付

这里使用的是apiv30.2.10版本。

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

在实现之前看文档让人头疼,各种版本的文档东一块西一块的,来回跳转,还有一些名词也让我感到头昏。
但是实际上手后又感觉蛮简单的,可能是我初次接触的原因吧。

这里分成四个部分,分别是:配置部分、controller、service和util。

  1. 配置部分内容是 appid、secret、apiV3key、MCHID、NotifyUrl等。
  2. service用来处理支付和退款的逻辑。
  3. controller 用来处理支付和退款的回调。
  4. util用来处理回调参数和加载私钥文件。

小程序方面只需要根据后端返回的参数调起支付就好了。

配置部分

在resources目录下创建配置文件config.properties,如果你使用了git等版本管理工具,记得将其添加到 .gitignore 中。

miniapp.appid=x
miniapp.secret=x
miniapp.salt=x
miniapp.mchid=x
miniapp.serialNo=x
miniapp.apiV3key=x
miniapp.payNotifyUrl=https://xxx.com/notify/pay
miniapp.refundNotifyUrl=https://xxx.com/notify/refund

添加WxPayConfig.java

@Configuration
@PropertySource("classpath:config.properties") //读取配置文件
@ConfigurationProperties(prefix="miniapp")
public class WxPayConfig {
    private String appid;
    // 省略其他属性
    
    public String getAppid(){
        return appid;
    }
    public String setAppid(String appid) {
        this.appid = appid;
    }
}

实现service

添加WechatPayService

@Service
public class WechatPayService {
    public static JsapiServiceExtension jsapiServiceExtension;
    public static RefundService refundService;

    private final WxPayConfig wxPayConfig;
    
    public static final String CNY = "CNY";
    
    @Autowired
    public WechatPayService(WxPayConfig wxPayConfig) throws Exception {
        this.wxPayConfig = wxPayConfig;
        this.init();
    }
    
    @PostConstruct
    public void init() throws Exception {
        String privateKey = WxPayUtil.loadKeyByResource("wxpay/apiclient_key.pem");
        // 初始化商户配置
        Config config =
                new RSAAutoCertificateConfig.Builder()
                        .merchantId(wxPayConfig.getMCHID())
                        .privateKey(privateKey)
                        .merchantSerialNumber(wxPayConfig.getSERIAL_NO())
                        .apiV3Key(wxPayConfig.getApiV3key())
                        .build();
        // 初始化服务
        jsapiServiceExtension =
                new JsapiServiceExtension.Builder()
                        .config(config)
                        .signType("RSA") // 不填默认为RSA
                        .build();
        refundService = new RefundService.Builder().config(config).build();
    }
    /**
     * JSAPI支付下单,并返回JSAPI调起支付数据
     * @param details               订单描述
     * @param outTradeNo            id
     * @param money                 金额
     * @param openId                用户openid
     * @param type                  购买商品 goods、充值 charge
     * @return PrepayWithRequestPaymentResponse 支付信息
     */
    public PrepayWithRequestPaymentResponse prepayWithRequestPayment(String details, String outTradeNo,
                                                                     BigDecimal money, String openId, OrderType type) {
        PrepayRequest request = new PrepayRequest();

        Amount amount = new Amount();
        amount.setTotal(decimalToInt(money));
        amount.setCurrency(CNY);

        // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
        request.setNotifyUrl(wxPayConfig.getPayNotifyUrl());
        request.setAmount(amount);
        request.setAttach(type.getName());
        request.setAppid(wxPayConfig.getAPPID());
        request.setMchid(wxPayConfig.getMCHID());
        request.setOutTradeNo(outTradeNo);
        request.setDescription(details);

        Payer payer = new Payer();
        payer.setOpenid(openId);
        request.setPayer(payer);
        // 调用接口
        return jsapiServiceExtension.prepayWithRequestPayment(request);
    }

    /**
     * 退款申请
     **/
    public Refund createRefund(String outTradeNo, BigDecimal finalAmount) {
        CreateRequest request = new CreateRequest();
        // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
        request.setOutTradeNo(outTradeNo);
        request.setOutRefundNo("REFUND_" + outTradeNo);

        AmountReq amount = new AmountReq();
        amount.setTotal(decimalToLong(finalAmount));
        amount.setRefund(decimalToLong(finalAmount));
        amount.setCurrency(CNY);

        request.setAmount(amount);
        request.setNotifyUrl(wxPayConfig.getRefundNotifyUrl());
        // 调用接口
        return refundService.create(request);
    }
	
	private static int decimalToInt(BigDecimal money) {
        return money.multiply(BigDecimal.valueOf(100)).intValue();
    }

    private static long decimalToLong(BigDecimal money) {
        return money.multiply(BigDecimal.valueOf(100)).longValue();
    }

实现util

添加WechatPayUtil.java

@Service
public class WxPayUtil {
    private final WxPayConfig wxPayConfig;

    @Autowired
    public WxPayUtil(WxPayConfig wxPayConfig) {
        this.wxPayConfig = wxPayConfig;
    }

    public  <T> T getNotificationParser(HttpServletRequest request, Map<String, Object> body, Class<T> clazz) throws Exception {
        String privateKey = loadKeyByResource("wxpay/apiclient_key.pem");
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(request.getHeader("Wechatpay-Serial"))
                .nonce(request.getHeader("Wechatpay-Nonce"))
                .signature(request.getHeader("Wechatpay-Signature"))
                .timestamp(request.getHeader("Wechatpay-Timestamp"))
                .signType(request.getHeader("Wechatpay-Signature-Type"))
                .body(JSON.toJSONString(body))
                .build();
        NotificationConfig config = new RSAAutoCertificateConfig.Builder()
                .merchantId(wxPayConfig.getMCHID())
                .privateKey(privateKey)
                .merchantSerialNumber(wxPayConfig.getSERIAL_NO())
                .apiV3Key(wxPayConfig.getApiV3key())
                .build();

        NotificationParser parser = new NotificationParser(config);
        return parser.parse(requestParam, clazz);
    }

    /**
     * 通过文件路径获取文件内容
     * ClassPathResource可以在jar包中运行,但不能使用其中getFile().getPath()
     * @param path          文件路径
     * @return              文件内容
     * @throws Exception    报错信息
     */
    public static String loadKeyByResource(String path) throws Exception {
        ClassPathResource resource = new ClassPathResource(path);

        byte[] byteArray = FileCopyUtils.copyToByteArray(resource.getInputStream());
        return new String(byteArray, StandardCharsets.UTF_8);
    }

}

实现回调

添加WeChatPayCallbackController.java

private final WechatPayUtil wechatPayUtil;
@Autowired
public WeChatPayCallbackController(WxPayUtil WechatPayUtil) {
    this.WechatPayUtil = WechatPayUtil;
}

@RestController
@RequestMapping("/notify")
public class WeChatPayCallbackController {
    @PostMapping(value = "/pay")
    public HashMap<String, String> callBackWeiXinPay(HttpServletRequest request, HttpServletResponse response, @RequestBody Map<String, Object> body) {
     Transaction payTransaction = wxPayUtil.getNotificationParser(request, body, Transaction.class);
     String outTradeNo = payTransaction.getOutTradeNo();
     // 根据payTransaction中的信息编写一些自己的实现逻辑
    }

    @PostMapping("/refund")
    public HashMap<String, String> callback(HttpServletRequest request, HttpServletResponse response,
                                            @RequestBody Map<String, Object> body) {
    RefundNotification refundNotification = wxPayUtil.getNotificationParser(request, body, RefundNotification.class);
    String orderTradeNo = refundNotification.getOutTradeNo();
    // 根据refundNotification中的信息编写一些自己的实现逻辑
    }
    
    public HashMap<String, String> generateResponseToWxPayCallback(String code, String message) {
        HashMap<String, String> jsonResponse = new HashMap<>();
        jsonResponse.put("code", code);
        jsonResponse.put("message", message);
        return jsonResponse;
    }
}

这是微信支付通知的文档退款通知支付通知,可以根据这些信息编写支付和退款的逻辑,比如扣减库存、设置订单状态。

小程序部分

这里比较简单,前端把后端需要的订单数据传给后端,后端将从微信得到的PrepayWithRequestPaymentResponse返回给前端,
注意将其中的packageVal改为package

const doPay = (res) => {
    const params = res;
    params['package'] = res.packageVal;
    delete params.packageVal;
    uni.requestPayment({
        ...params,
        success: res => {
            // 做一些处理
        },
        fail: f => {
            if (f.errMsg.includes('cancel')) {
                uni.showToast({
                title: '已取消',
                    icon: 'none'
                });
            }
        }
    });
}

const getPaymentInfo = async () => {
    const res = await createOrder({})
    if(res.xxx) {
        doPay(res)
    }
}

posted on 2023-08-29 16:38  东方来客  阅读(1714)  评论(0编辑  收藏  举报