如何通过 Realex Payment API 获取订单支付详情
结论
Realex Payment(现属 Global Payments)支持查询交易状态。已知 orderId 时,通常可通过 Transaction Status API(请求类型一般为 txn-status)查询 Realex 保存的交易结果,再结合响应中的交易状态、pasref、授权码和结算相关字段,判断款项是否已经 captured。
授权成功并不等于已经 captured。响应中的 result=00、authcode 或 pasref 只能说明交易已成功授权或受理,不能单独证明资金已经进入结算流程。判断交易是否 captured,需要结合商户账户采用的交易模式,以及状态查询响应中的结算状态。
如果商户账户尚未开通 Transaction Status API,或该接口返回的字段不足以完成对账,则需要申请 Realex/Global Payments 的 Reporting API、结算报告或 RealControl 报表权限。
推荐的处理流程
发送支付请求时,至少保存以下信息:
- 商户自己的
orderId - 请求发送时间和金额
- 币种
- Realex 返回的
pasref authcode- 原始响应代码
- 当前支付状态
支付成功后应立即将结果写入数据库。如果遇到网络超时、应用崩溃或数据库写入失败,不要直接重新扣款,应按以下步骤处理:
- 使用原始
orderId调用 Transaction Status API。 - 验证 Realex 响应签名。
- 核对响应中的金额、币种、订单号和商户账户。
- 确认交易已处于 captured、settled 或该账户定义的等价状态。
- 以
orderId或pasref为依据,幂等地补写支付记录。 - 保存完整的查询响应,供审计和对账使用。
Java 示例
以下代码使用 Java 11 的 HttpClient 展示基本调用方式。不同 Realex 接入版本的 XML 字段和签名串可能有所不同,因此 buildStatusXml 中签名字段的顺序必须以商户当前使用的官方集成文档为准,不要凭经验修改。
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.LocalDateTime;
public final class RealexStatusClient {
private final String endpoint;
private final String merchantId;
private final String account;
private final String sharedSecret;
private final HttpClient httpClient;
public RealexStatusClient(
String endpoint,
String merchantId,
String account,
String sharedSecret) {
this.endpoint = endpoint;
this.merchantId = merchantId;
this.account = account;
this.sharedSecret = sharedSecret;
this.httpClient = HttpClient.newHttpClient();
}
public String queryTransaction(String orderId) throws Exception {
String timestamp = LocalDateTime.now(ZoneOffset.UTC)
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
String requestXml = buildStatusXml(timestamp, orderId);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Content-Type", "application/xml; charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString(
requestXml,
StandardCharsets.UTF_8))
.build();
HttpResponse<String> response = httpClient.send(
request,
HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IllegalStateException(
"Realex HTTP error: " + response.statusCode());
}
return response.body();
}
private String buildStatusXml(String timestamp, String orderId)
throws Exception {
/*
* 注意:
* Realex 的签名字段、字段顺序及二次哈希规则可能随接口版本而异。
* 下面展示的是常见的“双重 SHA-1”形式,正式接入时必须按照
* 当前账户对应的 Transaction Status API 文档生成签名。
*/
String firstHashSource =
timestamp + "." + merchantId + "." + orderId;
String firstHash = sha1Hex(firstHashSource);
String signature = sha1Hex(firstHash + "." + sharedSecret);
return """
<request timestamp="%s" type="txn-status">
<merchantid>%s</merchantid>
<account>%s</account>
<orderid>%s</orderid>
<sha1hash>%s</sha1hash>
</request>
""".formatted(
xmlEscape(timestamp),
xmlEscape(merchantId),
xmlEscape(account),
xmlEscape(orderId),
xmlEscape(signature));
}
private static String sha1Hex(String value) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
byte[] bytes = digest.digest(value.getBytes(StandardCharsets.UTF_8));
StringBuilder result = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
result.append(String.format("%02x", b & 0xff));
}
return result.toString();
}
private static String xmlEscape(String value) {
return value
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
}
调用示例:
public class ReconciliationJob {
public static void main(String[] args) throws Exception {
RealexStatusClient client = new RealexStatusClient(
"https://REAL_REALex_ENDPOINT",
System.getenv("REALEX_MERCHANT_ID"),
System.getenv("REALEX_ACCOUNT"),
System.getenv("REALEX_SHARED_SECRET"));
String responseXml = client.queryTransaction("ORDER-20260913-10001");
// 生产环境中应使用安全配置的 XML 解析器解析响应,
// 验证响应签名后,再提取状态、pasref、authcode、金额和币种。
System.out.println(responseXml);
}
}
示例没有写死生产地址。Realex 的测试环境、生产环境以及不同产品线可能使用不同端点,正确地址应从当前商户账户的集成文档或后台配置中获取。
保存支付记录时的幂等处理
对账补录必须保证幂等。建议为数据库中的 merchant_id + order_id 或 pasref 建立唯一约束,并在同一个事务中完成查询和写入。
@Transactional
public void saveRecoveredPayment(RealexTransaction transaction) {
paymentRepository.findByMerchantIdAndOrderId(
transaction.merchantId(),
transaction.orderId()
).ifPresentOrElse(
existing -> {
existing.updateFromGateway(transaction);
paymentRepository.save(existing);
},
() -> paymentRepository.save(
PaymentRecord.fromGateway(transaction)
)
);
}
补录前至少要核对以下条件:
boolean canReconcile =
expectedOrderId.equals(transaction.orderId())
&& expectedAmount.equals(transaction.amount())
&& expectedCurrency.equals(transaction.currency())
&& transaction.responseSignatureValid()
&& transaction.isCaptured();
isCaptured() 不能只检查响应代码是否为 00,而应根据实际接口返回的结算状态实现。例如,某些接入模式会区分 authorized、captured、settled、voided 和 rebated。
注意事项
不要通过再次扣款来确认状态
超时后再次提交授权或扣款请求,可能产生重复交易。应先查询原订单的状态。如果确实需要重试,也必须沿用既定的幂等策略,并遵守 Realex 对重复 orderId 的处理规则。
验证响应签名
不能仅凭 HTTPS 返回的 XML 判断响应可信。应按照对应版本的 Realex 文档重新计算响应签名,并通过恒定时间比较方式完成校验。签名校验失败时,不得将响应写入正式支付记录。
不要把授权成功当作 captured
在自动结算模式下,授权成功后可能自动进入结算,但授权和 captured 仍是两个不同的状态。如果使用延迟结算模式,通常还需要执行 settle/capture 操作。实际行为由商户账户配置决定。
不要记录敏感卡数据
对账记录通常只需保存 orderId、pasref、状态、金额、币种、授权码、时间和脱敏后的卡片信息。不要保存 CVV、完整卡号或其他不必要的持卡人数据。
优先使用官方 SDK
如果项目当前使用的 Realex Java SDK 已提供 Transaction Status 请求对象,应优先通过 SDK 完成 XML 构造、签名和响应解析。不同年代的 Realex SDK 包名和方法名并不一致,在没有确认具体 SDK 版本前,不应假定某个 Java 类一定存在。
定期全量对账时,Transaction Status API 更适合查询已知订单。若要批量发现遗漏交易,通常应使用 Reporting API、结算报告或后台导出数据。
备注:内容仅供参考。