SpringBoot项目接入支付宝v3接口
1、接入准备
首先到支付宝开放平台注册或者登录,选择移动/网页支付,按照页面的指引进行接入(正式生产环境是必须要申请账号、创建应用、配置相关参数等)。如果只是开发测试,可以使用支付宝提供的沙箱环境,下面的所有的内容都是基于沙箱环境的。
打开控制台首页 - 开放平台,选择开发工具里的沙箱,可以查看支付宝分配给你的沙箱环境账号信息,特别是公钥和私钥,或者证书。本次开发,我用的是公钥和私钥的接口加签方式,使用证书也可以,只不过在1.0和2.0版本的接口,公私钥和证书的代码有些许不同,貌似在3.0版本中,代码都一样,只要在AlipayConfig中给相应字段赋值接可以了。
总之,接入准备的步骤,详细看支付宝官方文档,这里就不详细介绍了。
2、开发环境
开发工具:idea + maven
SpringBoot.version=2.7.8
alipay-sdk-java-v3.version=3.1.22.ALL
在pom文件引入支付宝v3版本的依赖:(注意如果SpringBoot的版本过低,会导致OkHttp的版本变低,导致接口调用失败,要么直接强制指定OkHttp的版本为alipay-sdk-java-v3中引用的,如4.9.3,要么升级SpringBoot的版本,2.7.8是没问题的)
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java-v3</artifactId>
</dependency>
3、编写测试代码
3.1 支付宝接口的配置信息
实际项目开发,配置信息放在配置文件中,如果用证书,证书一定要妥善保管,一般放在服务器里。这里直接就写死了,配置信息来自支付宝的沙箱账号信息。
private AlipayConfig getAlipayConfig() {
AlipayConfig alipayConfig = new AlipayConfig();
//alipayConfig.setServerUrl("https://openapi.alipay.com");
alipayConfig.setServerUrl("https://openapi-sandbox.dl.alipaydev.com");
alipayConfig.setAppId("9021xxxxxxxxxxxxx");
alipayConfig.setPrivateKey("MIIEvAIBADANxxxxxxxxxxxxxxx");
alipayConfig.setAlipayPublicKey("MIIBIjANBgxxxxxxxxxxxxxxx");
return alipayConfig;
}
3.2 电脑网站支付
这个就是常见的在电脑浏览器上点击支付,会出现二维码或者登录支付宝账户,扫码或者输入支付密码然后支付完成。详细文档:产品介绍 - 支付宝文档中心
支付宝的文档十分详细,但是初次接入肯定会有点眼花缭乱,下面我挑一些重点的内容来将和我踩过的一些坑。
支付流程:
电脑网站支付的支付接口 alipay.trade.page.pay(统一收单下单并支付页面接口)调用时序图如下:

调用流程如下:
- 商家系统调用 alipay.trade.page.pay(统一收单下单并支付页面接口)向支付宝发起支付请求,支付宝对商家请求参数进行校验,而后重新定向至用户登录页面。
- 用户确认支付后,支付宝通过 get 请求 returnUrl(商户入参传入),返回同步返回参数。
- 交易成功后,支付宝通过 post 请求 notifyUrl(商户入参传入),返回异步通知参数。
- 若由于网络等原因,导致商家系统没有收到异步通知,商家可自行调用 alipay.trade.query(统一收单交易查询接口)查询交易以及支付信息(商家也可以直接调用该查询接口,不需要依赖异步通知)。
注意:
- 由于同步返回的不可靠性,支付结果必须以异步通知或查询接口返回为准,不能依赖同步跳转。
- 商家系统接收到异步通知以后,必须通过验签(验证通知中的 sign 参数)来确保支付通知是由支付宝发送的。详细验签规则可查看 异步通知验签。
- 接收到异步通知并验签通过后,请务必核对通知中的 app_id、out_trade_no、total_amount 等参数值是否与请求中的一致,并根据 trade_status 进行后续业务处理。
- 在支付宝端,partnerId 与 out_trade_no 唯一对应一笔单据,商家端保证不同次支付 out_trade_no 不可重复;若重复,支付宝会关联到原单据,基本信息一致的情况下会以原单据为准进行支付。
调用示例
开放平台提供了支持主流开发语言的 SDK 接入的方式。对于页面跳转类 API,SDK 不会也无法像系统调用类 API 一样自动请求支付宝并获得结果,而是在接受 request 请求对象后,为开发者生成前台页面请求需要的完整 form 表单的 html(包含自动提交脚本),商家直接将这个表单的 String 输出到 http response 中即可。
注意:
- 付款页面生成的付款码每 2 分钟 会自动刷新一次。
- 电脑网站支付后使用 商家分账 完成分账,查询结果需使用 alipay.trade.query(统一收单交易查询接口)
query_options传入trade_settle_info查询分账信息,不能使用 alipay.trade.order.settle.query(交易分账查询接口)查询。
示例代码:
文档中的代码很长,但是最关键的是代码没有展示如何设置return_url和notify_url两个参数,这就有点坑了,return_url是支付完成了跳转回原来系统的地址,notify_url是接收支持成功的异步通知的,我自己试了几次没成功,然后再支付宝社区找答案,于是:

也就是说return_url和notify_url都放在bizContent业务参数中,结果还是不对。。。。。。,后来找技术支持,说还是要和bizContent同级,也就是放在bizParams中,这真是,说虽然是V3接口,但是还是走的V2的逻辑,好吧。关键的代码如下:
ApiClient defaultClient = Configuration.getDefaultApiClient();
// 初始化alipay参数(全局设置一次)
defaultClient.setAlipayConfig(getAlipayConfig());
GenericExecuteApi api = new GenericExecuteApi();
// 构造请求参数以调用接口
Map<String, Object> bizParams = new HashMap<>();
Map<String, Object> bizContent = new HashMap<>();
// 设置商户订单号
bizContent.put("out_trade_no", String.valueOf(System.currentTimeMillis()));
// 设置订单总金额
bizContent.put("total_amount", "6.88");
// 设置订单标题
bizContent.put("subject", "测试电脑网站支付");
// 设置产品码
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
// 设置订单附加信息
bizContent.put("body", "谢谢谢谢");
// 设置PC扫码支付的方式
bizContent.put("qr_pay_mode", "2");
bizParams.put("biz_content", bizContent);
//return_url 必须是 http 或 https 开头的完整的 url 地址。
//return_url 地址后不可带自定义参数。
//设置 return_url 时不要进行转义、urlencode 等数据处理。
//当面付和APP支付不支持 return_url 参数,即使设置了也没有任何效果。
//同步通知参数只可参考,不能作为判断是否支付成功的依据。
bizParams.put("return_url", "https://docs.open.alipay.com");
bizParams.put("notify_url", "http://xxxxx/pay/pcNotify");
try {
System.out.println(JSON.serialize(bizParams));
// 如果是第三方代调用模式,请设置app_auth_token(应用授权令牌)
String pageRedirectionData = api.pageExecute("alipay.trade.page.pay", "POST", bizParams);
// 如果需要返回GET请求,请使用
// String pageRedirectionData = api.pageExecute("alipay.trade.page.pay", "GET", bizParams);
System.out.println(pageRedirectionData);
return pageRedirectionData;
} catch (ApiException e) {
System.out.println("调用失败");
}
电脑网站支付就可以了。
3.3 APP支付
这个是最常见的了,在商家APP中集成支付宝SDK,然后支付。产品介绍 - 支付宝文档中心,后端调用app支付接口,返回订单的加密信息给前端,然后前端调用支付接口,完成支付。
交互流程:
以下对重点步骤做简要说明:
- 第 1 步用户在商户 App 客户端/小程序中购买商品下单。
- 第 2 步商户订单信息由商户 App 客户端/小程序发送到服务端。
- 第 3 步商家服务端调用 alipay.trade.app.pay(app支付接口2.0接口)通过支付宝服务端 SDK 获取 orderStr(orderStr 中包含了订单信息和签名)。
- 第 4 步商家将 orderStr 发送给商户 App 客户端/小程序。
- 第 5 步商家在客户端/小程序发起请求,将 orderStr 发送给支付宝。
- 第 6 步进行支付预下单:支付宝客户端将会按照商家客户端提供的请求参数进行支付预下单。正常场景下,会唤起支付宝收银台等待用户核身;异常场景下,会返回异常信息。
- 第 11 步返回商家 App/小程序:用户在支付宝 App 完成支付后,会跳转回商家页面,并返回最终的支付结果(即同步通知),可查看 同步通知说明。
- 第 13 步支付结果异步通知,支付宝会根据步骤3 传入的异步通知地址 notify_url,发送异步通知,可查看 异步通知说明。
除了正向支付流程外,支付宝也提供交易查询、关闭、退款、退款查询以及对账等配套 API。
示例代码:
ApiClient defaultClient = Configuration.getDefaultApiClient();
// 初始化alipay参数(全局设置一次)
defaultClient.setAlipayConfig(getAlipayConfig());
GenericExecuteApi api = new GenericExecuteApi();
// 构造请求参数以调用接口
Map<String, Object> bizParams = new HashMap<>();
Map<String, Object> bizContent = new HashMap<>();
// 设置商户订单号
bizContent.put("out_trade_no", "70501111111S001111119");
// 设置订单总金额
bizContent.put("total_amount", "9.00");
// 设置订单标题
bizContent.put("subject", "大乐透");
// 设置产品码
bizContent.put("product_code", "QUICK_MSECURITY_PAY");
// 设置订单附加信息
bizContent.put("body", "Iphone6 16G");
// 设置订单绝对超时时间
bizContent.put("time_expire", "2024-12-31 10:05:00");
// 设置建议使用time_expire字段
bizContent.put("timeout_express", "90m");
bizParams.put("biz_content", bizContent);
// 同步回调指定的页面 app不需要
// bizParams.put("return_url", "https://docs.open.alipay.com");
bizParams.put("notify_url", "http://xxxxx/pay/appNotify");
try {
String orderStr = api.sdkExecute("alipay.trade.app.pay", bizParams);
System.out.println(orderStr);
return orderStr;
} catch (ApiException e) {
System.out.println("调用失败");
}
3.4 手机网站支付
产品介绍 - 支付宝文档中心,手机网站支付是指商家在移动端网页展示商品或服务,用户在商家页面确认使用支付宝支付后,浏览器自动跳转支付宝 App 或支付宝网页完成付款的支付产品。该产品在签约完成后,需要技术集成方可使用。代码和电脑网站支付差不多。
建议手机网站支付转Native支付,也就是唤起支付宝APP支付,而不是在H5页面进行支付。手机网站支付转Native支付(推荐) - 支付宝文档中心
流程图

