当前位置:   article > 正文

开发笔记 | Springboot整合多平台支付(微信/支付宝)_spring boot 微信支付 异步回调

spring boot 微信支付 异步回调

目录

微信支付

开发前准备

支付程序编写

(1)创建订单

(2)微信的支付回调

(3)查询订单状态

(4)退款

(5)退款查询与关闭订单

(5)退款回调

alipay-sdk沙箱模拟支付宝支付

官方参考文档

开发前准备

开发

支付测试

整合alipay-easysdk支付宝支付

开发流程

yml配置文件

导入依赖

支付配置类

支付程序编写

网页支付(扫码支付)

App支付

移动端网站支付

支付回调

支付查询

退款

交易取消

其他辅助工具类

简易生成订单号

沙箱模拟时利用natapp内网穿透工具进行异步/同步回调


本篇笔记包含:微信支付,支付宝沙箱环境支付模拟,支付宝简单支付版(easysdk)实现

后续有其他平台会继续更新

微信支付

开发前准备

以小程序支付为例

接入前准备参考官方文档微信支付-开发者文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml

场景:通过小程序调起支付-》支付完成-》微信支付成功通知

业务流程:

用户创建商户订单-》结合商户订单号,价格等调起微信支付-》完成支付更新商户订单支付1.

1.导入依赖

  1. <!-- 微信支付 SDK-->
  2. <dependency>
  3. <groupId>com.github.binarywang</groupId>
  4. <artifactId>weixin-java-pay</artifactId>
  5. <version>4.0.0</version>
  6. </dependency>

2.yml配置基本参数

wxpayconfig:
  appId: xxxxxxxx (自己的appid)
  mch-id: xxxxxxxx (自己的商户id)
  mchKey: xxxxxxxx (自己的api密钥,非appSecret)
  appSecret:xxxxxxxx
  keyPath: classpath:test/apiclient_cert.p12 (微信商户下载的安全证书存放位置)
  notifyUrl: xxxxxxxx (微信支付回调的接口)
  refundNotifyUrl:xxxxxxxx
  officialAppId: xxxxxxxx (服务号appid)
  officialAppSecret: xxxxxxxx (服务号appSecret)

3.config配置类

  1. @Configuration
  2. @ConditionalOnClass(WxPayService.class)
  3. public class MyWxPayConfig {
  4. @Bean
  5. @ConditionalOnMissingBean
  6. public WxPayService wxService() {
  7. WxPayConfig payConfig = new WxPayConfig();
  8. payConfig.setAppId("xxxxxxxxx");
  9. payConfig.setMchId("xxxxxxxxx");
  10. payConfig.setMchKey("xxxxxxxxx");
  11. payConfig.setKeyPath("xxxxxxxxx");
  12. payConfig.setNotifyUrl("xxxxxxxx"); //微信支付后回调的接口
  13. payConfig.setRefundNotifyUrl("xxxxxxxx"); //微信退款后回调的接口
  14. payConfig.setTradeType(WxPayConstants.TradeType.JSAPI);
  15. payConfig.setUseSandboxEnv(false); //是否使用沙箱支付环境
  16. WxPayService wxPayService = new WxPayServiceImpl();
  17. wxPayService.setConfig(payConfig);
  18. return wxPayService;
  19. }
  20. }

支付程序编写

公共部分

  1. @RestController
  2. @RequestMapping("/wxpay")
  3. @Api(value = "微信支付")
  4. public class WXPayController{
  5. @Autowired
  6. private WxPayService wxPayService;
  7. ......
  8. }

