如何在 PhoneGap 中集成 Payment gateway?
结论
PhoneGap 应用不应直接处理银行卡信息,也不能仅靠一段 JavaScript 同时完成 Android 和 iOS 支付。更可靠的实现流程是:
- 根据商品类型判断是否必须使用 Apple In‑App Purchase 或 Google Play Billing。
- 由服务端创建支付订单和支付会话。
- PhoneGap 端调用支付服务商提供的 Cordova 插件、原生 SDK,或者打开托管收银台。
- 服务端通过支付网关的 Webhook 确认最终支付结果。
- App 向服务端查询订单状态,不直接信任客户端回调。
销售数字内容、会员、虚拟币或 App 内功能时,通常需要接入应用商店内购。对于实物商品、线下服务、捐赠等允许使用第三方支付的场景,外部 Payment Gateway 更合适。具体限制以应用商店当前的审核规则和支付服务商文档为准。
为什么需要服务端
支付网关通常需要 API Secret、商户私钥或签名密钥。这些凭据不能放在 PhoneGap 的 JavaScript、配置文件或安装包中,因为应用代码可以被反编译。
推荐架构如下:
PhoneGap App
│
│ 创建业务订单
▼
业务服务端
│
│ 使用商户密钥创建支付会话
▼
Payment Gateway
│
│ Webhook 通知支付结果
▼
业务服务端更新订单
│
│ App 查询订单状态
▼
PhoneGap App 展示结果
金额校验、订单状态转换、重复通知处理、退款和对账也应由服务端负责。
第一步:选择支付方式
接入前需要确认:
- 销售的是数字内容,还是实物或线下服务。
- 支付服务商是否支持目标国家或地区、币种和结算账户。
- 服务商是否提供 Cordova 插件或 Android、iOS 原生 SDK。
- 是否支持托管收银台、3-D Secure、Apple Pay 或 Google Pay。
- 是否允许在 WebView 中完成支付。有些支付服务商或银行明确要求使用系统浏览器。
- PhoneGap 项目所使用的 Cordova 版本是否满足插件要求。
PhoneGap 已是较老的技术栈。现有项目仍可通过 Cordova 插件维护,新项目则通常需要直接评估 Apache Cordova、Capacitor 或原生方案。
第二步:由服务端创建订单
App 只应向服务端提交商品编号、数量和订单请求,不能把客户端传入的金额直接作为最终收款金额。服务端必须根据商品数据重新计算金额。
下面是一个不依赖特定支付服务商的 Node.js 示例。gateway.createCheckoutSession() 需要替换为所选支付网关的服务端 SDK:
import express from "express";
import crypto from "node:crypto";
const app = express();
app.use(express.json());
app.post("/api/payments", async (req, res) => {
const { productId, quantity = 1 } = req.body;
// 必须从服务端数据库读取商品及价格
const product = await findProduct(productId);
if (!product || quantity < 1) {
return res.status(400).json({ error: "Invalid product" });
}
const order = await createOrder({
orderNo: crypto.randomUUID(),
productId,
quantity,
amount: product.price * quantity,
currency: product.currency,
status: "PENDING"
});
const session = await gateway.createCheckoutSession({
reference: order.orderNo,
amount: order.amount,
currency: order.currency,
successUrl: `https://pay.example.com/return?order=${order.orderNo}`,
cancelUrl: `https://pay.example.com/cancel?order=${order.orderNo}`
});
await saveGatewaySession(order.orderNo, session.id);
res.json({
orderNo: order.orderNo,
checkoutUrl: session.url
});
});
这里的 gateway、findProduct()、createOrder() 和字段名称仅用于说明架构,并非某个支付服务商的真实 API。实际参数应以该服务商当前的服务端 SDK 文档为准。
第三步:在 PhoneGap 中打开托管收银台
如果支付服务商没有合适的 Cordova 插件,托管收银台通常是成本较低的跨平台方案。可以先安装 InAppBrowser 插件:
cordova plugin add cordova-plugin-inappbrowser
设备就绪后,再请求服务端创建支付会话:
document.addEventListener("deviceready", function () {
const payButton = document.getElementById("pay-button");
payButton.addEventListener("click", async function () {
payButton.disabled = true;
try {
const response = await fetch("https://api.example.com/api/payments", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + getAccessToken()
},
body: JSON.stringify({
productId: "product_123",
quantity: 1
})
});
if (!response.ok) {
throw new Error("Unable to create payment");
}
const payment = await response.json();
localStorage.setItem("pendingOrderNo", payment.orderNo);
// _system 使用系统浏览器,兼容性和安全性通常优于普通 WebView
cordova.InAppBrowser.open(payment.checkoutUrl, "_system");
} catch (error) {
console.error(error);
alert("暂时无法发起支付,请稍后重试");
} finally {
payButton.disabled = false;
}
});
});
不要使用支付服务商未允许的 iframe,也不要通过 DOM 读取银行卡号、CVV 或支付页面内容。
第四步:处理支付返回
支付结束后,可以通过 HTTPS Universal Link、Android App Link,或者在确有需要时使用自定义 URL Scheme,将用户带回 App。
例如:
https://app.example.com/payment-result?order=ORDER_NUMBER
用户返回 App 后,URL 中的订单号只能作为查询线索,不能将 success=true 之类的参数视为付款凭证:
async function refreshPaymentStatus(orderNo) {
const response = await fetch(
"https://api.example.com/api/orders/" +
encodeURIComponent(orderNo),
{
headers: {
"Authorization": "Bearer " + getAccessToken()
}
}
);
if (!response.ok) {
throw new Error("Unable to query order");
}
const order = await response.json();
if (order.status === "PAID") {
showPaymentSuccess();
} else if (order.status === "FAILED") {
showPaymentFailure();
} else {
showPaymentPending();
}
}
Universal Link 和 App Link 的配置取决于项目使用的 Cordova 版本和深链插件。应选择仍在维护,并且明确支持当前 Android 和 iOS 版本的插件。
第五步:通过 Webhook 确认支付
用户可能关闭浏览器跳转,网络也可能中断,因此最终支付结果应由服务端根据支付网关的 Webhook 确认:
app.post(
"/api/payment-webhook",
express.raw({ type: "application/json" }),
async (req, res) => {
let event;
try {
event = gateway.verifyWebhook({
rawBody: req.body,
signature: req.headers["gateway-signature"]
});
} catch (error) {
return res.status(400).send("Invalid signature");
}
if (event.type === "payment.succeeded") {
await markOrderPaidOnce(event.reference, event.transactionId);
}
if (event.type === "payment.failed") {
await markOrderFailed(event.reference);
}
res.sendStatus(200);
}
);
gateway-signature、事件名称和验签方法也需要替换为支付服务商的真实定义。Webhook 处理必须具备幂等性,即使同一个通知重复到达,也不能重复发货或重复增加余额。
使用原生 SDK 或 Cordova 插件时
如果需要支持 Apple Pay、Google Pay、应用商店内购,或者支付服务商要求使用原生 SDK,就要选择对应的 Cordova 插件。接入过程通常包括:
- 安装插件。
- 在 Android 工程中配置 Gradle、Manifest 和返回 Activity。
- 在 iOS 工程中配置 URL Types、Associated Domains、Entitlements 或支付能力。
- 从服务端获取一次性支付参数。
- 调用插件拉起原生支付页面。
- 将返回的支付标识交给服务端验证。
- 最终仍以服务端查询或 Webhook 结果为准。
不要只根据插件名称或旧示例直接接入。使用前应检查插件最近一次更新时间、支持的 Cordova 和系统版本、原生依赖版本,以及是否由支付服务商官方维护。
安全和上线注意事项
- 商户私钥、API Secret 和 Webhook Secret 只能保存在服务端。
- 服务端必须重新计算金额、币种、税费和折扣。
- 所有接口和返回地址都应使用 HTTPS。
- 支付请求应包含不可预测且唯一的订单号。
- 创建订单、Webhook 和发货逻辑都应支持幂等处理。
- 不要记录银行卡号、CVV、完整支付令牌或敏感身份信息。
- App 从后台恢复时,应主动查询未完成订单。
- 支付中、支付失败、用户取消和结果待确认需要分别展示。
- Android 和 iOS 都要使用真机测试,不能只测试浏览器环境。
- 先使用支付服务商的测试环境,再切换到生产凭据。
- 上线前检查 App Store 和 Google Play 对当前商品类型及支付方式的最新要求。
按照”客户端发起、服务端创建、网关收款、Webhook 确认、客户端查询”的职责划分,同一套业务流程可以同时用于 Android 和 iOS。两个平台的差异主要在插件配置、深链回跳和应用商店支付政策。
备注:内容仅供参考。
