PAYATHON 2026

PayUMoney Android 支付成功后仅获取 paymentId

支付老李

结论

PayUMoney Android SDK 6.1.0 的 onActivityResult() 只返回 paymentId,通常不是代码解析出了问题。客户端回调只能用来判断支付流程是否已经结束,完整的交易信息需要由服务端获取并确认。

建议按以下流程处理:

  1. Android 保存自行生成的 txnid。
  2. PayUMoney 将支付结果 POST 到商户的 sUrl 或 fUrl。
  3. 商户服务端校验响应 Hash,并保存交易数据。
  4. 服务端调用 PayU 的交易查询接口,核验最终状态。
  5. Android 通过商户自己的订单查询接口获取完整交易结果。

不能仅凭 RESULT_OK 或客户端返回的 paymentId,就把订单标记为支付成功。

为什么只返回 paymentId

onActivityResult() 返回的是 SDK 层面的结果,不同版本的 SDK 写入 Intent 的字段并不完全相同。在 6.1.0 中,如果 Bundle 里只有 paymentId,Android 端遍历 Bundle 也拿不到其他字段。

下面的代码只能打印 SDK 已经返回的数据,不能让 SDK 生成额外的交易字段:

Bundle bundle = data.getExtras();

if (bundle != null) {
    for (String key : bundle.keySet()) {
        Log.d("PayU", key + ": " + bundle.get(key));
    }
}

因此,不能依赖 data.getExtras() 获取完整交易详情。这些信息应该来自服务端回调和交易查询接口。

正确的处理流程

1. 创建支付前,由服务端生成交易参数

txnid、支付 Hash,以及后续调用查询接口所需的 Hash,都应该在服务端生成。Salt 不能放进 Android 应用,否则攻击者反编译 APK 后可能获得商户密钥。

Android 可以先向自己的服务端申请支付参数:

{
  "txnid": "ORDER_1720000000000",
  "amount": "1.00",
  "key": "merchant_key",
  "hash": "generated_on_server",
  "surl": "https://example.com/payment/payu/success",
  "furl": "https://example.com/payment/payu/failure"
}

应用拿到这些参数后,再启动 PayUMoney SDK。

2. 在 sUrl 和 fUrl 接收支付结果

PayU 通常会向 sUrl 或 fUrl 发送表单形式的 POST 请求。服务端需要读取并保存收到的字段,例如:

  • txnid
  • mihpayid
  • status
  • unmappedstatus
  • amount
  • productinfo
  • firstname
  • email
  • bank_ref_num
  • error
  • error_Message
  • hash

具体有哪些字段,取决于账户接入方式和 PayU 的实际返回内容,不能假定每次都会包含全部字段。

下面是一个简化的 Spring Boot 示例:

@PostMapping(
    value = "/payment/payu/success",
    consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public ResponseEntity<String> paymentSuccess(
        @RequestParam Map<String, String> params) {

    String txnId = params.get("txnid");
    String paymentId = params.get("mihpayid");
    String status = params.get("status");
    String amount = params.get("amount");
    String responseHash = params.get("hash");

    boolean validHash = payuHashService.verifyResponseHash(params);

    if (!validHash) {
        return ResponseEntity.badRequest().body("Invalid response hash");
    }

    // 这里只记录回调,不要仅凭 status 直接完成订单。
    paymentRepository.saveCallback(
        txnId,
        paymentId,
        status,
        amount,
        params
    );

    // 随后由服务端查询 PayU,确认最终交易状态。
    paymentVerificationService.verifyAsync(txnId);

    return ResponseEntity.ok("Payment response received");
}

失败地址同样需要接收并校验响应:

@PostMapping(
    value = "/payment/payu/failure",
    consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public ResponseEntity<String> paymentFailure(
        @RequestParam Map<String, String> params) {

    if (!payuHashService.verifyResponseHash(params)) {
        return ResponseEntity.badRequest().body("Invalid response hash");
    }

    paymentRepository.saveCallback(
        params.get("txnid"),
        params.get("mihpayid"),
        params.get("status"),
        params.get("amount"),
        params
    );

    return ResponseEntity.ok("Failure response received");
}

响应 Hash 的拼接顺序必须遵循当前 PayUMoney 或 PayU 商户账户及 SDK 版本对应的官方规则,不能自行猜测。

3. 服务端主动查询交易状态

收到回调后,服务端应该使用原始 txnid 调用 PayU 的交易验证接口,例如当前接入版本中的 verify_payment 接口。查询接口通常需要商户 Key、命令、交易号,以及使用 Salt 计算出的 Hash。

常见的请求逻辑如下:

command = verify_payment
var1    = 原始 txnid
hash    = SHA-512(key|command|var1|salt)

Java Hash 计算示例:

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

public final class PayUHash {

    public static String sha512(String value) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-512");
            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();
        } catch (Exception e) {
            throw new IllegalStateException("Unable to calculate SHA-512", e);
        }
    }

    public static String verificationHash(
            String key,
            String command,
            String txnId,
            String salt) {

        return sha512(key + "|" + command + "|" + txnId + "|" + salt);
    }
}