(1)创建订单

  1. @ApiOperation(value = "确认支付")
  2. @GetMapping(value = "/confirm")
  3. public ResponseEntity<WxPayMpOrderResult> confirm(String orderId, HttpServletRequest request) throws Exception {
  4. /**
  5. *orderId 业务系统创建的订单表id
  6. *校验业务系统是否存在订单 且订单状态为待支付,不能为已取消,已支付
  7. *一些针对业务系统的业务处理 业务订单实体 bizOrder orderNum业务创建的订单号
  8. *BizAppUser appUser = appUserService.getById(bizOrder.getAuthId()); //获取当前登录小
  9. *程序用户
  10. **/
  11. try{
  12. //通过redis 杜绝 订单重复提交
  13. if(Boolean.toString(true).equals(redisUtils.get("PAY_" + bizOrder.getOrderNum()))) {
  14. return ResultUtil.error("订单已提交");
  15. }
  16. //将订单号存入redis 设置状态为 true表示已提交 5s后过期
  17. redisUtils.set("PAY_" + bizOrder.getOrderNum(), "true", 5);
  18. WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest =
  19. WxPayUnifiedOrderRequest.newBuilder()
  20. .outTradeNo(bizOrder.getOrderNum()) //订单号
  21. .body(bizOrder.getOrderNum())
  22. .totalFee(bizOrder.getAmount())) //支付金额
  23. .openid(appUser.getOpenid()) //用户小程序id
  24. .spbillCreateIp(getIpAddress(request))
  25. .build();
  26. //将参数传给微信 进行订单处理
  27. WxPayMpOrderResult result = this.wxPayService.createOrder(wxPayUnifiedOrderRequest);
  28. //更新支付操作至业务订单表 支付结果通过支付回调更新 业务订单中的支付状态
  29. bizOrder.setPayTime(LocalDateTime.now());
  30. orderService.updateById(order);
  31. }catch(Exception e){
  32. log.error("支付失败");
  33. return ResultUtil.error("支付失败");
  34. }finally{
  35. //最后删除 次订单的重复提交限制 标识
  36. redisUtils.delete("PAY_" + bizOrder.getOrderNum());
  37. }
  38. }

(2)微信的支付回调

  1. @ApiOperation(value = "微信支付回调")
  2. @PostMapping(value = "/callback")
  3. public String wxParentNotifyPage(@RequestBody String xmlData) throws Exception {
  4. final WxPayOrderNotifyResult notifyResult =
  5. this.wxPayService.parseOrderNotifyResult(xmlData);
  6. //获取支付结果后 更新 业务订单表 更新订单支付状态 等业务操作
  7. if (WxPayConstants.ResultCode.SUCCESS.equals(notifyResult.getReturnCode()) &&
  8. WxPayConstants.WxpayTradeStatus.SUCCESS.equals(notifyResult.getReturnCode())) {
  9. //更新支付状态
  10. BizOrder bizOrder = new BizOrder();
  11. bizOrder.setPayStatus("PAID");
  12. ......
  13. Wrappers.lambdaUpdate(BizOrder.class).eq(BizOrder::getOrderNum,
  14. notifyResult.getOutTradeNo()));
  15. }
  16. return WxPayNotifyResponse.success("成功");
  17. }

(3)查询订单状态

  1. //查询订单是否支付成功
  2. @ApiOperation(value = "查询支付状态")
  3. @GetMapping(value = "/queryOrder")
  4. public WxPayOrderQueryResult queryOrder(String orderId) throws Exception {
  5. BizOrder order = orderService.getById(orderId);
  6. Assert.notNull(order, "未查询到订单信息");
  7. //第一个参数为流水号 第二个参数为订单号
  8. return this.wxPayService.queryOrder(null,order.getOrderNum());
  9. }

(4)退款

  1. @ApiOperation(value = "退款")
  2. @PostMapping("/refund")
  3. public WxPayRefundResult refund(@RequestBody WxPayRefundRequest request) throws WxPayException {
  4. return this.wxPayService.refund(request);
  5. }

(5)退款查询与关闭订单

  1. //根据微信订单号/商户订单号/商户退款单号/微信退款单号查询退款
  2. @GetMapping("/refund/query")
  3. public WxPayRefundQueryResult refundQuery(@RequestParam(required = false) String transactionId,
  4. @RequestParam(required = false) String outTradeNo,
  5. @RequestParam(required = false) String outRefundNo,
  6. @RequestParam(required = false) String refundId) throws Exception {
  7. return this.wxPayService.refundQuery(transactionId, outTradeNo, outRefundNo, refundId);
  8. }
  9. //关闭订单
  10. @GetMapping("/close")
  11. public WxPayOrderCloseResult closeOrder(@RequestParam(required = false) String outTradeNo) throws Exception {
  12. return this.wxPayService.closeOrder(outTradeNo);
  13. }

