如何在 Android Studio 中集成 Phone Pay Card Payment
结论
Card Payment 不能通过“把请求发送给某个 UPI App”来完成。UPI Intent 只处理 UPI 支付。银行卡支付需要进入 PhonePe Payment Gateway 的统一收银台,再由收银台提供 Card、UPI 等支付方式。
ERROR_B2B_API_RETURNED_ERROR 并不是具体的银行卡错误。它是 Android SDK 在“PhonePe 后端拒绝或未能成功处理交易请求”时返回的通用提示。仅凭 onActivityResult 无法判断根因,还需要结合交易创建接口的响应、merchantTransactionId、PhonePe 服务端状态查询结果和商户后台日志进行排查。
正确的支付流程
调用链应当是:
Android App
↓ 请求创建订单
商户服务端
↓ 使用 merchantId / saltKey 生成支付请求和校验值
PhonePe Payment Gateway
↓ 返回可供 SDK 使用的 transaction request
Android App
↓ 启动 PhonePe PG SDK
PhonePe 收银台
↓ 用户选择 Card / UPI 等方式
商户服务端
↓ 查询交易状态并验证最终结果
Android App 展示支付结果
如果现有代码直接构造 UPI Intent,跳转到 PhonePe、Google Pay 或其他 UPI App,这条链路只能完成 UPI 支付,无法切换到银行卡支付。
Card Payment 通常不需要 Android 端调用单独的“银行卡 App”。正确做法是:
- 接入 PhonePe PG SDK 或官方托管收银台。
- 在 PhonePe 商户配置中开通 Card 支付方式。
- 由商户服务端生成合法的支付请求。
- 通过 SDK 打开统一收银台。
- 支付返回后,由服务端查询并确认交易状态。
为什么会出现 ERROR_B2B_API_RETURNED_ERROR
这个错误说明 SDK 调用 PhonePe B2B/PG 后端时收到了失败响应。常见原因有:
- 使用了 UPI Intent 请求,没有使用 PG Checkout 请求。
- 商户账户尚未开通 Card Payment。
- 混用了测试环境和生产环境的
merchantId、saltKey或saltIndex。 - 计算签名时使用了错误的 API path。
- Base64 编码内容与计算
X-VERIFY时使用的内容不一致。 merchantTransactionId重复、为空或格式不符合要求。- 金额单位错误。多数支付接口要求使用最小货币单位,例如 ₹1.00 应传
100,具体以当前 PhonePe 文档为准。 callbackUrl、redirectUrl或移动端回跳配置无效。- SDK、服务端 API 和接入文档不属于同一版本。
- 测试商户或测试卡不支持当前选择的支付方式。
- 服务端已经返回了具体的业务错误,但 Android 端只收到 SDK 的统一错误码。
因此,看到 ERROR_B2B_API_RETURNED_ERROR 时,不能直接认定 SDK 出现故障或银行卡支付失败。
Android 端应如何调用
不同版本的 PhonePe SDK 可能使用不同的类名和参数。下面采用部分旧版 Android PG SDK 文档中常见的 B2BPGRequestBuilder 写法,只用于说明调用结构。实际项目应以当前商户后台提供的 SDK 版本和官方文档为准。
String base64Body = paymentResponse.getBase64Body();
String checksum = paymentResponse.getChecksum();
String apiEndPoint = paymentResponse.getApiEndPoint();
B2BPGRequest b2BPGRequest = new B2BPGRequestBuilder()
.setData(base64Body)
.setChecksum(checksum)
.setUrl(apiEndPoint)
.build();
Intent intent = PhonePe.getImplicitIntent(
this,
b2BPGRequest,
""
);
if (intent != null) {
startActivityForResult(intent, PHONEPE_REQUEST_CODE);
} else {
// 当前设备无法处理该 Intent。
// 根据所用 SDK 的官方说明决定是否改用显式 Intent 或 Web Checkout。
}
base64Body 和 checksum 应由商户服务端生成并返回。不要在 Android App 中保存 saltKey,也不要直接在客户端生成签名。
如果使用新版 SDK,接入入口可能已经调整为新的 transaction API 或 Activity Result API。旧版示例中的请求对象、API path 和 checksum 算法不能直接套用到新版接口。
服务端生成请求的关键点
下面的代码只用于说明签名流程,不能作为固定接口定义直接复制。apiPath、请求字段和签名算法必须与当前 PhonePe 文档完全一致。
String requestJson = objectMapper.writeValueAsString(paymentRequest);
String base64Body = Base64.getEncoder()
.encodeToString(requestJson.getBytes(StandardCharsets.UTF_8));
String apiPath = "/pg/v1/pay"; // 示例;以实际接入版本为准
String stringToSign = base64Body + apiPath + saltKey;
String sha256 = sha256Hex(stringToSign);
String xVerify = sha256 + "###" + saltIndex;
需要重点核对:
X-VERIFY = SHA256(base64Body + apiPath + saltKey) + "###" + saltIndex
即使 JSON 内容看起来相同,只要出现以下任一差异,校验就可能失败:
- Android 端重新序列化了 JSON。
- Base64 使用了换行编码。
- 签名中的 API path 与实际请求地址不一致。
- 使用了错误环境的
saltKey。 saltIndex不匹配。- 完成签名后又修改了请求字段。
更稳妥的方式是由服务端一次性生成最终的 Base64 body 和 checksum,Android 端不做修改,直接传给 SDK。
不要只依赖 onActivityResult
旧版接入可能会收到类似结果:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != PHONEPE_REQUEST_CODE) {
return;
}
String errorCode = null;
if (data != null && data.getExtras() != null) {
for (String key : data.getExtras().keySet()) {
Object value = data.getExtras().get(key);
Log.d("PhonePeResult", key + " : " + value);
if ("key_error_code".equals(key) && value != null) {
errorCode = value.toString();
}
}
}
// 无论 Activity 返回什么,都应通知服务端查询交易状态。
verifyTransactionOnServer(merchantTransactionId);
}
需要注意:
RESULT_OK不代表资金一定已经到账。RESULT_CANCELED也不一定代表交易最终失败,因为支付状态可能仍是处理中。- 即使
Intent中没有数据,也要通过服务端查询交易状态。 - 最终结果应以 PhonePe 服务端状态查询和经过验证的回调为准。
- 如果 SDK 支持,新项目应使用 Activity Result API。是否可用取决于具体的 SDK 版本。
建议的排查顺序
1. 确认当前使用的支付模式
检查代码是否调用了普通 Android UPI Intent,例如:
intent.setData(Uri.parse("upi://pay?..."));
如果使用的是这种方式,Card Payment 不会出现。需要改用 PhonePe PG SDK 或托管收银台流程。
2. 确认商户是否已开通银行卡通道
即使统一收银台接入无误,如果商户配置没有启用 Card,收银台也可能不显示该选项。需要到 PhonePe 商户后台检查支付方式,或向 PhonePe 商户技术支持确认:
- Card Payment 是否已开通。
- 测试环境是否支持 Card。
- 当前
merchantId是否具备相应权限。 - 是否存在行业、账户状态或风控限制。
银行卡通道无法通过客户端代码自行开启。
3. 检查环境是否一致
以下配置必须来自同一个环境:
SDK environment
API base URL
merchantId
saltKey
saltIndex
测试账号或生产账号
使用测试凭证调用生产接口,或使用生产凭证调用测试接口,通常会直接收到后端错误。
4. 记录服务端原始响应
至少记录以下信息,但不要记录完整卡号、CVV、saltKey 或其他敏感数据:
merchantId
merchantTransactionId
API path
HTTP status
PhonePe response code
PhonePe response message
request timestamp
environment
ERROR_B2B_API_RETURNED_ERROR 只是 Android 端显示的通用错误。服务端响应中的 code、message 或 data 更有助于定位具体原因。
5. 主动查询交易状态
使用创建交易时的同一个 merchantTransactionId,从商户服务端调用对应版本的 PhonePe Status API。状态查询请求通常也需要校验签名,不能放在 Android 端完成。
示意接口:
GET /pg/v1/status/{merchantId}/{merchantTransactionId}
X-VERIFY: <server-generated-checksum>
X-MERCHANT-ID: <merchantId>
这只是旧版接口中常见的路径。如果当前文档使用其他路径、OAuth 认证或新版 Standard Checkout API,应严格按照当前版本的定义接入。
6. 将交易标识提交给 PhonePe 支持
如果已经确认请求签名、运行环境和商户权限均无误,但接口仍返回通用错误,可以向 PhonePe 支持提供:
merchantIdmerchantTransactionId- 请求时间和时区
- 测试或生产环境
- 服务端返回的错误码和消息
- SDK 版本
- Android 版本
- 已脱敏的请求结构
不要提交 saltKey、银行卡号、CVV 或用户的完整敏感信息。
安全与实现注意事项
- 不要把
saltKey、Client Secret 或其他商户密钥写入 APK。 - 不要由客户端判断订单支付成功后直接发货。
merchantTransactionId必须保持唯一,并在服务端持久化。- 验证回调签名,并防止同一回调被重复处理。
- 对
PENDING状态进行延迟查询,不要立即判定为失败。 - 为同一笔订单设计幂等逻辑,避免重复扣款或重复发货。
- 不要混用 PhonePe 的 UPI Intent、旧版 PG SDK 和新版 Checkout API 示例。
Card Payment 不能继续使用 UPI App 跳转方案。应先确认商户已经开通银行卡通道,再由服务端创建 PhonePe PG 交易并启动统一收银台。遇到 ERROR_B2B_API_RETURNED_ERROR 时,需要根据服务端原始响应和 Status API 查询结果定位具体原因。
备注:内容仅供参考。