接口地址、请求格式和 Hash 规则可能会因 PayUMoney 产品线及账户接入方式而异。SDK 6.1.0 是较旧的版本,应以商户账户对应的 PayU 文档或后台提供的接口为准,尤其不能混用 PayUMoney 旧接口和新版 PayU India 接口。

Android 端如何取得最终详情

Android 收到 SDK 回调后,可以把 paymentId 作为流程标识,但仍需通过自己的服务端查询订单:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode !=
            PayUmoneySdkInitilizer.PAYU_SDK_PAYMENT_REQUEST_CODE) {
        return;
    }

    if (resultCode == RESULT_OK) {
        String paymentId = null;

        if (data != null) {
            paymentId = data.getStringExtra("paymentId");
        }

        // 向自己的服务端查询 txnId 对应的最终状态。
        queryOrderFromBackend(txnId, paymentId);
    } else if (resultCode ==
            PayUmoneySdkInitilizer.RESULT_FAILED) {

        // 即使客户端显示失败,也可让服务端再次核验,
        // 以处理支付已完成但客户端回调异常的情况。
        queryOrderFromBackend(txnId, null);
    }
}

服务端返回给 Android 的数据可以整理成以下格式:

{
  "txnid": "ORDER_1720000000000",
  "paymentId": "123456789",
  "status": "success",
  "amount": "1.00",
  "bankReferenceNumber": "987654321",
  "verified": true
}

只有服务端完成 Hash 校验,并通过交易查询接口确认金额、交易号和状态全部一致后,应用才能显示”支付成功”。

当前代码还需要修正的地方

status 判断条件错误

原代码是:

if (status != null || status.equals("1")) {

这里使用 || 会带来两个问题:

  • 只要 status 不为 null,即使它不是 "1",代码也会进入成功分支。
  • 当 status 为 null 时,代码仍可能执行 status.equals("1"),从而触发 NullPointerException。

应改为:

if ("1".equals(status)) {

或者:

if (status != null && status.equals("1")) {

第一种写法更简洁,也能避免空指针。

不要在客户端请求测试 Hash 服务

下面的地址只适用于示例或测试:

https://test.payumoney.com/payment/op/calculateHashForTest

生产环境不应依赖公开的测试 Hash 服务。支付 Hash 应由商户自己的服务端计算,Salt 也必须只保存在服务端。

测试和生产配置必须一致

以下配置必须来自同一个环境:

  • Merchant Key
  • Merchant ID
  • Salt
  • Hash 生成接口
  • 支付环境
  • 交易查询接口
  • isDebug 设置

如果测试 Key 配合生产接口使用,或者生产 Key 配合测试接口使用,Hash 校验、支付请求或交易查询都可能失败。

注意事项

  • RESULT_OK 只表示 SDK 流程成功返回,不代表订单已经通过服务端核验。
  • 不要信任 Android 上传的金额、支付状态或商品信息,服务端应将其与订单记录进行比较。
  • 同一个 txnid 不能重复用于多次独立支付。
  • 回调可能重复到达,因此服务端必须进行幂等处理。
  • 网络中断时,支付可能已经完成,但 Android 没有收到正常结果,所以系统必须支持按 txnid 补查。
  • 在部分响应中,paymentId 可能对应 mihpayid,具体映射应以实际接口响应为准。
  • SDK 6.1.0 已经较旧。如果准备升级 SDK,需要同时核对回调字段、Hash 规则和交易查询接口,不能只替换依赖版本。

Android 负责发起支付并展示结果。服务端负责接收回调、校验 Hash、查询交易详情,并决定订单的最终状态。即使 SDK 只返回 paymentId,按照这一流程也能可靠地获取并保存完整的 Transaction Details。

备注:内容仅供参考。