(5)退款回调

  1. @ApiOperation(value = "退款回调")
  2. @PostMapping("/notify/refund")
  3. public String parseRefundNotifyResult(@RequestBody String xmlData) throws WxPayException{
  4. final WxPayRefundNotifyResult result =
  5. this.wxPayService.parseRefundNotifyResult(xmlData);
  6. if(WxPayConstants.RefundStatus.SUCCESS.equals(result.getReqInfo().getRefundStatus())){
  7. //更新业务订单表支付状态等业务操作
  8. ......
  9. }
  10. return WxPayNotifyResponse.success("成功");
  11. }

alipay-sdk沙箱模拟支付宝支付

官方参考文档

alipay.trade.page.pay(统一收单下单并支付页面接口) | API支付宝文档中心https://opendocs.alipay.com/apis/api_1/alipay.trade.page.pay

开发前准备

商户平台

登录 - 支付宝

创建流程​​​​​​创建应用 | 网页&移动应用

【沙箱测试环境流程】本文以沙箱环境模拟

1.用支付宝账号登录【开放控制平台】创建应用获取appid

2.选择沙箱模拟环境

3.沙箱应用-》获取appid(一个appid绑定一个收款支付宝账户)

4.利用开发助手工具生成RSA2密钥

生成密钥 | 开放平台支付宝文档中心https://opendocs.alipay.com/common/02kipl生成,一对 RSA 2密钥【应用公钥、应用私钥】以及公钥证书申请 CSR 文件

【公钥】传给支付宝平台

【私钥】配置代码中,签名用

一个密钥与一个应用绑定

5.生成密钥后,进行配置

返回平台-》开发信息-》自定义密钥-》设置并启用(加签)-》应用公钥

保存后生成支付宝公钥(需要配置到项目中)

【注意代码中需要配置应用私钥,支付宝公钥(非应用公钥)】

应用公钥,支付宝公钥个人理解

应用公钥配置到支付宝平台,应用私钥配置代码中,我方发起请求,支付方通过应用公钥验证。

支付宝公钥配置代码中,当支付方回调我方接口,我方进行校验

两者完成相互校验提高安全性

6.支付宝网关(配置代码中)

https://openapi.alipaydev.com/gateway.do

至此前期配置准备完成

开发

导入依赖

  1. <dependency>
  2. <groupId>com.alipay.sdk</groupId>
  3. <artifactId>alipay-sdk-java</artifactId>
  4. <version>4.9.9</version>
  5. </dependency>
  6. <!-- thymeleaf 依赖 用于渲染html页面 -->
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  10. </dependency>

配置文件

  1. alipay:
  2. appId: 收款账号对应的应用id
  3. privateKey: 应用私钥
  4. publicKey: 支付宝公钥
  5. returnUrl: 127.0.0.1:9999/order/return
  6. notifyUrl: 127.0.0.1:9999/order/notify-url(异步回调地址,http/https开头必须外网能访问)
  7. refundNotifyUrl: https://blog.csdn.net/qq_37630282(同步回调地址,需要外网能够访问)
  8. gatewayUrl: https://openapi.alipaydev.com/gateway.do(沙箱官网与正式网关不同,此处为沙箱网关)
  9. charset: utf-8
  10. signType: RSA2
  11. server:
  12. port: 9999
  13. spring:
  14. application:
  15. name: alipay-demo
  16. thymeleaf:
  17. prefix: classpath:templates/
  18. suffix: .html

配置类

  1. @Data
  2. @Component
  3. @ConfigurationProperties(prefix = "alipay")
  4. public class AlipayConfig {
  5. private String appId;
  6. private String privateKey;
  7. private String publicKey;
  8. private String returnUrl;
  9. private String notifyUrl;
  10. private String refundNotifyUrl;
  11. private String gatewayUrl;
  12. private String charset;
  13. private String signType;
  14. }

请求实体

