PAYATHON 2026

React Native 集成 PhonePe PG SDK 时如何传递交易参数?

支付老李

结论

startTransaction 的四个参数分别是:

  • body:支付请求 JSON 使用 UTF-8 序列化后生成的 Base64 字符串。
  • checkSum:根据 body 和支付接口路径生成的签名。签名必须由服务端生成,不要把 saltKey 放进 React Native 应用。
  • packageName:Android 用它指定接收支付 Intent 的应用包名。如果不限定支付应用,通常传 null。iOS 通常也传 null。
  • appSchema:iOS 支付完成后返回商户 App 所用的自定义 URL Scheme。Android 通常传 null。

典型调用方式如下:

const result = await PhonePePaymentSDK.startTransaction(
  body,
  checkSum,
  Platform.OS === 'android' ? packageName : null,
  Platform.OS === 'ios' ? appSchema : null
);

PhonePe 不同版本的 PG SDK、Standard Checkout 和接口认证方案可能并不相同。下面的 SHA256(...)+###... 方式适用于使用 saltKey、saltIndex 和 /pg/v1/pay 的旧版或对应版本接口。接入时,应以当前商户后台和所用版本的 PhonePe 文档为准。

body 应包含什么

常见的支付请求数据如下:

{
  "merchantId": "YOUR_MERCHANT_ID",
  "merchantTransactionId": "ORDER_20260910_0001",
  "merchantUserId": "USER_10001",
  "amount": 10000,
  "redirectUrl": "https://merchant.example.com/payment/redirect",
  "redirectMode": "POST",
  "callbackUrl": "https://merchant.example.com/api/payment/phonepe/callback",
  "mobileNumber": "9999999999",
  "paymentInstrument": {
    "type": "PAY_PAGE"
  }
}

字段含义如下:

  • merchantId:PhonePe 分配的商户 ID。
  • merchantTransactionId:商户侧的唯一交易号。每笔支付都必须使用不同的交易号,建议直接关联商户自己的订单号。
  • merchantUserId:用户在商户系统中的标识,不应包含敏感信息。
  • amount:金额,以印度最小货币单位 paise 表示。例如 ₹100 应传 10000,不能传 100 或浮点数。
  • redirectUrl:支付页面完成后跳转的地址。
  • redirectMode:跳转请求方式,可用值以对应 API 版本为准。
  • callbackUrl:PhonePe 向商户后端发送服务端通知的地址,必须是外部可以访问的 HTTPS 地址。
  • mobileNumber:用户手机号。是否必填取决于当前接口版本和商户配置。
  • paymentInstrument.type:使用托管支付页时通常为 PAY_PAGE。

示例里出现的字段不一定在所有 PhonePe API 版本中都是必填项。实际使用哪些字段,应以接入版本的请求 Schema 为准。

如何生成 Base64 body

JSON 序列化和 Base64 编码最好都在商户后端完成:

const payload = {
  merchantId: process.env.PHONEPE_MERCHANT_ID,
  merchantTransactionId: order.transactionId,
  merchantUserId: order.userId,
  amount: order.amountInPaise,
  redirectUrl: "https://merchant.example.com/payment/redirect",
  redirectMode: "POST",
  callbackUrl: "https://merchant.example.com/api/payment/phonepe/callback",
  mobileNumber: order.mobileNumber,
  paymentInstrument: {
    type: "PAY_PAGE"
  }
};

const body = Buffer
  .from(JSON.stringify(payload), "utf8")
  .toString("base64");

签名前先将请求数据序列化:

const json = JSON.stringify(payload);

再把生成的 Base64 字符串传给 SDK:

const body = Buffer.from(json, "utf8").toString("base64");

生成签名和调用 SDK 时,必须使用同一份 Base64 字符串。签名生成后,不要重新解析、格式化或编码 JSON。空格、字段顺序或字符编码发生变化,都可能导致验签失败。

如何生成 checkSum

使用旧版 X-VERIFY 校验方式的 /pg/v1/pay 接口时,常见公式为:

SHA256(base64Body + "/pg/v1/pay" + saltKey) + "###" + saltIndex

Node.js 服务端示例:

import crypto from "node:crypto";

const apiPath = "/pg/v1/pay";
const saltKey = process.env.PHONEPE_SALT_KEY;
const saltIndex = process.env.PHONEPE_SALT_INDEX;

