微信支付宝统一扫码支付接口

Last Modified: 2023/09/10

统一的的究竟是什么接口

解决什么问题

有些网站需要同时接入支付宝扫码支付和微信扫码支付,但是微信扫码支付和支付宝扫码支付接口不同,写起来有没有很坑?本sdk的目标是统一这两家的扫码支付接口,让接入变得简单。

使用方法:

引入依赖

<dependency>
    <groupId>net.verytools</groupId>
    <artifactId>unipay-pc</artifactId>
    <version>1.0.0</version>
</dependency>

如果要支持支付宝支付,需要引入alipay-sdk-java。

<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>3.6.0.ALL</version>
</dependency>

如果要支持微信支付,需要引入weixin-java-pay(推荐)或者weixin-popular,二选一即可!

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

<!-- 或者 -->
<dependency>
    <groupId>com.github.liyiorg</groupId>
    <artifactId>weixin-popular</artifactId>
    <version>2.8.32</version>
</dependency>

依赖的版本尽可能和上面推荐的一致,否则有可能会出现 NoSuchMethodExeption 之类的错误,如果不幸遇到这类错误,可以尝试修改依赖版本。

使用统一下单接口

将项目 samples 目录下的 zfb.properties 和 wx.properties 文件复制到我们自己项目的 src/main/resources 目录下。注意要修改这两个文件,将里面的配置信息配置正确。

import net.verytools.unipay.api.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Order order = new Order();
order.setSubject("腾讯充值中心-企鹅币充值"); // 商品
// 交易编号,由我们自己设置,需保证唯一即可。
order.setOutTradeNo("Q12345678923");
order.setTotalFee(100);  // 支付金额,注意:无论支付宝还是微信,单位为**分**,内部会自动转换。

OrderContext context = new OrderContext();
// 设置接收支付回调的url,一定要是外网可访问的url地址,否则支付宝或微信无法通知支付结果。
context.setNotifyUrl("http://www.youdomain/pay/notify/callback/" + payType);
MchInfo mchInfo = payType == PayType.alipay ? MchInfo.create(PayType.alipay, "zfb.properties") : MchInfo.create(PayType.wx, "wx.properties");
PushOrderResult result = service.unifyOrder(context, order, mchInfo);
if (result.isOk()) {
    // 将这个字符串传到网页上,使用一个二维码生成js库生成二维码就可以了。
    String qrcodeContent = result.getQrCodeContent();
    logger.info("qrcode content is: {}", qrcodeContent);
} else {
    logger.error("unify order error, msg: {}, code: {}", result.getMsg(), result.getCode());
    // 处理下单失败业务逻辑
}

通过 PushOrderResult#getQrCodeContent,我们可以获得二维码内容字符串,使用该字符串生成二维码即可。不知道怎么生成二维码的同学,可以看看这个二维码的js库

上面代码中,unifyOrder 方法接收一个 MchInfo 类型的对象,该对象是商户配置信息,支付宝和微信的配置信息是不同的,MchInfo#create 工具方法可以根据配置文件自动生成该对象。目前 MchInfo 共有三个子类:

MchInfo#create 暂时不支持生成 WxSpMchInfo 类型的对象,如有需要,可以直接实例化该类的对象。如果商户配置信息是存在数据库中,我完全可以自己手动实例化这些 MchInfo 子类对象,不一定非要通过 create 方法。

统一支付回调

支付回调是支付宝或者微信在用户支付成功后,给商户服务器异步推送的支付通知,告知商户支付结果。商户应以支付结果为依据,正确的处理业务逻辑,同时按照支付宝和微信要求的回复格式进行应答。由于网络原因或者其他未知原因,支付通知可能会多次推送,开发者需要自行保证正确处理。

这里有几点需要注意:

  • 商户服务器应该能够正确的处理重复通知;
  • 应该按照支付宝和微信要求的格式正确应答;
  • 为了防止虚假支付通知,应该能够校验支付通知的真伪。

为了屏蔽处理的复杂性,本项目同样提供了工具类让开发者专注于业务逻辑的开发:

import net.verytools.unipay.api.*;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@RequestMapping("/callback/{payType}")
@ResponseBody
public String handlePayNotify(@PathVariable String payType, HttpServletRequest request) {
    PayNotifyHandler h = NotifyHandlerFactory.getNotifyHandler(payType); // payType 是 wx或者alipay
    return h.handle(request, new PayNotifyCallback() {

        @Override
        public void onPaySuccess(String outTradeNo, Map<String, String> notifyParas) {
            // 用户支付成功后,在这里处理支付成功后的业务逻辑。
        }

        @Override
        public boolean isNotifyHandled(String outTradeNo) {
            // 根据 outTradeNo 查询数据库中该订单是否已经成功处理,如果处理过了,返回 true,否则返回 false。
            // 返回 false 则会执行 onPaySuccess,在 onPaySuccess 中处理支付成功后的逻辑。
            return false;
        }

        @Override
        public MchInfo resolveMchInfo(Map<String, String> notifyParas) {
            // 支付回调需要校验支付通知的真伪,需要商户信息,这里返回校验需要的商户信息。
            return payType.equals(PayType.alipay.toString()) ? MchInfo.create(PayType.alipay, "zfb.properties") : MchInfo.create(PayType.wx, "wx.properties");
        }
    }, null);
}

作为开发者,我们只需要,实现好三个回调方法即可:

  • onPaySuccess,处理支付成功的逻辑,举个例子,用户充值1000条短信,用户付款成功后,该回调方法会执行,你要做的是在该方法中给用户增加1000条短信。
  • isNotifyHandled,该方法判断某一笔支付是否已经处理过,如果处理过了一定要返回 true,返回 true 时, onPaySuccess 不会执行,这样即便我们收到多次重复通知,也不用担心给用户充值多次。如果没有处理过,那么 onPaySuccess 回调方法会执行。
  • resolveMchInfo,支付回调需要校验支付通知的真伪,需要商户信息,这里返回校验通知真伪所需要的商户信息。

在极端情况下,isNotifyHandled 可能会产生竞争(race condition),所以我们需要一把锁来阻止竞争,上面代码的 h.handle 最后一个参数就是锁。关于锁的详细说明看下一节。

Locker接口的说明

锁是为了防止竞争的,如果传入 null,内部会自动使用 SimpleLocker,一般情况下能够满足需求,如果需要实现分布式锁,可以自行实现 Locker 接口

Demo 项目

https://github.com/isaltyfish/unipay-demo

有问题吗?点此反馈!

温馨提示:反馈需要登录