此处不能用驼峰,不然请求参数会错误 支付宝接口参数接收规范

  1. @Data
  2. public class AlipayDTO {
  3. //商户订单号
  4. private String out_trade_no;
  5. //订单名称
  6. private String subject;
  7. //金额
  8. private String total_amount;
  9. //商品描述
  10. private String body;
  11. //超时时间参数
  12. private String timeout_express = "50m";
  13. //产品编号
  14. private String product_code = "FAST_INSTANT_TRADE_PAY";
  15. }

模拟支付界面html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>支付模拟页面</title>
  6. </head>
  7. <body>
  8. <form action="/order/pay" method="post">
  9. 订单号:<input type="text" name="outTradeNo" required><br/>
  10. 订单名称:<input type="text" name="subject" required><br/>
  11. 支付金额:<input type="text" name="totalAmount" required><br/>
  12. 商品描述:<input type="text" name="body" ><br/>
  13. <input type="submit" value="支付"> <input type="reset" value="重置">
  14. </form>
  15. </body>
  16. </html>

支付完跳转的商户界面

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>支付成功</title>
  6. </head>
  7. <body>
  8. 支付成功
  9. </body>
  10. </html>

Controller

  1. @Controller
  2. @RequestMapping("/order")
  3. public class PayController {
  4. @Resource
  5. private PayService payService;
  6. @RequestMapping("/index")
  7. public String payPage(){
  8. //模拟支付界面
  9. return "index";
  10. }
  11. @PostMapping("/pay")
  12. @ResponseBody
  13. public String pay(@RequestBody AlipayDTO dto) throws AlipayApiException{
  14. //支付 整合实际业务创建或者更新订单
  15. return this.payService.pay(dto);
  16. }
  17. @RequestMapping("/notify-url")
  18. @ResponseBody
  19. public String afterPay(){
  20. //支付后回调 整合实际业务更新订单数据
  21. return "支付完成";
  22. }
  23. @RequestMapping(value = "/return")
  24. public String returnPage(){
  25. //模拟支付界面
  26. return "success";
  27. }
  28. }

servie

  1. @Service
  2. public class PayService {
  3. @Resource
  4. private AlipayConfig alipayConfig;
  5. public String pay(AlipayDTO dto) throws AlipayApiException {
  6. AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig.getGatewayUrl(),alipayConfig.getAppId(),alipayConfig.getPrivateKey(),
  7. "json",alipayConfig.getCharset(),alipayConfig.getPublicKey(),alipayConfig.getSignType());
  8. AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
  9. //回调地址 结合业务更新我方数据库
  10. request.setNotifyUrl(alipayConfig.getNotifyUrl());
  11. //支付完 支付界面跳转的界面
  12. request.setReturnUrl(alipayConfig.getReturnUrl());
  13. request.setBizContent(JSON.toJSONString(dto));
  14. String result = alipayClient.pageExecute(request).getBody();
  15. return result;
  16. }
  17. }

代码编写完成

支付测试

1.可能存在的问题

除了上方参数格式错误报错【订单信息无法识别,建议联系卖家】外

还可能存在支付成功后,返回界面【支付存在钓鱼风险!防钓鱼网站的方法】问题

解决方法:

1.关闭支付宝界面,清除浏览器缓存 2.换个未开过支付宝沙页面的箱浏览器

2.解决完后,出现支付宝登录界面

 3.输入沙箱提供的买家账号,登录支付

 支付成功

支付完-》同步回调return_url,异步回调notify_url

支付完稍等一会儿会跳转配置return_url对应的接口页面(此处设置return_url为博客地址) 

[不跳转的情况排查]

1.return_url必须外网能访问,且http/https开头

2.考虑延迟,或者跟换浏览器尝试或者为本地服务地址等情况。

*以上支付宝沙箱支付流程整合完毕,以下为进一步整合内容

整合alipay-easysdk支付宝支付

开发流程

相关密钥配置同上

yml配置文件

alipay:
  appId: 应用id
  privateKey: 应用私钥
  publicKey: 支付宝公钥(非应用公钥)
  serverUrl: 应用地址
  domain: openapi.alipay.com
  returnUrl: 
  notifyUrl: 回调地址 需外网可调用
  refundNotifyUrl:

