环境说明:SpringBoot3.0.2
支付宝沙箱地址:沙箱地址
获取配置信息
因支付需要回调地址,回调地址必须是公网,如果有公网的话,那直接在下面配置文件填写自己的公网,没有的话,就需要我们借助第三方工具来进行回调。具体操作请看这篇博客
引入依赖
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.22.110.ALL</version>
</dependency>
配置文件
yz:
alipay:
gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do
appId: 你的appid
privateKey: 你的私钥
format: json
publicKey: 你的公钥
charset: UTF-8
signType: RSA2
returnUrl: http://xxxxxx.natappfree.cc/api/pay/finish # 回调成功后,调用的地址
notifyUrl: http://xxxxxx.natappfree.cc/api/pay/notify # 回调地址
expireTime: 15m
return:
url: http://localhost:8000/account/center # 这个就写你支付成功跳转的页面
配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "yz.alipay")
public class AliPayProperties {
private String gatewayUrl;
private String appId;
private String privateKey;
private String format;
private String publicKey;
private String charset;
private String signType;
private String notifyUrl;
private String returnUrl;
private String expireTime;
}
@Data
@Configuration
@ConfigurationProperties(prefix = "yz.return")
public class ReturnProperties {
private String url;
}
订单信息
@Data
public class PayOrderRequest {
private static final long serialVersionUID = 3191241716373120793L;
@Schema(description = "订单号")
private Long id;
@Schema(description = "用户id")
private Long uid;
@Schema(description = "主题")
private String subject;
@Schema(description = "充值金额")
private Long amount;
@Schema(description = "实际金额")
private Float realAmount;
@Schema(description = "优惠率")
private Float discountRate;
}
支付的回调信息,封装成类,
@Data
public class AlipayNotifyDto {
private String gmt_create;
private String charset;
private String gmt_payment;
private String notify_time;
private String subject;
private String sign;
private String buyer_id;
private String invoice_amount;
private String version;
private String notify_id;
private String fund_bill_list;
private String notify_type;
private String out_trade_no;
private String total_amount;
private String trade_status;
private String trade_no;
private String auth_app_id;
private String receipt_amount;
private String point_amount;
private String buyer_pay_amount;
private String app_id;
private String sign_type;
private String seller_id;
}
Facade接口
@Tag(name = "支付接口")
public interface PayFacade {
@Operation(summary = "支付")
@Parameters(value = {@Parameter(name = "uid", description = "用户id", in = ParameterIn.HEADER)})
@PostMapping("/pay/")
BaseResponse<String> reCharge(@RequestHeader Long uid, @RequestBody PayOrderRequest payOrderRequest);
@Operation(summary = "支付宝异步通知,支付宝支付后,会回调该接口")
@PostMapping("/pay/notify")
String notify(AlipayNotifyDto alipayNotifyDto);
@Operation(summary = "同步跳转,告诉你是否调用成功,不能拿来判断支付成功")
@GetMapping("/pay/finish")
void finish(HttpServletResponse response);
}
Controller类
@Slf4j
@RestController
@RequiredArgsConstructor
public class PayController implements PayFacade {
private final PayService payService;
@Override
public BaseResponse<String> reCharge(Long uid, PayOrderRequest payOrderRequest) {
if(payOrderRequest == null || uid == null){
throw new RuntimeException("参数不能为空");
}
String res = payService.reCharge(uid, payOrderRequest);
return ResultUtils.success("",res);
}
public String notify(AlipayNotifyDto alipayNotifyDto) {
if(alipayNotifyDto == null){
throw new RuntimeException("参数不能为空");
}
String res = payService.notifyUrl(alipayNotifyDto);
return res;
}
public void finish(HttpServletResponse response) {
try{
response.sendRedirect(returnProperties.getUrl());
} catch (Exception e){
log.error("【同步跳转,告诉你是否调用成功,不能拿来判断支付成功】",e);
}
}
}
Service类
@Slf4j
@Service
@RequiredArgsConstructor
public class PayServiceImpl extends ServiceImpl<PayMapper, PayFlow> implements PayService {
private final AliPayProperties aliPayProperties;
private final PayMessageProducer payMessageProducer;
private final UserMessageProducer userMessageProducer;
private final PromotionMessageProducer promotionMessageProducer;
private final OrderMessageProducer orderMessageProducer;
// 携带订单信息获取支付url
@Override
public String reCharge(Long uid, PayOrderRequest payOrderRequest) {
try{
log.info("payOrderRequest支付信息:{}", payOrderRequest);
//保存流水---交给mq处理
payMessageProducer.sendMessage(JSON.toJSONString(payOrderRequest));
// 交易
String path = sendPayment(payOrderRequest);
return path;
}catch (Exception e){
log.info("支付异常", e);
throw new RuntimeException("系统出错");
}
}
// 处理回调逻辑
@Override
public String notifyUrl(AlipayNotifyDto alipayNotifyDto) {
log.info("setTradeStatus: {}", alipayNotifyDto.getTrade_status());
log.info("回调了");
String payStatus = "fail";
boolean signVerified = false;
Map<String, String> mp = JSON.parseObject(JSON.toJSONString(alipayNotifyDto), Map.class);
try {
signVerified = AlipaySignature.rsaCheckV1(mp, aliPayProperties.getPublicKey(), aliPayProperties.getCharset(), aliPayProperties.getSignType());
log.info("【异步通知签名验证】" + signVerified);
}catch (AlipayApiException e){
System.out.println("【异步签名异常】" + e.getErrMsg());
return payStatus;
}
if(signVerified && "TRADE_SUCCESS".equals(alipayNotifyDto.getTrade_status())){
// 更新流水---这块应该也交给mq处理,不想写了,就这样吧
updatePayFlowSuccess(alipayNotifyDto.getOut_trade_no());
// 更新订单---交给mq处理
orderMessageProducer.sendMessage(alipayNotifyDto.getOut_trade_no());
// 消费优惠卷---交给mq处理
promotionMessageProducer.sendMessage(alipayNotifyDto.getOut_trade_no());
// 用户金币新增---交给mq处理
userMessageProducer.sendMessage(alipayNotifyDto.getOut_trade_no());
payStatus = "success";
}
log.info("【异步通知签名验证】" + payStatus);
return payStatus;
}
@Override
public void returnUrl(HttpServletRequest request) throws UnsupportedEncodingException {
}
// 更新支付流水
private void updatePayFlowSuccess(String outTradeNo){
QueryWrapper<PayFlow> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("out_trade_no", Long.parseLong(outTradeNo));
PayFlow payFlow = PayFlow.builder().build();
payFlow.setTradeStatus(PayConstant.TRADE_SUCCESS);
payFlow.setPaySuccess(true);
boolean update = this.update(payFlow, queryWrapper);
if(!update){
log.info("更新流水失败");
throw new RuntimeException("更新流水失败");
}
}
// 支付配置
private AlipayConfig getAlipayConfig() {
AlipayConfig alipayConfig = new AlipayConfig();
alipayConfig.setServerUrl(aliPayProperties.getGatewayUrl());
alipayConfig.setAlipayPublicKey(aliPayProperties.getPublicKey());
alipayConfig.setPrivateKey(aliPayProperties.getPrivateKey());
alipayConfig.setAppId(aliPayProperties.getAppId());
alipayConfig.setFormat(aliPayProperties.getFormat());
alipayConfig.setCharset(aliPayProperties.getCharset());
alipayConfig.setSignType(aliPayProperties.getSignType());
return alipayConfig;
}
// 返回支付url
private String sendPayment(PayOrderRequest payOrderRequest){
try {
AlipayClient alipayClient = new DefaultAlipayClient(this.getAlipayConfig());
AlipayTradePagePayRequest request = this.getAlipayTradePagePayRequest(payOrderRequest);
request.setReturnUrl(aliPayProperties.getReturnUrl());
request.setNotifyUrl(aliPayProperties.getNotifyUrl());
// 调用SDK生成表单
AlipayTradePagePayResponse alipayTradePagePayResponse = alipayClient.pageExecute(request, "GET");
String body = alipayTradePagePayResponse.getBody();
return body;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private AlipayTradePagePayRequest getAlipayTradePagePayRequest(PayOrderRequest payOrderRequest) {
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
//异步接收地址,仅支持http/https,公网可访问
request.setNotifyUrl("");
//同步跳转地址,仅支持http/https
request.setReturnUrl("");
/******必传参数******/
JSONObject bizContent = new JSONObject();
//商户订单号,商家自定义,保持唯一性
bizContent.put("out_trade_no", payOrderRequest.getId());
//支付金额,最小值0.01元
bizContent.put("total_amount", payOrderRequest.getRealAmount());
//订单标题,不可使用特殊符号
bizContent.put("subject", payOrderRequest.getSubject());
//电脑网站支付场景固定传值FAST_INSTANT_TRADE_PAY
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
bizContent.put("timeout_express", aliPayProperties.getExpireTime());
request.setBizContent(bizContent.toString());
return request;
}
}