支付宝购买功能的使用
项目中支付宝的支付流程
一.工具与配置
想要在项目中使用支付宝首先我们得使用一个支付宝自带的沙箱,如果你有自己的个人商户也可以去申请使用正式的,反正我是没有,就用沙箱了。
1.我们打开支付宝的控制台支付宝控制平台找到沙箱
2.我们要在spring boot里面整合需要一些参数
3.点击查看我们还有一些参数需要用到
4.在spring boot的yaml文件中加入配置
<!--支付宝沙箱-->
<dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-sdk-java</artifactId><version>4.22.110.ALL</version>
</dependency>
alipay:app-id: # 沙箱APP_IDgateway: # 沙箱网关(支付宝网关地址)notify-url: https://内网/api/alipay/notify# 异步通知地址return-url: https://内网/api/alipay/success# 同步返回地址 merchant-private-key: #应用私钥alipay-public-key: #支付宝公钥
异步通知地址(你扫描支付宝付款二维码后,支付宝会给你发送一个post的请求你可以通过解析HTTP请求来判断是否支付成功)这里必须用内网穿透,后面会有讲notify代码干什么的
同步的可以用内网,也可以不用,用localhost(这个我看别的博客是这么写的,我没有试过)
5.下载支付宝的沙箱软件(手机软件)
这里我建议下载好用手机应用分身分两个,一个当商家一个当用户
这里是手机软件的登录账号和密码。后续使用沙箱支付用买家的扫码支付,商家端接收支付数据
必须用下好的沙箱软件扫码,正常的支付宝是不行的
二、逻辑和代码
1.我们先写一个config类来配置
/*** 支付宝配置类*/
@Configuration
@Data
public class AlipayConfig {@Value("${alipay.app-id}")private String appId;@Value("${alipay.gateway}")private String gateway;@Value("${alipay.merchant-private-key}")private String privateKey;@Value("${alipay.alipay-public-key}")private String alipayPublicKey;@Value("${alipay.notify-url}")private String notifyUrl;@Value("${alipay.return-url}")private String returnUrl;// 字符集private static final String CHARSET = "UTF-8";// 签名类型private static final String SIGN_TYPE = "RSA2";// 数据格式private static final String FORMAT = "json";/*** 创建支付宝客户端* @return AlipayClient*/@Beanpublic AlipayClient alipayClient() {return new DefaultAlipayClient(gateway, appId, privateKey, FORMAT, CHARSET, alipayPublicKey, SIGN_TYPE);}
}
2.我们来梳理支付的流程
3.代码
/*** 支付宝支付控制器*/
@RestController
@RequestMapping("/alipay")
@Tag(name = "支付宝支付接口")
@Slf4j
public class AlipayController {@Autowiredprivate AlipayService alipayService;@Autowiredprivate PayOrderService payOrderService;/*** 创建支付订单*/@PostMapping("/pay")@Operation(summary = "创建支付订单")public void createPayOrder(@RequestBody PayOrderDTO payOrderDTO, HttpServletResponse response) throws IOException {log.info("创建支付订单请求:用户ID={}, 金额={}, 标题={}", payOrderDTO.getUserId(), payOrderDTO.getTotalAmount(), payOrderDTO.getSubject());try {// 创建支付订单String payForm = payOrderService.createPayOrder(payOrderDTO);// 直接将支付表单写入响应response.setContentType("text/html;charset=UTF-8");response.getWriter().write(payForm);response.getWriter().flush();} catch (Exception e) {log.error("创建支付订单失败", e);response.setContentType("text/html;charset=UTF-8");response.getWriter().write("<h1>支付订单创建失败:" + e.getMessage() + "</h1>");}}/*** 支付宝异步通知*/@PostMapping("/notify")@Operation(summary = "支付宝异步通知")public String handleNotify(HttpServletRequest request) {log.info("收到支付宝异步通知");try {// 验证签名boolean signVerified = alipayService.verifyNotify(request);if (signVerified) {// 获取通知参数String outTradeNo = request.getParameter("out_trade_no");String tradeNo = request.getParameter("trade_no");String tradeStatus = request.getParameter("trade_status");String totalAmount = request.getParameter("total_amount");log.info("支付通知验证成功:订单号={}, 支付宝交易号={}, 交易状态={}, 金额={}", outTradeNo, tradeNo, tradeStatus, totalAmount);// 判断交易状态if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {// 处理支付成功boolean handleResult = payOrderService.handlePaySuccess(outTradeNo, tradeNo, totalAmount);if (handleResult) {log.info("订单支付成功处理完成:{}", outTradeNo);return "success";} else {log.error("订单支付成功处理失败:{}", outTradeNo);return "failure";}} else {log.info("订单交易状态:{}, 订单号:{}", tradeStatus, outTradeNo);return "success";}} else {log.warn("支付宝异步通知签名验证失败");return "failure";}} catch (Exception e) {log.error("处理支付宝异步通知异常", e);return "failure";}}/*** 支付成功页面*/@GetMapping("/success")@Operation(summary = "支付成功页面")public Result<String> paySuccess() {return Result.success("支付成功!");}/*** 重新发起支付* @param outTradeNo 商户订单号* @return 支付页面HTML*/@GetMapping("/re-pay/{outTradeNo}")public String rePayOrder(@PathVariable String outTradeNo) {if (alipayService.canRePayOrder(outTradeNo)) {PayOrder payOrder = payOrderService.getByOutTradeNo(outTradeNo);// 获取订单金额、标题等信息(建议从数据库中获取)String totalAmount = payOrder.getTotalAmount().toString(); // 示例金额String subject =payOrder.getSubject();//"商品名称";String body = payOrder.getBody();//"商品描述";return alipayService.createOrder(outTradeNo, totalAmount, subject, body);} else {return "<h1>订单已支付或不存在</h1>";}}
}
/*** 支付宝服务类*/
@Service
@Slf4j
public class AlipayService {@Autowiredprivate AlipayClient alipayClient;@Autowiredprivate AlipayConfig alipayConfig;/*** 创建支付订单* @param outTradeNo 商户订单号* @param totalAmount 订单总金额* @param subject 订单标题* @param body 订单描述* @return 支付页面HTML*/public String createOrder(String outTradeNo, String totalAmount, String subject, String body) {try {// 创建API对应的requestAlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();// 设置回调地址alipayRequest.setReturnUrl(alipayConfig.getReturnUrl());alipayRequest.setNotifyUrl(alipayConfig.getNotifyUrl());// 填充业务参数AlipayTradePagePayModel model = new AlipayTradePagePayModel();model.setOutTradeNo(outTradeNo);model.setTotalAmount(totalAmount);model.setSubject(subject);model.setBody(body);model.setTimeoutExpress("30m");model.setProductCode("FAST_INSTANT_TRADE_PAY");alipayRequest.setBizModel(model);// 调用SDK生成表单,支付页面在这里AlipayTradePagePayResponse response = alipayClient.pageExecute(alipayRequest);if (response.isSuccess()) {log.info("支付订单创建成功,订单号:{}", outTradeNo);return response.getBody();} else {log.error("支付订单创建失败:{}", response.getMsg());throw new RuntimeException("支付订单创建失败:" + response.getMsg());}} catch (AlipayApiException e) {log.error("支付宝API调用异常", e);throw new RuntimeException("支付宝API调用异常:" + e.getMessage());}}/*** 查询订单状态* @param outTradeNo 商户订单号* @return 查询结果*/public String queryOrder(String outTradeNo) {try {AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();JSONObject bizContent = new JSONObject();bizContent.put("out_trade_no", outTradeNo);request.setBizContent(bizContent.toString());AlipayTradeQueryResponse response = alipayClient.execute(request);log.info("订单查询结果:{}", response.getBody());if (response.isSuccess()){log.info("订单查询成功,订单号:{}", outTradeNo);return response.getBody();} else {log.warn("订单查询失败:{}", response.getMsg());return response.getBody();}} catch (AlipayApiException e) {log.error("订单查询异常", e);throw new RuntimeException("订单查询异常:" + e.getMessage());}}/*** 验证支付宝回调签名* @param request HTTP请求* @return 验证结果*/public boolean verifyNotify(HttpServletRequest request) {try {// 获取支付宝POST过来反馈信息Map<String, String> params = new HashMap<>();Map<String, String[]> requestParams = request.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] + ",";}params.put(name, valueStr);}// 调用SDK验证签名boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.getAlipayPublicKey(), "UTF-8", "RSA2");if (signVerified) {log.info("支付宝回调签名验证成功");return true;} else {log.warn("支付宝回调签名验证失败");return false;}} catch (AlipayApiException e) {log.error("支付宝回调签名验证异常", e);return false;}}public boolean canRePayOrder(String outTradeNo) {try {String response = queryOrder(outTradeNo);JSONObject result = JSONObject.parseObject(response);JSONObject queryResponse = result.getJSONObject("alipay_trade_query_response");log.info("订单查询结果:{}", queryResponse);String tradeStatus = queryResponse.getString("trade_status");// 只有处于“等待付款”状态的订单才允许重新支付return "WAIT_BUYER_PAY".equals(tradeStatus) || tradeStatus == null;} catch (Exception e) {log.error("查询订单状态失败", e);return false;}}
}
import static com.example.aiagent.constant.ResponseEnum.ORDER_NOT_EXIST;
import static com.example.aiagent.constant.ResponseEnum.PAY_ORDER_CREATE_ERROR;/*** 支付订单服务实现类*/
@Service
@Slf4j
public class PayOrderServiceImpl extends ServiceImpl<PayOrderMapper, PayOrder> implements PayOrderService {@Autowiredprivate AlipayService alipayService;@Autowiredprivate SysUserService sysUserService;@Autowiredprivate VipOrdersService vipOrdersService;@Autowiredprivate VipPlansService vipPlansService;@Autowiredprivate CouponsService couponsService;@Autowiredprivate UserCouponsService userCouponsService;@Resourceprivate ApplicationContext applicationContext;@Resourceprivate StringRedisTemplate stringRedisTemplate;@Resourceprivate AmqpTemplate rabbitMqTemplate;@Override@Transactionalpublic String createPayOrder(PayOrderDTO payOrderDTO) {// 生成商户订单号String outTradeNo = "PAY_" + System.currentTimeMillis() + "_" + payOrderDTO.getUserId();// 创建订单记录PayOrder payOrder = new PayOrder();payOrder.setOutTradeNo(outTradeNo);payOrder.setTotalAmount(payOrderDTO.getTotalAmount());payOrder.setSubject(payOrderDTO.getSubject());payOrder.setBody(payOrderDTO.getBody());payOrder.setUserId(payOrderDTO.getUserId());payOrder.setOrderType(payOrderDTO.getOrderType());payOrder.setStatus(0); // 待支付payOrder.setCreateTime(LocalDateTime.now());payOrder.setUpdateTime(LocalDateTime.now());// 保存订单到数据库this.save(payOrder);log.info("创建支付订单成功:订单号={}, 用户ID={}, 金额={}", outTradeNo, payOrderDTO.getUserId(), payOrderDTO.getTotalAmount());// 调用支付宝创建支付订单return alipayService.createOrder(outTradeNo, payOrderDTO.getTotalAmount().toString(), payOrderDTO.getSubject(), payOrderDTO.getBody());}@Override@Transactional(rollbackFor = Exception.class)public boolean handlePaySuccess(String outTradeNo, String tradeNo, String totalAmount) {try {// 查询订单PayOrder payOrder = getByOutTradeNo(outTradeNo);if (payOrder == null) {log.error("订单不存在:{}", outTradeNo);return false;}// 检查订单状态if (payOrder.getStatus() == 1) {log.info("订单已支付:{}", outTradeNo);return true;}// 验证金额BigDecimal payAmount = new BigDecimal(totalAmount);if (payOrder.getTotalAmount().compareTo(payAmount) != 0) {log.error("订单金额不匹配:订单金额={}, 支付金额={}", payOrder.getTotalAmount(), payAmount);return false;}// 更新订单状态payOrder.setTradeNo(tradeNo);payOrder.setStatus(1); // 支付成功payOrder.setPayTime(LocalDateTime.now());payOrder.setUpdateTime(LocalDateTime.now());boolean updateResult = this.updateById(payOrder);if (updateResult) {log.info("订单支付成功处理完成:订单号={}, 支付宝交易号={}", outTradeNo, tradeNo);// 这里可以添加支付成功后的业务逻辑// 比如:充值余额、开通VIP等handlePaySuccessCallback(payOrder);return true;} else {log.error("更新订单状态失败:{}", outTradeNo);return false;}} catch (Exception e) {log.error("处理支付成功回调异常:订单号={}", outTradeNo, e);return false;}}@Overridepublic PayOrder getByOutTradeNo(String outTradeNo) {LambdaQueryWrapper<PayOrder> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(PayOrder::getOutTradeNo, outTradeNo);return this.getOne(queryWrapper);}@Overridepublic IPage<PayOrder> getAllOrderInfo(Integer page, Integer pageSize, String outTradeNo, Integer status, String orderType) {try {Page<PayOrder> pageParam = new Page<>(page, pageSize);LambdaQueryWrapper<PayOrder> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.like(outTradeNo != null,PayOrder::getOutTradeNo, outTradeNo).eq(status != null,PayOrder::getStatus, status).like(orderType != null,PayOrder::getOrderType, orderType);IPage<PayOrder> pageResult = this.page(pageParam, queryWrapper);return pageResult;}catch (Exception e){throw new BusinessException(ORDER_NOT_EXIST);}}/*** 处理支付成功后的业务逻辑* @param payOrder 支付订单*/private void handlePaySuccessCallback(PayOrder payOrder) {try {String orderType = payOrder.getOrderType();BigDecimal amount = payOrder.getTotalAmount();Long userId = payOrder.getUserId();ApplicationContext applicationContext = SpringUtil.getApplicationContext();PayOrderServiceImpl payOrderService = applicationContext.getBean(PayOrderServiceImpl.class);switch (orderType) {case "RECHARGE":// 充值逻辑log.info("处理充值业务:用户ID={}, 充值金额={}", userId, amount);payOrderService.handleRechargeSuccess(userId, amount, payOrder.getOutTradeNo());break;case "VIP":// VIP购买逻辑log.info("处理VIP购买业务:用户ID={}, 支付金额={}", userId, amount);payOrderService.handleVipPurchaseSuccess(userId, amount, payOrder.getOutTradeNo());break;case "COUPONS":// 优惠券逻辑log.info("处理优惠券业务:用户ID={}, 优惠券金额={}", userId, amount);payOrderService.handleCouponsSuccess(userId, amount, payOrder.getOutTradeNo());break;default:throw new BusinessException(ORDER_NOT_EXIST);}} catch (Exception e) {throw new BusinessException(PAY_ORDER_CREATE_ERROR);}}/*** 处理充值成功逻辑* @param userId 用户ID* @param amount 充值金额* @param outTradeNo 商户订单号*/@Transactionalpublic void handleRechargeSuccess(Long userId, BigDecimal amount, String outTradeNo) {try {// 1. 创建充值记录DTORechargeDTO rechargeDTO = new RechargeDTO();rechargeDTO.setAmount(amount.doubleValue());rechargeDTO.setPaymentMethod("alipay");// 2. 临时设置用户登录状态(因为充值服务需要获取当前登录用户)// 注意:这里需要根据实际情况调整,可能需要修改RechargeRecordService的方法签名// 直接调用充值服务的核心逻辑// 3. 查询用户信息SysUser user = sysUserService.getById(userId);if (user == null) {throw new RuntimeException("用户不存在:" + userId);}// 4. 更新用户余额user.setRemainingSum(user.getRemainingSum() + amount.doubleValue());boolean updateResult = sysUserService.updateById(user);if (updateResult) {log.info("充值成功:用户ID={}, 充值金额={}, 当前余额={}", userId, amount, user.getRemainingSum());} else {throw new RuntimeException("更新用户余额失败");}} catch (Exception e) {log.error("处理充值成功逻辑失败:用户ID={}, 金额={}, 错误:{}", userId, amount, e.getMessage(), e);throw new RuntimeException("处理充值成功逻辑失败", e);}}/*** 处理VIP购买成功逻辑* @param userId 用户ID* @param amount 支付金额* @param outTradeNo 商户订单号*/@Transactionalpublic void handleVipPurchaseSuccess(Long userId, BigDecimal amount, String outTradeNo) {try {// 1. 根据支付金额查找对应的VIP套餐LambdaQueryWrapper<VipPlans> planQuery = new LambdaQueryWrapper<>();planQuery.eq(VipPlans::getCurrentPrice, amount).eq(VipPlans::getIsActive, 1).orderByAsc(VipPlans::getId).last("LIMIT 1");VipPlans vipPlan = vipPlansService.getOne(planQuery);if (vipPlan == null) {log.warn("未找到匹配的VIP套餐:金额={}", amount);// 如果没有找到精确匹配的套餐,可以选择默认套餐或抛出异常throw new BusinessException("未找到匹配的VIP套餐",456);}// 2. 创建VIP订单记录VipOrders vipOrder = new VipOrders();vipOrder.setOrderNo(outTradeNo);vipOrder.setUserId(userId);vipOrder.setVipPlanId(vipPlan.getId());vipOrder.setVipPlanName(vipPlan.getPlanName());vipOrder.setVipDuration(vipPlan.getDurationDays());vipOrder.setOriginalPrice(vipPlan.getOriginalPrice());vipOrder.setDiscountPrice(BigDecimal.ZERO);vipOrder.setFinalPrice(amount);vipOrder.setPaymentMethod("alipay");vipOrder.setPaymentStatus(1); // 已支付vipOrder.setPaymentTime(new Date());vipOrder.setOrderStatus(1); // 已完成// 3. 计算VIP开始和结束时间Date now = new Date();vipOrder.setVipStartTime(now);Calendar calendar = Calendar.getInstance();calendar.setTime(now);calendar.add(Calendar.DAY_OF_MONTH, vipPlan.getDurationDays());vipOrder.setVipEndTime(calendar.getTime());vipOrder.setCreateTime(now);vipOrder.setUpdateTime(now);// 4. 保存VIP订单boolean saveResult = vipOrdersService.save(vipOrder);if (saveResult) {SysUser sysUser = sysUserService.getById(userId);switch (vipPlan.getPlanName()) {case "VIP月卡" -> sysUser.setRole(1);case "VIP季卡" -> sysUser.setRole(2);case "VIP年卡" -> sysUser.setRole(3);}sysUserService.updateById(sysUser);log.info("VIP购买成功:用户ID={}, 套餐={}, 时长={}天, 结束时间={}", userId, vipPlan.getPlanName(), vipPlan.getDurationDays(), vipOrder.getVipEndTime());} else {throw new BusinessException("保存VIP订单失败",458);}// 5. 这里可以添加其他VIP相关的业务逻辑// 比如:发送VIP开通通知、更新用户VIP状态等} catch (Exception e) {log.error("处理VIP购买成功逻辑失败:用户ID={}, 金额={}, 错误:{}", userId, amount, e.getMessage(), e);throw new BusinessException("处理VIP购买成功逻辑失败"+e, 478);}}/*** 处理优惠劵购买成功逻辑* @param userId 用户ID* @param amount 充值金额* @param outTradeNo 商户订单号*///TODO 库存扣减未做public void handleCouponsSuccess(Long userId, BigDecimal amount, String outTradeNo) {try {// 1. 根据支付金额查找对应的VIP套餐LambdaQueryWrapper<Coupons> planQuery = new LambdaQueryWrapper<>();planQuery.eq(Coupons::getDiscount, amount).eq(Coupons::getStatus, 1).orderByAsc(Coupons::getId).last("LIMIT 1");Coupons coupons = couponsService.getOne(planQuery);if (coupons == null) {log.warn("未找到匹配的优惠劵:金额={}", amount);// 如果没有找到精确匹配的套餐,可以选择默认套餐或抛出异常throw new BusinessException("未找到匹配的优惠劵",456);}//利用lua脚本实现判断是否有资格购买优惠劵String luaScript ="";// 2. 创建优惠劵订单记录applicationContext.getBean(PayOrderServiceImpl.class).isEligibleForCoupons(userId, amount, outTradeNo, coupons);}catch (Exception e){throw new BusinessException("处理优惠劵购买成功逻辑失败"+e, 479);}}
}