开发笔记 | Springboot整合多平台支付(微信/支付宝)

目录

微信支付

开发前准备

支付程序编写

(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.导入依赖

<!-- 微信支付 SDK-->
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-pay</artifactId>
    <version>4.0.0</version>
</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配置类

@Configuration
@ConditionalOnClass(WxPayService.class)
public class MyWxPayConfig {
  @Bean
  @ConditionalOnMissingBean
  public WxPayService wxService() {
    WxPayConfig payConfig = new WxPayConfig();
    payConfig.setAppId("xxxxxxxxx");
    payConfig.setMchId("xxxxxxxxx");
    payConfig.setMchKey("xxxxxxxxx");
    payConfig.setKeyPath("xxxxxxxxx");
    payConfig.setNotifyUrl("xxxxxxxx"); //微信支付后回调的接口
    payConfig.setRefundNotifyUrl("xxxxxxxx"); //微信退款后回调的接口
    payConfig.setTradeType(WxPayConstants.TradeType.JSAPI);
    payConfig.setUseSandboxEnv(false); //是否使用沙箱支付环境
    WxPayService wxPayService = new WxPayServiceImpl();
    wxPayService.setConfig(payConfig);
    return wxPayService;
  } 
}

支付程序编写

公共部分

@RestController
@RequestMapping("/wxpay")
@Api(value = "微信支付")
public class WXPayController{
    @Autowired
    private WxPayService wxPayService;
    ......
}

(1)创建订单

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

(2)微信的支付回调

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

(3)查询订单状态

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

(4)退款

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

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

//根据微信订单号/商户订单号/商户退款单号/微信退款单号查询退款
@GetMapping("/refund/query")
public WxPayRefundQueryResult refundQuery(@RequestParam(required = false) String transactionId,
                                          @RequestParam(required = false) String outTradeNo,
                                          @RequestParam(required = false) String outRefundNo,
                                          @RequestParam(required = false) String refundId) throws Exception {
     return this.wxPayService.refundQuery(transactionId, outTradeNo, outRefundNo, refundId);
}

//关闭订单
@GetMapping("/close")
public WxPayOrderCloseResult closeOrder(@RequestParam(required = false) String outTradeNo) throws Exception {
     return this.wxPayService.closeOrder(outTradeNo);
}

(5)退款回调

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

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

至此前期配置准备完成

开发

导入依赖

<dependency>
   <groupId>com.alipay.sdk</groupId>
   <artifactId>alipay-sdk-java</artifactId>
   <version>4.9.9</version>
</dependency>
<!-- thymeleaf 依赖 用于渲染html页面 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

配置文件

alipay:
  appId: 收款账号对应的应用id
  privateKey: 应用私钥
  publicKey: 支付宝公钥
  returnUrl: 127.0.0.1:9999/order/return
  notifyUrl: 127.0.0.1:9999/order/notify-url(异步回调地址,http/https开头必须外网能访问)
  refundNotifyUrl: https://blog.csdn.net/qq_37630282(同步回调地址,需要外网能够访问)
  gatewayUrl: https://openapi.alipaydev.com/gateway.do(沙箱官网与正式网关不同,此处为沙箱网关)
  charset: utf-8
  signType: RSA2

server:
  port: 9999

spring:
  application:
    name: alipay-demo
  thymeleaf:
    prefix: classpath:templates/
    suffix: .html

配置类

@Data
@Component
@ConfigurationProperties(prefix = "alipay")
public class AlipayConfig {
    private String appId;
    private String privateKey;
    private String publicKey;
    private String returnUrl;
    private String notifyUrl;
    private String refundNotifyUrl;
    private String gatewayUrl;
    private String charset;
    private String signType;
}

请求实体

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

@Data
public class AlipayDTO {
    //商户订单号
    private String out_trade_no;
    //订单名称
    private String subject;
    //金额
    private String total_amount;
    //商品描述
    private String body;
    //超时时间参数
    private String timeout_express = "50m";
    //产品编号
    private String product_code = "FAST_INSTANT_TRADE_PAY";
}

模拟支付界面html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>支付模拟页面</title>
</head>
<body>
<form action="/order/pay" method="post">
    订单号:<input type="text" name="outTradeNo" required><br/>
    订单名称:<input type="text" name="subject" required><br/>
    支付金额:<input type="text" name="totalAmount" required><br/>
    商品描述:<input type="text" name="body" ><br/>
    <input type="submit" value="支付"> <input type="reset" value="重置">
</form>
</body>
</html>

支付完跳转的商户界面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>支付成功</title>
</head>
<body>
支付成功
</body>
</html>

Controller

@Controller
@RequestMapping("/order")
public class PayController {

    @Resource
    private PayService payService;

    @RequestMapping("/index")
    public String payPage(){
        //模拟支付界面
        return "index";
    }
    
    @PostMapping("/pay")
    @ResponseBody
    public String pay(@RequestBody AlipayDTO dto) throws AlipayApiException{
        //支付 整合实际业务创建或者更新订单
        return this.payService.pay(dto);
    }

    @RequestMapping("/notify-url")
    @ResponseBody
    public String afterPay(){
        //支付后回调 整合实际业务更新订单数据
        return "支付完成";
    }

   @RequestMapping(value = "/return")
    public String returnPage(){
        //模拟支付界面
        return "success";
    }
}

servie

@Service
public class PayService {

    @Resource
    private AlipayConfig alipayConfig;

    public String pay(AlipayDTO dto) throws AlipayApiException {
        AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig.getGatewayUrl(),alipayConfig.getAppId(),alipayConfig.getPrivateKey(),
                "json",alipayConfig.getCharset(),alipayConfig.getPublicKey(),alipayConfig.getSignType());
        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
       //回调地址 结合业务更新我方数据库
        request.setNotifyUrl(alipayConfig.getNotifyUrl());
        //支付完 支付界面跳转的界面
        request.setReturnUrl(alipayConfig.getReturnUrl());
        request.setBizContent(JSON.toJSONString(dto));
        String result = alipayClient.pageExecute(request).getBody();
        return result;
    }
}

