Skip to content

微信工具包

这里介绍一下微信工具包的使用。

功能介绍

支持的功能有:

引入公共依赖:

xml
<dependency>
    <groupId>com.wmeimob.fastboot</groupId>
    <artifactId>wmeimob-spring-boot-starter-wechat-miniapp</artifactId>
</dependency>

引入公共依赖:

xml
<dependency>
    <groupId>com.wmeimob.fastboot</groupId>
    <artifactId>wmeimob-spring-boot-starter-wechat-cp</artifactId>
</dependency>

引入公共依赖:

xml
<dependency>
    <groupId>com.wmeimob.fastboot</groupId>
    <artifactId>wmeimob-spring-boot-starter-wechat-mp</artifactId>
</dependency>

引入公共依赖:

xml
<dependency>
    <groupId>com.wmeimob.fastboot</groupId>
    <artifactId>wmeimob-spring-boot-starter-pay</artifactId>
</dependency>

微信小程序

最简配置如下:

yml
wx:
    miniapp:
        appid: xxx
        secret: xxxx

这是一个单 appid 场景。如果需要支持多 appid 场景,可以这么设置:

yml
wx:
    miniapp:
        configs:
            - appid: xxx
              secret: xxxx
            - appid: xxx
              secret: xxxx

除此之外,还可以进行如下配置:

yml
wx:
    miniapp:
        configStorage:
            type: Memory                 #存储类型,支持(Memory,Jedis,RedisTemplate),默认 Memory
            keyPrefix: wa                #存储 key 前缀,默认 wa
            httpClientType: HttpClient   #http客户端类型,支持(HttpClient,OkHttp,JoddHttp),默认 HttpClient
            httpProxyHost:               #http代理主机
            httpProxyPort:               #http代理端口
            httpProxyUsername:           #http代理用户名
            httpProxyPassword:           #http代理密码

前台服务如果部署多台服务器,那储存类型可以根据实际情况调整成 Jedis 或 RedisTemplate。

代码中使用:

java
/**
 * 小程序码控制器
 *
 * @author mjyang
 * @date 2022/3/30 23:06
 */
@Api(tags = {"小程序码"})
@RestController
@RequestMapping("/api/qrCode")
@RequiredArgsConstructor
public class QrCodeController extends BaseController {

    private final WxMaService wxMaService;

    @ApiOperation(value = "获取小程序码 - getUnlimited")
    @PostMapping("getUnlimited")
    public JsonResult<String> getUnlimited(@RequestBody @Validated UnlimitedInputDto dto) throws WxErrorException {
        if (StrUtil.isEmpty(dto.version)) {
            dto.setVersion("release");
        }
        if (ObjectUtil.isNull(dto.getHyaline())) {
            dto.setHyaline(Boolean.FALSE);
        }
        File file = this.wxMaService.getQrcodeService().createWxaCodeUnlimit(dto.scene, dto.page, false, dto.version,
                430, true, null, dto.hyaline);
        String base64 = "data:image/png;base64," + Base64.encode(file);
        return JsonResult.ok(base64);
    }
}
java
/**
 * 小程序码控制器
 *
 * @author mjyang
 * @date 2022/3/30 23:06
 */
@Api(tags = {"小程序码"})
@RestController
@RequestMapping("/api/qrCode")
@RequiredArgsConstructor
public class QrCodeController extends BaseController {

    private final WxMaService wxMaService;

    @ApiOperation(value = "获取小程序码 - getUnlimited")
    @PostMapping("getUnlimited")
    public JsonResult<String> getUnlimited(@RequestBody @Validated UnlimitedInputDto dto) throws WxErrorException {
       String appid = wxMaConfigProperties.getAppid(dto.getType());      
       AssertUtil.isTrue(wxMaService.switchover(appid), "不支持的小程序类型"); 
       if (StrUtil.isEmpty(dto.version)) {
            dto.setVersion("release");
       }
       if (ObjectUtil.isNull(dto.getHyaline())) {
            dto.setHyaline(Boolean.FALSE);
       }
       try {
            File file = this.wxMaService.getQrcodeService().createWxaCodeUnlimit(dto.scene, dto.page, false, dto.version,
                430, true, null, dto.hyaline);
            String base64 = "data:image/png;base64," + Base64.encode(file);
            return JsonResult.ok(base64);
       } catch (WxErrorException e) {
           log.error("获取小程序码 - getUnlimited failed! reason:", e);
           throw SimpleException.getInstance(SysResStatus.ERROR_DATA_NULL, "小程序码生成失败");
       } finally {
           WxMaConfigHolder.remove(); 
       }
   }
}

多 appid 场景要注意新增的代码

微信企微

用法基本与小程序一致。配置 miniapp 换成 cp,WxMaService 换成 WxCpService。其中包里定义了一系列工具类。例如 CpUtils 里的通知回调验证。以及默认的路由处理策略。

微信公众号

用法基本与小程序一致。配置 miniapp 换成 mp,WxMaService 换成 WxMpService。

微信支付、退款

