一、准备工作
1、注册开发者账号
先去支付宝开发平台注册账号,登录后点击控制台
进入控制台后,翻到最下面,就能看见沙箱支付点击进去就能看见我们所需要的基本信息
2、密钥相关
可以直接在右上角搜索密钥工具,然后点击进去,下载自己系统对应的软件
打开后生成密钥
然后就可以看见自己的密钥了
应用私钥要记住
如果使用自定义密钥
没使用前点击设置并查看
输入自己密钥就可以生成了
3、引入依赖
<!--支付宝支付-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.40.298.ALL</version>
</dependency>
然后在自己的.yml中写入基本配置
alipay:
app-id: 9021000150615149
private-key: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyprnmNu6MCnBP757Hwo2NFBSG3/Mg/d+x1eXe7IxkxZxHoexhiVtev9NiI0vX+jadvLBrR8Zdfz0IP4li9pOVMEqPTepVapnDOlDyi3s5Hy7oSKH8jcG90/hfHIayLmZSKGEipTdm8I/sC9ST4NPiFC/EcdfCObpj/vzm2pGU/wjTttXHjVHZWeRlMqLLs3uV8DuiIOzzrJAiox7dt9tvJriTwPSLvcFxjLAKaZ0iL9llPWfQbUsb8M/jBRpVv1wOIVmXF3Ef34cTEn4L7ppoixq0Z8Hqt74THlBtwm+Z/c85T4LDSAA0+bjjqsz1c6nrKgMru5/+CrXcrC+h4ENDAgMBAAECggEABWI+4fAv2iqKXe5Ts6NqTqEXSUVAd3MPJbXTepdYV8UYw5PeosXX6hAP17pGk9ingrNoAaOlF7Y46aG4izHxGqLc7FqlwDM5NQfx3o1ThlIpQLjHoOvItebIal1atc3uKXWsD7Y46DJBtBRbMKWq3PpMwjfID8NbmZ3akz5s+rdz5ZLmWpOAVUmPgL9UG3fFool4mtBvm950ScxG9S3muHgRkEOlU8yRd/eh7mxUi8u3r4I+DaTL8Nb9xTFHBqUPHnqEo2d17VSrBmO2Wcz7yWAniNxj3h+tusxATwuFAm+ix42rcsjzcKMnAkPLyqN+zE/wf+mlIzOVnmyQMpCCEQKBgQDpKPA3ITD4/TzNHMNBI5I4cAPbaPzqdU/za5RWASThAsrXlDiM/1lomuENoFWfS8+AWI/FzTddTmDRbehvKLnEm7Vk0FNPp7KwlvW+RijvchliV+GKcKrUI5Kot+SMSxzYP9n7653gVAeDGnIc3/mQCIXOoO4g1XosdDI09mS+OwKBgQDEJtlLXzfOUrrdsQ97N+T5vjahQW5bX6Iypyu4usLzYDOxQTkhPI83dOx1K3AGop+rdWu2gd59Nck1APdPSQJAiB2FUCaZZSxnIjCdW0+B9MRMxk3d+OC7eEwgm5N6lKkSCmGQonfurSafX1jTiyKNA1b51+hzgzSHl6J4QVyWmQKBgEM+aFEuBGa6+k8ZjYJ61M/sFmCwHcksvjsw+p33RICUgpW8DLc1ab7OdDuu0yLXBWg5onVkoyQI18luzktIzpLSaXVC09Q0REfDseoHaATuvj1rpQN9TPdTrxdzublwbEg92gtav7ST53q7JNAJFHuiyZx3M1ZK2yp49IXrzdBDAoGBAIfTMuJ7Q8RuCxGaGtNDCB5bta6CBW3mUgVrNt5LDNXERuc1995o9e772wTjmEYv9AqhW33cffkL9pDqY1YWgyW2W2LF9oR/x3h+91TvNqYRnKBhLOhI/y0xht2JfV0DdfOVFqw1pIC0i6sONg7+KGWm62JEPKVINGaz/H/zGq75AoGBAOFlishy0x80RD+AIwwtc+Rw2bd+Puh3oWVp4qXxuXD67Pzr7llqRIZDMv1k9VRUIqv3LZzUY1UWSxNBRn3prr+EZJjmveSP0geEiCKdsiAJwdFyflolLWJxh1rm7M3pU0ma83NPiHv+mfLQ9lm2GuJ+1eo6SuoqpdLzAKlGezy7
public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmbRz7Qy7E2l0VMvbdxIuZifHA+FPgiX+lsvWJkJdRzD0fgnnKucQ1ieFbXbuFuhkOjhQWzpDdk9FqiGI3uO9VTlR4NH2yVkMGrEOk7x11LgUxWbv92UV/7X7lGnX14Y4D7tvYINeYs5gmTuNEwbBFIodVVjb85moYtrgMm3xt2try77U40lHBLTPeTmKW3nDCxRBGMiijtacXn39Ks7a7vFrOXqUn2YkXjZuPNbIhfqh5qG5dAcKhm9ThYJEvpPpcCWFI0Ye0vgQxaeR6zWUSRSuco7S+EUndzTcrGXitxQ1We2Vj2gDdiK825ni5xNRj0BoEjruA+3ZJ87Td9xm4wIDAQAB
notify-url: http://q6d8e65c.natappfree.cc/app/alipay/notify
return-url: http://localhost:5050/pay/success
gateway-url: https://openapi-sandbox.alipay.com/gateway.do
app-id:就是基本信息中的APPID private-key:自己的私钥 public-key:支付宝公钥 notify-url: http://se7658ad.natappfree.cc/app/alipay/notify 支付宝服务器主动通知商户支付结果的异步回调地址 return-url: http://localhost:5050/pay/success 支付完成后用户浏览器同步跳转的页面地址 gateway-url: https://openapi-sandbox.dl.alipaydev.com/gateway.do 沙箱接口地址
4、内网穿透工具
因为支付宝是外网,所以如果想要访问我们的项目就必须需要内网穿透技术
直接访问natapp官网就可以了 NATAPP-内网穿透 基于ngrok的国内高速内网映射工具,进入后先注册登录一个账户,然后购买一个免费隧道就可以了
根据自己的端口号输入端口号购买
然后记住这个authtoken
最后根据自己的系统下载客户端
下载好后不需要点击exe文件使用,只需要新建一个.bat文件
输入natapp.exe -authtoken=自己的authtoken号,然后保存双击运行就可以实现
弹出这种画面
就代表运行成功了
然后就可以进行后端代码的编写了
二、后端代码实现
1、支付宝支付的具体实现类AlipayAdapter
package com.old.senior.payment;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.old.senior.entity.Order;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @TODO
* @Author yang
* 2025/7/6
*/
@Component("alipayAdapter")
public class AlipayAdapter implements PaymentAdapter {
@Value("${alipay.app-id}")
private String appId;
@Value("${alipay.private-key}")
private String privateKey;
@Value("${alipay.public-key}")
private String alipayPublicKey;
@Value("${alipay.notify-url}")
private String notifyUrl;
@Value("${alipay.return-url}")
private String returnUrl;
@Value("${alipay.gateway-url}")
private String gatewayUrl;
@Override
public PaymentResult pay(Order order) {
PaymentResult result = new PaymentResult();
try {
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl, appId, privateKey, "json", "UTF-8", alipayPublicKey, "RSA2");
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setNotifyUrl(notifyUrl);
request.setReturnUrl(returnUrl);
String uniqueOutTradeNo = order.getNumber() + "-" + System.currentTimeMillis();
String bizContent = "{" +
"\"out_trade_no\":\"" + uniqueOutTradeNo + "\"," +
"\"total_amount\":\"" + order.getPrice() + "\"," +
"\"subject\":\"Elderly Care Service Order\"," +
"\"product_code\":\"FAST_INSTANT_TRADE_PAY\"" +
"}";
// 在发送前,将确切的 bizContent 打印到控制台
System.out.println("======================================================================");
System.out.println("ALIPAY DEBUG: The exact bizContent string being sent to Alipay SDK is:");
System.out.println(bizContent);
System.out.println("======================================================================");
request.setBizContent(bizContent);
// ======================= 诊断结束 =======================
// 生成支付表单
String form = alipayClient.pageExecute(request).getBody();
result.setSuccess(true);
result.setMessage("OK");
result.setThirdPartyOrderId(order.getNumber());
result.setForm(form);
} catch (AlipayApiException e) {
result.setSuccess(false);
result.setMessage("支付宝支付异常: " + e.getErrMsg());
}
return result;
}
@Override
public PaymentResult refund(Order order) {
// 退款实现同理,调用alipay的退款接口
PaymentResult result = new PaymentResult();
result.setSuccess(false);
result.setMessage("未实现");
return result;
}
}
2、支付宝通知控制器(AlipayNotifyController)、
AlipayNotifyController是处理支付宝异步支付通知的关键组件,它负责接收支付宝服务器在用户完成支付后发送的异步通知,并根据通知内容更新订单状态。
package com.old.senior.controller;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.old.senior.entity.Order;
import com.old.senior.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
public class AlipayNotifyController {
@Autowired
private OrderService orderService;
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${alipay.public-key}")
private String alipayPublicKey;
@PostMapping("/app/alipay/notify")
public String alipayNotify(HttpServletRequest request) throws IOException {
System.out.println("支付宝回调收到!");
// 1. 获取支付宝POST过来的参数
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (String name : requestParams.keySet()) {
String[] values = requestParams.get(name);
StringBuilder valueStr = new StringBuilder();
for (int i = 0; i < values.length; i++) {
valueStr.append(i == values.length - 1 ? values[i] : values[i] + ",");
}
params.put(name, valueStr.toString());
}
// 2. 验签
boolean signVerified = false;
try {
signVerified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (!signVerified) {
// 验签失败
return "fail";
}
// 3. 判断支付状态,更新订单
String outTradeNoWithTimestamp = params.get("out_trade_no"); // 从支付宝回调中获取带时间戳的商户订单号
String tradeStatus = params.get("trade_status");
// 从支付宝的订单号中解析出我们系统的原始订单号
String originalOrderNumber = outTradeNoWithTimestamp;
if (outTradeNoWithTimestamp != null && outTradeNoWithTimestamp.contains("-")) {
originalOrderNumber = outTradeNoWithTimestamp.substring(0, outTradeNoWithTimestamp.lastIndexOf('-'));
}
// 查询数据库用的原始订单号
Order order = orderService.getOrderByNumber(originalOrderNumber);
if (order == null) {
System.out.println("支付宝回调:根据订单号 " + originalOrderNumber + " 未找到订单。");
return "fail"; // 订单不存在,直接返回失败
}
// Redis锁的key必须使用数据库中的主键ID,才能和支付接口加的锁对应上
String redisKey = "order:pay:" + order.getId();
try {
if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
// 支付成功
if (order.getStatus() != 1) { // 检查订单状态,防止重复处理
order.setStatus(1); // 1=已支付
orderService.updateById(order);
System.out.println("支付宝回调:订单 " + originalOrderNumber + " 状态更新为已支付。");
}
// 不论订单是否已处理,都将Redis锁更新为"paid",确保幂等性
redisTemplate.opsForValue().set(redisKey, "paid", 60, TimeUnit.MINUTES); // 延长锁时间
} else if ("TRADE_CLOSED".equals(tradeStatus)) {
// 交易关闭(未付款超时关闭,或支付完成后全额退款)
// 支付失败或关闭,删除Redis锁,允许用户重新发起支付
redisTemplate.delete(redisKey);
System.out.println("支付宝回调:订单 " + originalOrderNumber + " 交易关闭,释放锁。");
}
// 其他状态(如WAIT_BUYER_PAY)可以暂时不处理锁,等待最终状态
} catch (Exception e) {
e.printStackTrace();
// 异常情况,返回fail,让支付宝重试
return "fail";
}
return "success";
}
}
3、PaymentAdapterFactory工厂类
为了方便后续多种支付方式的结合可以使用一个工厂类创建具体的支付适配器实例,这样就可以根据客户端选择的支付类型返回对应的适配器。代码如下:
package com.old.senior.payment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @TODO
* @Author yang
* 2025/7/6
*/
@Component
public class PaymentAdapterFactory {
@Autowired
private AlipayAdapter alipayAdapter;
@Autowired
private WechatAdapter wechatAdapter;
@Autowired
private UnionPayAdapter unionPayAdapter;
public PaymentAdapter getAdapter(String type) {
switch (type) {
case "alipay": return alipayAdapter;
case "wechat": return wechatAdapter;
case "unionpay": return unionPayAdapter;
default: throw new IllegalArgumentException("不支持的支付方式");
}
}
}
4、PaymentAdapter接口
这个接口定义了支付系统的标准接口:pay和refund,所有支付方式都遵循此接口,确保代码一致性
package com.old.senior.payment;
import com.old.senior.entity.Order;
public interface PaymentAdapter {
PaymentResult pay(Order order);
PaymentResult refund(Order order);
}
5、PaymentResult支付返回的统一结果
PaymentResult是统一的支付结果返回对象,封装支付结果信息,包括成功标志、消息和表单等
package com.old.senior.payment;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @TODO
* @Author yang
* 2025/7/6
*/
@Data
@NoArgsConstructor
@ApiModel("支付结果集")
public class PaymentResult {
private boolean success;
private String message;
private String thirdPartyOrderId;
private String form; // 支付宝支付表单
}
6、订单支付接口
实现了 支付功能,并且利用Redis防止了订单重复提交。
@ApiOperation("订单支付")
@PostMapping("/{id}/pay")
public Result pay(@PathVariable Integer id, @RequestParam String payType) {
String redisKey = "order:pay:" + id;
// 1. 尝试加锁,这次的过期时间可以设置得长一些,比如30分钟,覆盖典型的支付窗口期
Boolean lock = redisTemplate.opsForValue().setIfAbsent(redisKey, "paying", 30, TimeUnit.MINUTES);
if (lock == null || !lock) {
// 如果锁已存在,检查锁的值。可以进一步区分是"paying"还是"paid"
String lockValue = redisTemplate.opsForValue().get(redisKey);
if ("paid".equals(lockValue)) {
return new Result(CodeConstant.FAIL, "订单已支付成功,请勿重复支付", null);
}
return new Result(CodeConstant.FAIL, "订单正在支付中,请勿重复操作", null);
}
try {
// 2. 查询订单 (这里可以加一步检查数据库状态,双重保险)
Order order = orderService.getById(id);
if (order == null) {
redisTemplate.delete(redisKey); // 订单不存在,释放锁
return new Result(CodeConstant.FAIL, "订单不存在", null);
}
if (order.getStatus() == 1) {
// 如果数据库已是支付状态,更新Redis锁并返回
redisTemplate.opsForValue().set(redisKey, "paid", 30, TimeUnit.MINUTES);
return new Result(CodeConstant.FAIL, "订单已支付,请勿重复操作", null);
}
// 3. 发起支付逻辑
PaymentResult paymentResult = paymentAdapterFactory.getAdapter(payType).pay(order);
if (paymentResult.isSuccess()) {
return new Result(CodeConstant.SUCCESS, "支付请求成功", paymentResult.getForm());
} else {
// 如果支付网关返回失败,要释放锁,允许用户重试
redisTemplate.delete(redisKey);
return new Result(CodeConstant.FAIL, paymentResult.getMessage(), null);
}
} catch (Exception e) {
// 如果发生异常,也要释放锁,允许用户重试
redisTemplate.delete(redisKey);
e.printStackTrace();
return new Result(CodeConstant.FAIL, "支付处理异常,请重试", null);
}
// 如果成功发起了支付,锁会一直保留,直到有异步通知
}