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 可能表示用户取消、应用切换中断,也可能表示当前支付状态仍不确定。
可靠的处理流程是:
- 后端创建商户交易号,并生成
body和checkSum。 - React Native 调用
startTransaction。 - SDK 返回后,客户端通知后端查询这笔交易。
- 后端调用 PhonePe 的状态查询接口,同时处理
callbackUrl收到的通知。 - 后端确认 PhonePe 返回的交易状态、金额和交易号都正确后,再把订单标记为已支付。
回调处理还应具备幂等性,防止 PhonePe 重复通知时重复发货或多次更新订单。生产环境和测试环境的 merchantId、密钥、接口地址及 SDK 配置也不能混用。
备注:内容仅供参考。