导入依赖

<dependency>
   <groupId>com.alipay.sdk</groupId>
   <artifactId>alipay-easysdk</artifactId>
   <version>2.2.0</version>
</dependency>

支付配置类

  1. @Data
  2. @Component
  3. @ConfigurationProperties(prefix = "alipay")
  4. public class AliPayPropertiesConfig {
  5. //应用id
  6. private String appId;
  7. private String privateKey;
  8. private String publicKey;
  9. private String appCertPath;
  10. private String aliPayCertPath;
  11. private String aliPayRootCertPath;
  12. private String serverUrl;
  13. private String domain;
  14. private String returnUrl;
  15. //支付回调的接口
  16. private String notifyUrl;
  17. private String refundNotifyUrl;
  18. }

支付程序编写

网页支付(扫码支付)

  1. @Resource
  2. private AliPayPropertiesConfig aliPayConfig;
  3. ......
  4. AlipayTradePagePayResponse response = Factory.Payment.Page().pay("支付标题", "订单号", "50", aliPayConfig.getReturnUrl());
  5. String form = response.getBody();
  6. return form;

App支付

AlipayTradePagePayResponse alipayTradePagePayResponse = Factory.Payment.Page().pay(subject, outTradeNo, totalAmount, returnUrl);

移动端网站支付

AlipayTradeWapPayResponse alipayTradeWapPayResponse = Factory.Payment.Wap().pay(subject, outTradeNo, totalAmount, quitUrl, returnUrl);

支付回调

  1. @ApiOperation("阿里支付异步回调")
  2. @PostMapping("notify")
  3. public String notifyUrl(HttpServletRequest request) throws Exception{
  4. Map<String, String> params = request.getParameterMap();
  5. boolean flag = Factory.Payment.Common().verifyNotify(params);
  6. String out_trade_no = params.get("out_trade_no");
  7. String trade_no = params.get("trade_no");
  8. String total_amount = params.get("total_amount");
  9. if(flag){
  10. if(params.get("trade_status").equals("TRADE_SUCCESS")){
  11. // todo 业务
  12. }
  13. }
  14. return null;
  15. }

支付查询

  1. AlipayTradeQueryResponse alipayTradeQueryResponse =
  2. Factory.Payment.Common().query(outTradeNo);
  3. Assert.isTrue(ResponseChecker.success(alipayTradeQueryResponse),"查询异常");
  4. return alipayTradeQueryResponse;

退款

  1. AlipayTradeRefundResponse alipayTradeRefundResponse = Factory.Payment.Common().refund(outTradeNo, refundAmount);
  2. Assert.isTrue(ResponseChecker.success(alipayTradeRefundResponse), "退款异常");
  3. return alipayTradeRefundResponse;

交易取消

  1. AlipayTradeCloseResponse alipayTradeCloseResponse = Factory.Payment.Common().close(outTradeNo);
  2. Assert.isTrue(ResponseChecker.success(alipayTradeCloseResponse),"交易取消失败");
  3. return alipayTradeCloseResponse;

其他辅助工具类

简易生成订单号

  1. public class OrderNumerUtils {
  2. //生成yyyyMMddHHmmss+随机数的订单号
  3. public static String getOrderNumer(){
  4. Date date = new Date();
  5. SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
  6. String strDate = formatter.format(date);
  7. String strRandom = RandomStringUtils.randomNumeric(8);
  8. return strDate + strRandom;
  9. }
  10. }

沙箱模拟时利用natapp内网穿透工具进行异步/同步回调

 利用natapp注册本地ip即可生成web隧道,外网即可临时调用本地回调接口,具体搜索natapp教程

natapp_百度百科natapp 基于ngrok的反向代理软件,通过在公网和本地运行的 Web 服务器之间建立一个安全的通道。natapp 可捕获和分析所有通道上的流量,便于后期分析和重放.https://baike.baidu.com/item/natapp/19762535?fr=aladdin

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/132154
推荐阅读
相关标签
  

闽ICP备14008679号