一 什么是沙箱:
沙箱环境是支付宝开放平台为开发者提供的安全低门槛的测试环境
支付宝正式和沙箱环境的区别 :
AI:
-
从沙箱到正式环境:
-
当应用程序开发完成后,需要将应用程序从沙箱环境迁移到正式环境。
-
这通常涉及到更新应用程序中的配置文件,更换正式的 AppID 和密钥等凭证。
二 注册使用
网址:登录 - 支付宝 点击 进行注册
查看信息:appid
点击查看 应用密钥与支付宝公钥 :appPrivateKey 与 publiceKey
记录一下卖家信息:
三 实现
支付实现的步骤:1.导入依赖 2.配置参数 3.调用方法(API调用) 4.处理响应或异常
具体实现可查看官网文档: 小程序文档 - 支付宝文档中心
小程序文档 - 支付宝文档中心
简易版概述小程序文档 - 支付宝文档中心
小程序文档 - 支付宝文档中心
查看api使用规范 根据需求选择对应的接口方法
比如下面我们要实现的电脑网站的支付功能
对应的就是 Factory.Payment.Page.pay()
Easy 版
有拦截器的 注意放开路径
3.1 导入项目依赖
<!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-easysdk -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-easysdk</artifactId>
<version>2.2.0</version>
</dependency>
3.2 编写配置信息:
其中关于//@PostConstruct 是一个 Java 注解,用于标记需要在依赖注入完成后执行的方法。它表示该方法应该在对象创建和属性注入完毕后由容器自动调用,以完成初始化工作。
@Component
@ConfigurationProperties(prefix = "alipay")
@Data
public class AlipayConfig {
// 应用Id
private String appId;
// 应用私钥
private String appPrivateKey;
// 支付宝公钥
private String publiceKey;
// 回调接口路径
private String notifyUrl;
// 支付宝网关地址
private String gatewayHost;
@PostConstruct
//@PostConstruct 是一个 Java 注解,用于标记需要在依赖注入完成后执行的方法。
// 它表示该方法应该在对象创建和属性注入完毕后由容器自动调用,以完成初始化工作。
// 此注解的方法无参数,返回类型可为空或其他类型。主要用于确保对象完全初始化。
public void init(){
Config config = new Config();
// 基础配置
config.protocol = "https";
config.gatewayHost = this.gatewayHost;// 支付宝网关地址
config.signType = "RSA2";
// 业务配置
config.appId = this.appId;
config.merchantPrivateKey = this.appPrivateKey;
config.alipayPublicKey = this.publiceKey;
config.notifyUrl = this.notifyUrl;
// 将配置信息, 添加到相应的工厂类
Factory.setOptions(config);
System.out.println("支付宝初始化配置完成");
}
}
application.properties
其中 appid appPrivateKey publiceKey 在开发者平台获取
gatewayHost 大家都一样 无需更改
notifyUrl 是内网穿透这里使用不到 现在随便写都可以
alipay.appid=9021000139682312
alipay.appPrivateKey=MIIEvwIBADANBgkqhkiG9w0BAQEFAAS...
alipay.publiceKey=MIIBIjANBgkqhkiG9w0BAQEFAAOC.......
alipay.notifyUrl=http://xxxxxxx/api/alipay/notify
alipay.gatewayHost=openapi-sandbox.dl.alipaydev.com
3.3 调用支付方法
我这里直接在cotroller层编写
@RestController
@RequestMapping("/api/alipay")
public class PayController {
/**
* 订单支付接口, 核心是调用支付宝的 Factory.Payment.Page().pay() 方法
* @param subject 支付对象信息
* @param outTradeNo 订单号
* @param totalAmount 订单金额
* @param returnUrl 支付成功以后返回的页面地址
* @return
*/
@GetMapping("/pay")
public void pay(String subject, String outTradeNo, String totalAmount, String returnUrl, HttpServletResponse httpResponse) throws Exception {
// 使用支付宝支付页面接口进行支付
AlipayTradePagePayResponse response = Factory.Payment.Page().pay(subject, outTradeNo, totalAmount, returnUrl);
System.out.println(response);
// response.getBody();
// 设置HTTP响应内容类型为HTML,编码为UTF-8
httpResponse.setContentType("text/html;charset=utf-8");
// 向HTTP响应写入支付宝支付页面接口返回的响应体
httpResponse.getWriter().write(response.getBody());
// 刷新HTTP响应输出流,确保数据立即发送到客户端
httpResponse.getWriter().flush();
// 关闭HTTP响应输出流,释放资源
httpResponse.getWriter().close();
}
}
Java
可以进行测试后端了!!!!!!!!
可以进行测试后端了!!!!!!!!
可以进行测试后端了!!!!!!!!
http://localhost:8080/api/alipay/pay?subject=%E7%81%AB%E8%BD%A6%E7%A5%A8%E6%94%AF%E4%BB%98&outTradeNo=63172637&totalAmount=16.8&returnUrl=http%3A%2F%2Flocalhost%3A8080%2Fpayok.html%3ForderId%3D312323
3.4 回调方法 配合内网穿透使用
回调就是支付宝在进行扣款操作之后 调用我们的后端 告诉我们支付结果
回调方法如下:
回调中有个验证操作来源于:调用官方给我们提供的方法来验证数据的真实性。
@PostMapping("/notify")
public String notify(@RequestParam Map parameterMap) throws Exception {
String tradeStatus = parameterMap.getOrDefault("trade_status","").toString();
if (tradeStatus.trim().equals("TRADE_SUCCESS")) {
// 验证请求的有效性
if (Factory.Payment.Common().verifyNotify(parameterMap)) {
System.out.println("通过支付宝的验证");
System.out.println("订单id:" + parameterMap.get("out_trade_no"));
}else {
System.out.println("支付验证不通过");
}
}
return "success";
}
需要先开启内网穿透
服务器为localhost:8080 支付宝无法调用:
注意:如果是部署云服务器就不需要内网穿透了 只不过需要我我们对回调的参数进行相对应的修改!!!!
3.5 内网穿透工具
1.路由侠-局域网变公网
设置 信息 主机号
查看内网映射:
测试: 说明映射是没有问题的 成功调用了后端
修改 alipay.notifyUrl 的 配置信息
alipay.notifyUrl=http://xxxk.w1.luyouxia.net/api/alipay/notify
再次进行支付测试:s
这里加上vue进行测试:
前端 vue:
3.6 前端
<template>
<h2>支付</h2>
<el-form :inline="true" :model="form" class="demo-form-inline">
<el-form-item label="支付对象信息">
<el-input v-model="form.subject" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="订单号">
<el-input v-model="form.outTradeNo" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="订单金额">
<el-input-number v-model="form.totalAmount" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="支付成功以后返回的页面地址">
<el-input v-model="form.returnUrl" placeholder="请输入" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">提交</el-button>
</el-form-item>
</el-form>
<br>
<h2>退款</h2>
<el-form :inline="true" :model="form2" class="demo-form-inline">
<el-form-item label="退款单号">
<el-input v-model="form2.outTradeNo" placeholder="请输入" clearable />
</el-form-item>
<el-form-item label="退款金额">
<el-input-number v-model="form2.refundAmount" placeholder="请输入" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit2">提交</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import dayjs from 'dayjs';
import router from '@/router';
import { ElNotification } from 'element-plus'
import { alipayApi } from '@/api';
const form = ref({
subject: '小米su8',
outTradeNo: dayjs().format('YYYYMMDDHHmmss'),
totalAmount: 188888,
returnUrl: 'http://localhost:5000/paysuccess'
})
const form2 = ref({
outTradeNo: '20220422164101',
refundAmount: 18888,
})
const onSubmit = () => {
let returnUrl = encodeURIComponent(form.value.returnUrl)
window.location.href = "http://localhost:8080/api/alipay/pay?subject=" + `${form.value.subject}` + "&outTradeNo=" + `${form.value.outTradeNo}`
+ "&totalAmount=" + `${form.value.totalAmount}` + "&returnUrl=" + `${returnUrl}`
}
const onSubmit2 = () => {
alipayApi.refund.call({
outTradeNo: form2.value.outTradeNo,
refundAmount: form2.value.refundAmount
}).then(_res => {
ElNotification({
title: '提示',
message: '退款成功',
type: 'success',
})
})
}
</script>
测试结果
一方面 : 前端返回我们设置的 returnUrl 界面
另一方: 后端执行 配置的 alipay.notifyUrl=http://xxxk.w1.luyouxia.net/api/alipay/notify
成功
3.7 退款
调用 refund 交易退款方法
@GetMapping("/refund")
public String refund( String outTradeNo,Float refundAmount) {
try {
com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse response = Factory.Payment.Common().refund(outTradeNo, String.valueOf(refundAmount));
System.out.println(response);
if (response.msg.equals("Success")){
return "退款成功";
}else {
throw new BizException(777, "退款失败");
}
}catch (Exception e){
e.printStackTrace();
throw new BizException(777, "退款失败");
}
}
通用版
小程序文档 - 支付宝文档中心
通用版
导入依赖
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.34.0.ALL</version>
</dependency>
配置
@Component
@ConfigurationProperties(prefix = "alipay2")
@Data
public class Alipay2Config extends com.alipay.api.AlipayConfig {
// 网关地址
private String serverUrl;
// 应用Id
private String appId;
// 应用私钥
private String appPrivateKey;
//请求格式
private String format;
//字符集编码
private String charset;
//签名类型
private String signType;
// 支付宝公钥
private String publiceKey;
// 回调接口路径
private String notifyUrl;
}
支付
@GetMapping("/pay2")
public void pay2(String subject, String outTradeNo, String totalAmount, String returnUrl, HttpServletResponse httpResponse) throws Exception {
AlipayClient alipayClient = new DefaultAlipayClient(alipay2Config.getServerUrl(), alipay2Config.getAppId(),
alipay2Config.getAppPrivateKey(), alipay2Config.getFormat(), alipay2Config.getCharset(), alipay2Config.getAlipayPublicKey(), alipay2Config.getSignType());
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setNotifyUrl(alipay2Config.getNotifyUrl());
request.setReturnUrl(returnUrl);
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", outTradeNo);
bizContent.put("total_amount", totalAmount);
bizContent.put("subject", subject);
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
request.setBizContent(bizContent.toString());
String form = "";
try {
// 调用SDK生成表单
form = alipayClient.pageExecute(request).getBody();
} catch (AlipayApiException e) {
e.printStackTrace();
}
httpResponse.setContentType("text/html;charset=UTF-8");
// 直接将完整的表单html输出到页面
httpResponse.getWriter().write(form);
httpResponse.getWriter().flush();
httpResponse.getWriter().close();
}
退款
/**
* 退款接口
*
* @return
*/
@GetMapping("/refund2")
public String refund2 (String outTradeNo, Float refundAmount){
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi-sandbox.dl.alipaydev.com/gateway.do",
alipayConfig.getAppId(),
alipayConfig.getAppPrivateKey(),
"JSON",
"utf-8",
alipayConfig.getPubliceKey(),
"RSA2");
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
// 获取获取对应订单信息
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", outTradeNo);
bizContent.put("refund_amount", refundAmount);
bizContent.put("out_request_no", "HZ01RF002"); //退款标识,用于查询状态是操作
request.setBizContent(bizContent.toString());
// 使用支付宝支付页面接口进行支付
try {
AlipayTradeRefundResponse response = alipayClient.execute(request);
System.out.println(response);
if (response.isSuccess()) {
System.out.println("退款成功");
return "退款成功";
} else {
System.out.println("退款失败");
throw new BizException(777, "退款失败");
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("退款失败");
throw new BizException(777, "退款失败");
}
}