PAYATHON 2026

如何在 PhoneGap 中集成 Payment gateway?

支付老李

结论

PhoneGap 应用不应直接处理银行卡信息,也不能仅靠一段 JavaScript 同时完成 Android 和 iOS 支付。更可靠的实现流程是:

  1. 根据商品类型判断是否必须使用 Apple In‑App Purchase 或 Google Play Billing。
  2. 由服务端创建支付订单和支付会话。
  3. PhoneGap 端调用支付服务商提供的 Cordova 插件、原生 SDK,或者打开托管收银台。
  4. 服务端通过支付网关的 Webhook 确认最终支付结果。
  5. 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 插件。接入过程通常包括:

  1. 安装插件。
  2. 在 Android 工程中配置 Gradle、Manifest 和返回 Activity。
  3. 在 iOS 工程中配置 URL Types、Associated Domains、Entitlements 或支付能力。
  4. 从服务端获取一次性支付参数。
  5. 调用插件拉起原生支付页面。
  6. 将返回的支付标识交给服务端验证。
  7. 最终仍以服务端查询或 Webhook 结果为准。

不要只根据插件名称或旧示例直接接入。使用前应检查插件最近一次更新时间、支持的 Cordova 和系统版本、原生依赖版本,以及是否由支付服务商官方维护。

安全和上线注意事项

  • 商户私钥、API Secret 和 Webhook Secret 只能保存在服务端。
  • 服务端必须重新计算金额、币种、税费和折扣。
  • 所有接口和返回地址都应使用 HTTPS。
  • 支付请求应包含不可预测且唯一的订单号。
  • 创建订单、Webhook 和发货逻辑都应支持幂等处理。
  • 不要记录银行卡号、CVV、完整支付令牌或敏感身份信息。
  • App 从后台恢复时,应主动查询未完成订单。
  • 支付中、支付失败、用户取消和结果待确认需要分别展示。
  • Android 和 iOS 都要使用真机测试,不能只测试浏览器环境。
  • 先使用支付服务商的测试环境,再切换到生产凭据。
  • 上线前检查 App Store 和 Google Play 对当前商品类型及支付方式的最新要求。

按照”客户端发起、服务端创建、网关收款、Webhook 确认、客户端查询”的职责划分,同一套业务流程可以同时用于 Android 和 iOS。两个平台的差异主要在插件配置、深链回跳和应用商店支付政策。

备注:内容仅供参考。