yml
wx:
    pay:
        appId: ${wmeimob.wx.miniapp.appid}
        mchId: xxx       #支付商户号
        mchKey: xxx      #商户密钥
        apiv3Key: xxx    #v3密钥
        publicKeyId: xxx   #v3公钥id
        publicKeyPath: xxx #v3公钥文件路径
        publicKeyString: xxx #v3公钥字符串
        host:            #回调域名
        pay-notify-url: ${wmeimob.wx.pay.host}/wechat/wepay/notify   #支付回调地址
        refund-notify-url: ${wmeimob.wx.pay.host}/wechat/wepay/refund-notify #退款回调地址
        test: false   #true表示测试,不需要发起支付
yml
wx:
    pay:
        appId: ${wmeimob.wx.miniapp.appid}
        mchId: xxx       #支付商户号
        mchKey: xxx      #商户密钥
        apiv3Key: xxx    #v3密钥
        publicKeyId: xxx   #v3公钥id
        publicKeyPath: xxx #v3公钥文件路径
        publicKeyString: xxx #v3公钥字符串
        host:            #回调域名
        pay-notify-url: ${wmeimob.wx.pay.host}/wechat/wepay/notify   #支付回调地址
        refund-notify-url: ${wmeimob.wx.pay.host}/wechat/wepay/refund-notify #退款回调地址
        keyPath: xxxx         #商户apiclient_cert.p12
        privateKeyPath: xxxx  #商户apiclient_key.pem
        privateCertPath: xxxx #商户apiclient_cert.pem
        test: false   #true表示测试,不需要发起支付

注意 test 开关的配置,推荐根据环境去配置。例如 dev 为 true,prod 为 false。支付与退款需要同步设置。

v3支付需要传入公钥信息,参见这里

与标品的集成:

java
/**
 * 支付服务策略
 *
 * @author mjyang
 * @date 2022/8/26 10:01
 */
public interface PayService extends AbstractStrategy<Void, Void> {

    /**
     * 获取支付链接
     *
     * @param dto dto
     * @return str
     */
    Object payment(PaymentInputDto dto);

    /**
     * 查询支付结果
     *
     * @param dto dto
     * @return obj
     */
    default PaymentQueryOutputDto queryPayResult(PaymentQueryInputDto dto) {
        throw SimpleException.getInstance("不支持该方式");
    }

    /**
     * 解析支付结果
     *
     * @param data object data
     * @return obj
     */
    default Object parseOrderNotifyResult(Object data) {
        throw SimpleException.getInstance("不支持该方式");
    }

    /**
     * 解析退款结果
     *
     * @param data data
     * @return obj
     */
    default Object parseRefundNotifyResult(Object data) {
        throw SimpleException.getInstance("不支持该方式");
    }

    /**
     * 退款
     *
     * @param dto dto
     */
    void refund(RefundInputDto dto);

    /**
     * 转账
     *
     * @param dto dto
     * @return object
     */
    default PartnerTransferResult transfer(TransferInputDto dto) {
        throw SimpleException.getInstance("不支持该方式");
    }
}
java
/**
 * 微信支付服务类
 *
 * @author mjyang
 * @date 2024/2/28 15:35
 */
@Service
@Slf4j
public class WxPayServiceImpl implements PayService {

    @Resource
    private WxPayService wxPayService;

