PAYATHON 2026

如何将 Payment Gateway 集成到 NativeScript App

支付老李

结论

不要在 NativeScript 前端直接集成 Payment Gateway,也不要把”是否有开源 JavaScript API”作为首要选型标准。

更合适的结构通常是:

  1. NativeScript App 请求业务后端创建订单。
  2. 后端调用 Payment Gateway 的服务端 API。
  3. App 通过支付服务商提供的原生 SDK、Hosted Checkout 或安全的浏览器跳转完成付款。
  4. Payment Gateway 使用 Webhook 将最终支付结果通知后端。
  5. App 从后端查询订单状态。

JavaScript 库能够运行,不代表它能安全、完整地用于 NativeScript。NativeScript 既不是普通浏览器,也不具备完整的 Node.js 环境。一个库能否使用,要看它依赖哪些运行时 API,以及支付服务商是否支持 Android、iOS 和移动端支付流程。

如果 App 销售应用内使用的数字内容或数字服务,还应先确认 Apple App Store 和 Google Play 的应用内购买规则。这类商品未必允许使用第三方 Payment Gateway。普通网关支付通常更适合实体商品和线下服务,但仍要遵守应用商店的现行规则及所在地区的要求。

推荐的集成架构

可以采用下面的支付流程:

NativeScript App
      |
      | 1. 创建订单
      v
业务后端
      |
      | 2. 使用服务端密钥调用网关
      v
Payment Gateway / IPG
      |
      | 3. 返回 checkout URL、session ID 或 client secret
      v
NativeScript App
      |
      | 4. Hosted Checkout / 原生 SDK / 系统浏览器
      v
用户完成支付
      |
      | 5. Webhook 通知
      v
业务后端更新订单状态
      |
      | 6. App 查询结果
      v
显示支付成功或失败

支付金额、货币、商品信息和订单状态必须由后端控制。不要信任 App 上传的金额,也不能因为客户端跳转到了”成功页面”,就将订单标记为已付款。

编写代码前需要确认的信息

商品类型

先确认销售的是哪类商品或服务:

  • 实体商品
  • 线下服务
  • 应用外消费的服务
  • 数字内容
  • 会员、订阅或应用内功能

如果销售数字商品、订阅或应用内功能,应优先研究 Apple In-App Purchase 和 Google Play Billing,而不是直接选择 TeleCash 一类普通 Payment Gateway。

目标市场和付款方式

“Supported payment methods”并不是由 JavaScript 库决定的。实际可用的付款方式取决于服务提供商、商户账户、签约地区、币种和风险审核,可能包括:

  • Credit/Debit Card
  • Apple Pay
  • Google Pay
  • PayPal
  • SEPA Direct Debit
  • Bank Transfer
  • 本地钱包或本地银行支付
  • 分期付款
  • Recurring Payment

即使 Gateway 的文档列出了某种付款方式,也不代表你的商户账户会自动获得相应能力。

服务提供商的移动端能力

选型时需要检查:

  • 是否提供 Android SDK
  • 是否提供 iOS SDK
  • 是否支持 Hosted Checkout
  • 是否支持系统浏览器和 Deep Link 回跳
  • 是否支持 3-D Secure 2
  • 是否提供 Webhook
  • 是否提供 Sandbox/Test Environment
  • 是否支持 Refund、Partial Refund 和取消授权
  • 是否支持 Tokenization
  • 是否支持目标国家、币种和结算账户
  • 如何划分 PCI DSS 责任范围
  • 是否有正式维护的文档和支持渠道

如果现有网站已经能够收款,应先了解网站目前使用的 Gateway。复用同一个商户账户和后端订单系统,通常比为 App 重新选择一套网关更合理。

通用 JavaScript API 未必适用于 NativeScript

NativeScript 可以使用 JavaScript 或 TypeScript 编写业务逻辑,也能访问 Android 和 iOS 原生 API,但它不是浏览器环境。

JavaScript 库可能依赖以下能力:

  • window
  • document
  • DOM
  • <script> 标签
  • 浏览器 Cookie
  • localStorage
  • iframe
  • Node.js 的 fs、http、crypto 或 Buffer
  • 浏览器专用的重定向和弹窗机制

NativeScript 可能不提供这些能力,或者其行为与浏览器不同。因此,不能只因为一个库使用 JavaScript 编写,就认定它兼容 NativeScript。

这类库大致可以分成三类。

纯 JavaScript 库

如果一个库只负责数据转换、签名格式处理或普通业务计算,不依赖 DOM、Node.js 内置模块或特定浏览器 API,它可能可以使用,但仍需实际测试。

浏览器端支付库

依赖 DOM、iframe 或服务商网页脚本的库,通常不能直接放入 NativeScript 页面运行。此类方案一般应改用 Hosted Checkout,并通过系统浏览器或服务商认可的 WebView 流程打开。

