支付宝沙箱支付
支付宝沙箱支付
支付宝沙箱(Alipay Sandbox)是支付宝提供的一个模拟环境,用于开发者在不影响真实交易的情况下进行支付宝相关功能的测试和调试。在软件开发中,沙箱环境通常指的是一个隔离的测试环境,可以模拟真实环境的行为,但不会对真实数据产生影响。
支付宝沙箱为开发者提供了一套模拟的支付流程和接口,使开发者能够在沙箱环境中进行支付测试,验证其支付功能是否正常工作,而无需使用真实的资金进行交易。这有助于开发者在应用上线之前进行充分的测试,确保支付流程的稳定性和安全性。
使用支付宝沙箱,开发者可以模拟各种支付场景,包括扫码支付、APP支付、H5支付等。此外,支付宝沙箱还提供了一些调试工具和接口文档,帮助开发者更方便地进行测试和开发。
使用支付宝沙箱环境可以帮助开发者在开发和测试阶段及时发现和解决问题,确保支付功能的稳定和安全。一旦开发者在支付宝沙箱环境中完成了测试和调试,就可以将代码和配置切换到真实的支付宝环境中进行生产部署。
支付宝开放平台 (alipay.com)
准备工作
引入依赖
<!--支付宝-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.38.133.ALL</version>
</dependency>
<!--其他-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>
配置文件
alipay:
appId: # 应用id
appPrivateKey: # 应用私钥
alipayPublicKey: # 支付宝公钥
notifyUrl: # 异步通知地址
gateway: # 支付宝网关
server:
port: 8088
异步通知地址需要能在公网访问:内外穿透教程
代码编写
配置类
AliPayConfig 支付宝支付配置类 方便我们获取配置参数
@Data
@Component
@ConfigurationProperties(prefix = "alipay")
public class AliPayConfig {
private String appId;
private String appPrivateKey;
private String alipayPublicKey;
private String notifyUrl;
private String gateway;
}
AlipayClientConfig 支付宝客户端配置类
@Configuration
public class AlipayClientConfig {
@Autowired
private AliPayConfig aliPayConfig;
@Bean
public AlipayClient getAlipayClient(){
AlipayClient alipayClient = new DefaultAlipayClient(
aliPayConfig.getGateway(),
aliPayConfig.getAppId(),
aliPayConfig.getAppPrivateKey(),
AlipayConstants.FORMAT_JSON,
AlipayConstants.CHARSET_UTF8,
aliPayConfig.getAlipayPublicKey(),
AlipayConstants.SIGN_TYPE_RSA2);
return alipayClient;
}
}
controller
AlipayController
@RestController
@RequestMapping("/alipay")
public class AlipayController {
@Autowired
private AlipayService alipayService;
/**
* 支付接口
* @param order 订单
* @return
*/
@PostMapping("/pay")
public R pay(@RequestBody Order order){
// 使用时间当订单号
order.setOrderId(String.valueOf(System.currentTimeMillis()));
return alipayService.pay(order);
}
/**
* 订单状态通知 异步通知
* @param request
* @return
* @throws AlipayApiException
*/
@GetMapping("/notify")
public R payNotify(HttpServletRequest request) throws AlipayApiException {
return alipayService.payNotify(request);
}
}
Service
AlipayService
public interface AlipayService {
/**
* 支付接口
* @param order
* @return
*/
R pay(Order order);
/**
* 异步通知
* @param request
* @return
*/
R payNotify(HttpServletRequest request);
}
AlipayServiceImpl
@Service
public class AlipayServiceImpl implements AlipayService {
@Value("${alipay.alipayPublicKey}")
private String publicKey;
@Value("${alipay.notifyUrl}")
private String notifyUrl;
@Autowired
private AlipayClient alipayClient;
@Override
public R pay(Order order){
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
// 异步通知的地址
request.setNotifyUrl(notifyUrl);
Map<String,String> map = new HashMap<>();
map.put("out_trade_no",order.getOrderId());
map.put("total_amount",order.getPrice());
map.put("subject",order.getSubject());
map.put("body",order.getBody());
map.put("product_code","FAST_INSTANT_TRADE_PAY");
// 设置业务参数
request.setBizContent(JSONObject.toJSONString(map));
// 发起支付请求
// 发起支付请求
AlipayTradePagePayResponse response = null;
try {
response = alipayClient.pageExecute(request);
} catch (AlipayApiException e) {
throw new RuntimeException(e);
}
// 获取响应结果
if (response.isSuccess()) {
System.out.println("调用成功");
System.out.println("支付宝支付链接:" + response.getBody());
return R.ok(response.getBody());
} else {
System.out.println("调用失败");
System.out.println("错误信息:" + response.getMsg());
return R.fail("调用失败",40010);
}
}
@Override
public R payNotify(HttpServletRequest request) {
Map<String, String[]> paramMap = request.getParameterMap();
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
String name = entry.getKey();
String[] values = entry.getValue();
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());
}
// 调用SDK验证签名
boolean verifyResult = false;
try {
verifyResult = AlipaySignature.rsaCheckV1(params, publicKey, AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2);
} catch (AlipayApiException e) {
throw new RuntimeException(e);
}
if (verifyResult) {
// 验证成功,处理业务逻辑,更新订单状态等
// 注意:请防止重复处理,可以通过记录支付状态或者订单号来判断是否已经处理过
System.out.println("支付宝异步通知验证成功");
// 返回给支付宝成功处理的响应
return R.ok();
} else {
// 验证失败,不处理业务逻辑
System.out.println("支付宝异步通知验证失败");
// 返回给支付宝失败处理的响应
return R.fail("支付失败",40010);
}
}
}
工具类
Order 订单类
@Data
public class Order {
// 订单id
private String orderId;
// 价格
private String price;
// 商品名称
private String subject;
// 商品描述
private String body;
// 支付场景
/**
* FAST_INSTANT_TRADE_PAY(即时到账):适用于即时交易场景,买家付款后,卖家立即收到款项。
* QUICK_MSECURITY_PAY(手机网页支付):适用于手机网页支付场景。
* FACE_TO_FACE_PAYMENT(当面付):适用于线下面对面付款场景,比如扫码支付。
* APP支付(APP支付场景):适用于在APP内的支付场景。
* WAP支付(手机网站支付场景):适用于手机网站支付场景。
* PRE_AUTH(预授权):适用于预先授权场景,买家授权预先冻结资金,商家在完成业务后调用支付宝解冻资金
*/
private String code;
}
R 响应类
@Data
public class R {
private String data;
private String msg;
private int code;
public R() {
}
public R (String msg, int code) {
this.msg = msg;
this.code = code;
}
public R( String msg, int code,String data) {
this.data = data;
this.msg = msg;
this.code = code;
}
public static R ok(){
return new R("成功",200);
}
public static R ok(String data){
return new R("成功",200,data);
}
public static R fail(String message, int code) {
return new R( message,code);
}
}
测试页面
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>商城</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="box">
<div>
<h1>订单列表</h1>
<ul>
<li v-for="order in orders" :key="order.orderId">
<h3>{{ order.subject }}</h3>
<p>价格: ¥{{ order.price }}</p>
<p>{{ order.body }}</p>
<button v-on:click="pay(order)">购买</button>
<hr>
</li>
</ul>
</div>
<div v-html="paymentFormHtml"></div>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#box',
data: {
orders: [
{
"price": "12148.00",
"subject": "iPhone 15 Pro Max 512GB 原色钛金属",
"body": "iPhone 15 Pro Max 512GB 原色钛金属"
},
{
"price": "4999",
"subject": "xiaomi 14 钛金属",
"body": "xiaomi 14 钛金属 256gb"
}
],
paymentFormHtml: "" // 新增 paymentFormHtml 属性
},
methods: {
pay: function (order) {
axios.post('http://127.0.0.1:8088/alipay/pay', order)
.then(res => {
// 处理支付成功的逻辑
console.log('支付成功', res.data);
this.paymentFormHtml = res.data.data;
// 手动提交表单
// 在延时后提交表单
setTimeout(function () {
document.forms['punchout_form'].submit();
}, 1000); // 1000毫秒,即1秒
})
.catch(error => {
// 处理支付失败或其他错误的逻辑
console.error('支付失败', error);
});
}
}
});
</script>
</body>
</html>