代码编写完成

支付测试

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>

支付配置类

@Data
@Component
@ConfigurationProperties(prefix = "alipay")
public class AliPayPropertiesConfig {
    //应用id
    private String appId;
    private String privateKey;
    private String publicKey;
    private String appCertPath;
    private String aliPayCertPath;
    private String aliPayRootCertPath;
    private String serverUrl;
    private String domain;
    private String returnUrl;
    //支付回调的接口
    private String notifyUrl;
    private String refundNotifyUrl;
}

支付程序编写

网页支付(扫码支付)

@Resource
private AliPayPropertiesConfig aliPayConfig;
......
AlipayTradePagePayResponse response = Factory.Payment.Page().pay("支付标题", "订单号", "50", aliPayConfig.getReturnUrl());

String form = response.getBody();
return form;

App支付

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

移动端网站支付

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

支付回调

@ApiOperation("阿里支付异步回调")
@PostMapping("notify")
public String notifyUrl(HttpServletRequest request) throws Exception{
    Map<String, String> params = request.getParameterMap();
    boolean flag = Factory.Payment.Common().verifyNotify(params);
    String out_trade_no = params.get("out_trade_no");
    String trade_no = params.get("trade_no");
    String total_amount = params.get("total_amount");
    if(flag){
        if(params.get("trade_status").equals("TRADE_SUCCESS")){
        // todo 业务
        }
    }
   return null;
   
}

支付查询

AlipayTradeQueryResponse alipayTradeQueryResponse = 
Factory.Payment.Common().query(outTradeNo);

Assert.isTrue(ResponseChecker.success(alipayTradeQueryResponse),"查询异常");
return alipayTradeQueryResponse;

退款

AlipayTradeRefundResponse alipayTradeRefundResponse = Factory.Payment.Common().refund(outTradeNo, refundAmount);

Assert.isTrue(ResponseChecker.success(alipayTradeRefundResponse), "退款异常");
return alipayTradeRefundResponse;

交易取消

AlipayTradeCloseResponse alipayTradeCloseResponse =  Factory.Payment.Common().close(outTradeNo);

Assert.isTrue(ResponseChecker.success(alipayTradeCloseResponse),"交易取消失败");
return alipayTradeCloseResponse;

其他辅助工具类

简易生成订单号

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

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

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

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

  • 1
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot框架提供了强大的集成能力,可以很容易地将微信支付支付宝支付集成到一个二维码支付系统中。 首先,我们需要添加相关的依赖项。对于微信支付,我们可以使用微信支付Java SDK,而对于支付宝支付,我们可以使用支付宝支付SDK。通过在pom.xml文件中添加相应的依赖项,我们可以将它们导入我们的项目中。 在Spring Boot中,我们可以使用@Controller注解创建一个控制器类来接受支付请求。我们可以创建一个API端点,当接收到支付请求时,生成一个包含支付信息的二维码。对于微信支付,我们可以使用微信支付SDK提供的API来生成一个微信支付二维码;对于支付宝支付,我们可以使用支付宝支付SDK提供的API来生成一个支付宝支付二维码。 将生成的二维码展示给用户后,用户可以扫描并完成支付。我们可以创建一个回调接口来接收支付结果通知,并根据支付结果来更新订单状态。 在Spring Boot中,我们可以使用@NotifyMapping注解创建一个消息回调处理器,用于接收支付结果通知。对于微信支付,我们可以使用微信支付SDK提供的回调处理方法;对于支付宝支付,我们可以使用支付宝支付SDK提供的回调处理方法。 最后,我们可以使用Spring Boot的日志记录功能来记录支付过程中产生的日志信息,以便后续进行排查和分析。 综上所述,Spring Boot提供了一个简单而强大的集成能力,可以轻松地将微信支付支付宝支付集成到一个二维码支付系统中。通过使用相应的SDK和技术,我们可以实现一张二维码同时集成微信支付宝支付的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值