不要自行认定 WebView 一定受支持。由于 3-D Secure、Cookie、安全验证或第三方钱包兼容性等原因,有些服务商会明确要求使用系统浏览器或原生 SDK。

Node.js 服务端 SDK

这类 SDK 应运行在业务后端,而不是 NativeScript App 中。即使使用 polyfill 后能够勉强打包,也不能将 Secret API Key 放入客户端。

App 安装包中的密钥可以被提取,因此不要把以下内容放入 App:

Secret API Key
Merchant Secret
Webhook Secret
Private Signing Key
网关管理账号密码

三种常见接入方式

Hosted Checkout

对于刚开始接触支付集成的项目,Hosted Checkout 通常是最合适的方式。

后端创建支付会话后,将网关提供的支付页面 URL 返回给 App。App 使用系统浏览器打开该页面,付款完成后再通过 Deep Link 或 Universal Link/App Link 返回。

它有以下优点:

  • App 不直接处理银行卡数据
  • PCI DSS 范围通常较小
  • 3-D Secure 等验证流程由服务商处理
  • 支付页面可以统一呈现多种付款方式
  • Android 与 iOS 的实现差异较少

回跳只能用于改善用户体验,不能作为最终的支付凭证。最终状态仍应以经过验证的 Webhook 或服务端查询结果为准。

原生 Android/iOS SDK

如果服务商提供 Android 和 iOS SDK,却没有 NativeScript 插件,可以采用以下方式:

  • 寻找仍在维护的 NativeScript 社区插件
  • 自行封装 NativeScript 插件
  • 在 NativeScript 中直接调用对外暴露的原生 API

原生 SDK 可以提供更好的原生体验,但 Android 和 iOS 需要分别处理,包括回调、生命周期、Deep Link、Gradle/CocoaPods 依赖及 SDK 升级。

自建银行卡输入界面

除非服务商提供合规的 Hosted Fields、Tokenization 组件或原生安全控件,否则不建议自行采集银行卡号、CVV 等敏感信息。

直接处理银行卡数据会明显扩大安全和 PCI DSS 合规范围,仅使用 HTTPS 无法解决这些问题。

一个简化的实现示例

下面的代码只用于说明推荐的职责划分。gateway.example、字段名和返回结构都是占位示例,实际代码必须遵循所选服务商的 API 文档。

NativeScript 创建支付订单

import { ApplicationSettings, Utils } from "@nativescript/core";

interface CheckoutResponse {
  orderId: string;
  checkoutUrl: string;
}

export async function startCheckout(productId: string): Promise<void> {
  const response = await fetch("https://api.example.com/orders/checkout", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${ApplicationSettings.getString("accessToken")}`
    },
    body: JSON.stringify({
      productId,
      quantity: 1
    })
  });

  if (!response.ok) {
    throw new Error(`Unable to create checkout: ${response.status}`);
  }

  const checkout = await response.json() as CheckoutResponse;

  // 示例使用系统浏览器。实际项目还需要处理 Deep Link 回跳。
  await Utils.openUrl(checkout.checkoutUrl);
}

这里有意只发送 productId 和数量,不发送可被视为可信的金额。后端应根据数据库中的商品价格计算应付金额。

后端创建支付会话

import express from "express";
import crypto from "node:crypto";

const app = express();
app.use(express.json());

app.post("/orders/checkout", requireUser, async (req, res) => {
  const product = await products.findById(req.body.productId);

  if (!product || !product.active) {
    return res.status(404).json({ error: "Product not found" });
  }

  const order = await orders.create({
    userId: req.user.id,
    productId: product.id,
    quantity: 1,
    amount: product.price,
    currency: product.currency,
    status: "pending"
  });

  const gatewayResponse = await fetch(
    "https://gateway.example/v1/checkout-sessions",
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.GATEWAY_SECRET_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": order.id
      },
      body: JSON.stringify({
        amount: order.amount,
        currency: order.currency,
        merchantReference: order.id,
        successUrl: `https://example.com/payment/return?order=${order.id}`,
        cancelUrl: `https://example.com/payment/cancel?order=${order.id}`
      })
    }
  );

  if (!gatewayResponse.ok) {
    await orders.markPaymentCreationFailed(order.id);
    return res.status(502).json({ error: "Payment provider rejected request" });
  }

  const session = await gatewayResponse.json();

  await orders.attachGatewaySession(order.id, session.id);

  res.json({
    orderId: order.id,
    checkoutUrl: session.checkoutUrl
  });
});

生产环境还需要处理超时、重试、日志脱敏、幂等冲突和 Gateway 返回的具体错误码。

Webhook 更新订单

