支付宝支付功能
- 步骤一:沙箱配置
- 支付宝沙箱配置
- 步骤二:使用内网穿透
- 步骤三:开始对接
- SDK
- 配置文件
- 支付
步骤一:沙箱配置
支付宝沙箱配置
需要有支付宝沙箱:提供一个虚拟的支付环境,用于测验调试,点击这里进入沙箱环境,可以打开支付宝开放平台
支付宝开放平台
在这里我们只需要里面的几个数据,分别为:
- 支付宝网关地址
- 支付宝的公钥和私钥
- 支付宝的APPID
- 支付宝提供的SDK
步骤二:使用内网穿透
目的是为了允许外部网络用户访问,这里可以使用natapp
为什么需要内网穿透呢?因为本质上调用人家的服务,当请求以后,支付宝需要返回给你信息,这就需要你的地址。这样才能找到我们的接口,我们才能根据他返回的信息做出一些业务处理。
natapp官网
使用免费的即可,需要进行实名认证
填写信息
在我的隧道中查看
下载客户端,根据自己需要下载,我是windows64的
运行natapp
双击natapp
注意:需要将本地的web服务端口改为80,也就是将我们的项目端口改为80端口,这个80端口可以在natapp-》我的隧道-》隧道配置中进行修改
然后启动自己的web项目就可以访问到了,注意隧道的80端口一定要和项目的端口一样也是80,如果要修改,则去natapp中修改完再和项目保持一致
步骤三:开始对接
SDK
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.9.28.ALL</version>
</dependency>
配置文件
server:
port: 80
alipay:
# appid
appId: 自己的支付宝APPID
# 应用私钥
appPrivateKey: 支付宝开放平台应用私钥,前面已经介绍
# 支付宝公钥
alipayPublicKey: 支付宝开放平台应用公钥,前面已经介绍
# 回调接口,支付宝通过什么接口通知你,这里就是填写具体接口地址,我们使用公网的地址以及接口uri /公网地址/uri
# http://iw3naq.natappfree.cc是内网穿透的路径
notifyUrl: http://iw3naq.natappfree.cc/alipay/notify
/**
* 读取yml中的配置信息,自动填充到对应的属性
*/
@Data
@Component
@ConfigurationProperties(prefix = "alipay")
public class AliPayConfig {
private String appId;
private String appPrivateKey;
private String alipayPublicKey;
private String notifyUrl;
}
支付
1.具体步骤
- 必须传递如下参数(支付订单号(必须唯一), 订单名称,订单金额)
- 创建支付客户端 设定appid 公钥密钥等等信息,用于知道这个订单谁发出的,钱给到谁的账户
- 将请求的订单号等等写入支付的请求对象中 并且请求对象设置回调接口以及支付后的接口
- 支付客户端对象根据支付请求对象去执行,调用支付宝API
- 这个API接口会返回一个表单页面,让用户去输入帐号密码(也就是谁来支付),成功显示金额等等信息,填写支付密码进行转账
- 转账以后无论成功与否,支付宝都会调用你的回调接口,传入数据
2.代码
业务参数
/**
* 支付宝支付请求对象 所需要的参数
*/
@Data
public class PayVO {
private String out_trade_no; // 商户订单号 必填
private String subject; // 订单名称 必填
private BigDecimal total_amount; // 付款金额 必填
private String body; // 商品描述 可空
}
支付宝客户端执行
@RestController
@RequestMapping("/alipay")
@Transactional(rollbackFor = Exception.class)
public class AliPayController {
@Resource
AliPayConfig aliPayConfig;
@Resource
private ShopOrderDao shopOrderMapper;
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://loaclhost:9090/hello/pay");
// 3. 客户端执行请求
// 客户端执行请求,拿到响应的结果,返回给浏览器
String form = "";
try {
// 调用阿里的SDK生成表单
// 会收到支付宝的响应,响应的是一个页面,一开始是登陆,然后显示金额,让用户输入密码进行付款
form = alipayClient.pageExecute(request).getBody();
} catch (AlipayApiException e) {
e.printStackTrace();
}
httpResponse.setContentType("text/html;charset=" + CHARSET);
// 直接将完整的表单html输出到页面
httpResponse.getWriter().write(form);
httpResponse.getWriter().flush();
httpResponse.getWriter().close();
}
3.效果
输入地址
http://localhost:80/alipay/pay?subject=测试商品&total_amount=1000
支付宝返回页面
输入帐号密码(沙箱有测试的密码)
输入支付密码支付
4.回调接口
支付宝返回信息
"gmt_create" -> "2024-03-16 22:40:26"
"charset" -> "utf-8"
"gmt_payment" -> "2024-03-16 22:40:30"
"notify_time" -> "2024-03-16 22:40:31"
"subject" -> "测试商品"
"sign" -> "XfBcgT1lIYpxYm0DzaBtLz7WjzxHxhBK4gUdmDCtD/JTAwhohqu"
"buyer_id" -> "2088722031942622"
"body" -> "null"
"invoice_amount" -> "1000.00"
"version" -> "1.0"
"notify_id" -> "2024031601222224031142620502419"
"fund_bill_list" -> "[{"amount":"1000.00","fundChannel":"ALIPAYACCOUNT"}]"
"notify_type" -> "trade_status_sync"
"out_trade_no" -> "96b14931-b0a7-49bb-aa93-498432247a4"
"total_amount" -> "1000.00"
"trade_status" -> "TRADE_SUCCESS"
"trade_no" -> "20240316220014426205023040"
"auth_app_id" -> "9021000135634074"
"receipt_amount" -> "1000.00"
"point_amount" -> "0.00"
"buyer_pay_amount" -> "1000.00"
"app_id" -> "90210001354"
"sign_type" -> "RSA2"
"seller_id" -> "20887976059"
5.具体实现
@PostMapping("/notify") // 注意这里必须是POST接口
public String payNotify(HttpServletRequest request) throws Exception {
// 判断返回状态trade_status 支付成功是TRADE_SUCCESS
if (request.getParameter("trade_status").equals("TRADE_SUCCESS")) {
System.out.println("=========支付宝异步回调========");
Map<String, String> params = new HashMap<>();
// 返回的所有元素 其中有gmt_create=2024-03-16 22:26:17, charset=utf-8, gmt_payment=2024-03-16 22:26:21, notify_time=2024-03-16 22:26:23, subject=测试商品
Map<String, String[]> requestParams = request.getParameterMap();
for (String name : requestParams.keySet()) {
// servlet写法 通过key获取value
params.put(name, request.getParameter(name));
}
System.out.println(params);
System.out.println(params.size());
String tradeNo = params.get("out_trade_no");
String gmtPayment = params.get("gmt_payment");
// 支付宝验签
// 这里必须要初始化不然报错
if (Factory.Payment.Common().verifyNotify(params)) {
// 验签通过
System.out.println("交易名称: " + params.get("subject"));
System.out.println("交易状态: " + params.get("trade_status"));
System.out.println("支付宝交易凭证号: " + params.get("trade_no"));
System.out.println("商户订单号: " + params.get("out_trade_no"));
System.out.println("交易金额: " + params.get("total_amount"));
System.out.println("买家在支付宝唯一id: " + params.get("buyer_id"));
System.out.println("买家付款时间: " + params.get("gmt_payment"));
System.out.println("买家付款金额: " + params.get("buyer_pay_amount"));
// 更新订单未已支付
// 做一些业务上的处理 例如说支付成功以后 更新订单状态 改为已支付等等
ShopOrder order = new ShopOrder();
order.setId(tradeNo);
order.setStatus("1");
order.setZhhifuTime(gmtPayment);
shopOrderMapper.save(order);
}
}
return "success";
}
以上代码可能有问题,需要调整,sdk没有初始化
修改代码
/**
* 读取yml中的配置信息,自动填充到对应的属性
*/
@Data
@Component
@ConfigurationProperties(prefix = "alipay")
public class AliPayConfig {
private String appId;
private String appPrivateKey;
private String alipayPublicKey;
private String notifyUrl;
/**
* 初始化sdk 这样回调接口的时候 才能知道sdk的基础信息
*/
@PostConstruct
public void init() {
// 设置参数(全局只需设置一次)
Config config = new Config();
config.protocol = "https";
config.gatewayHost = "openapi-sandbox.dl.alipaydev.com";
config.signType = "RSA2";
config.appId = this.appId;
config.merchantPrivateKey = this.appPrivateKey;
config.alipayPublicKey = this.alipayPublicKey;
config.notifyUrl = this.notifyUrl;
Factory.setOptions(config);
System.out.println("=======支付宝SDK初始化成功=======");
}
}
完成