前言
前段时间,和朋友们一起搭建的一个网站需要实现支付功能,使得用户支付后可以成为会员并且能使用更多一些强大的功能。让网站也具备一定的盈利能力。
平时大家用的支付基本上也就是微信支付、支付宝支付、银行卡支付了。我们也是成功接入了微信支付,并且上线。目前网站仍处于一个内测阶段,大家如果有什么问题或者有更好的建议我们可以在下方评论区一起交流一下🌹
下面以最新版本的微信支付为例,展示一个接入支付功能较为通用的解决方案:
实现网站的微信支付功能
理清支付流程
一个完整的微信支付(此处以最简单的Native支付为例子)升级为会员流程包括有:
- 用户获取各种支付类型信息
- 选择某种类型拉取订单并生成二维码
- 用户扫码支付成功后,回调通知服务器已支付
- 对用户进行升级会员业务操作
在此我们主要讲如何开发并调试2、3步骤,1、4步骤就根据不同场景进行不同开发哩
第一步:准备商户基本信息
要实现微信支付的功能,首先需要有微信支付商户收款方的资质,也就是你的收款方信息。
包含信息有
- 商户号 mch-id
- AppID
- 商户API证书序列号 mch-serial-no
- API v3密钥 (目前最新版v3)
- 商户私钥文件 private-key (可以是文件形式也可以把文件内容拷出来)
微信支付商户平台 ---> pay.weixin.qq.com/index.php/c…
我们在微信商户平台准备上述需要的基础信息
第二步:编写配置信息注入配置类
配置信息放入yml中
yaml
代码解读
复制代码
wechat: pay-config: #认证类型 auth-type: WECHATPAY2-SHA256-RSA2048 # 商户号 mch-id: 1···7 # APPID appid: wx·····a2 # 商户API证书序列号 mch-serial-no: 5··················6 # APIv3密钥 api-v3-key: J··············123 # 支付结果微信回调 notify-url: https://······/notify //可以先不管,后续会讲到 # 商户私钥文件 private-key: MIME····
配置类 WeChatPayConfig
less
代码解读
复制代码
@Data // @RefreshScope 可以搭配Nacos配置中心进行热更新处理 若没有忽略 @Configuration @ConfigurationProperties(prefix = "wechat.pay-config") public class WeChatPayConfig { private String authType; private String mchId; private String mchSerialNo; private String privateKey; private String apiV3Key; private String appId; private String notifyUrl; }
初始化单例RSA签名配置
scss
代码解读
复制代码
@Configuration public class BeanConfig { @Autowired private WeChatPayConfig weChatPayConfig; /** * 单例一个RSAAutoCertConfig微信API认证config * @return wechatAPICertConfig */ @Bean public RSAAutoCertificateConfig rsaAutoCertificateConfig() { return new RSAAutoCertificateConfig.Builder() .merchantId(weChatPayConfig.getMchId()) .privateKey(weChatPayConfig.getPrivateKey()) .merchantSerialNumber(weChatPayConfig.getMchSerialNo()) .apiV3Key(weChatPayConfig.getApiV3Key()) .build(); } }
这样配置好之后,在需要使用到的类注入配置类校验是否注入成功
@Autowired
private WeChatPayConfig weChatPayConfig;
log或者sout打印出来校验,校验注入成功后我们进行下一步。
第三步:开发 点击支付拉起二维码
微信支付官方API文档: pay.weixin.qq.com/docs/mercha…
微信支付官方JavaSDK仓库: github.com/wechatpay-a…
首先将SDK依赖导入你的支付模块中,然后开始code
java
代码解读
复制代码
package com.wechat.pay.java.service; import com.wechat.pay.java.core.Config; import com.wechat.pay.java.core.RSAAutoCertificateConfig; import com.wechat.pay.java.service.payments.nativepay.NativePayService; import com.wechat.pay.java.service.payments.nativepay.model.Amount; import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest; import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse; /** Native 支付下单为例 */ public class QuickStart { @Autowired private WeChatPayConfig weChatPayConfig; @Autowired private RSAAutoCertificateConfig certificateConfig; public static void main(String[] args) { // 构建service NativePayService service = new NativePayService.Builder().config(certificateConfig).build(); // request.setXxx(val)设置所需参数,具体参数可见Request定义 PrepayRequest request = new PrepayRequest(); Amount amount = new Amount(); amount.setTotal(100); request.setAmount(amount); request.setAppid("wxa9d9651ae******"); request.setMchid("190000****"); request.setDescription("测试商品标题"); request.setNotifyUrl("https://notify_url"); request.setOutTradeNo("out_trade_no_001"); // 调用下单方法,得到应答 PrepayResponse response = service.prepay(request); // 使用微信扫描 code_url 对应的二维码,即可体验Native支付 System.out.println(response.getCodeUrl()); } }
通过上述代码就可以将用户扫码支付的二维码调用出来。
那么我的服务端如何知道用户是否支付成功了呢?
这个就是配置文件中的回调地址的作用了,微信支付中台会使用POST请求你的这个url,发送支付结果。
所以我们需要编写一个接口进行接收处理微信支付中台发送给我们的、用户的支付结果。
例如:
typescript
代码解读
复制代码
/** * 处理wechatNative支付回调通知端点 */ @PostMapping("/notify") public Map<String,String> getNativeNotify(HttpServletRequest request){ String resultCode = payService.solveNotify(request); Map<String,String> resultMap = new HashMap<>(); resultMap.put("code", resultCode); //通知wechat平台是否成功,成功后不再回调 return resultMap; }
❗注意这个回调地址只能是https的形式
此处解释为什么:
- 微信支付中台在公网,所以你的端点需要能被它所访问到,也要在公网能被访问到。
- https表示是可靠可信任的端点。
总结一下就是微信支付中台需要能在公网一个可信任的端点告诉你的服务器这个二维码订单的支付结果。
在开发中,每次都将这个接收微信发送的支付结果的接口上线到我们的服务器,才能通过https进行调试,未免太过烦琐了.
所以我们需要一个更好的办法,能够满足我们的本地开发调试。
此处我们使用ngrok内网穿透的方式,将本地服务暴露成公网可访问的形式进行开发调试。
具体实操请看此篇文章,讲的很好 --> juejin.cn/post/684490…
记得将这个回调端点url写在配置文件中噢。
😋 然后我们就可以进行支付后的调试了!!
第四步: 前端轮询支付结果
在微信中台告诉服务器的支付结果并处理之后,正常来说前后端分离的项目,前端是还不知道支付是否成功的。
这里我们大致有两种解决方案 让前端能得知支付结果渲染不同页面。
- 轮询接口
- 服务端主动推送
在支付结果的这件小通知来说,我认为前端进行轮询的方式是更好的。
服务端主动推送有两种网络协议能够做到,WebSocket和SSE协议都能让服务端推送信息到客户端。但是如果用户频繁调用订单,成交率很低的话,需要维护多条连接,一时间可能消耗服务器非常多的内存和cpu占有率。
所以我们采取的解决方案是:
- 我们可以通过redis缓存订单支付结果,将轮询的请求打在缓存中
- 采取布隆过滤器防止订单不存在的缓存穿透问题。
伪代码如下:
less
代码解读
复制代码
/** * 提供前端轮询获取支付结果 * @param tradeNo 外部订单号 * @return 支付成功与否 */ @GetMapping("/{tradeNo}/result") public R<String> getPayResult(@PathVariable String tradeNo){ //布隆过滤器防止缓存穿透 // if(!bloomService.contains(RedisConstants.ORDER_IS_DEAL+tradeNo, tradeNo)) // return R.error(ResultCode.VALIDATE_FAILED, "订单不存在"); if("1".equals(redisService.getValue(RedisConstants.ORDER_IS_DEAL+tradeNo))){ Order order = orderDao.getOrderByTradeNo(tradeNo); String vipExpiration = redisService.getValue( RedisConstants.USER_VIP+ order.getUid()); redisService.deleteValue(RedisConstants.ORDER_IS_DEAL+tradeNo); return R.success(vipExpiration, "支付已成功, 本用户vip截至"); } return R.error(ResultCode.FAILED, "订单未完成或不存在"); }