    @Override
    @SneakyThrows
    public Object payment(PaymentInputDto dto) {
        WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
        orderRequest.setBody(dto.getDescription());
        orderRequest.setOutTradeNo(dto.getOrderNo());
        // 元转成分
        orderRequest.setTotalFee(NumberUtil.mul(dto.getPayAmount(), 100).intValue());
        orderRequest.setSpbillCreateIp(IPUtils.getIpAddr());
        orderRequest.setTimeStart(DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_PATTERN));
        orderRequest.setNotifyUrl(dto.getNotifyUrl());
        orderRequest.setTradeType(dto.getTradeType());
        if (StrUtil.equals(orderRequest.getTradeType(), WxPayConstants.TradeType.JSAPI)) {
            orderRequest.setOpenid(dto.getUserId());
        }
        return this.wxPayService.createOrder(orderRequest);
    }

    @Override
    public void refund(RefundInputDto dto) {
        WxPayRefundRequest request = new WxPayRefundRequest();
        request.setTransactionId(dto.getPaysn());
        request.setOutRefundNo(dto.getOutRefundNo());
        request.setTotalFee(NumberUtil.mul(dto.getTotalFee(), 100).intValue());
        request.setRefundFee(NumberUtil.mul(dto.getRefundFee(), 100).intValue());
        request.setNotifyUrl(dto.getNotifyUrl());
        try {
            this.wxPayService.refund(request);
        } catch (WxPayException e) {
            log.error("微信退款出现未知错误:" + dto.getOutRefundNo(), e);
            if (StrUtil.equals(e.getErrCode(), WxPayErrorCode.Refund.NOTENOUGH)) {
                // 余额不足
                throw SimpleException.getInstance(e.getErrCodeDes());
            }
            throw SimpleException.getInstance("微信退款出现未知错误");
        }
    }

    @Override
    @SneakyThrows
    public Object parseOrderNotifyResult(Object data) {
        return this.wxPayService.parseOrderNotifyResult((String) data);
    }

    @Override
    @SneakyThrows
    public Object parseRefundNotifyResult(Object data) {
        return this.wxPayService.parseRefundNotifyResult((String) data);
    }

    @Override
    @SneakyThrows
    public PartnerTransferResult transfer(TransferInputDto dto) {
        PartnerTransferRequest transferRequest = new PartnerTransferRequest();
        if (StrUtil.isBlank(this.wxPayService.getConfig().getSubMchId())) {
            transferRequest.setSubMchid(this.wxPayService.getConfig().getMchId());
        } else {
            transferRequest.setSubMchid(this.wxPayService.getConfig().getSubMchId());
        }
        if (StrUtil.isBlank(this.wxPayService.getConfig().getSubAppId())) {
            transferRequest.setSubAppid(this.wxPayService.getConfig().getAppId());
        } else {
            transferRequest.setSubAppid(this.wxPayService.getConfig().getSubAppId());
        }
        transferRequest.setAuthorizationType("INFORMATION_AUTHORIZATION_TYPE");
        transferRequest.setOutBatchNo(dto.getOrderNo());
        transferRequest.setBatchName(dto.getRemark());
        transferRequest.setBatchRemark(dto.getRemark());
        transferRequest.setTotalAmount(NumberUtil.mul(dto.getPayAmount(), 100).intValue());
        transferRequest.setTotalNum(1);

        List<PartnerTransferRequest.TransferDetail> transferDetails = new ArrayList<>(1);
        PartnerTransferRequest.TransferDetail transferDetail = new PartnerTransferRequest.TransferDetail();
        transferDetail.setOutDetailNo(dto.getOrderNo());
        transferDetail.setTransferAmount(transferRequest.getTotalAmount());
        transferDetail.setTransferRemark(transferRequest.getBatchRemark());
        transferDetail.setOpenid(dto.getUserId());
        transferDetail.setUserName(dto.getUserName());
        transferDetails.add(transferDetail);

        transferRequest.setTransferDetailList(transferDetails);
        return this.wxPayService.getPartnerTransferService().batchTransfer(transferRequest);
    }

    @Override
    public String mark() {
        return PayType.WECHAT.getPayChannelStrategyKey();
    }
}

例如下单使用:

java

@Resource
private AbstractStrategyFactory abstractStrategyFactory;

@ApiOperation("待支付订单获取支付参数")
@GetMapping("pay/{orderNo}")
public JsonResult<OrderPayDTO> payOrderJsApi(@PathVariable("orderNo") String orderNo,
                                                @RequestParam("tradeType") @ApiParam(name = "tradeType", value = "交易方式", type = "com.wmeimob.mall.core.mall.order.value.TradeType") String tradeType,
                                                @RequestParam("payType") @ApiParam(name = "payType", value = "支付类型", type = "com.wmeimob.mall.core.mall.order.value.PayType") Integer payType) {
    return payOrder(
            orderNo,
            StrUtil.blankToDefault(tradeType, TradeType.JSAPI.name()),
            Optional.ofNullable(payType).orElse(PayType.Wechat.getValue())
    );
}

private JsonResult<OrderPayDTO> payOrder(String orderNo, String tradeType, Integer payType) {
    AssertUtil.notNull(payType, "支付方式不能为空");
    AssertUtil.isTrue(PayType.exist(payType), "错误的支付类型");
    AssertUtil.isTrue(TradeType.exist(tradeType), "错误的交易类型");
    OrderVO orderVO = this.orderService.payOrderCheck(orderNo, this.teancyUser.getUserId());
    if (this.payProperties.getTest()) {
        orderVO.setTransactionId(orderNo);
        orderVO.setPayType(payType);
        this.orderService.updateForPaySuccess(orderVO);
        return JsonResult.ok();
    }
    String openId = MapUtil.getStr(this.teancyUser.getExtendInfo(), "openId");
    Object payment = ((PayService) this.abstractStrategyFactory
            .get(PayType.getInstance(payType).getPayChannelStrategyKey()))
            .payment(new PaymentInputDto()
                    .setOrderNo(orderNo)
                    .setPayAmount(orderVO.getPayAmount())
                    .setDescription("普通订单")
                    .setNotifyUrl(this.payProperties.getPayNotifyUrl())
                    .setTradeType(tradeType)
                    .setUserId(openId)
            );
    OrderPayDTO orderPayDTO = new OrderPayDTO();
    orderPayDTO.setPayParam(payment);
    return JsonResult.ok(orderPayDTO);
}

TIP

注意这里用到了 策略 模式,后续有在新增支付渠道,只要在增加一个实现类即可。 同时标品里涉及到支付退款的地方有如下几处:
com.wmeimob.mall.web.controller.mall.OrdersController#payOrderJsApi
com.wmeimob.mall.service.mall.user.card.impl.MemCardServiceImpl#purchase
com.wmeimob.mall.service.mall.order.impl.RefundServiceImpl#refund com.wmeimob.mall.service.mall.user.balance.BalanceService#recharge #开启余额功能,则需要调整

更多详情可查看 这里