SpringBoot对接支付宝完成扫码支付

文章目录

  • 1、支付方式选择
  • 2、交互流程
  • 3、对接准备
    • 1)加密解密 + 签名验签
    • 2)沙箱环境
    • 3)内网穿透
  • 4、二维码
  • 5、下单
  • 6、异步通知回调
  • 7、查询支付结果
  • 8、退款
  • 9、通用版SDK

需求:系统A对接支付宝,实现支持用户扫码支付

1、支付方式选择

对接的API文档:https://open.alipay.com/api

可选的支付方式有:

  • 扫码付:出示付款码或者用户扫码付款
  • APP支付:在APP中唤起支付宝
  • 手机网站支付:在移动端网页中唤起支付宝 App 或支付宝网页
  • 电脑网站支付:在PC端唤起支付宝App或者网页登录支付宝账户
  • 刷脸付:需硬件支持
  • 商家扣款:类似每月会员扣款
  • 预授权支付:冻结对应额度,交易完成后给商家
  • JSAPI支付:小程序

这里选择扫码付的方式,点击下单后,返回支付二维码,用户扫码支付。

2、交互流程

画个下单流程的时序图:

在这里插入图片描述

大致流程:

  • 用户下单,系统A组装信息后(订单信息、回调地址、签名),调用支付宝预下单接口,返回二维码链接
  • 系统A将二维码链接转二维码图片
  • 用户扫码,唤醒本地支付宝,完成支付
  • 支付宝返回支付成功信息给用户
  • 支付宝异步通知系统A支付成功的消息(回调地址),如果用户支付成功,支付宝就调用回调地址的API,回调接口中自然是系统A收到用户支付成功消息后的动作
  • 上一步如果通知失败,比如网络异常或支付宝调用异步通知接口时系统A正好挂了 ⇒ 可主动调支付宝提供的查询支付结果接口,或者加定时任务轮询来查询交易状态,如3s-5s
  • 还可以考虑在第一步请求支付宝接口时加上二维码的有效时间,过期就重新发起

查询支付结果流程:

在这里插入图片描述

退款流程同上查询支付结果。PS:注意下单、退款过程中,相关订单的业务数据落库到系统A。

3、对接准备

1)加密解密 + 签名验签

支付信息不能在网络上明文传输,以防被篡改。系统A到支付宝的方向,采用:

  • 支付宝公钥加密 + 系统A的私钥签名(系统A做的事)
  • 支付宝私钥解密 + 系统A的公钥验签(收到信息后,支付宝做的事)

同理,支付宝返回支付结果时,就是在支付宝中用系统A的公钥加密+支付宝的私钥签名,传输到系统A后,则是先用支付宝的公钥验签,再用系统A的私钥解密支付结果
在这里插入图片描述

2)沙箱环境

调试过程中,可采用支付宝提供的沙箱环境,点击右上角控制台,登录后选择沙箱:

在这里插入图片描述

这里有一套可调试的APPID、系统A的公钥、密钥、支付宝的公钥、支付宝的网关地址,以及商家账户和用户账户(用于后续登录沙箱版本支付宝APP完成支付)

在这里插入图片描述

在这里插入图片描述

点击【沙箱工具】侧边栏,下载沙箱版支付宝APP,等于上面的买家账户。

3)内网穿透

前面提到,用户支付成功后,支付宝需要回调系统A接口来通知系统A,但我的开发环境在内网,支付宝访问不到,考虑做内网穿透,让支付宝通知到一个中转地址,再由中转地址到我的内网。穿透工具选择cpolar,下载地址 https://dashboard.cpolar.com/get-started,下载后,解压并安装msi包

在这里插入图片描述

双击exe文件,执行认证:

cpolar authtoken xxxx

在这里插入图片描述

创建隧道,建立链接:

cpolar http 9527//返回结果
Forwarding http://maggie.cpolar.io  -> localhost:9527
Forwarding https://maggie.cpolar.io  -> localhost:9527

转发成功。此时,给支付宝访问forward的地址即可,比如系统A的异步通知接口:

localhost:9527/notify

那就是:

http://maggie.cpolar.io/notify

4、二维码

