如何在 Android application 中实现 credit card payment
明确结论
Android 没有用于“验证信用卡并完成扣款”的通用 API。应用需要接入具备收单能力的支付服务商,例如 Stripe、Adyen、Braintree,或所在地区合规的支付机构,并使用其 Android SDK、托管支付页面或 Payment Sheet。
不要在应用内自行判断卡号是否有效,也不要把卡号和 CVV 直接发送到普通业务服务器。卡片能否用于支付,最终取决于支付服务商、收单行和发卡行的授权结果。
推荐的支付流程
完整的支付流程通常如下:
- Android 向业务后端请求创建支付订单。
- 后端根据商品和订单记录确定金额,再调用支付服务商 API 创建支付对象。
- 后端向 Android 返回一次性的客户端支付参数。
- Android 使用支付服务商 SDK 展示安全的卡片输入界面。
- SDK 将卡片信息直接提交给支付服务商,并完成 3D Secure 等身份验证。
- 支付服务商向客户端返回授权结果,同时通过 Webhook 通知后端。
- 后端验证 Webhook 签名,并根据最终支付状态更新订单。
- Android 从后端查询订单状态,展示成功、失败或处理中。
Android App
│
├── 创建业务订单 ──────────────> 业务后端
│ │
│ ├── 创建支付 ──> 支付服务商
│ │
│<──────── 返回客户端支付参数 ──────┤
│
├── SDK 收集卡片并确认支付 ───────> 支付服务商
│ │
│ ├── 银行授权 / 3D Secure
│ │
│ └── Webhook ──> 业务后端
│
└── 查询订单最终状态 ─────────────> 业务后端
Android 端示例结构
不同服务商和 SDK 版本使用的类名可能不同。下面用抽象接口说明客户端需要负责的工作。生产代码应改用所选支付服务商官方 SDK 提供的 Payment Sheet 或托管组件。
data class CreatePaymentRequest(
val orderId: String
)
data class CreatePaymentResponse(
val clientToken: String
)
interface PaymentApi {
@POST("payments")
suspend fun createPayment(
@Body request: CreatePaymentRequest
): CreatePaymentResponse
@GET("orders/{orderId}")
suspend fun getOrder(
@Path("orderId") orderId: String
): OrderResponse
}
用户点击支付按钮后,先请求后端创建支付:
lifecycleScope.launch {
try {
payButton.isEnabled = false
val payment = paymentApi.createPayment(
CreatePaymentRequest(orderId = orderId)
)
paymentSdk.presentPaymentSheet(
activity = this@CheckoutActivity,
clientToken = payment.clientToken
)
} catch (e: Exception) {
showMessage("暂时无法发起支付,请稍后重试")
payButton.isEnabled = true
}
}
支付组件返回结果后,客户端可以更新界面,但不能仅根据客户端回调把订单标记为已支付:
fun handlePaymentResult(result: PaymentResult) {
when (result) {
PaymentResult.Completed -> {
showMessage("支付已提交,正在确认结果")
refreshOrderFromServer()
}
PaymentResult.Canceled -> {
showMessage("支付已取消")
payButton.isEnabled = true
}
is PaymentResult.Failed -> {
showMessage(result.displayMessage ?: "支付失败,请检查卡片或更换支付方式")
payButton.isEnabled = true
}
}
}
这里的 paymentSdk 和 PaymentResult 只是示意名称,并非 Android 标准 API。实际接入时,必须使用服务商当前官方文档提供的类和方法。
后端必须完成的工作
支付金额不能由 Android 客户端直接决定,因为客户端数据可能被修改。后端应根据 orderId 查询数据库并重新计算金额。
下面是与具体支付服务商无关的伪代码:
app.post("/payments", async (req, res) => {
const order = await database.orders.findById(req.body.orderId);
if (!order || order.userId !== req.user.id) {
return res.status(404).json({ error: "ORDER_NOT_FOUND" });
}
if (order.status === "PAID") {
return res.status(409).json({ error: "ORDER_ALREADY_PAID" });
}
const amount = calculateAmountFromServerData(order);
const payment = await paymentProvider.createPayment({
amount,
currency: order.currency,
reference: order.id,
idempotencyKey: `order:${order.id}`
});
await database.orders.savePaymentReference(
order.id,
payment.id
);
res.json({
clientToken: payment.clientToken
});
});
后端还需要接收 Webhook:
app.post("/payment-webhook", rawBodyMiddleware, async (req, res) => {
const event = paymentProvider.verifyWebhook(
req.rawBody,
req.headers,
process.env.PAYMENT_WEBHOOK_SECRET
);
if (event.type === "payment.succeeded") {
await markOrderAsPaidOnce(event.reference, event.paymentId);
} else if (event.type === "payment.failed") {
await markOrderAsFailed(event.reference, event.paymentId);
}
res.sendStatus(200);
});
Webhook 必须使用支付服务商提供的方法验证签名,不能只相信请求体中的状态。更新订单时也要实现幂等处理,避免服务商重复发送通知,进而造成重复发货或重复记账。
“验证信用卡”具体指什么
这里需要区分两类验证。
本地格式检查
客户端可以检查:
- 卡号是否符合基本长度要求
- Luhn 校验是否通过
- 有效期是否已经过去
- 必填字段是否完整
这些检查只能改善表单填写体验,无法证明卡片真实存在,也无法判断余额、信用额度、挂失状态,或银行是否会批准这笔交易。
银行授权
真正的接受或拒绝发生在支付授权阶段。支付服务商会把请求发送到银行卡网络和发卡行,可能得到以下结果:
- 支付成功
- 卡片被拒绝
- 余额或额度不足
- 需要 3D Secure 验证
- 网络超时,状态暂时未知
- 支付仍在处理中
因此,支付状态不适合简单设计为同步返回的 true 或 false。业务订单至少要考虑 PENDING、PAID、FAILED 和 CANCELED 等状态。
安全与合规注意事项
不要自行保存敏感卡片数据
通常不应保存:
- 完整卡号
- CVV/CVC
- 磁道数据
- PIN
这些信息尤其不能写入日志、崩溃报告、分析平台或截图。即使卡号通过 HTTPS 传输,只要数据经过自己的服务器,仍可能显著扩大 PCI DSS 的合规范围。
如果需要支持“保存银行卡”,应保存支付服务商返回的 token 或 payment method identifier,而不是原始卡号。
使用 HTTPS 和后端密钥
支付服务商的 secret key 只能保存在后端,不能写入 APK。APK 可以被反编译,因此任何打包进客户端的长期密钥都应视为可被公开获取。
Android 端通常只保存服务商允许公开使用的 publishable key,以及后端临时返回的一次性支付参数。
以后端最终状态为准
客户端可能被篡改,也可能在支付完成后断网或被系统终止。因此:
- 不要根据客户端显示的“成功”页面直接发货。
- 使用经过签名验证的 Webhook 更新支付状态。
- 客户端恢复后应从后端重新查询订单。
- 创建支付、处理回调和发货操作都要实现幂等。
支持强客户认证
部分国家和地区要求 SCA 或 3D Secure。选择支付 SDK 时,需要确认它可以处理页面跳转、验证界面,以及应用返回后的状态恢复。不要自行模拟这些流程。
如何选择 API 和教程
优先查看支付服务商当前官方文档中的以下内容:
- Android SDK quickstart
- Payment Sheet 或 Drop-in UI
- Create payment / payment intent
- 3D Secure
- Webhook signature verification
- Idempotency
- Test cards 和 sandbox
- PCI DSS integration guide
如果业务只需要银行卡付款,通常可以优先采用 Payment Sheet 或托管支付页面。与自行绘制卡片表单相比,这些方案更容易处理校验、令牌化、3D Secure 和合规要求。
Google Pay 可以改善支付体验,但它属于钱包和支付信息传递方式,并不等同于完整的收单后端。实际扣款通常仍要配合支付服务商完成。
上线前检查
在生产环境开放真实付款前,至少要完成以下检查:
- 使用服务商 sandbox 和官方测试卡覆盖成功、拒绝、3D Secure、超时和取消场景。
- 验证 Webhook 签名,并妥善处理重复、延迟和乱序通知。
- 确认金额和币种完全由后端计算。
- 确认 secret key 和 Webhook secret 没有进入 APK 或代码仓库。
- 检查日志和监控系统,确保它们不会采集卡号、CVV 等敏感字段。
- 根据业务所在地、用户所在地和支付服务商要求,评估 PCI DSS、隐私和税务合规。
- 使用真实的小额交易完成上线前的端到端验证,并准备退款和争议处理流程。
备注:内容仅供参考。
