【SpringBoot】✈️本地集成支付宝支付功能
目录
👋前言
🍸一、准备
🍻二、开发
1、sdk 接入
2、配置
3、支付实现
🍹三、项目部署
🍸四、测试
💞️ 五、章末
👋前言
小伙伴们大家好,前片文章记录了在 mybatis 的mapper 中不建议使用方法重载,文章链接如下:
【踩坑】⭐️MyBatis的Mapper接口中不建议使用重载方法_mybatis的mapper可以重载吗-CSDN博客
今天来本地集成下支付宝的支付功能,使用的框架是 SpringBoot,本地环境 jdk1.8,服务器使用的是 腾讯云(支付宝的接口回调需要提供可以公网访问的接口)
🍸一、准备
需要先去支付宝官方提供的沙箱环境,进行必要参数获取,沙箱环境链接如下:
登录 - 支付宝
点击后使用自己支付宝账号扫码登录即可 ,登录后可以进入如下页面,需要获取到 appId,支付宝的公钥私钥参数
以上获取到的信息,就足够了,另外就是需要提供一个供支付宝回调用的接口,既然是提供给外部应用回调的,那么必须保证公网可访问,本地启动的项目默认是自己可访问,可以通过以下方式做到公网访问:
第一种就是使用内网穿透工具,内网穿透就是允许外部网络的用户访问内部网络中的服务,内网穿透工具的选择以及如何使用,可以参考之前的文章,链接如下:
【服务器搭建】✈️用自己电脑搭建一个服务器!_服务器怎么搭建-CSDN博客
第二种就是将项目部署到服务器上,本次实现就是将项目打包放到服务器上运行测试 ,至于服务器的选择,怎么搭建服务器的环境,怎么将项目部署到服务器上可以参考之前的文章,链接如下:
【服务器项目部署】⭐️将本地项目部署到服务器!_如何把本地项目部署到服务器上-CSDN博客
🍻二、开发
1、sdk 接入
本地使用的是 springboot 框架,集成很方便,先在项目的 pom.xml 文件中引入支付的 sdk 即可,版本看个人需求,引入后刷新 maven 会自动下载文件
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.9.28.ALL</version>
</dependency>
2、配置
这里的配置是指,要使用支付宝支付功能必须要提供的参数,具体信息有以下:
appId:就是沙箱页面上的 appId,复制到这里即可
appPrivateKey:应用私钥,在沙箱环境中点击“查看”后可以找到“复制私钥”的 地方,快速粘贴
alipayPublicKey:支付宝公钥,与私钥一样,在下面可以复制到公钥
notifyUrl:提供给支付宝回调的接口地址,需要公网可以访问
3、支付实现
3.1 配置类代码如下:
将在 yml 文件中的配置内容映射到配置类中,注册为bean 方便管理和使用
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author HuangBenben
*/
@Data
@Component
@ConfigurationProperties(prefix="alipay")
public class AliPayConfig {
private String appId;
private String appPrivateKey;
private String alipayPublicKey;
private String notifyUrl;
}
3.2 支付宝支付请求对象类代码如下:
import lombok.Data;
import java.math.BigDecimal;
/**
* @author HuangBenben
* 支付宝支付请求对象 所需要的参数
*/
@Data
public class PayVO {
private String out_trade_no; // 商户订单号 必填
private String subject; // 订单名称 必填
private BigDecimal total_amount; // 付款金额 必填
private String body; // 商品描述 可空
}
3.3 业务代码实现:
该Controller 中值提供了两个接口,一个发送支付请求,一个用户支付宝回调
支付接口里会记录该订单的信息到数据库中,订单状态(status)初始化为 0,并且这里传给了一个支付完成后自动跳转的地址(本地传的是之前做的一个简单ai服务的首页)
支付宝回调接口会打印回调的参数(如果合法性校验通过),并且更改订单状态为200
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import org.example.mapper.OrderDetailMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/alipay")
@Transactional(rollbackFor = Exception.class)
public class AliPayController {
@Resource
AliPayConfig aliPayConfig;
@Resource
private OrderDetailMapper orderDetailMapper;
private static final String GATEWAY_URL = "https://openapi-sandbox.dl.alipaydev.com/gateway.do";
private static final String FORMAT = "JSON";
private static final String CHARSET = "utf-8";
private static final String SIGN_TYPE = "RSA2";
/**
* 支付接口 传入业务参数
* 支付是一个我向你要钱的过程,设置api参数就是为了,知道收钱的人是谁,
* 当执行以后支付宝会返回一个登录页面,支付的人输入帐号密码。并且确定金额输入支付密码进行支付
* @param aliPay
* @param httpResponse
* @throws Exception
*/
// 这里使用Get其实不是很恰当,应该使用post,这里为了调试方便使用Get
@GetMapping("/pay")
public void pay(PayVO aliPay, HttpServletResponse httpResponse) throws Exception {
// 1、根据支付宝的配置生成一个支付客户端 客户端用于去调用支付宝的API
// 官方写法
AlipayClient alipayClient = new DefaultAlipayClient(GATEWAY_URL, aliPayConfig.getAppId(),
aliPayConfig.getAppPrivateKey(), FORMAT, CHARSET, aliPayConfig.getAlipayPublicKey(), SIGN_TYPE);
// 2、创建一个支付请求对象
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
// 设置回调接口
request.setNotifyUrl(aliPayConfig.getNotifyUrl());
// 商户订单号,商户网站订单系统中唯一订单号,必填,支付宝不允许有两个相同的订单号
// 使用uuid生成 避免重复
aliPay.setOut_trade_no(UUID.randomUUID().toString());
String out_trade_no = aliPay.getOut_trade_no();
// 付款金额,必填
BigDecimal total_amount = aliPay.getTotal_amount();
// 订单名称,必填
String subject = aliPay.getSubject();
// 商品描述,可空
String body = aliPay.getBody();
// 设置 业务参数 是一个json对象
// 这个json对象 支付宝后台回去识别,根据这些参数进行处理,例如 金额,订单名称,商品描述
request.setBizContent("{" +
"\"out_trade_no\":\"" + out_trade_no + "\"," +
"\"total_amount\":\"" + total_amount + "\"," +
"\"subject\":\"" + subject + "\"," +
"\"body\":\"" + body + "\"," +
"\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
// 支付完以后跳转的地址
request.setReturnUrl("http://****自己服务地址****:8890/login.html");
// 3. 客户端执行请求
// 客户端执行请求,拿到响应的结果,返回给浏览器
String form = "";
try {
// 调用阿里的SDK生成表单
// 会收到支付宝的响应,响应的是一个页面,一开始是登陆,然后显示金额,让用户输入密码进行付款
form = alipayClient.pageExecute(request).getBody();
} catch (AlipayApiException e) {
e.printStackTrace();
}
OrderDetailEntity order = new OrderDetailEntity();
BeanUtils.copyProperties(aliPay,order);
orderDetailMapper.insert(order);
httpResponse.setContentType("text/html;charset=" + CHARSET);
// 直接将完整的表单html输出到页面
httpResponse.getWriter().write(form);
httpResponse.getWriter().flush();
httpResponse.getWriter().close();
}
@PostMapping("/notify") // 注意这里必须是POST接口
public String returnUrl(HttpServletRequest httpRequest,
HttpServletResponse httpResponse) throws Exception {
System.out.println("支付成功, 进入同步通知接口...");
// 获取支付宝GET过来反馈信息
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = httpRequest.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
String name = iter.next();
String[] values = requestParams.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);
}
// 调用SDK验证签名
boolean signVerified = AlipaySignature.rsaCheckV1(params,
aliPayConfig.getAlipayPublicKey(),
CHARSET,
SIGN_TYPE
);
//验签成功
if (signVerified) {
// 同步通知返回的参数(部分说明)
// out_trade_no : 商户订单号
// trade_no : 支付宝交易号
// total_amount : 交易金额
// auth_app_id/app_id : 商户APPID
// seller_id :收款支付宝账号对应的支付宝唯一用户号(商户UID )
System.out.println("****************** 支付宝同步通知成功 ******************");
System.out.println("同步通知返回参数:" + params.toString());
System.out.println("****************** 支付宝同步通知成功 ******************");
String outTradeNo = params.get("out_trade_no");
OrderDetailEntity detail = orderDetailMapper.getByNo(outTradeNo);
detail.setStatus(200);
orderDetailMapper.updateByNo(200,outTradeNo);
} else {
System.out.println("支付, 验签失败...");
}
// 返回支付操作完成后需要跳转的页面,这里把返回的参数直接传给页面
return params.toString();
}
}
🍹三、项目部署
使用 maven 快速打包,这里已经将支付完成之后自动跳转的地址换成服务器地址了,另外本地项目中有操作数据库表的部分,也已经在服务器上的数据库中进行了同步,只需要将项目部署到服务器即可!
本地连接到服务器后,将打包好的jar包上传到服务器
java -jar 命令,项目启动成功!
由于是服务器上部署的,而大部端口默认是禁止外部 ip 访问的,所以需要将该项目使用的端口号(8890)开放,可以在防火墙设置里面添加规则:
以上步骤完成之后,开发+部署就算完成了!
🍸四、测试
浏览器输入一下地址(访问支付接口,传递了两个参数),会跳出如下页面,可以看到传入的参数在页面上都有显示,订单名称和金额,以及收款账户
http://118.*.*.*:8890/alipay/pay?subject=测试商品&total_amount=666
可以在沙箱环境控制台获取到商户的信息和测试账户(用来付钱的账户)信息,如下:
输入账号密码(测试账号的,不是商家的),会自动跳转到付款成功页面,然后跳转到自定义的页面(发送支付请求的时候,传的地址)
支付成功之后可以看到,商家的余额增加了 666,对应用户的金额减少了 666
到服务器上的数据库看下数据,如下,订单状态成功更改为200,操作时间是在订单提交后的9秒后,说明从我们发起支付请求后到支付宝处理完成,回调我们提供的接口花费不到十秒钟;在服务器的项目日志中也可以查看详细的回调参数(做了打印)
💞️ 五、章末
文章到这里就结束了~