支付宝免押支付集成指南
1. 前置准备
1.1 企业支付宝账号开通产品(预授权支付、芝麻免押)
所需参数:信用服务ID、信用服务类目、商户PID
操作流程:
-
进入B站信用服务管理平台
-
配置信用服务
-
添加验收白名单
1.2 O站开放平台配置
所需参数:应用公钥证书、支付宝公钥证书、支付宝根证书、私钥、AppId
操作流程:
- 进入开放平台创建小程序
- 配置接口加签方式(证书模式)
- 设置应用网关(芝麻免押回调接口)
- 配置服务器域白名单(后端域名)
2. 技术验收要求

- 需要完成两个订单:一个支付成功,一个支付失败
- 关键参数获取:
- 交易单号(trade_no)
- 授权单号(auth_no)
- 用户ID(userId2088xxx)
3. 接口清单
功能 | 接口名称 | 接口代码 |
---|---|---|
资金冻结 | alipay.fund.auth.order.app.freeze | freeze() |
发起扣款 | alipay.trade.pay | pay() |
资金解冻 | alipay.fund.auth.order.unfreeze | unfreeze() |
交易关闭 | alipay.trade.close | close() |
交易退款 | alipay.trade.refund | refund() |
授权撤销 | alipay.fund.auth.operation.cancel | cancel() |
4. 代码实现
4.1 支付宝配置类
java
package com.example.config;
import com.alipay.api.AlipayConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AliConfig {
@Bean
public AlipayConfig getConfig() {
AlipayConfig alipayConfig = new AlipayConfig();
alipayConfig.setServerUrl("https://openapi.alipay.com/gateway.do");
alipayConfig.setAppId("your_app_id");
alipayConfig.setPrivateKey("your_private_key");
alipayConfig.setFormat("json");
alipayConfig.setCharset("UTF-8");
alipayConfig.setSignType("RSA2");
alipayConfig.setAppCertPath("cert/appCertPublicKey.crt");
alipayConfig.setAlipayPublicCertPath("cert/alipayCertPublicKey_RSA2.crt");
alipayConfig.setRootCertPath("cert/alipayRootCert.crt");
return alipayConfig;
}
}
4.2 支付接口
接口重要参数
扩展参数 | 参数值 | 对应场景/api |
---|---|---|
ExtraParam | CREDIT_RENT_OFFLINE | 信用服务类目->freeze接口 |
DepositProductMode | DEPOSIT_ONLY | 芝麻免押受理台模式 |
bizContent | "enable_pay_channels", "balance" | 关闭交易场景->pay接口 |
java
package com.example.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.domain.*;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import com.example.config.AliConfig;
import com.example.model.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RequestMapping("/alipay")
@RestController
@Slf4j
public class AliPayController {
@Autowired
private AliConfig aliConfig;
// 回调接口需要外网访问,建议本地开启内网穿透 对应本地8080端口也就是NotifyController类的接口
private static final String ALIPAY_NOTIFY_URL = "https://60155d5f.r26.cpolar.top/notify";
/**
* 创建免押订单接口
*/
@RequestMapping("/freeze")
public String freeze() throws AlipayApiException {
String OutOrderNo = System.currentTimeMillis() + "";
String OutRequestNo = System.currentTimeMillis() + "";
// 构造client
AlipayClient alipayClient = new DefaultAlipayClient(aliConfig.getConfig());
// 构造请求参数以调用接口
AlipayFundAuthOrderAppFreezeRequest request = new AlipayFundAuthOrderAppFreezeRequest();
AlipayFundAuthOrderAppFreezeModel model = new AlipayFundAuthOrderAppFreezeModel();
// 设置订单标题
model.setOrderTitle("租车押金");
// 设置商户授权资金订单号
model.setOutOrderNo(OutOrderNo);
// 设置商户本次资金操作的请求流水号
model.setOutRequestNo(OutRequestNo);
// 设置需要冻结的金额
model.setAmount("0.01");
// 设置预授权订单相对超时时间
model.setTimeoutExpress("2d");
// 设置销售产品码
model.setProductCode("PRE_AUTH_ONLINE");
// 设置业务扩展参数 信用服务类目CREDIT_RENT_OFFLINE
model.setExtraParam("{\"category\": \"CREDIT_RENT_OFFLINE\",\"serviceId\": \"信用服务id\"}");
// 设置免押受理台模式
model.setDepositProductMode("DEPOSIT_ONLY");
request.setBizModel(model);
request.setNotifyUrl(ALIPAY_NOTIFY_URL);
// 发送请求
AlipayFundAuthOrderAppFreezeResponse response = alipayClient.sdkExecute(request);
log.info("线上资金授权冻结接口:{}", response.getBody());
return response.getBody();
}
/**
* 发起扣款接口 alipay.trade.pay
* bizContent和BizModel不能同时使用,model未封装enable_pay_channels参数
*/
@RequestMapping("/pay")
public R<JSONObject> pay(@RequestBody Map<String, String> params) throws AlipayApiException {
AlipayClient alipayClient = new DefaultAlipayClient(aliConfig.getConfig());
// 构造请求参数以调用接口
AlipayTradePayRequest request = new AlipayTradePayRequest();
JSONObject bizContent = new JSONObject();
// 商家自定义订单号
bizContent.put("out_trade_no", params.get("outTradeNo"));
// 创建免押订单接口freeze回调接口返回内容中的auth_no
bizContent.put("auth_no", params.get("authNo"));
// 指定付款方式为余额支付,关闭交易场景
// bizContent.put("enable_pay_channels", "balance");
bizContent.put("product_code", "PREAUTH_PAY");
bizContent.put("total_amount", "0.01");
bizContent.put("subject", "租车租金");
bizContent.put("auth_confirm_mode", "COMPLETE");
request.setBizContent(bizContent.toString());
request.setNotifyUrl(ALIPAY_NOTIFY_URL);
AlipayTradePayResponse response = alipayClient.certificateExecute(request);
JSONObject jsonObject = JSONObject.parseObject(response.getBody());
log.info("发起扣款接口参数:{}", response.getBody());
return R.ok(jsonObject);
}
/**
* 资金授权解冻接口 alipay.fund.auth.order.unfreeze
* 以authNo为准,OutRequestNo随便填 如报错参数异常或参数缺失则是某次请求已经使用过该OutRequestNo
*/
@RequestMapping("/unfreeze")
public String unfreeze(@RequestBody Map<String, String> params) throws AlipayApiException {
AlipayClient alipayClient = new DefaultAlipayClient(aliConfig.getConfig());
// 构造请求参数以调用接口
AlipayFundAuthOrderUnfreezeRequest request = new AlipayFundAuthOrderUnfreezeRequest();
AlipayFundAuthOrderUnfreezeModel model = new AlipayFundAuthOrderUnfreezeModel();
// 设置支付宝资金授权订单号
model.setAuthNo(params.get("authNo"));
// 设置解冻请求流水号 保证不重复
model.setOutRequestNo(params.get("outRequestNo"));
// 设置本次操作解冻的金额
model.setAmount("0.01");
// 设置商户对本次解冻操作的附言描述
model.setRemark("2025-08期解冻0.01元");
request.setBizModel(model);
AlipayFundAuthOrderUnfreezeResponse response = alipayClient.certificateExecute(request);
log.info("资金授权解冻:{}", response.getBody());
return response.getBody();
}
/**
* 统一收单交易关闭接口 alipay.trade.close
*/
@RequestMapping("/close")
public String close(@RequestBody Map<String, String> params) throws AlipayApiException {
AlipayClient alipayClient = new DefaultAlipayClient(aliConfig.getConfig());
// 构造请求参数以调用接口
AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
AlipayTradeCloseModel model = new AlipayTradeCloseModel();
// 设置该交易在支付宝系统中的交易流水号
model.setTradeNo(params.get("tradeNo"));
request.setBizModel(model);
AlipayTradeCloseResponse response = alipayClient.certificateExecute(request);
log.info("交易关闭:{}", response.getBody());
return response.getBody();
}
/**
* 统一收单交易退款接口 alipay.trade.refund
*/
@RequestMapping("/refund")
public R<String> refund(@RequestBody Map<String, String> params) throws AlipayApiException {
AlipayClient alipayClient = new DefaultAlipayClient(aliConfig.getConfig());
// 构造请求参数以调用接口
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
// 设置商家订单号
model.setOutTradeNo(params.get("outTradeNo"));
// 设置退款金额
model.setRefundAmount("0.01");
// 设置退款原因说明
model.setRefundReason("正常退款");
request.setBizModel(model);
AlipayTradeRefundResponse response = alipayClient.certificateExecute(request);
log.info("交易退款:{}", response.getBody());
return R.ok(response.getBody());
}
/**
* 资金授权撤销接口 alipay.fund.auth.operation.cancel
* 此接口可以关闭免押订单,参数可以在芝麻免押下单成功记录获取
*/
@RequestMapping("/cancel")
public String cancel(@RequestBody Map<String, String> params) throws AlipayApiException {
AlipayClient alipayClient = new DefaultAlipayClient(aliConfig.getConfig());
// 构造请求参数以调用接口
AlipayFundAuthOperationCancelRequest request = new AlipayFundAuthOperationCancelRequest();
AlipayFundAuthOperationCancelModel model = new AlipayFundAuthOperationCancelModel();
// 支付宝订单号
model.setOperationId(params.get("operationId"));
// 授权号
model.setAuthNo(params.get("authNo"));
model.setRemark("撤销授权");
request.setBizModel(model);
AlipayFundAuthOperationCancelResponse response = alipayClient.certificateExecute(request);
log.info("资金授权撤销:{}", response.getBody());
return response.getBody();
}
}
4.3 回调接口
java
package com.example.controller;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConfig;
import com.alipay.api.internal.util.AlipaySignature;
import com.example.config.AliConfig;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@RestController
@Slf4j
public class NotifyController {
@Autowired
private AliConfig aliConfig;
/**
* 支付宝异步通知
*/
@RequestMapping("/notify")
public String notify(HttpServletRequest request) throws AlipayApiException {
Map<String, String> params = convertRequestParamsToMap(request);
String paramsJson = JSON.toJSONString(params);
AlipayConfig alipayConfig = aliConfig.getConfig();
boolean signVerified = AlipaySignature.rsaCertCheckV1(params, alipayConfig.getAlipayPublicCertPath(),
alipayConfig.getCharset(), alipayConfig.getSignType());
if (signVerified) {
log.info("支付宝异步通知验签成功");
log.info("支付宝异步通知: {}", paramsJson);
return "success";
}
return "failure";
}
private static Map<String, String> convertRequestParamsToMap(HttpServletRequest request) {
Map<String, String> retMap = new HashMap<>();
Set<Map.Entry<String, String[]>> entrySet = request.getParameterMap().entrySet();
for (Map.Entry<String, String[]> entry : entrySet) {
String name = entry.getKey();
String[] values = entry.getValue();
int valLen = values.length;
if (valLen == 1) {
retMap.put(name, values[0]);
} else if (valLen > 1) {
StringBuilder sb = new StringBuilder();
for (String val : values) {
sb.append(",").append(val);
}
retMap.put(name, sb.substring(1));
} else {
retMap.put(name, "");
}
}
return retMap;
}
}
4.4 日志配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<configuration debug="true">
<property name="LOG-DIR" value="./logs"/>
<property name="LOG-PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5level] - %m%n"/>
<property name="APP-NAME" value="zmPayDemo"/>
<!--控制台-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
<pattern>${LOG-PATTERN}</pattern>
</encoder>
</appender>
<!--日志文件-->
<appender name="FILE-ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式,把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>${LOG-DIR}/${APP-NAME}-%d{yyyyMMdd}.log</FileNamePattern>
<!--只保留最近30天的日志-->
<maxHistory>30</maxHistory>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<!--日志输出编码格式化-->
<encoder>
<charset>UTF-8</charset>
<pattern>${LOG-PATTERN}</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE-ALL"/>
</root>
</configuration>
5.前端示例
- 小程序示例