对比总结
手机网站支付与手机网站转 Native 支付的主要区别为:
- 如果用户手机安装了支付宝客户端,手机网站转 Native 支付方式将跳转到支付宝客户端中进行订单支付,用户体验和支付成功率均优于手机网站支付方式。除此之外,还能使用手机网站支付没有提供的指纹支付、手环支付、手表支付、免密支付等功能。
- 如果用户手机没有安装支付宝客户端,将在 SDK 提供的 Web-view 中打开 H5 页面进行支付。即便如此,由于 SDK 与服务端的交互携带账号信息,仍比不携带任何账号信息的普通手机网站支付体验更好。
如何实现手机网站转Native支付
要实现上述功能需接入支付宝提供的 SDK。接入过程十分简单,可以以上述 Demo 为参考,该 Demo 程序只有一个功能:创建一个 Web-view,在 Web-view 中拦截每个 URL,然后调用 SDK 提供的接口检查该 URL 是否是有效的支付宝订单支付 URL,如果是则将该 URL 传给 SDK 提供的支付接口进行支付。
3.5 退款接口
退款接口没啥好说的,主要是区分部分退款和全额退款交易状态的不同,参数传递的些许差异。统一收单交易退款接口 - 支付宝文档中心。先上代码:
@GetMapping ("/refundPay")
public String refundPay(String orderNo,String amount) throws ApiException {
ApiClient defaultClient = Configuration.getDefaultApiClient();
// 初始化alipay参数(全局设置一次)
defaultClient.setAlipayConfig(getAlipayConfig());
// 构造请求参数以调用接口
AlipayTradeApi api = new AlipayTradeApi();
AlipayTradeRefundModel data = new AlipayTradeRefundModel();
// 部分退款时,outRequestNo必传,同一笔交易多次退款需要保证唯一
data.outRequestNo(String.valueOf(System.currentTimeMillis()));
data.setOutTradeNo(orderNo);
data.setRefundAmount(amount);
data.setRefundReason("测试退款");
// 第三方代调用模式下请设置app_auth_token
CustomizedParams params = new CustomizedParams();
params.setAppAuthToken("<-- 请填写应用授权令牌 -->");
try {
AlipayTradeRefundResponseModel response = api.refund(data);
//{"buyer_logon_id":"rpv***@sandbox.com","buyer_user_id":"2088722013720112","fund_change":"Y","gmt_refund_pay":"2024-12-19 14:16:39","out_trade_no":"1734423455786","refund_fee":"15.88","send_back_fee":"0.00","trade_no":"2024121722001420110504891744"}
System.out.println("调用成功:" + JSON.serialize(response));
return JSON.serialize(response);
} catch (ApiException e) {
AlipayTradeRefundDefaultResponse errorObject = (AlipayTradeRefundDefaultResponse) e.getErrorObject();
System.out.println("调用失败:" + errorObject);
}
return "";
}
首先全额退款时,trade_no(支付宝交易号)和out_trade_no(商户订单号)二选一传入,refund_amount是必传的,退款成功后,交易状态变为:TRADE_CLOSED;
部分退款时,参数在全额基础上,加了out_request_no退款请求号,必传。 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。部分退款成功后,交易状态仍为 TRADE_SUCCESS。
APP、手机网站支付退款成功会触发异步通知,就是在调用pay接口时传入的notify_url,电脑网站只有部分退款时有通知(交易状态TRADE_SUCCESS)。但是不同的交易状态触发机制不同,后面会详说。退款是否成功建议还是调用统一收单交易退款查询接口 - 支付宝文档中心来确认,异步通知虽然也可以,但是判断条件有点复杂,而且会和支付成功时的通知业务耦合在一起,还要判断是全额退款和部分退款,所以,异步通知接口只用来接收支付成功的消息比较好,退款可以根据退款接口返回的字段fund_change=Y判断,再结合退款查询接口。
退款说明
- 退款周期:12 个月,即交易发生后 12 个月内可发起退款,超过 12 个月则不可发起退款。
- 退款方式:资金原路返回用户账号。
- 退款退费:退款时手续费不退回。
- 一笔退款失败后重新提交,要采用原来的退款单号。
- 总退款金额不能超过用户实际支付金额。
- 退款信息以退款接口同步返回或者 alipay.trade.fastpay.refund.query(统一收单交易退款查询接口)为准。
退款存在退到银行卡场景下时,开发者需要先订阅 alipay.trade.refund.depositback.completed(收单退款冲退完成通知)如果是使用 From 蚂蚁消息服务 需要先设置好应用网关地址,支付宝会根据银行回执消息发送退款完成信息至应用网关地址。具体消息订阅步骤可查看 订阅消息。
3.6 订单查询接口
统一收单交易查询接口 - 支付宝文档中心,直接查就可以了。
3.7 交易关闭接口
通常交易关闭是通过 alipay.trade.page.pay 中的超时时间来控制,支付宝也提供给商家 alipay.trade.close(统一收单交易关闭接口)。若用户一直未支付,商家可以调用该接口关闭指定交易;成功关闭交易后该交易不可支付。
3.8 异步通知接口
支付成功和退款成功都会触发异步通知,建议只处理支付成功的通知,这样的话电脑网站支付和app支付可以用同一个notify_url,同一个接口,如果处理退款的通知,电脑网站支付和app支付触发条件不一样,判断条件也不一样,会有一些复杂的场景,比如:部分退款时,最后一笔退款会改变交易状态为TRADE_CLOSED,而没有退完时,状态仍是TRADE_SUCCESS,需要各种条件判断,所以退款还是调用退款查询接口比较好,建议在退款后10s后调用,可以用MQ延时消息处理。
我写了两个异步通知方法,简单判断了通知类型,但是肯定没有包含所有情况。代码如下:
@PostMapping("/pcNotify")
public String notifyPay(HttpServletRequest request) throws ApiException {
System.out.println("电脑网站收到异步通知=====支付宝回调");
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);
}
//{"gmt_create":"2024-12-19 14:04:16","charset":"UTF-8","gmt_payment":"2024-12-19 14:04:31","notify_time":"2024-12-19 14:04:33","subject":"测试电脑网站支付","sign":"XAzGwc/dZ6ON1TwXvw92GnhF7X4PTZNJ6h6TfH/5T0sgfTtIRVgnny150B7Ip1xHgjpuNoz+T8XlMpMNgvkpxNlcWmlbDfs72Ls/OyyV7ttEKgRos4VWKIrojJ1Apy06H9kie6cCfmBC3mmW9Gh+QAh5oejdZDSq+NDgJIjBlz8S6x85GEQ0BprcVHDfPKubaOWl5nCri7YKxTPOLZcqwTgV9mWXzoIa9hSp32bqyTiLtDlM1h5Z7IJKuj9/EhKuOAz6PF/vws/lftp9gzG3bJVkwwP4z50neyJNWzENIHIKQoGDInZF+T2yNyJJ0YPg/x8LejPzyfdkHjM9bZjBEA\u003d\u003d","buyer_id":"2088722013720112","invoice_amount":"6.88","version":"1.0","notify_id":"2024121901222140432120110505059148","fund_bill_list":"[{\"amount\":\"6.88\",\"fundChannel\":\"ALIPAYACCOUNT\"}]","notify_type":"trade_status_sync","out_trade_no":"1734588234593","total_amount":"6.88","trade_status":"TRADE_SUCCESS","trade_no":"2024121922001420110504898164","auth_app_id":"9021000128652691","receipt_amount":"6.88","point_amount":"0.00","buyer_pay_amount":"6.88","app_id":"9021000128652691","sign_type":"RSA2","seller_id":"2088721013742803"}
System.out.println(JSON.serialize(params));
// SHA256WithRSA(对应 sign_type 为 RSA2)或 SHA1WithRSA(对应 sign_type 为 RSA)
boolean verified = AlipaySignature.verifyV1(params, getAlipayConfig().getAlipayPublicKey(), "UTF-8", "RSA2");
if (verified) {
//todo 参数验证,out_trade_no,total_amount等
//1. 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。
//2. 判断 total_amount 是否确实为该订单的实际金额(即商家订单创建时的金额)。
//3. 校验通知中的 seller_id(或者 seller_email ) 是否为 out_trade_no 这笔单据的对应的操作方(有的时候,一个商家可能有多个seller_id/seller_email)。
//4. 验证 app_id 是否为该商家本身。
System.out.println("支付异步验签成功");
String tradeStatus = params.get("trade_status");
//总退款金额
String refundFee = params.get("refund_fee");
//交易退款时间
String gmtRefund = params.get("gmt_refund");
if (StringUtils.isEmpty(refundFee) && StringUtils.isEmpty(gmtRefund)){
//只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
System.out.println("支付成功异步通知");
}
}else {
System.out.println("退款异步通知");
String gmtClose = params.get("gmt_close");
if ("TRADE_SUCCESS".equals(tradeStatus)) {
//最后一次部分退款没有通知,因为状态已经变成TRADE_CLOSED
System.out.println("部分退款异步通知");
}
//电脑网站支付应该不会有全额退款通知,因为TRADE_CLOSED不会触发异步通知
if ("TRADE_CLOSED".equals(tradeStatus) && StringUtils.isNotEmpty(gmtClose)){
System.out.println("全额退款异步通知");
}
}
return "success";
}else {
System.out.println("验签失败,支付失败");
return "failure";
}
}
@PostMapping("/appNotify")
public String notifyAppPay(HttpServletRequest request) throws ApiException {
System.out.println("app收到异步通知=====支付宝回调");
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);
}
System.out.println(JSON.serialize(params));
// SHA256WithRSA(对应 sign_type 为 RSA2)或 SHA1WithRSA(对应 sign_type 为 RSA)
boolean verified = AlipaySignature.verifyV1(params, getAlipayConfig().getAlipayPublicKey(), "UTF-8", "RSA2");
if (verified) {
System.out.println("支付异步验签成功");
String tradeStatus = params.get("trade_status");
//总退款金额
String refundFee = params.get("refund_fee");
//交易退款时间
String gmtRefund = params.get("gmt_refund");
if (StringUtils.isEmpty(refundFee) && StringUtils.isEmpty(gmtRefund)){
if ("TRADE_SUCCESS".equals(tradeStatus)) {
System.out.println("支付成功异步通知");
}
if ("TRADE_CLOSED".equals(tradeStatus)) {
System.out.println("交易关闭异步通知");
}
if ("TRADE_FINISHED".equals(tradeStatus)) {
System.out.println("交易关闭异步通知");
}
}else {
System.out.println("退款异步通知");
String gmtClose = params.get("gmt_close");
if ("TRADE_SUCCESS".equals(tradeStatus)) {
System.out.println("部分退款异步通知");
}
//全额退款,或者最后一次部分退款
if ("TRADE_CLOSED".equals(tradeStatus) && StringUtils.isNotEmpty(gmtClose)){
System.out.println("全额退款异步通知");
}
}
return "success";
}else {
System.out.println("支付异步验签失败,支付失败");
return "failure";
}
}
3.9 一些要注意的信息
交易状态流程

