前置需求
支付宝沙箱配置
需要有支付宝沙箱:提供一个虚拟的支付环境,用于测验调试,
- 点击这里进入沙箱环境登录 - 支付宝
因为是沙箱默认的密钥即可,这其中我们需要如下数据
- 支付宝的公钥和私钥
- 支付的网关
- 支付的APPID
- 支付宝sdk
内网穿透***
系统启动只是在本地,如何让不同ip都能访问呢?内网穿透
内网穿透是一种技术手段,它允许外部网络的用户访问内部网络中的设备或服务。这项技术通常用于远程访问、建站、调试和游戏联机等场景,使得本地开发或维护的工作可以不受地理位置的限制。
为什么需要内网穿透呢? 因为本质上调用人家的服务,当请求以后,支付宝需要返回给你信息,这就需要你的地址。这样才能找到我们的接口,我们才能根据他返回的信息做出一些业务处理。
免费即可,第一次需要实名认证
设置免费隧道,
我的隧道进行查询 以下红框中的码需要用到,
下载pc端app,解压出来,只有一个exe文件
这里68cvzs地址就是我本地映射到公网上的地址。
可以启动本地的9090服务,使用公网地址进行测试,试一试能不能访问成功
成功,则内网穿透成功。
具体开发
sdk
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.9.28.ALL</version>
</dependency>
配置
相当于设置收钱的人是谁
server:
port: 9090
alipay:
# appid
appId: 9021000135634074
# 应用私钥
appPrivateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8bdoXTGIiXXkt/FBg0LPoOSvQANWo4wIYKVSjodGa60y+7Dk9d6DavrcWR7xrBYaJcSt9jr35rYGZQsXLuYJya0X1bkuiE4C1B26jrTdvPMu2UXlo9dQM2XdwuAb7ohkgMdp0jkMYag2IbtfjRFv0h+jnlUNfAZjJyQ8l7TePqFnJ8S94L9jVloYIRoeoJG3vj701HJ4IccEEFzpiNoznJnw33elr9Zu5K1gZqvQxp61D2pbMJ0daiof94uh3J0XwqeyB8HNbGlZj3OIyZMibU1AZfIpE2+cYVSGYt7+JawayNybhuTlb540OZGRjFGS7KCysqvEqIH6KbHqZJpwpAgMBAAECggEAVmPLDMmBaJ4Qc+vtciXdhgfINYpcax0BFdDFNGx5bBybSCGHsM3LGt87k+R511tmLleLh/pM0U8iTwEVLG02CH10Sq0x0fI9HUJ2EGbXNpHaGBHMpzml6eo+X5iP4wiTmnYg4TVkP6pH4BhptJHf0wII3zxI8iMKSOF8p3fV9G+Ay8i4zTmJtnzTopMshmH0IjemGBsImVLPjlrGNtt28ibarFNu+y8zq82H8ZKW9Gf2aokU3juml7ztIeVuEJEJGZo9D7hv+IwjCUr4q273pNSVE/RW0Nyi2moHmlaJzvSBY9iGPegmZDZAdG2pTUvfbj7KMUHUpTATSXkOwUg8AQKBgQD7zyspR0V9XAlWIYVNNu9aNgtN6lpYtG0MlQYAREsWF9/HCRgghnSE5/TfvKtDsdk2E2nw6WTp9D9ATPFf5TXTWNCcQp2bhiZttFgr4ii+inFpOf8zNJKovuLEa8SB9ImvPQXF7H1pbvWMnDoFtVTbmtiL7RtfOYK/GaK3I3Z2gQKBgQC/kKcXdieMY0jA6Ho6+5W5LmwBWiuhp/8S+o29494JzLr9cG074q69jaXHBNaEMgLfn3oM9Te2SzaPf8dEIzmfjmOJOrfzz8WOI9vWfW14u9ev6h52p21CjKCVsaxcjle9rXWoO4L8UfPpvjA3gz+/KLFdjyz16ctIeKqLWkvhqQKBgQCxcklBGnduorf1mUOdqRO8p48JxhcKoYKjNjT3ZSjglcxdLxxwdy+PJQJb1FGAL8kYoU+rtF0nvLYB8va5lAV9PP4sz4jPOxbDgi4MJqB9vYO0GmmrROYAwht9PVBKH/ASrFbwJfounUGuZ70/nowBATqOSHVcgmOPlVoj4nqtgQKBgBJdgNdr+XCpGBF+eDFtazY8sBgVF/fAjmC4apxY2zGfUhH4FFDYc54ylUEWQqab0NC1jlLts3Cjl1B8lJMAuYaFdR9z5KzYL31oDKxsi/E2OQBhdpgzhvMJXl/bj8Wz08+YcJ43TmB4TuUK2b6Is7TU5uQsVMgiqN2Cy/7eIkY5AoGANDCwDwrSTsHPzmrdL+8ACgWNv7KIj81mjSRIxGQrYYwuxMUAKWRBzEPcgFj3c6KfbyR/PxH+4jf8weYUIMZqY16pjmTUPs1Ba2UX0C2DSIcZeF7SRTrRX3CT0e5ycRBXu1EoqGPmtmO1YFnObTsGY+ZwMUKIFSS1ip2IkPaDyvA=
# 支付宝公钥
alipayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx8Z31UrIoS1tL3f0VVqia8tlUze7RFnEVi3VbwOzC3RV9tt/crZaR+B21DrWkzjCw36tfrlL2WKYW4dWE/4KKwBY+TB67dZmIUShLf65imxysrB5woofe00uXv5GRYNVEgF+XAyUl3lVO5CHCwr2TGnBdOFOBJzvjmBViwEXHFLqECc4UqqnxvrP0AjMggwExktBEba3Z+i14o1jNznntf52FR2A/RGXiuj9FZHXTOlsc0FHodA3uO5V7WCwMlb7QI0HUm4UvTgPziLv6L3Gee2925Mu5t3NXQuRSVrOVh/W2xUCdwoPVeXd6mNh5v25qrR8TsKbmuO556haJbbCTQIDAQAB
# 回调接口,支付宝通过什么接口通知你,这里就是填写具体接口地址,我们使用公网的地址以及接口uri /公网地址/uri
notifyUrl: http://v6tqyw.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;
}
具体支付***
步骤
- 必须传递如下参数(支付订单号(必须唯一), 订单名称,订单金额)
- 创建支付客户端 设定appid 公钥密钥等等信息,用于知道这个订单谁发出的,钱给到谁的账户
- 将请求的订单号等等写入支付的请求对象中 并且请求对象设置回调接口以及支付后的接口
- 支付客户端对象根据支付请求对象去执行,调用支付宝API
- 这个API接口会返回一个表单页面,让用户去输入帐号密码(也就是谁来支付),成功显示金额等等信息,填写支付密码进行转账
- 转账以后无论成功与否,支付宝都会调用你的回调接口,传入数据
代码实现
业务参数代码
/**
* 支付宝支付请求对象 所需要的参数
*/
@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();
}
效果
输入如下地址
http://localhost:9090/alipay/pay?subject=测试商品&total_amount=1000
支付宝返回页面
输入帐号密码(沙箱有测试的密码)
输入支付密码支付
回调接口
以下是支付宝返回给我们的信息
"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"
具体实现
@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";
}
注意:以上代码会报错,显示空指针异常
报错信息如下
Cannot invoke "com.alipay.easysdk.kernel.Context.getCertEnvironment()" because "this.context" is null
这是因为我们并未初始化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初始化成功=======");
}
}
结果