const hash = crypto
  .createHash("sha256")
  .update(body + apiPath + saltKey, "utf8")
  .digest("hex");

const checkSum = `${hash}###${saltIndex}`;

后端只需把 SDK 调用所需的数据返回给 React Native:

return {
  body,
  checkSum
};

不要在 React Native 代码、Android resources、iOS plist 或打包环境变量中保存 saltKey。移动端应用容易被反编译,密钥一旦泄露,攻击者就可能伪造支付请求。订单金额和交易号也应由后端按照真实订单生成,不能直接采用客户端上传的数据。

如果商户使用的是较新的 OAuth、X-MERCHANT-ID 或其他认证方案,上述校验公式便不适用。此时应按照对应版本的服务端鉴权流程生成请求。

Android 的 packageName

Android 的 packageName 是 SDK 用于定向支付 Intent 的支付应用包名,并不是商户自己的 React Native Android 包名。

不需要限定支付应用时,通常传:

const packageName = null;

如果当前 PhonePe SDK 文档明确要求定向到 PhonePe App,常见包名为:

const packageName = "com.phonepe.app";

不要仅凭这个示例硬编码包名。不同环境、应用渠道或 SDK 版本可能有不同要求,应使用 SDK 提供的应用检查能力,或者采用当前官方文档指定的包名。如果指定的应用没有安装或与 SDK 不兼容,支付应用可能无法打开。

Android 调用示例:

const result = await PhonePePaymentSDK.startTransaction(
  body,
  checkSum,
  null, // 或当前 SDK 文档要求的支付应用包名
  null
);

iOS 的 appSchema

appSchema 是商户 iOS App 注册的自定义 URL Scheme,支付流程通过它返回商户 App。它不是完整 URL,也不是 Bundle Identifier。

如果注册的 URL Scheme 是:

merchantpay

调用时应传:

const appSchema = "merchantpay";

还需要在 iOS 工程的 Info.plist 中注册同一个 Scheme,例如:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>com.example.merchant</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>merchantpay</string>
    </array>
  </dict>
</array>

iOS 调用示例:

const result = await PhonePePaymentSDK.startTransaction(
  body,
  checkSum,
  null,
  "merchantpay"
);

传入的 Scheme 必须与 Xcode 工程配置完全一致。建议使用与公司域名或 Bundle Identifier 相关的唯一值,以免与其他 App 的 Scheme 冲突。

React Native 完整调用示例

先由后端生成支付参数:

import { Platform } from "react-native";

async function startPhonePePayment(orderId) {
  const response = await fetch(
    "https://merchant.example.com/api/payment/phonepe/init",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ orderId })
    }
  );

  if (!response.ok) {
    throw new Error("创建支付请求失败");
  }

  const { body, checkSum } = await response.json();

  const packageName =
    Platform.OS === "android"
      ? null
      : null;

  const appSchema =
    Platform.OS === "ios"
      ? "merchantpay"
      : null;

  const sdkResult = await PhonePePaymentSDK.startTransaction(
    body,
    checkSum,
    packageName,
    appSchema
  );

  console.log("PhonePe SDK result:", sdkResult);
  return sdkResult;
}

也可以使用 Promise:

PhonePePaymentSDK
  .startTransaction(body, checkSum, packageName, appSchema)
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error("PhonePe SDK error:", error);
  });

不要把 SDK 返回值当作最终支付结果

startTransaction 返回 SUCCESS,通常只表示 SDK 支付流程已经结束,或者已经返回商户应用,不能单独证明资金支付成功。INTERRUPTED 可能表示用户取消、应用切换中断,也可能表示当前支付状态仍不确定。

可靠的处理流程是:

  1. 后端创建商户交易号,并生成 body 和 checkSum。
  2. React Native 调用 startTransaction。
  3. SDK 返回后,客户端通知后端查询这笔交易。
  4. 后端调用 PhonePe 的状态查询接口,同时处理 callbackUrl 收到的通知。
  5. 后端确认 PhonePe 返回的交易状态、金额和交易号都正确后,再把订单标记为已支付。

回调处理还应具备幂等性,防止 PhonePe 重复通知时重复发货或多次更新订单。生产环境和测试环境的 merchantId、密钥、接口地址及 SDK 配置也不能混用。

备注:内容仅供参考。