随着订单支付成功、退款、关闭等操作,订单交易的每一个环节 trade_status(交易状态)也不同。
- 交易创建成功后,用户支付成功,交易状态转为 TRADE_SUCCESS(交易成功)。
- 交易成功后,规定退款时间内没有退款,交易状态转为 TRADE_FINISHED(交易完成)。
- 交易支付成功后,交易部分退款,交易状态仍为 TRADE_SUCCESS(交易成功)。
- 交易成功后,交易全额退款,交易状态转为 TRADE_CLOSED(交易关闭)。
- 交易创建成功后,用户未付款交易超时关闭,交易状态转为 TRADE_CLOSED(交易关闭)。
- 交易创建成功后,用户支付成功后,若用户商品不支持退款,交易状态直接转为 TRADE_FINISHED(交易完成)。
注意:交易成功后部分退款,交易状态仍为 TRADE_SUCCESS(交易成功)。
如果一直部分退款退完所有交易金额则交易状态转为 TRADE_CLOSED(交易关闭)。
如果未退完所有交易金额,超过有效退款时间后交易状态转为 TRADE_FINISHED(交易完成)不可退款。
异步通知:
先上代码:
@PostMapping("/appNotify")
public String notifyAppPay(HttpServletRequest request) throws ApiException {
System.out.println("app收到异步通知=====支付宝回调");
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);
}
System.out.println(JSON.serialize(params));
// SHA256WithRSA(对应 sign_type 为 RSA2)或 SHA1WithRSA(对应 sign_type 为 RSA)
boolean verified = AlipaySignature.verifyV1(params, getAlipayConfig().getAlipayPublicKey(), "UTF-8", "RSA2");
if (verified) {
System.out.println("支付异步验签成功");
String tradeStatus = params.get("trade_status");
//总退款金额
String refundFee = params.get("refund_fee");
//交易退款时间
String gmtRefund = params.get("gmt_refund");
if (StringUtils.isEmpty(refundFee) && StringUtils.isEmpty(gmtRefund)){
if ("TRADE_SUCCESS".equals(tradeStatus)) {
System.out.println("支付成功异步通知");
}
if ("TRADE_CLOSED".equals(tradeStatus)) {
System.out.println("交易关闭异步通知");
}
if ("TRADE_FINISHED".equals(tradeStatus)) {
System.out.println("交易关闭异步通知");
}
}else {
System.out.println("退款异步通知");
String gmtClose = params.get("gmt_close");
if ("TRADE_SUCCESS".equals(tradeStatus)) {
System.out.println("部分退款异步通知");
}
//全额退款,或者最后一次部分退款
if ("TRADE_CLOSED".equals(tradeStatus) && StringUtils.isNotEmpty(gmtClose)){
System.out.println("全额退款异步通知");
}
}
return "success";
}else {
System.out.println("支付异步验签失败,支付失败");
return "failure";
}
}
交易退款接口触发异步通知:产品介绍 - 支付宝文档中心,前面已经说了,退款不建议在异步通知中处理,可以调用退款查询接口来判断,
这里的交易退款接口是指统一收单交易退款接口(alipay.trade.refund),统一收单交易退款接口本身接口不支持设置 notify_url 参数,因此退款导致触发的异步通知是发送到支付接口中设置的 notify_url。
历史版本的退款接口 refund_fastpay_by_platform_pwd(即时到账有密退款接口)不同于新版本的统一收单交易退款接口(alipay.trade.refund)。历史版本的退款接口本身支持设置 notify_url,但由于历史接口目前不支持签约,无法使用等情况,本文只阐述新版本的统一收单交易退款接口(alipay.trade.refund)是否会触发异步通知。
以下是交易状态说明:

