首先我们先了解一下支付宝沙箱支付是什么?
沙箱环境是支付宝开放平台为开发者提供的与生产环境完全隔离的联调测试环境,开发者在沙箱环境中完成的接口调用不会对生产环境中的数据造成任何影响。
沙箱为开放的产品提供 有限功能范围 的支持,可以覆盖产品的绝大部分核心链路和对接逻辑,便于开发者快速学习、尝试、开发和调试。
沙箱环境会自动完成或忽略一些场景的业务门槛,例如:开发者无需等待产品开通,即可直接在沙箱环境调用接口,使得开发集成工作可以与业务流程并行,从而提高项目整体的交付效率。
一、准备支付宝沙箱环境
官方地址:快速接入 - 支付宝文档中心 (alipay.com)
1、找到后端开发
2、点击进入沙箱控制台
支付宝扫码登录,我的是这样的,有可能你们刚开始不是这样的
后面会用到APPID和支付宝网关地址
(1)、下载密钥生成器
1、点击自定义密钥
2、这里需要我们的密钥生成工具 密钥工具下载 - 支付宝文档中心 (alipay.com)
3、点击你的电脑需要的版本下载
(2)、生成密钥
1、打开支付宝密钥生成工具,点击生成密钥,生成成功之后,会在你本地保存一份密钥文件
2、复制应用公钥,粘贴到(看第2步的那张图片),这里面的应用私钥待会会用到。
3、生成成功,保存支付宝私钥,到此支付宝沙箱环境配置完成。
二、整合支付宝沙箱支付
1、创建springboot项目
现在开始先准备好一个springboot项目,这里就不做演示了,我这里已经提前创建好了
2、pom导入支付宝沙箱支付依赖
<!-- 支付宝支付的依赖 -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.16.2.ALL</version>
</dependency>
因为我使用的是前后端分离项目所以还需要导入thymeleaf,后续有用
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
3、准备代码
(1)、创建支付controller类
package com.xhj.order.controller;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.lyx.common.base.result.R;
import com.lyx.common.mp.utils.PageUtils;
import com.xhj.order.entity.req.OrderListPageReq;
import com.xhj.order.entity.req.OrderPaymentReq;
import com.xhj.order.entity.vo.PayVo;
import com.xhj.order.service.OrderService;
import com.xhj.order.service.PayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @Author: xhj
* @Date: 2023/09/26/14:27
* @Description: 支付
*/
@Controller
@RequestMapping("/pay")
public class PayController {
@Autowired
private PayService payService;
//必须加ResponseBody注解,否则spring会寻找thymeleaf页面
// 这些@RequestParam里面的数据都是我自定义的,你们可对其进行修改
@ResponseBody
@RequestMapping("/pay/alipay")
public String alipay(HttpSession session, Model model,
@RequestParam("dona_money") float dona_money,
@RequestParam("orderId") Long orderId,
@RequestParam("orderSn") String orderSn,
@RequestParam("memberId") Long memberId) throws Exception {
//调用封装好的方法(给支付宝接口发送请求)
return payService.sendRequestToAlipay(memberId,orderId,orderSn,dona_money,"xhj");
}
@RequestMapping("/returnUrl")
public String returnUrlMethod(@RequestParam(value = "orderId",required = false) Long orderId,
@RequestParam(value = "memberId",required = false) Long memberId,
HttpServletRequest request, HttpSession session,
Model model) throws Exception {
System.out.println("=================================支付成功回调=====================================");
boolean pay = payService.returnUrlMethod(orderId,memberId,request);
if (pay){
model.addAttribute("orderId",orderId);
return "index";
}else{
return "shibai";
}
}
}
(2)、创建service和实现类
import com.alipay.api.AlipayApiException;
import com.baomidou.mybatisplus.extension.service.IService;
import com.lyx.common.mp.utils.PageUtils;
import com.xhj.order.entity.OrderAddr;
import com.xhj.order.entity.Pay;
import com.xhj.order.entity.req.OrderListPageReq;
import com.xhj.order.entity.vo.PayVo;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
/**
* @Author: xhj
* @Date: 2023/09/26/18:10
* @Description:
*/
public interface PayService extends IService<Pay> {
/*
memberId:用户id
orderId:订单id
orderSn:订单号
dona_money:订单金额
xhj:订单名称
*/
//支付宝官方提供的接口
String sendRequestToAlipay(Long memberId, Long orderId, String orderSn, float dona_money, String xhj) throws Exception;
}
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lyx.common.mp.utils.PageUtils;
import com.xhj.order.entity.Order;
import com.xhj.order.entity.Pay;
import com.xhj.order.entity.req.OrderListPageReq;
import com.xhj.order.entity.req.OrderPaymentReq;
import com.xhj.order.entity.vo.PayVo;
import com.xhj.order.feign.GoodsFeignService;
import com.xhj.order.feign.MemberFeignService;
import com.xhj.order.mapper.PayMapper;
import com.xhj.order.service.OrderAddrService;
import com.xhj.order.service.OrderService;
import com.xhj.order.service.PayService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author xhj
* @description 针对表【oms_pay】的数据库操作Service实现
* @createDate 2023-09-26 14:39:50
*/
@Service
public class PayServiceImpl extends ServiceImpl<PayMapper, Pay> implements PayService {
@Autowired
private OrderService orderService;
@Autowired
private GoodsFeignService goodsFeignService;
private final String APP_ID = "APP_ID";
//应用私钥
private final String APP_PRIVATE_KEY = "应用私钥";
private final String CHARSET = "UTF-8";
// 支付宝公钥
private final String ALIPAY_PUBLIC_KEY = "支付宝公钥" ;
//这是沙箱接口路径
private final String GATEWAY_URL ="支付宝网关";
private final String FORMAT = "JSON";
//签名方式
private final String SIGN_TYPE = "RSA2";
//支付宝异步通知路径,付款完毕后会异步调用本项目的方法,必须为公网地址
private final String NOTIFY_URL = "http://127.0.0.1:20000/api/cloud-order/pay/returnUrl";
//支付宝同步通知路径,也就是当付款完毕后跳转本项目的页面,可以不是公网地址
private final String RETURN_URL = "http://127.0.0.1:20000/api/cloud-order/pay/returnUrl";
/**
* 支付宝支付
* @param memberId
* @param orderId
* @param orderSn
* @param dona_money
* @param subject
* @return
* @throws AlipayApiException
*/
@Override
public String sendRequestToAlipay(Long memberId, Long orderId, String orderSn, float dona_money, String subject) throws Exception {
//获得初始化的AlipayClient
AlipayClient alipayClient = new DefaultAlipayClient(GATEWAY_URL,APP_ID,APP_PRIVATE_KEY,FORMAT,CHARSET,ALIPAY_PUBLIC_KEY,SIGN_TYPE);
//设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(RETURN_URL+"?orderId="+orderId+"&memberId="+memberId);
// alipayRequest.setNotifyUrl(NOTIFY_URL+"?orderId="+orderId+"&memberId="+memberId);
//商品描述(可空)
String body="";
alipayRequest.setBizContent("{\"out_trade_no\":\"" + orderSn + "\","
+ "\"total_amount\":\"" + dona_money + "\","
+ "\"subject\":\"" + subject + "\","
+ "\"body\":\"" + body + "\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//请求
String result = alipayClient.pageExecute(alipayRequest).getBody();
return result;
}
/**
* 支付宝成功回调
* @param orderId
* @param memberId
* @param request
* @return
*/
@Override
public boolean returnUrlMethod(Long orderId, Long memberId, HttpServletRequest request) throws Exception {
// 获取支付宝GET过来反馈信息
Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 乱码解决,这段代码在出现乱码时使用
valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
System.out.println(params);//查看参数都有哪些
// 这是我自己放的数据,所以要删除,否则支付宝会校验失败
params.remove("orderId");
params.remove("memberId");
//验证签名(支付宝公钥)
boolean signVerified = AlipaySignature.rsaCheckV1(params, ALIPAY_PUBLIC_KEY, CHARSET, SIGN_TYPE); // 调用SDK验证签名
//验证签名通过
if(signVerified){
// 商户订单号
String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");
// 支付宝交易流水号
String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");
// 付款金额
float money = Float.parseFloat(new String(request.getParameter("total_amount").getBytes("ISO-8859-1"), "UTF-8"));
System.out.println("商户订单号="+out_trade_no);
System.out.println("支付宝交易号="+trade_no);
System.out.println("付款金额="+money);
System.out.println("用户="+memberId);
//在这里编写自己的业务代码(对数据库的操作)
// 支付成功,修改订单状态,添加支付信息
//成功
return true;
}else{
//失败
return false;
}
}
}
(3)、在项目的resources创建文件夹templates,然后在templates里面创建index.html页面,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>请稍后...</h1>
<input type="hidden" th:value="${orderId}" id="orderId"/>
<script>
window.location.href = "http://127.0.0.1:8080/OrderDetails?orderId="+ [[${orderId}]]
//Js 获取hidden的隐藏域
var orderId=document.getElementById("orderId").val();
console.log(orderId)
</script>
</body>
</html>
注意:因为我使用的是前后端分离项目,支付回调之后的调用,当时我脑子抽了,使用的是thymeleaf跳转页面
所以要在项目的配置文件中application.yml加入
spring:
# 模板引擎
thymeleaf:
mode: HTML5
encoding: utf-8
# 禁用缓存
cache: false
支付宝沙箱支付整合完毕
测试:必须要使用支付宝沙箱版支付,可以下载,也可以使用账号支付
支付成功回调,中间经过了index之后跳转到前端页面
另外一种方式
修改controller的支付成功回调接口,支付成功之后直接重定向return "redirect:http://127.0.0.1:8080/OrderDetails?orderId="+orderId;
@RequestMapping("/returnUrl")
public String returnUrlMethod(@RequestParam(value = "orderId",required = false) Long orderId,
@RequestParam(value = "memberId",required = false) Long memberId,
HttpServletRequest request, HttpSession session,
Model model) throws Exception {
System.out.println("=================================支付成功回调=====================================");
boolean pay = payService.returnUrlMethod(orderId,memberId,request);
if (pay){
return "redirect:http://127.0.0.1:8080/OrderDetails?orderId="+orderId;
}else{
return "shibai";
}
三、支付宝沙箱支付退款
1、在controller添加
/**
* 订单取消退款
* @return
*/
@RequestMapping("/refund")
@ResponseBody
public R refund(String bizPayNo){
try{
payService.refund(bizPayNo,null);
return R.ok();
}catch (Exception e){
e.printStackTrace();
return R.failed();
}
}
2、service层
/**
* 订单退款
* @param bizPayNo
*/
boolean refund(String bizPayNo,Float money);
3、实现层
/**
* 订单取消退款
* @param bizPayNo
* @return
*/
@Override
public boolean refund(String bizPayNo,Float money) {
AlipayClient alipayClient = new DefaultAlipayClient(GATEWAY_URL,
APP_ID,
APP_PRIVATE_KEY,
FORMAT,
CHARSET,
ALIPAY_PUBLIC_KEY,
SIGN_TYPE);
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
// 根据bizPayNo查订单金额 这是我自己的业务代码
//LambdaQueryWrapper<Pay> wrapper = Wrappers.lambdaQuery();
/wrapper.eq(Pay::getBizPayNo,bizPayNo);
//Pay pay = getOne(wrapper);
// bizPayNo订单号 money 价格
JSONObject bizContent = new JSONObject();
bizContent.put("trade_no", bizPayNo);
bizContent.put("refund_amount", money!=null?money:pay.getPayAmount());
bizContent.put("out_request_no", "HZ01RF001");
bizContent.put("body", money!=null?"支付超时":"订单取消");
// // 返回参数选项,按需传入
// JSONArray queryOptions = new JSONArray();
// queryOptions.add("refund_detail_item_list");
// bizContent.put("query_options", queryOptions);
request.setBizContent(bizContent.toString());
try{
AlipayTradeRefundResponse response = alipayClient.execute(request);
if(response.isSuccess()){
System.out.println("退款成功");
return true;
} else {
System.out.println("退款失败");
return false;
}
}catch (Exception e){
e.printStackTrace();
return false;
}
}
退款功能需要前端携带订单号调用/pay/refund,我的代码是不需要前端传入money,是经过后端调用去数据库里面查。
退款只需要两个参数,bizPayNo 订单号 ,money 价格
测试:支付退款
以上内容仅供参考