- index.axml
js
<view class="container">
<!-- 芝麻免押受理台 -->
<view class="section">
<view class="section-title">芝麻免押</view>
<button class="btn" onTap="onPullPay" type="primary">拉起芝麻免押受理台</button>
</view>
<!-- 发起扣款 -->
<view class="section">
<view class="section-title">发起扣款</view>
<view class="input-group">
<view class="input-item">
<text class="label">outTradeNo:</text>
<input class="input" placeholder="请输入订单号" value="{{payOutTradeNo}}" onInput="onPayOutTradeNo" />
</view>
<view class="input-item">
<text class="label">authNo:</text>
<input class="input" placeholder="请输入授权号" value="{{payAuthNo}}" onInput="onPayAuthNo" />
</view>
<button class="btn" onTap="onPay" type="primary">发起扣款</button>
</view>
</view>
<!-- 完结订单 -->
<view class="section">
<view class="section-title">完结订单</view>
<view class="input-group">
<view class="input-item">
<text class="label">outTradeNo:</text>
<input class="input" placeholder="请输入订单号" />
</view>
<view class="input-item">
<text class="label">authNo:</text>
<input class="input" placeholder="请输入授权号" />
</view>
<button class="btn" onTap="pay" type="primary">完结订单</button>
</view>
</view>
<!-- 订单退款 -->
<view class="section">
<view class="section-title">关闭订单</view>
<view class="input-group">
<view class="input-item">
<text class="label">outTradeNo:</text>
<input class="input" placeholder="请输入订单号" value="{{closeOutTradeNo}}" onInput="onCloseOutTradeNo" />
</view>
<button class="btn" onTap="close" type="primary">关闭订单</button>
</view>
</view>
<!-- 关闭订单 -->
<view class="section">
<view class="section-title">订单退款</view>
<view class="input-group">
<view class="input-item">
<text class="label">outTradeNo:</text>
<input class="input" placeholder="请输入订单号" value="{{refundOutTradeNo}}" onInput="onRefundOutTradeNo" />
</view>
<button class="btn" onTap="refund" type="primary">立即退款</button>
</view>
</view>
</view>
- index.acss
css
.container {
padding: 20px;
background-color: #f5f5f5;
}
.section {
margin-bottom: 30px;
background-color: #fff;
border-radius: 10px;
padding: 15px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.section-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
}
.input-group {
margin-top: 10px;
}
.input-item {
margin-bottom: 15px;
}
.label {
display: block;
margin-bottom: 5px;
color: #666;
}
.input {
width: 100%;
height: 40px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 0 10px;
box-sizing: border-box;
}
.btn {
margin-top: 10px;
width: 100%;
}
- index.js
js
Page({
data: {
// 发起扣款页面数据
payOutTradeNo: '',
payAuthNo: '',
// 完结订单页面数据
finishOutTradeNo: '',
finishAuthNo: '',
// 关闭订单页面数据
closeOutTradeNo: '',
closeAuthNo: '',
url: 'https://60155d5f.r26.cpolar.top',
// 订单退款
refundOutTradeNo: '',
},
// 处理输入框变化
onInputChange(e) {
const { field } = e.currentTarget.dataset;
this.setData({
[field]: e.detail.value
});
},
// 拉起芝麻免押受理台
onPullPay() {
my.request({
url: this.data.url+'/alipay/freeze',
method: 'POST',
success: (res) => {
my.tradePay({
orderStr : res.data,
success: (res) => {
my.alert({
title: '支付结果',
content: JSON.stringify(res),
});
},
fail: (e) => {
my.alert({
title: '支付失败',
content: JSON.stringify(e),
});
}
})
},
fail: (e) => {
my.hideLoading();
my.alert({
title: '请求失败',
content: JSON.stringify(e),
});
}
});
},
//发起扣款 输入
onPayOutTradeNo(e) {
this.data.payOutTradeNo = e.detail.value;
},
onPayAuthNo(e) {
this.data.payAuthNo = e.detail.value;
},
// 发起扣款
onPay() {
console.log(this.data)
my.request({
url: this.data.url+'/alipay/pay',
data : {
outTradeNo: this.data.payOutTradeNo,
authNo: this.data.payAuthNo,
},
method: 'POST',
success: (res) => {
my.tradePay({
success: (res2) => {
tradeNo: res2.data.alipay_trade_pay_response.trade_no,
my.alert({
title: '支付结果',
content: JSON.stringify(res),
});
},
fail: (e) => {
my.alert({
title: '支付失败',
content: JSON.stringify(e),
});
}
})
},
fail: (e) => {
my.hideLoading();
my.alert({
title: '请求失败',
content: JSON.stringify(e),
});
}
});
},
pay() {
if (!this.data.payOutTradeNo || !this.data.payAuthNo) {
my.alert({
title: '参数错误',
content: '请输入订单号和授权号',
});
return;
}
my.showLoading({
title: '发起扣款中...',
});
// 实际应用中替换为您的API请求
setTimeout(() => {
my.hideLoading();
my.alert({
title: '扣款成功',
content: `订单号:${this.data.payOutTradeNo}\n授权号:${this.data.payAuthNo}\n金额:0.01元`,
});
// 实际请求示例(请替换为您的API)
/*
my.request({
url: 'https://your-domain/alipay/pay',
method: 'POST',
data: {
outTradeNo: this.data.payOutTradeNo,
authNo: this.data.payAuthNo
},
success: (res) => {
my.hideLoading();
const tradeNO = res.data.data;
my.tradePay({
tradeNO,
success: (res) => {
my.alert({
title: '支付结果',
content: JSON.stringify(res),
});
},
fail: (e) => {
my.alert({
title: '支付失败',
content: JSON.stringify(e),
});
}
})
},
fail: (e) => {
my.hideLoading();
my.alert({
title: '请求失败',
content: JSON.stringify(e),
});
}
});
*/
}, 2000);
},
// 完结订单
finish() {
if (!this.data.finishOutTradeNo || !this.data.finishAuthNo) {
my.alert({
title: '参数错误',
content: '请输入订单号和授权号',
});
return;
}
my.showLoading({
title: '完结处理中...',
});
// 模拟完结订单请求
setTimeout(() => {
my.hideLoading();
my.alert({
title: '完结成功',
content: `订单号:${this.data.finishOutTradeNo}\n授权号:${this.data.finishAuthNo}`,
});
// 实际请求示例(请替换为您的API)
/*
my.request({
url: 'https://your-domain/alipay/finish',
method: 'POST',
data: {
outTradeNo: this.data.finishOutTradeNo,
authNo: this.data.finishAuthNo
},
success: (res) => {
my.hideLoading();
my.showToast({
title: '订单已完结',
icon: 'success',
});
},
fail: (e) => {
my.hideLoading();
my.alert({
title: '完结失败',
content: JSON.stringify(e),
});
}
});
*/
}, 1500);
},
//关闭扣款 输入
onCloseOutTradeNo(e) {
this.data.closeOutTradeNo = e.detail.value;
},
// 关闭订单
close() {
// 模拟关闭订单请求
my.request({
url: this.data.url+'/alipay/close',
method: 'POST',
data: {
tradeNo: this.data.closeOutTradeNo,
authNo: this.data.closeAuthNo
},
success: (res) => {
my.hideLoading();
my.showToast({
title: '订单已关闭',
icon: 'success',
});
},
fail: (e) => {
my.hideLoading();
my.alert({
title: '关闭失败',
content: JSON.stringify(e),
});
}
});
},
//订单退款
onRefundOutTradeNo(e) {
this.data.refundOutTradeNo = e.detail.value;
},
// 订单退款
refund() {
// 模拟订单退款请求
my.request({
url: this.data.url+'/alipay/refund',
method: 'POST',
data: {
outTradeNo: this.data.refundOutTradeNo,
},
success: (res) => {
my.hideLoading();
my.showToast({
title: '订单已关闭',
icon: 'success',
});
},
fail: (e) => {
my.hideLoading();
my.alert({
title: '关闭失败',
content: JSON.stringify(e),
});
}
});
}
});
6.完成效果
- 芝麻免押台

- 下单示例

- 支付示例

- 退款示例

7.键注意事项
- 证书配置:确保证书文件路径正确,推荐使用绝对路径
- 回调URL:必须为公网可访问地址,本地开发可使用内网穿透
- 参数验证:所有接口调用前应验证输入参数有效性
- 错误处理:妥善处理支付宝接口返回的错误码和错误信息
- 幂等性:资金操作类接口需保证幂等性设计
8.常见问题解决
问题现象 | 可能原因 | 解决方案 |
---|---|---|
验签失败 | 证书路径错误/证书内容不匹配 | 检查证书路径和内容 |
接口调用失败 | 参数格式错误/必填参数缺失 | 检查请求参数和文档 |
回调未收到 | 网络问题/应用网关配置错误 | 检查服务器网络和应用网关配置 |
资金操作异常 | 订单状态不满足条件 | 检查订单当前状态 |