触发异步通知条件
异步通知是根据交易状态的改变进行触发的,不同的支付产品触发异步通知的条件不同。
| 产品 | 触发异步通知条件 |
|---|---|
| 当面付 | 当面付的支付接口,详见 当面付异步通知-仅用于扫码支付默认 TRADE_SUCCESS(交易成功)触发。TRADE_CLOSED(交易关闭)、TRADE_FINISHED(交易完成)、WAIT_BUYER_PAY(交易创建)不触发异步通知。 |
| App 支付 | App 支付接口,详见 App 支付异步通知触发条件默认 TRADE_SUCCESS(交易成功)、TRADE_CLOSED(交易关闭)、TRADE_FINISHED(交易完成)三种状态均会触发异步通知。WAIT_BUYER_PAY(交易创建)不触发异步通知。 |
| 手机网站支付 | 手机网站支付接口,详见手机网站支付结果异步通知触发条件默认 TRADE_SUCCESS(交易成功)、TRADE_CLOSED(交易关闭)、TRADE_FINISHED(交易完成)三种状态均会触发异步通知。WAIT_BUYER_PAY(交易创建)不触发异步通知。 |
| 电脑网站支付 | 电脑网站支付接口,详见电脑网站支付异步通知触发条件默认 TRADE_SUCCESS(交易成功)状态触发异步通知。TRADE_CLOSED(交易关闭)、TRADE_FINISHED(交易完成)、WAIT_BUYER_PAY(交易创建)不触发异步通知。 |
退款是否会收到异步
根据退款的行为可分为全额退款和部分退款。
- 全额退款,交易状态变为 TRADE_CLOSED(交易关闭)。只有 App 支付和手机网站支付交易状态变为 TRADE_CLOSED(交易关闭)会触发异步通知。
- 部分退款,交易状态仍为 TRADE_SUCCESS(交易成功)。当面付、电脑网站支付、App 支付和手机网站支付交易状态为 TRADE_SUCCESS(交易成功)都会触发异步通知。
注意事项
由于不同操作导致不同的交易状态,异步通知对交易状态的常见问题如下。
关于异步通知的验签
收到异步通知后需要先验签,V3的SDK提供的验签方法与V2相比,换了方法。
// SHA256WithRSA(对应 sign_type 为 RSA2)或 SHA1WithRSA(对应 sign_type 为 RSA)
boolean verified = AlipaySignature.verifyV1(params, getAlipayConfig().getAlipayPublicKey(), "UTF-8", "RSA2");
当然也可以自定义方法验签。
4、完整代码:只是测试代码,接口都是get请求,正式接入还需完善。
package com.zqg.pay.alipay.web;
import com.alipay.v3.ApiClient;
import com.alipay.v3.ApiException;
import com.alipay.v3.Configuration;
import com.alipay.v3.JSON;
import com.alipay.v3.api.AlipayTradeApi;
import com.alipay.v3.api.AlipayTradeFastpayRefundApi;
import com.alipay.v3.model.*;
import com.alipay.v3.util.AlipaySignature;
import com.alipay.v3.util.GenericExecuteApi;
import com.alipay.v3.util.model.AlipayConfig;
import com.alipay.v3.util.model.CustomizedParams;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
@RestController
@RequestMapping("/pay")
public class PayController {
/**
* https://opendocs.alipay.com/open-v3/2423fad5_alipay.trade.page.pay?scene=22&pathHash=b20c762a
*
* @param response
* @return String
* @throws ApiException
* @throws IOException
*/
@GetMapping(value = "/payOrder",produces = "text/html;charset=UTF-8")
public String pay(HttpServletResponse response) throws ApiException, IOException {
ApiClient defaultClient = Configuration.getDefaultApiClient();
// 初始化alipay参数(全局设置一次)
defaultClient.setAlipayConfig(getAlipayConfig());
GenericExecuteApi api = new GenericExecuteApi();
// 构造请求参数以调用接口
Map<String, Object> bizParams = new HashMap<>();
Map<String, Object> bizContent = new HashMap<>();
// 设置商户订单号
bizContent.put("out_trade_no", String.valueOf(System.currentTimeMillis()));
// 设置订单总金额
bizContent.put("total_amount", "6.88");
// 设置订单标题
bizContent.put("subject", "测试电脑网站支付");
// 设置产品码
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
// 设置订单附加信息
// bizContent.put("body", "http://localhost:8080/hello");
// 设置PC扫码支付的方式
bizContent.put("qr_pay_mode", "2");
// 设置商户自定义二维码宽度
//bizContent.put("qrcode_width", 100);
// 设置订单包含的商品列表信息
List<Map<String, Object>> goodsDetail = new ArrayList<>();
Map<String, Object> goodsDetail0 = new HashMap<>();
goodsDetail0.put("out_sku_id", "outSku_01");
goodsDetail0.put("goods_name", "ipad");
goodsDetail0.put("alipay_goods_id", "20010001");
goodsDetail0.put("quantity", 1);
goodsDetail0.put("price", 2000);
goodsDetail0.put("out_item_id", "outItem_01");
goodsDetail0.put("goods_id", "apple-01");
goodsDetail0.put("goods_category", "34543238");
goodsDetail0.put("categories_tree", "124868003|126232002|126252004");
goodsDetail0.put("show_url", "http://www.alipay.com/xxx.jpg");
goodsDetail.add(goodsDetail0);
//bizContent.put("goods_detail", goodsDetail);
// 设置订单绝对超时时间
// bizContent.put("time_expire", "2024-12-31 10:05:01");
// 设置建议使用time_expire字段
// bizContent.put("timeout_express", "90m");
// 设置描述分账信息
Map<String, Object> royaltyInfo = new HashMap<>();
royaltyInfo.put("royalty_type", "ROYALTY");
List<Map<String, Object>> royaltyDetailInfos = new ArrayList<>();
Map<String, Object> royaltyDetailInfos0 = new HashMap<>();
royaltyDetailInfos0.put("out_relation_id", "20131124001");
royaltyDetailInfos0.put("amount_percentage", "100");
royaltyDetailInfos0.put("amount", "0.1");
royaltyDetailInfos0.put("batch_no", "123");
royaltyDetailInfos0.put("trans_in", "2088101126708402");
royaltyDetailInfos0.put("trans_out_type", "userId");
royaltyDetailInfos0.put("trans_out", "2088101126765726");
royaltyDetailInfos0.put("serial_no", 1);
royaltyDetailInfos0.put("trans_in_type", "userId");
royaltyDetailInfos0.put("desc", "分账测试1");
royaltyDetailInfos.add(royaltyDetailInfos0);
royaltyInfo.put("royalty_detail_infos", royaltyDetailInfos);
// bizContent.put("royalty_info", royaltyInfo);
// 设置二级商户信息
Map<String, Object> subMerchantvxgvh = new HashMap<>();
subMerchantvxgvh.put("merchant_id", "2088000603999128");
subMerchantvxgvh.put("merchant_type", "alipay");
//bizContent.put("sub_merchant", subMerchantvxgvh);
// 设置描述结算信息
Map<String, Object> settleInfo = new HashMap<>();
settleInfo.put("settle_period_time", "7d");
List<Map<String, Object>> settleDetailInfos = new ArrayList<>();
Map<String, Object> settleDetailInfos0 = new HashMap<>();
settleDetailInfos0.put("amount", "0.1");
settleDetailInfos0.put("trans_in", "A0001");
settleDetailInfos0.put("settle_entity_type", "SecondMerchant");
settleDetailInfos0.put("summary_dimension", "A0001");
settleDetailInfos0.put("actual_amount", "0.1");
settleDetailInfos0.put("settle_entity_id", "2088xxxxx;ST_0001");
settleDetailInfos0.put("trans_in_type", "cardAliasNo");
settleDetailInfos.add(settleDetailInfos0);
settleInfo.put("settle_detail_infos", settleDetailInfos);
// bizContent.put("settle_info", settleInfo);
// 设置业务扩展参数
Map<String, Object> extendParams = new HashMap<>();
extendParams.put("sys_service_provider_id", "2088511833207846");
extendParams.put("hb_fq_seller_percent", "100");
extendParams.put("hb_fq_num", "3");
extendParams.put("tc_installment_order_id", "2015042321001004720200028594");
extendParams.put("industry_reflux_info", "{\"scene_code\":\"metro_tradeorder\",\"channel\":\"xxxx\",\"scene_data\":{\"asset_name\":\"ALIPAY\"}}");
extendParams.put("specified_seller_name", "XXX的跨境小铺");
extendParams.put("royalty_freeze", "true");
extendParams.put("card_type", "S0JP0000");
extendParams.put("credit_ext_info", "{\"category\":\"CHARGE_PILE_CAR\",\"serviceId\":\"2020042800000000000001450466\"}");
extendParams.put("trade_component_order_id", "2023060801502300000008810000005657");
// bizContent.put("extend_params", extendParams);
// 设置商户传入业务信息
// bizContent.put("business_params", "{\"mc_create_trade_ip\":\"127.0.0.1\"}");
// 设置优惠参数
// bizContent.put("promo_params", "{\"storeIdType\":\"1\"}");
// 设置请求后页面的集成方式
// bizContent.put("integration_type", "PCWEB");
// 设置请求来源地址
// bizContent.put("request_from_url", "https://");
// 设置签约参数
Map<String, Object> agreementSignParams = new HashMap<>();
Map<String, Object> subMerchantRWfdn = new HashMap<>();
subMerchantRWfdn.put("sub_merchant_name", "滴滴出行");
subMerchantRWfdn.put("sub_merchant_service_name", "滴滴出行免密支付");
subMerchantRWfdn.put("sub_merchant_service_description", "免密付车费,单次最高500");
subMerchantRWfdn.put("sub_merchant_id", "2088123412341234");
agreementSignParams.put("sub_merchant", subMerchantRWfdn);
agreementSignParams.put("buckle_app_id", "1001164");
agreementSignParams.put("sign_validity_period", "2m");
agreementSignParams.put("buckle_merchant_id", "268820000000414397785");
agreementSignParams.put("external_logon_id", "138****8888");
agreementSignParams.put("third_party_type", "PARTNER");
agreementSignParams.put("personal_product_code", "GENERAL_WITHHOLDING_P");
agreementSignParams.put("external_agreement_no", "test");
agreementSignParams.put("promo_params", "{\"key\",\"value\"}");
agreementSignParams.put("sign_scene", "INDUSTRY|CARRENTAL");
// bizContent.put("agreement_sign_params", agreementSignParams);
// 设置商户门店编号
// bizContent.put("store_id", "NJ_001");
// 设置指定支付渠道
// bizContent.put("enable_pay_channels", "pcredit,moneyFund,debitCardExpress");
// 设置禁用渠道
// bizContent.put("disable_pay_channels", "pcredit,moneyFund,debitCardExpress");
// 设置商户的原始订单号
// bizContent.put("merchant_order_no", "20161008001");
// 设置外部指定买家
Map<String, Object> extUserInfo = new HashMap<>();
extUserInfo.put("cert_type", "IDENTITY_CARD");
extUserInfo.put("cert_no", "362334768769238881");
extUserInfo.put("name", "李明");
extUserInfo.put("mobile", "16587658765");
extUserInfo.put("min_age", "18");
extUserInfo.put("need_check_info", "F");
extUserInfo.put("identity_hash", "27bfcd1dee4f22c8fe8a2374af9b660419d1361b1c207e9b41a754a113f38fcc");
// bizContent.put("ext_user_info", extUserInfo);
// 设置开票信息
Map<String, Object> invoiceInfo = new HashMap<>();
Map<String, Object> keyInfo = new HashMap<>();
keyInfo.put("tax_num", "1464888883494");
keyInfo.put("is_support_invoice", true);
keyInfo.put("invoice_merchant_name", "ABC|003");
invoiceInfo.put("key_info", keyInfo);
invoiceInfo.put("details", "[{\"code\":\"100294400\",\"name\":\"服饰\",\"num\":\"2\",\"sumPrice\":\"200.00\",\"taxRate\":\"6%\"}]");
// bizContent.put("invoice_info", invoiceInfo);
// 设置返回参数选项
List<String> queryOptions = new ArrayList<>();
queryOptions.add("hyb_amount");
queryOptions.add("enterprise_pay_info");
// bizContent.put("query_options", queryOptions);
bizParams.put("biz_content", bizContent);
//return_url 必须是 http 或 https 开头的完整的 url 地址。
//return_url 地址后不可带自定义参数。
//设置 return_url 时不要进行转义、urlencode 等数据处理。
//当面付和APP支付不支持 return_url 参数,即使设置了也没有任何效果。
//同步通知参数只可参考,不能作为判断是否支付成功的依据。
bizParams.put("return_url", "https://docs.open.alipay.com");
bizParams.put("notify_url", "http://lql5520.yunmv.cn/pay/pcNotify");
try {
//[email protected]
System.out.println(JSON.serialize(bizParams));
System.out.println("==============================");
// 如果是第三方代调用模式,请设置app_auth_token(应用授权令牌)
String pageRedirectionData = api.pageExecute("alipay.trade.page.pay", "POST", bizParams);
// 如果需要返回GET请求,请使用
// String pageRedirectionData = api.pageExecute("alipay.trade.page.pay", "GET", bizParams);
System.out.println(pageRedirectionData);
return pageRedirectionData;
/* //produces = "text/html"
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write(pageRedirectionData);
out.flush();
out.close();
*/
} catch (ApiException e) {
System.out.println("调用失败");
}
return "";
}
/**
* https://opendocs.alipay.com/support/01rawc?pathHash=4ad70fe3
* App 支付 App 支付接口,详见 App 支付异步通知触发条件
* 默认 TRADE_SUCCESS(交易成功)、TRADE_CLOSED(交易关闭)、TRADE_FINISHED(交易完成)三种状态均会触发异步通知。
* WAIT_BUYER_PAY(交易创建)不触发异步通知。
*
* 电脑网站支付 电脑网站支付接口,详见电脑网站支付异步通知触发条件
* 默认 TRADE_SUCCESS(交易成功)状态触发异步通知。
* TRADE_CLOSED(交易关闭)、TRADE_FINISHED(交易完成)、WAIT_BUYER_PAY(交易创建)不触发异步通知。
*
* 退款是否会收到异步
* 根据退款的行为可分为全额退款和部分退款。
* ● 全额退款,交易状态变为 TRADE_CLOSED(交易关闭)。只有 App 支付和手机网站支付交易状态变为 TRADE_CLOSED(交易关闭)会触发异步通知。
* ● 部分退款,交易状态仍为 TRADE_SUCCESS(交易成功)。当面付、电脑网站支付、App 支付和手机网站支付交易状态为 TRADE_SUCCESS(交易成功)都会触发异步通知。
* 如何区分部分退款和全额退款: https://opendocs.alipay.com/support/01rawd
*
* 25 小时以内完成 8 次通知(通知的间隔频率一般是 4m,10m,10m,1h,2h,6h,15h)
* @param request
* @return
* @throws ApiException
*/
@PostMapping("/pcNotify")
public String notifyPay(HttpServletRequest request) throws ApiException {
System.out.println("电脑网站收到异步通知=====支付宝回调");
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);
}
//{"gmt_create":"2024-12-19 14:04:16","charset":"UTF-8","gmt_payment":"2024-12-19 14:04:31","notify_time":"2024-12-19 14:04:33","subject":"测试电脑网站支付","sign":"XAzGwc/dZ6ON1TwXvw92GnhF7X4PTZNJ6h6TfH/5T0sgfTtIRVgnny150B7Ip1xHgjpuNoz+T8XlMpMNgvkpxNlcWmlbDfs72Ls/OyyV7ttEKgRos4VWKIrojJ1Apy06H9kie6cCfmBC3mmW9Gh+QAh5oejdZDSq+NDgJIjBlz8S6x85GEQ0BprcVHDfPKubaOWl5nCri7YKxTPOLZcqwTgV9mWXzoIa9hSp32bqyTiLtDlM1h5Z7IJKuj9/EhKuOAz6PF/vws/lftp9gzG3bJVkwwP4z50neyJNWzENIHIKQoGDInZF+T2yNyJJ0YPg/x8LejPzyfdkHjM9bZjBEA\u003d\u003d","buyer_id":"2088722013720112","invoice_amount":"6.88","version":"1.0","notify_id":"2024121901222140432120110505059148","fund_bill_list":"[{\"amount\":\"6.88\",\"fundChannel\":\"ALIPAYACCOUNT\"}]","notify_type":"trade_status_sync","out_trade_no":"1734588234593","total_amount":"6.88","trade_status":"TRADE_SUCCESS","trade_no":"2024121922001420110504898164","auth_app_id":"9021000128652691","receipt_amount":"6.88","point_amount":"0.00","buyer_pay_amount":"6.88","app_id":"9021000128652691","sign_type":"RSA2","seller_id":"2088721013742803"}
System.out.println(JSON.serialize(params));
// SHA256WithRSA(对应 sign_type 为 RSA2)或 SHA1WithRSA(对应 sign_type 为 RSA)
boolean verified = AlipaySignature.verifyV1(params, getAlipayConfig().getAlipayPublicKey(), "UTF-8", "RSA2");
if (verified) {
//todo 参数验证,out_trade_no,total_amount等
//1. 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。
//2. 判断 total_amount 是否确实为该订单的实际金额(即商家订单创建时的金额)。
//3. 校验通知中的 seller_id(或者 seller_email ) 是否为 out_trade_no 这笔单据的对应的操作方(有的时候,一个商家可能有多个seller_id/seller_email)。
//4. 验证 app_id 是否为该商家本身。
System.out.println("支付异步验签成功");
String tradeStatus = params.get("trade_status");
//总退款金额
String refundFee = params.get("refund_fee");
//交易退款时间
String gmtRefund = params.get("gmt_refund");
if (StringUtils.isEmpty(refundFee) && StringUtils.isEmpty(gmtRefund)){
//只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
System.out.println("支付成功异步通知");
}
}else {
System.out.println("退款异步通知");
String gmtClose = params.get("gmt_close");
if ("TRADE_SUCCESS".equals(tradeStatus)) {
//最后一次部分退款没有通知,因为状态已经变成TRADE_CLOSED
System.out.println("部分退款异步通知");
}
//电脑网站支付应该不会有全额退款通知,因为TRADE_CLOSED不会触发异步通知
if ("TRADE_CLOSED".equals(tradeStatus) && StringUtils.isNotEmpty(gmtClose)){
System.out.println("全额退款异步通知");
}
}
return "success";
}else {
System.out.println("验签失败,支付失败");
return "failure";
}
}
@PostMapping("/appNotify")
public String notifyAppPay(HttpServletRequest request) throws ApiException {
System.out.println("app收到异步通知=====支付宝回调");
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);
}
System.out.println(JSON.serialize(params));
// SHA256WithRSA(对应 sign_type 为 RSA2)或 SHA1WithRSA(对应 sign_type 为 RSA)
boolean verified = AlipaySignature.verifyV1(params, getAlipayConfig().getAlipayPublicKey(), "UTF-8", "RSA2");
if (verified) {
System.out.println("支付异步验签成功");
String tradeStatus = params.get("trade_status");
//总退款金额
String refundFee = params.get("refund_fee");
//交易退款时间
String gmtRefund = params.get("gmt_refund");
if (StringUtils.isEmpty(refundFee) && StringUtils.isEmpty(gmtRefund)){
if ("TRADE_SUCCESS".equals(tradeStatus)) {
System.out.println("支付成功异步通知");
}
if ("TRADE_CLOSED".equals(tradeStatus)) {
System.out.println("交易关闭异步通知");
}
if ("TRADE_FINISHED".equals(tradeStatus)) {
System.out.println("交易关闭异步通知");
}
}else {
System.out.println("退款异步通知");
String gmtClose = params.get("gmt_close");
if ("TRADE_SUCCESS".equals(tradeStatus)) {
System.out.println("部分退款异步通知");
}
//全额退款,或者最后一次部分退款
if ("TRADE_CLOSED".equals(tradeStatus) && StringUtils.isNotEmpty(gmtClose)){
System.out.println("全额退款异步通知");
}
}
return "success";
}else {
System.out.println("支付异步验签失败,支付失败");
return "failure";
}
}
@GetMapping("/queryOrder")
public String queryPage(String orderNo) throws ApiException {
//1733990822574
ApiClient defaultClient = Configuration.getDefaultApiClient();
// 初始化alipay参数(全局设置一次)
defaultClient.setAlipayConfig(getAlipayConfig());
AlipayTradeApi api = new AlipayTradeApi();
AlipayTradeQueryModel data = new AlipayTradeQueryModel();
// 设置订单支付时传入的商户订单号
data.setOutTradeNo(orderNo);
try {
AlipayTradeQueryResponseModel response = api.query(data);
System.out.println("调用成功:" + JSON.serialize(response));
return JSON.serialize(response);
} catch (ApiException e) {
AlipayTradeQueryDefaultResponse errorObject = (AlipayTradeQueryDefaultResponse) e.getErrorObject();
System.out.println("调用失败:" + errorObject);
}
return "";
}
@GetMapping(value = "/payAppOrder")
public String payOrderApp() throws ApiException {
ApiClient defaultClient = Configuration.getDefaultApiClient();
// 初始化alipay参数(全局设置一次)
defaultClient.setAlipayConfig(getAlipayConfig());
GenericExecuteApi api = new GenericExecuteApi();
// 构造请求参数以调用接口
Map<String, Object> bizParams = new HashMap<>();
Map<String, Object> bizContent = new HashMap<>();
// 设置商户订单号
bizContent.put("out_trade_no", "70501111111S001111119");
// 设置订单总金额
bizContent.put("total_amount", "9.00");
// 设置订单标题
bizContent.put("subject", "大乐透");
// 设置产品码
bizContent.put("product_code", "QUICK_MSECURITY_PAY");
// 设置订单附加信息
bizContent.put("body", "Iphone6 16G");
// 设置订单绝对超时时间
bizContent.put("time_expire", "2024-12-31 10:05:00");
// 设置建议使用time_expire字段
bizContent.put("timeout_express", "90m");
bizParams.put("biz_content", bizContent);
// 同步回调指定的页面 app不需要
// bizParams.put("return_url", "https://docs.open.alipay.com");
bizParams.put("notify_url", "http://xxxx/pay/appNotify");
try {
String orderStr = api.sdkExecute("alipay.trade.app.pay", bizParams);
System.out.println(orderStr);
return orderStr;
} catch (ApiException e) {
System.out.println("调用失败");
}
return "";
}
/**
* 预下单(沙箱环境调用显示无权限;ACCESS_FORBIDDEN)
* @return
* @throws ApiException
*/
@GetMapping(value = "/createPay")
public String getOrderPreCreatePay() throws ApiException {
ApiClient defaultClient = Configuration.getDefaultApiClient();
// 初始化alipay参数(全局设置一次)
defaultClient.setAlipayConfig(getAlipayConfig());
AlipayTradeApi alipayTradeApi = new AlipayTradeApi();
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
model.setOutTradeNo(String.valueOf(System.currentTimeMillis()));
model.setTotalAmount("9.00");
model.setSubject("扫码测试");
model.setProductCode("QR_CODE_OFFLINE");
model.setNotifyUrl("http://xxxx/pay/notify");
try {
AlipayTradePrecreateResponseModel responseModel = alipayTradeApi.precreate(model);
//{"code":"ACQ.ACCESS_FORBIDDEN","message":"ACCESS_FORBIDDEN"} 没有权限,沙箱环境没有alipay.trade.precreate权限
System.out.println("调用成功:"+JSON.serialize(responseModel));
return responseModel.getQrCode();
} catch (ApiException e) {
System.out.println("调用失败:"+e);
}
return "";
}
@GetMapping(value = "/createPay2",produces = "text/html;charset=UTF-8")
public String getOrderPreCreatePay2() throws ApiException {
ApiClient defaultClient = Configuration.getDefaultApiClient();
// 初始化alipay参数(全局设置一次)
defaultClient.setAlipayConfig(getAlipayConfig());
GenericExecuteApi api = new GenericExecuteApi();
HashMap<String, Object> bizContent = new HashMap<>();
HashMap<String, Object> bizParams = new HashMap<>();
bizContent.put("out_trade_no", String.valueOf(System.currentTimeMillis()));
bizContent.put("total_amount", "9.00");
bizContent.put("subject", "扫码测试");
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
/**
* 支持前置模式和跳转模式。
* 前置模式是将二维码前置到商户的订单确认页的模式。需要商户在自己的页面中以 iframe 方式请求支付宝页面。具体支持的枚举值有以下几种:
* 0:订单码-简约前置模式,对应 iframe 宽度不能小于600px,高度不能小于300px;
* 1:订单码-前置模式,对应iframe 宽度不能小于 300px,高度不能小于600px;
* 3:订单码-迷你前置模式,对应 iframe 宽度不能小于 75px,高度不能小于75px;
* 4:订单码-可定义宽度的嵌入式二维码,商户可根据需要设定二维码的大小。
* 跳转模式下,用户的扫码界面是由支付宝生成的,不在商户的域名下。支持传入的枚举值有:
* 2:订单码-跳转模式
*/
bizContent.put("qr_pay_mode", "4");
bizContent.put("qrcode_width", 100);
bizParams.put("notify_url", "http://lql5520.yunmv.cn/pay/pcNotify");
bizParams.put("return_url", "https://docs.open.alipay.com");
bizParams.put("biz_content", bizContent);
try {
String form = api.pageExecute("alipay.trade.page.pay", "POST", bizParams);
System.out.println("调用成功:"+JSON.serialize(form));
return form;
} catch (ApiException e) {
System.out.println("调用失败:"+e);
}
return "";
}
/**
* https://opendocs.alipay.com/open-v3/01073208_alipay.trade.refund
* 退款时根据异步的返回信息可进行判断,但部分接口存在全额退款时不进行触发异步(电脑网站支付,全额退款时,TRADE_CLOSED,没有异步通知),
* 因此建议根据退款同步响应参数以及退款查询接口进行判断。
*
* 部分退款:
* 检查是否设置out_request_no参数,该参数是标识一次退款请求,同一笔交易多次退款需要保证唯一,且 部分退款,则此参数必传。
* @param orderNo
* @param amount
* @return
* @throws ApiException
*/
@GetMapping ("/refundPay")
public String refundPay(String orderNo,String amount) throws ApiException {
ApiClient defaultClient = Configuration.getDefaultApiClient();
// 初始化alipay参数(全局设置一次)
defaultClient.setAlipayConfig(getAlipayConfig());
// 构造请求参数以调用接口
AlipayTradeApi api = new AlipayTradeApi();
AlipayTradeRefundModel data = new AlipayTradeRefundModel();
// 部分退款时,outRequestNo必传,同一笔交易多次退款需要保证唯一
data.outRequestNo(String.valueOf(System.currentTimeMillis()));
data.setOutTradeNo(orderNo);
data.setRefundAmount(amount);
data.setRefundReason("测试退款");
// 第三方代调用模式下请设置app_auth_token
CustomizedParams params = new CustomizedParams();
params.setAppAuthToken("<-- 请填写应用授权令牌 -->");
try {
AlipayTradeRefundResponseModel response = api.refund(data);
//{"buyer_logon_id":"rpv***@sandbox.com","buyer_user_id":"2088722013720112","fund_change":"Y","gmt_refund_pay":"2024-12-19 14:16:39","out_trade_no":"1734423455786","refund_fee":"15.88","send_back_fee":"0.00","trade_no":"2024121722001420110504891744"}
System.out.println("调用成功:" + JSON.serialize(response));
return JSON.serialize(response);
} catch (ApiException e) {
AlipayTradeRefundDefaultResponse errorObject = (AlipayTradeRefundDefaultResponse) e.getErrorObject();
System.out.println("调用失败:" + errorObject);
}
return "";
}
/**
* 退款查询接口返回 refund_status=REFUND_SUCCESS 表示退款处理成功,否则表示退款没有执行成功。
* @param orderNo
* @return
* @throws ApiException
*/
@GetMapping ("/refundPayQuery")
public String refundPayQuery(String orderNo,String outRequestNo) throws ApiException {
ApiClient defaultClient = Configuration.getDefaultApiClient();
// 初始化alipay参数(全局设置一次)
defaultClient.setAlipayConfig(getAlipayConfig());
// 构造请求参数以调用接口
AlipayTradeFastpayRefundApi api = new AlipayTradeFastpayRefundApi();
AlipayTradeFastpayRefundQueryModel data = new AlipayTradeFastpayRefundQueryModel();
data.setOutTradeNo(orderNo);
//部分退款需要传入,哪次退款的outRequestNo
data.setOutRequestNo(outRequestNo);
// 第三方代调用模式下请设置app_auth_token
CustomizedParams params = new CustomizedParams();
params.setAppAuthToken("<-- 请填写应用授权令牌 -->");
try {
AlipayTradeFastpayRefundQueryResponseModel response = api.query(data);
//{"out_request_no":"1734575090879","out_trade_no":"1734575090879","refund_amount":"5.88","refund_status":"REFUND_SUCCESS","total_amount":"5.88","trade_no":"2024121922001420110504904489"}
System.out.println("调用成功:" + JSON.serialize(response));
return JSON.serialize(response);
} catch (ApiException e) {
AlipayTradeFastpayRefundQueryDefaultResponse errorObject = (AlipayTradeFastpayRefundQueryDefaultResponse) e.getErrorObject();
System.out.println("调用失败:" + errorObject);
}
return "";
}
@GetMapping("/closeOrder")
public String close(String orderNo) throws ApiException {
//1733990822574
ApiClient defaultClient = Configuration.getDefaultApiClient();
// 初始化alipay参数(全局设置一次)
defaultClient.setAlipayConfig(getAlipayConfig());
AlipayTradeApi api = new AlipayTradeApi();
AlipayTradeCloseModel data = new AlipayTradeCloseModel();
// 设置订单支付时传入的商户订单号
data.setOutTradeNo(orderNo);
try {
AlipayTradeCloseResponseModel response = api.close(data);
System.out.println("调用成功:" + JSON.serialize(response));
return JSON.serialize(response);
} catch (ApiException e) {
AlipayTradeCloseDefaultResponse errorObject = (AlipayTradeCloseDefaultResponse) e.getErrorObject();
System.out.println("调用失败:" + errorObject);
}
return "";
}
@GetMapping("/cancelOrder")
public String cancel(String orderNo) throws ApiException {
//1733990822574
ApiClient defaultClient = Configuration.getDefaultApiClient();
// 初始化alipay参数(全局设置一次)
defaultClient.setAlipayConfig(getAlipayConfig());
AlipayTradeApi api = new AlipayTradeApi();
AlipayTradeCancelModel data = new AlipayTradeCancelModel();
// 设置订单支付时传入的商户订单号
data.setOutTradeNo(orderNo);
try {
AlipayTradeCancelResponseModel response = api.cancel(data);
System.out.println("调用成功:" + JSON.serialize(response));
return JSON.serialize(response);
} catch (ApiException e) {
AlipayTradeCancelDefaultResponse errorObject = (AlipayTradeCancelDefaultResponse) e.getErrorObject();
System.out.println("调用失败:" + errorObject.getAlipayTradeCancelErrorResponseModel());
}
return "";
}
private AlipayConfig getAlipayConfig() {
AlipayConfig alipayConfig = new AlipayConfig();
//alipayConfig.setServerUrl("https://openapi.alipay.com");
alipayConfig.setServerUrl("https://openapi-sandbox.dl.alipaydev.com");
alipayConfig.setAppId("902xxxxxxx");
alipayConfig.setPrivateKey("MIIEvAIBADAxxxxx");
alipayConfig.setAlipayPublicKey("MIIBIjANxxxxxx");
return alipayConfig;
}
}