app.post(
  "/webhooks/payment",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const signature = req.get("X-Gateway-Signature");

    if (!verifyWebhook(req.body, signature, process.env.WEBHOOK_SECRET)) {
      return res.sendStatus(401);
    }

    const event = JSON.parse(req.body.toString("utf8"));

    if (await webhookEvents.exists(event.id)) {
      return res.sendStatus(200);
    }

    await database.transaction(async (tx) => {
      await webhookEvents.record(event.id, tx);

      if (event.type === "payment.succeeded") {
        await orders.markPaid(
          event.data.merchantReference,
          event.data.paymentId,
          tx
        );
      }

      if (event.type === "payment.failed") {
        await orders.markFailed(
          event.data.merchantReference,
          event.data.reason,
          tx
        );
      }
    });

    res.sendStatus(200);
  }
);

verifyWebhook 的算法、签名 Header 和原始请求体要求,必须严格按照服务商文档实现,不能自行设计。Webhook 还必须具备幂等性,因为同一个事件可能被重复投递。

Payment Gateway 与 IPG 的区别

Payment Gateway 是一个通用术语,指商户系统与支付处理体系之间的接入服务。它通常负责安全传输支付信息、发起授权、处理验证,并将支付结果返回给商户。

IPG 常见的完整写法是 Internet Payment Gateway,侧重互联网或电子商务支付。在许多商业文档中,IPG 和 Payment Gateway 指的是同一类产品,只是命名范围不同。

IPG 并不是一种含义全球统一的技术标准,具体含义应以服务商合同和文档中的定义为准。

Payment Gateway 也不一定是最终完成资金清算的一方。一次银行卡支付还可能涉及:

  • Merchant
  • Payment Gateway
  • Payment Service Provider
  • Acquirer
  • Card Network
  • Issuer
  • Payment Processor

有些服务商会打包提供上述多种能力,因此产品页面使用的名称未必严格一致。

“开源 API”不是合适的判断标准

API 是服务提供商公开的调用接口,SDK 或客户端库才可能是开源软件。即使 SDK 开源,实际交易仍依赖服务商的服务器、商户合同和密钥,不会因此成为免费、独立的支付系统。

评估集成方案时,更需要关注:

  • 是否提供正式、稳定的 API
  • SDK 是否持续维护
  • 是否符合当前 Android 和 iOS 的要求
  • 是否提供测试环境
  • 是否支持 Webhook 和幂等机制
  • 是否支持所需的付款方式
  • 合规责任是否划分清楚
  • 费率、结算周期和退款流程是否合适
  • 出现争议、拒付或故障时能否获得支持

社区开源插件可以减少封装工作,但不能成为支付系统的信任边界。使用前应检查插件最近一次维护的时间、依赖版本、Issue、敏感数据处理方式,以及 Android 和 iOS 是否都得到了实际支持。

关于 TeleCash

不能只根据”文档是否完善”或”SDK 是否开源”,判断 TeleCash 的文档或 API 是否适合使用。还需要向服务商或现有网站的维护方确认:

  • 现有网站使用的是哪个具体产品和接口版本
  • 当前商户合同是否允许 Mobile App 交易
  • 支持哪些国家、币种和付款方式
  • 是否提供 Hosted Checkout 或移动 SDK
  • 是否支持 3-D Secure 2
  • 是否允许在 WebView 中运行
  • App 回跳应如何配置
  • Webhook 如何验签
  • 如何申请测试账号和完成认证流程
  • 是否支持 Tokenization、退款和周期性付款

如果网站已经通过 TeleCash 收款,通常更适合让 NativeScript App 复用网站的支付后端,而不是将 TeleCash 接口重新直接移植到 App。

上线前不能忽略的问题

支付功能至少需要处理以下情况:

  • 用户重复点击付款按钮
  • 网络超时,但支付实际已经成功
  • App 在付款过程中被关闭
  • Deep Link 未能成功返回 App
  • Webhook 重复、延迟或乱序到达
  • 客户端篡改支付金额或币种
  • 付款成功后业务发货失败
  • 退款、部分退款和拒付
  • 订单过期
  • 3-D Secure 验证被取消或失败
  • 日志意外记录卡号、Token 或 Secret
  • 测试环境与生产环境的密钥被混用

订单状态也不应只有 success 和 failed。通常至少需要区分:

created
pending
authorized
paid
failed
cancelled
expired
partially_refunded
refunded
chargeback

具体状态仍要根据业务需求和 Gateway 的状态模型确定。

实际开始时,不必继续寻找”适用于所有 IPG 的 NativeScript 开源 API”。应先确认商品类型、应用商店规则、现有网站使用的支付提供商和所需付款方式。确定服务商后,再从其 Hosted Checkout、Android SDK、iOS SDK 和服务端 API 中选择合适的接入方式。

备注:内容仅供参考。