微信支付踩坑血泪史(JAVA -V3版本)


背景介绍

项目中使用的接入方式是JSAPI(参考 ),当然也可以使用其他接入方式,根据需求来。

项目中我们选择的非官方SDK binarywang,没什么别的原因,主要是更方便。

官网:;

GitHub:

一、接入前准备

接入前需要创建账号等步骤,可以参考官方文档:

二、引入依赖

<dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-mp</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-pay</artifactId>
        </dependency>

三、编写配置文件

开发环境下可以把秘钥存到本地,线上建议放到服务器。

因为支付会有回调和退款等操作,本地接口不能被访问。建议使用内网穿透工具进行测试()

四、数据库设计

订单表


退款表 支付单表

订单与支付单和退款单关系为一对多 不同用户扫码会产生多个支付记录,同样退款有多个退款记录。业务代码需要做支付幂等,同一笔不允许重复支付,重复支付需要调用退款接口。

五、业务代码

1.控制层

@Api(tags = "订单管理")
@RestController
public class OrderController {
    @Autowired
    private OrderService orderService;

    @Operation(summary = "提交订单")
    @PostMapping("/order/create")
    public BizResult<String> createOrder(@Valid @RequestBody OrderCreateReq req) {
        String orderNO = orderService.createOrder(req);
        return BizResult.create(orderNO);
    }

    @Operation(summary = "订单详情")
    @GetMapping("/order/detail")
    public BizResult<OrderRsp> orderDetail(@RequestParam String orderNo) {
        OrderRsp orderRsp = orderService.orderDetail(orderNo);
        return BizResult.create(orderRsp);
    }

    @Operation(summary = "轮询订单")
    @GetMapping("/order/polling")
    public BizResult<Boolean> pollingOrder(@RequestParam String orderNo) {
        Boolean result = orderService.pollingOrder(orderNo);
        return BizResult.create(result);
    }

    @Operation(summary = "订单列表")
    @PostMapping("/order/list")
    public BizResult<PageData<OrderListRsp>> queryOrderList(@RequestBody OrderListReq req) {
        req.check();
        PageData<OrderListRsp> pageData = orderService.queryOrderList(req);
        return BizResult.create(pageData);
    }

}

2.业务层

3.manager

@Slf4j
@Component
public class WxManager {

    @Autowired
    WxPayProperties wxPayProperties;
    @Resource
    private RedissonClient redissonClient;
    @Autowired
    private WxMpProperties wxMpProperties;

    public WxMpService getWxMpService() {
        WxMpRedissonConfigImpl config = new WxMpRedissonConfigImpl(redissonClient, CommonConstants.NAMESPACE);
        config.setAppId(wxMpProperties.getAppId());
        config.setSecret(wxMpProperties.getSecret());

        WxMpService service = new WxMpServiceImpl();
        service.setWxMpConfigStorage(config);
        return service;
    }

    public WxPayService getWxPayService() {
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(wxPayProperties.getAppid());
        payConfig.setMchId(wxPayProperties.getMchId());
        payConfig.setPrivateKeyPath(wxPayProperties.getPrivateKeyPath());
        payConfig.setNotifyUrl(wxPayProperties.getNotifyUrl());
        payConfig.setApiV3Key(wxPayProperties.getApiV3key());
        payConfig.setPrivateCertPath(wxPayProperties.getPrivateCertPath());
        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
        return wxPayService;
    }

    public WxPayService getWxPayRefundService() {
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(wxPayProperties.getAppid());
        payConfig.setMchId(wxPayProperties.getMchId());
        payConfig.setPrivateKeyPath(wxPayProperties.getPrivateKeyPath());
        payConfig.setNotifyUrl(wxPayProperties.getRefundNotifyUrl());
        payConfig.setApiV3Key(wxPayProperties.getApiV3key());
        payConfig.setPrivateCertPath(wxPayProperties.getPrivateCertPath());
        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
        return wxPayService;
    }


}

4.Properties

@Data
@Component
@Configuration
@ConfigurationProperties(prefix = "wechat-pay")
public class WxPayProperties {
    /**
     * 商户号
     */
    private String mchId;
    /**
     * 证书解密的密钥
     */
    private String apiV3key;
    /**
     * 商户私钥文件
     */
    private String privateKeyPath;

    /**
     * apiclient_cert.pem证书文件
     */
    private String privateCertPath;

    /**
     * 商户证书序列号
     */
    private String serialNo;
    /**
     * 赋选供应链 服务号appid
     */
    private String appid;
    /**
     * 支付回调通知地址
     */
    private String notifyUrl;

    /**
     * 退款回调通知地址
     */
    private String refundNotifyUrl;
}

总结

以上就是核心业务代码,涉及到具体业务mapper没有放上来,但是核心的处理步骤已经体现出来了。主要是对订单状态需要仔细判断,不同操作会引起状态的变更。业务中,当多个人扫同一个码,后续扫码成功会调用退款接口。

经验分享 程序员 微信小程序 职场和发展