1. Antom支付系统对接实战指南
在跨境电商和国际化业务场景中,支付系统的对接往往是技术实现的关键难点之一。Antom作为国际化的支付解决方案,提供了标准化的API接口和Java SDK,但其官方文档多为英文且细节分散,实际开发中会遇到不少"坑"。本文将基于真实项目经验,详细解析如何在Spring Boot项目中完整对接Antom支付系统。
1.1 核心组件与架构设计
Antom支付对接的核心在于正确处理以下几个模块:
- 支付会话管理:创建临时支付会话,生成支付页面URL
- 异步通知处理:接收支付结果回调并验证签名
- 订单状态查询:主动查询支付状态
- 退款流程:处理退款请求及状态跟踪
典型的系统架构如下图所示(省略图示,用文字描述):
前端发起支付请求 → Spring Boot后端生成支付会话 → 跳转Antom支付页面 → 用户完成支付 → Antom回调通知 → 后端更新订单状态。整个过程需要处理货币转换、跨境结算、异常重试等复杂场景。
2. 环境准备与基础配置
2.1 Maven依赖配置
首先在pom.xml中添加SDK依赖,建议使用最新稳定版:
xml复制<dependency>
<groupId>com.alipay.global.sdk</groupId>
<artifactId>global-open-sdk-java</artifactId>
<version>2.1.12</version>
</dependency>
注意:实际项目中应该将版本号提取到properties部分管理,方便统一升级。同时建议添加jacoco-maven-plugin进行代码覆盖率测试,因为支付模块对代码质量要求极高。
2.2 关键配置参数
在application.yml中配置必要参数:
yaml复制antom:
client-id: "your-client-id" # 从Antom商户后台获取
server-public-key: "MII...AB" # Antom提供的公钥
client-private-key: "MII...AQAB" # 商户自己的私钥
endpoint: "SG" # 接入区域:SG(新加坡)、US(美国)、HK(香港)等
notify-url: "https://yourdomain.com/api/payment/notify" # 支付结果回调地址
redirect-url: "https://yourdomain.com/payment/result" # 支付完成跳转地址
参数获取要点:
- 公私钥需使用2048位RSA密钥,推荐用OpenSSL生成
- 回调地址必须在Antom商户后台预先配置白名单
- 不同区域的Endpoint对应不同的服务器集群,选择离你用户最近的区域
3. 支付会话创建实现
3.1 服务层核心代码
创建AntomPaymentService作为支付核心服务:
java复制@Service
public class AntomPaymentService {
@Value("${antom.server-public-key}")
private String serverPublicKey;
@Value("${antom.client-private-key}")
private String clientPrivateKey;
@Value("${antom.client-id}")
private String clientId;
@Value("${antom.endpoint}")
private String endpoint;
private AlipayClient alipayClient;
@PostConstruct
public void init() {
this.alipayClient = new DefaultAlipayClient(
EndPointConstants.valueOf(endpoint),
clientPrivateKey,
serverPublicKey,
clientId
);
}
public AlipayClient getClient() {
return alipayClient;
}
}
关键点说明:
- 使用@PostConstruct初始化AlipayClient,避免重复创建
- EndPointConstants需与配置的region严格对应
- 私钥需保留完整的PKCS#8格式(包含BEGIN/END标记)
3.2 控制器层实现
PaymentController处理支付请求:
java复制@PostMapping("/createSession")
public Result<PaymentSessionResponse> createSession(@Valid @RequestBody CreateSessionRequest request) {
try {
AlipayPaymentSessionRequest alipayRequest = new AlipayPaymentSessionRequest();
// 设置产品代码(固定值)
alipayRequest.setProductCode(ProductCodeType.CASHIER_PAYMENT);
// 构建支付金额
Amount paymentAmount = new Amount();
paymentAmount.setCurrency(request.getCurrency());
paymentAmount.setValue(AmountUtils.toFiatAmount(request.getAmount()));
alipayRequest.setPaymentAmount(paymentAmount);
// 设置订单信息
Order order = new Order();
order.setReferenceOrderId(request.getOrderId());
order.setOrderDescription(request.getDescription());
alipayRequest.setOrder(order);
// 执行请求
AlipayPaymentSessionResponse response = alipayClient.execute(alipayRequest);
return Result.success(convertResponse(response));
} catch (AlipayApiException e) {
log.error("Antom API error", e);
return Result.failed("PAYMENT_ERROR", e.getMessage());
}
}
金额处理特别注意:
- Antom要求金额以"分"为单位传递(如$10.00应传1000)
- 使用工具类处理金额转换:
java复制public class AmountUtils { public static String toFiatAmount(BigDecimal amount) { return amount.multiply(new BigDecimal(100)).setScale(0).toString(); } }
4. 异步通知处理
4.1 通知接口实现
java复制@PostMapping("/notify")
public String handleNotify(
@RequestBody String body,
@RequestHeader("signature") String signature,
@RequestHeader("request-time") String requestTime) {
try {
// 1. 验证签名
boolean valid = AlipaySignature.rsaCheck(
body,
serverPublicKey,
"UTF-8",
signature);
if (!valid) {
throw new RuntimeException("Invalid signature");
}
// 2. 解析通知内容
PaymentNotify notify = JSON.parseObject(body, PaymentNotify.class);
// 3. 处理业务逻辑
paymentService.processNotify(notify);
return "success";
} catch (Exception e) {
log.error("处理支付通知失败", e);
return "fail";
}
}
安全注意事项:
- 必须验证签名,防止伪造请求
- 处理逻辑需要幂等设计,同个通知可能重复发送
- 成功必须返回"success"字符串(区分大小写)
4.2 通知参数解析
典型通知报文示例:
json复制{
"paymentRequestId": "20231125123456",
"paymentId": "P123456789",
"paymentAmount": {
"currency": "USD",
"value": "1000"
},
"paymentTime": "2023-11-25T12:34:56+08:00",
"paymentStatus": "SUCCESS"
}
建议使用专门DTO类接收:
java复制@Data
public class PaymentNotify {
private String paymentRequestId;
private String paymentId;
private Amount paymentAmount;
private String paymentTime;
private String paymentStatus;
// 其他字段...
}
5. 支付状态查询与退款
5.1 支付状态查询
java复制public PaymentQueryResponse queryPayment(String paymentRequestId) {
AlipayPaymentQueryRequest request = new AlipayPaymentQueryRequest();
request.setPaymentRequestId(paymentRequestId);
AlipayPaymentQueryResponse response = alipayClient.execute(request);
PaymentQueryResponse result = new PaymentQueryResponse();
result.setStatus(response.getPaymentStatus());
result.setPaymentTime(response.getPaymentTime());
// 其他字段映射...
return result;
}
状态处理要点:
- 主要状态:SUCCESS、FAILED、PROCESSING
- 对于PROCESSING状态需要设置定时任务轮询
- 建议添加本地订单状态缓存,减少API调用
5.2 退款流程实现
java复制public RefundResponse refund(RefundRequest request) {
AlipayRefundRequest alipayRequest = new AlipayRefundRequest();
alipayRequest.setPaymentId(request.getPaymentId());
alipayRequest.setRefundRequestId(UUID.randomUUID().toString());
Amount refundAmount = new Amount();
refundAmount.setCurrency(request.getCurrency());
refundAmount.setValue(AmountUtils.toFiatAmount(request.getAmount()));
alipayRequest.setRefundAmount(refundAmount);
AlipayRefundResponse response = alipayClient.execute(alipayRequest);
return convertRefundResponse(response);
}
退款注意事项:
- 退款金额不能超过原支付金额
- 部分退款需要商户后台开启权限
- 国际退款通常需要3-15个工作日到账
6. 常见问题与解决方案
6.1 签名验证失败
典型症状:
- 收到通知但签名验证不通过
- 创建会话时返回"INVALID_SIGNATURE"
排查步骤:
- 确认公私钥配对正确(使用OpenSSL验证)
- 检查密钥格式是否为PKCS#8
- 验证签名算法是否为SHA256WithRSA
- 检查报文是否在签名前被修改(空格/换行符问题)
6.2 金额格式错误
错误示例:
code复制{
"code": "ILLEGAL_PARAMETER",
"message": "Invalid paymentAmount value"
}
正确处理:
- 金额必须为整数(单位为分)
- 使用字符串传递而非数值类型
- 货币代码必须符合ISO 4217标准
6.3 异步通知未收到
排查清单:
- 检查商户后台配置的通知URL
- 验证服务器是否可被外网访问
- 查看Antom的发送记录(需联系技术支持)
- 检查防火墙/安全组设置
7. 性能优化建议
7.1 客户端缓存策略
java复制@Configuration
public class AntomConfig {
@Bean
public AlipayClient alipayClient() {
return new DefaultAlipayClient(
EndPointConstants.SG,
privateKey,
publicKey,
clientId
);
}
@Bean
public CacheManager cacheManager() {
return new CaffeineCacheManager("antomCache");
}
}
建议缓存:
- 支付会话数据(有效期15分钟)
- 订单查询结果(短期缓存)
- 汇率信息(定时更新)
7.2 异步处理设计
对于非实时性操作:
- 使用@Async处理通知逻辑
- 退款操作放入消息队列
- 状态查询使用定时任务
java复制@Async("paymentExecutor")
public void processNotifyAsync(PaymentNotify notify) {
// 处理逻辑...
}
7.3 日志监控方案
推荐日志格式:
code复制[AntomPay] [2023-11-25 12:34:56] [ORDER_12345]
- Action: CreateSession
- Status: Success
- Cost: 245ms
- RequestId: xyz123
监控指标:
- API成功率
- 平均响应时间
- 通知延迟时间
8. 安全最佳实践
-
密钥管理:
- 私钥必须加密存储
- 生产环境和测试环境使用不同密钥对
- 定期轮换密钥(建议每6个月)
-
请求验证:
java复制public void validateRequest(CreateSessionRequest request) { if (request.getAmount().compareTo(MAX_AMOUNT) > 0) { throw new IllegalArgumentException("金额超过限额"); } // 其他验证规则... } -
防重放攻击:
- 检查paymentRequestId唯一性
- 实现nonce校验机制
- 设置合理的请求时间窗口(如5分钟)
-
敏感数据保护:
- 日志脱敏处理
- 不返回完整卡号等敏感信息
- 数据库加密存储
在实际项目中,我们还需要考虑货币转换、跨境税费计算、多语言支持等复杂场景。Antom支付对接看似简单,但魔鬼藏在细节中。建议在正式上线前完成:
- 全流程沙箱测试
- 压力测试(特别是大促场景)
- 制定完备的对账机制
- 设计人工干预流程
通过以上实现,我们构建了一个健壮的Antom支付集成方案,可以满足大多数跨境电商的支付需求。对于更复杂的场景,如分账、组合支付等,可以在本方案基础上进行扩展。