二维码是消息的载体。平时玩可直接在草料二维码UI页面,这里需要给系统A的订单服务用代码生成二维码。二维码中的信息自然是支付宝预下单返回的url。Java生成二维码可集成zxing库,但这样得自己两层for填充方格子:【SpringBoot整合ZXing创建二维码和条形码】 ,这里选择hutool工具类库(对zxing的二次封装),引入依赖:

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.22</version>
</dependency><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.4.1</version>
</dependency>

调用方式:

//生成直到url对应的二维码,宽高均300像素,可到路径,也可到Http响应
QrCodeUtil.generate("https://url/path", 300, 300, "png", httpServletResponse.getOutPutStream());

也可引入QrConfig对象,设置其他属性:

QrConfig config = new QrConfig(300, 300);
//纠错级别
config.setErrorCorrection(ErrorCorrectionLevel.H);
//二维码颜色
config.setBackColor(Color.BLUE);
QrCodeUtil.generate("https://www.baidu.com", config, new File("D:\\code.png"));

5、下单

支付宝提供的SDK 中已经对加签验签逻辑做了封装,使用 SDK 时传入支付宝公钥等内容可直接通过 SDK 自动进行加验签。 SDK文档地址:https://opendocs.alipay.com/open/54/103419?pathHash=d6bc7c2b 。支付宝提供了两种SDK:

  • 通用版SDK
  • 简易版SDK

官网有通用版的API代码示例,这里走简易版的。引入简易版SDK的依赖:

<!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-easysdk -->
<dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-easysdk</artifactId><version>2.2.0</version>
</dependency>

在application.yml配置文件中统一写密钥、通知地址等(生产环境不要将私钥信息配置在源码中,例如配置为常量或储存在配置文件中,这样源码一丢,这些保密信息都泄漏了,放安全区域或服务器,运行时读取即可)

alipay:easy:protocol: httpsgatewayHost: openapi-sandbox.dl.alipaydev.comsignType: RSA2appId: 9021000133624745merchantPrivateKey: MIIEvQIBADANBgkqhkiG9w0BalipayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCnotifyUrl: http://maggie.cpolar.io/notify
server:port: 9527

@ConfigurationProperties注解统一读到:

@Configuration
@Data
@ConfigurationProperties(prefix = "alipay.easy")
public class AliPayConfigInfo {/*** 请求协议*/private String protocol;/*** 请求网关*/private String gatewayHost;/*** 签名类型*/private String signType;/*** 应用ID(来自支付宝申请)*/private String appId;/*** 应用秘钥*/private String merchantPrivateKey;/*** 支付宝公钥*/private String alipayPublicKey;/*** 支付结果异步通知的地址*/private String notifyUrl;/*** 设施AES秘钥*/private String encryptKey;
}

将配置处理成Config类型的Bean,方便后面传入Config对象:

@Configuration
public class AliPayConfig {@Beanpublic Config config(AliPayConfigInfo configInfo){Config config = new Config();config.protocol = configInfo.getProtocol();config.gatewayHost = configInfo.getGatewayHost();config.signType = configInfo.getSignType();config.appId = configInfo.getAppId();config.merchantPrivateKey = configInfo.getMerchantPrivateKey();config.alipayPublicKey = configInfo.getAlipayPublicKey();config.notifyUrl = configInfo.getNotifyUrl();config.encryptKey = "";return config;}
}

写下单接口,响应一个二维码给前端,这里业务数据、订单编号直接写死,只做示意:

@RestController
@Slf4j
public class PayController {@Resourceprivate Config config;/*** 收银台点击结账* 发起下单请求*/@GetMapping("/pay")public void pay(HttpServletRequest request, HttpServletResponse response) throws Exception {Factory.setOptions(config);//调用支付宝的接口AlipayTradePrecreateResponse payResponse = Factory.Payment.FaceToFace().preCreate("订单主题:Mac笔记本", "LS123qwe123", "19999");//参照官方文档响应示例,解析返回结果String httpBodyStr = payResponse.getHttpBody();JSONObject jsonObject = JSONObject.parseObject(httpBodyStr);String qrUrl = jsonObject.getJSONObject("alipay_trade_precreate_response").get("qr_code").toString();QrCodeUtil.generate(qrUrl, 300, 300, "png", response.getOutputStream());}
}

6、异步通知回调

异步回调参考文档:https://opendocs.alipay.com/open/194/103296?pathHash=e43f422e&ref=api,实现先全放Controller层了:

@RestController
@Slf4j
public class PayController {@Resourceprivate Config config;/*** 给支付宝的回调接口*/@PostMapping("/notify")public void notify(HttpServletRequest request, HttpServletResponse response) throws Exception {Map<String, String> params = new HashMap<>();//获取支付宝POST过来反馈信息,将异步通知中收到的待验证所有参数都存放到map中Map<String, String[]> parameterMap = request.getParameterMap();for (String name : parameterMap.keySet()) {String[] values = parameterMap.get(name);String valueStr = "";for (int i = 0; i < values.length; i++) {valueStr = (i == values.length - 1) ? valueStr + values[i]: valueStr + values[i] + ",";}//乱码解决valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");params.put(name, valueStr);}//验签Boolean signResult = Factory.Payment.Common().verifyNotify(params);if (signResult) {log.info("收到支付宝发送的支付结果通知");String out_trade_no = request.getParameter("out_trade_no");log.info("交易流水号:{}", out_trade_no);//交易状态String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8");//交易成功switch (trade_status) {case "TRADE_SUCCESS"://支付成功的业务逻辑,比如落库,开vip权限等log.info("订单:{} 交易成功", out_trade_no);break;case "TRADE_FINISHED":log.info("交易结束,不可退款");//其余业务逻辑break;case "TRADE_CLOSED":log.info("超时未支付,交易已关闭,或支付完成后全额退款");//其余业务逻辑break;case "WAIT_BUYER_PAY":log.info("交易创建,等待买家付款");//其余业务逻辑break;}response.getWriter().write("success");   //返回success给支付宝,表示消息我已收到,不用重调} else {response.getWriter().write("fail");   ///返回fail给支付宝,表示消息我没收到,请重试}}
}

到此,看下效果,请求下单接口:

在这里插入图片描述

用沙箱版app扫码:

在这里插入图片描述

支付,查看余额:

在这里插入图片描述

7、查询支付结果

主动查询用户的支付结果,订单编号依然写死:

@RestController
@Slf4j
public class PayController {@Resourceprivate Config config;@GetMapping("/query")public String query() throws Exception {Factory.setOptions(config);AlipayTradeQueryResponse result = Factory.Payment.Common().query("LS123qwe123");return result.getHttpBody();}}

在这里插入图片描述

8、退款

退款操作:

@RestController
@Slf4j
public class PayController {@Resourceprivate Config config;@GetMapping("/refund")public String refund() throws Exception {Factory.setOptions(config);AlipayTradeRefundResponse refundResponse = Factory.Payment.Common().refund("LS123qwe123", "19999");return refundResponse.getHttpBody();}
}

在这里插入图片描述

9、通用版SDK

官方文档就是以这个SDK为例的,贴个代码示例:

private static final String GATEWAY_URL = "https://openapi.alipaydev.com/gateway.do";
private static final String FORMAT = "JSON";
private static final String CHARSET = "UTF-8";//签名方式private static final String SIGN_TYPE = "RSA2";
@Resource
private AliPayConfig aliPayConfig;@Resource
private OrdersMapper ordersMapper;@GetMapping("/pay") // &subject=xxx&traceNo=xxx&totalAmount=xxx
public void pay(AliPay aliPay, HttpServletResponse httpResponse) throws Exception {// 1. 创建Client,通用SDK提供的Client,负责调用支付宝的APIAlipayClient alipayClient = new DefaultAlipayClient(GATEWAY_URL, aliPayConfig.getAppId(),aliPayConfig.getAppPrivateKey(), FORMAT, CHARSET, aliPayConfig.getAlipayPublicKey(), SIGN_TYPE);// 2. 创建 Request并设置Request参数AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();  // 发送请求的 Request类request.setNotifyUrl(aliPayConfig.getNotifyUrl());JSONObject bizContent = new JSONObject();bizContent.set("out_trade_no", aliPay.getTraceNo());  // 我们自己生成的订单编号bizContent.set("total_amount", aliPay.getTotalAmount()); // 订单的总金额bizContent.set("subject", aliPay.getSubject());   // 支付的名称bizContent.set("product_code", "FAST_INSTANT_TRADE_PAY");  // 固定配置request.setBizContent(bizContent.toString());// 执行请求,拿到响应的结果,返回给浏览器String form = "";try {form = alipayClient.pageExecute(request).getBody(); // 调用SDK生成表单} catch (AlipayApiException e) {e.printStackTrace();}httpResponse.setContentType("text/html;charset=" + CHARSET);httpResponse.getWriter().write(form);// 直接将完整的表单html输出到页面httpResponse.getWriter().flush();httpResponse.getWriter().close();
}

具体有业务数据逻辑的对接支付宝接口,可跳转【支付宝业务对接】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/239080.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

成为一名FPGA工程师:面试题与经验分享

在现代科技领域&#xff0c;随着数字电子技术的迅猛发展&#xff0c;FPGA&#xff08;可编程逻辑器件&#xff09;工程师成为了备受瞩目的职业之一。FPGA工程师不仅需要掌握硬件设计的基本原理&#xff0c;还需要具备良好的编程能力和解决问题的实践经验。面对如此竞争激烈的行…

语音识别之百度语音试用和OpenAiGPT开源Whisper使用

0.前言: 本文作者亲自使用了百度云语音识别,腾讯云,java的SpeechRecognition语言识别包 和OpenAI近期免费开源的语言识别Whisper(真香警告)介绍了常见的语言识别实现原理 1.NLP 自然语言处理(人类语言处理) 你好不同人说出来是不同的信号表示 单位k 16k16000个数字表示 1秒160…

C语言中关于操作符的理解

本篇文章只会列出大家在生活中经常使用的操作符 算术操作符 在算数操作符中常用的有&#xff0c;&#xff0c;-&#xff0c;*&#xff0c;/&#xff0c;% &#xff0c;我们重点讲一讲 / (除) 和 % (模) " / "运算 #include <stdio.h>int main() {int a5/2;fl…

探讨APP自动化测试工具的重要性

随着移动应用市场的蓬勃发展&#xff0c;企业对于保证其移动应用质量和用户体验的需求日益迫切。在这一背景下&#xff0c;APP自动化测试工具正变得越来越重要&#xff0c;成为企业成功的关键组成部分。本文将探讨APP自动化测试工具对企业的重要性&#xff0c;并为您解析其在提…

Spring中你一定要知道的@PostConstruct/@PreDestroy

文章目录 功能源码解析执行 功能 Spring中存在很多回调&#xff0c;但是执行他们的时机都不相同&#xff0c;也许大家用的最多的是InitializingBean.afterPropertiesSet&#xff0c;这个方法的作用如名称一样&#xff0c;是bean初始化后执行的一个回调操作&#xff0c;而PostC…

nodejs+vue+ElementUi家政服务系统c90g5

项目中登录模块用到token家政服务平台有管理员&#xff0c;雇主&#xff0c;雇员三个角色。管理员功能有个人中心&#xff0c;雇主管理&#xff0c;雇员管理&#xff0c;资料认证管理&#xff0c;项目类型管理&#xff0c;服务项目管理&#xff0c;需求信息管理&#xff0c;服务…

C# WPF上位机开发(软件的发布和部署)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 软件编译完成之后&#xff0c;本地测试完之后&#xff0c;一般都要发布和部署到客户的电脑上面。客户电脑的配置未必和开发的电脑是一样的&#xf…

mysql 23day DDL常用约束,数据类型

目录 数据库的四种语言常用约束&#xff08;DDL&#xff09;创建格式PRIMARY KEY&#xff1a;主键约束FOREIGN KEY&#xff1a;外键约束主键外键 联合测试开始测试 NOT NULL&#xff1a;非空约束UNIQUE&#xff1a;唯一约束DEFAULT&#xff1a;默认值约束zerofill 零填充auto_i…

自制数据库空洞率清理工具-C版-01-EasyClean-V1.0(支持南大通用数据库Gbase8a)

目录 一、环境信息 二、简述 三、支持功能 四、空洞率 五、工具流程图 六、安装包下载地址 七、参数介绍 1、命令模板 2、命令样例 3、参数表格 八、安装步骤 1、配置环境变量 2、生效环境变量 3、检验动态链接是否正常 九、运行效果 一、环境信息 名称值CPUInt…

Windows电脑向ipad和iOS系统共享文件夹

Windows电脑向ipad和iOS系统共享文件夹 这个方案不需要下载任何软件&#xff0c;但是要求 iOS 和 Windows 在同一个局域网内。再大的文件都可以在 iOS13 自带的的“文件App”里实时显示&#xff0c;可以直接打开。这个解决方案需要你 Windows 电脑上登陆了微软账号&#xff0c…

原生JavaScript实现 元素全屏与退出全屏效果

之前写过 前端screenfull实现界面全屏展示功能 突然发现自己犯傻了 其实元素js中就有全屏与取消全屏的方式 html代码如下 <!DOCTYPE html> <html> <head><title>全屏实验</title><style></style> </head> <body><d…

数据结构考试测试编程题

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

flutter开发实战-第一帧布局完成回调实现

flutter开发实战-第一帧布局完成回调实现 在开发中&#xff0c;我们有时候需要在第一帧布局完成后调用一些相关的方法。这里记录一下是实现过程。 Flutter中有多种不同的Binding&#xff0c;每种Binding都负责不同的功能。下面是Flutter中常见的Binding&#xff1a; 这里简单…

nginx userid到底做了啥?

我们公司在用nginx的userid模块作为简单的用户请求追踪使用。这个模块其实并不能真正记录用户的请求状态&#xff0c;只能作为一个辅助使用。但是在一些场景下会有一些异常。下面我们简单介绍一下这个模块到底做了什么。 userid 模块简介 官网说明文档 ngx_http_userid_modul…

【三维生成与重建】ZeroRF:Zero Pretraining的快速稀疏视图360°重建

系列文章目录 题目&#xff1a;ZeroRF: Fast Sparse View 360◦ Reconstruction with Zero Pretraining 任务&#xff1a;稀疏重建&#xff1b;拓展&#xff1a;Image to 3D、文本到3D 作者&#xff1a;Ruoxi Shi* Xinyue Wei* Cheng Wang Hao Su &#xff0c;来自UC San Dieg…

为实体服务器配置Ubuntu

简介 我们在使用虚拟机时&#xff0c;直接在网上找到镜像然后下载到本地&#xff0c;在VMware创建实例时将该iso文件作为镜像源然后进行基础配置就可以轻松安装配置好Linux虚拟机。 在为实体服务器安装Linux系统&#xff0c;同样的&#xff0c;我们也需要镜像源&#xff08;即…

Jmeter多种定时器实现方法解析

1、固定定时器&#xff08;Constant Timer&#xff09; 用法(场景)&#xff1a;更真实的模拟用户场景&#xff0c;需要设置等待时间&#xff0c;或是等待上一个请求的时间才执行&#xff0c;给 sampler 之间的思考时间 备注&#xff1a;如果需要每个步骤均延迟&#xff0c;则…

Python实现AR协方差结构线性回归模型(GLSAR算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 GLSAR是具有AR协方差结构的广义最小二乘法线性回归模型。 本项目通过GLSAR回归算法来构建AR协方差结构…

搭建 ElasticSearch 集群环境

安装基础环境 我们用虚拟机创建出3台机器&#xff0c;修改主机名为s1&#xff0c;s2和s3 # 打开如下文件&#xff0c;修改主机名 vim /etc/hostname # 重启机器 reboot查看centos版本为7.9 [roots1 ~]# cat /etc/centos-release CentOS Linux release 7.9.2009 (AltArch)下载…

【C语言】记录一次自己犯下的低级错误 o(╯□╰)o(局部数组与指针数组的传参、赋值)

在这里分享一下本人犯下的低级错误&#xff0c;希望大家别掉同样的坑 o(╥﹏╥)o 文章目录 事情原委错误分析及解救办法错误一&#xff1a; 使用局部数组arr并将其作为返回值解决方法&#xff1a;使用动态内存分配来创建数组&#xff0c;并在函数结束前手动释放内存。 错误二&…