PAYATHON 2026

React Native 无 SDK 时如何集成 ADCB payment gateway

支付小周

结论

没有 React Native SDK 也可以集成 ADCB 支付。常见做法是使用支付网关提供的 Hosted Payment Page:

  1. React Native 向业务后端请求创建支付订单。
  2. 后端调用 ADCB 支付接口,生成支付页面 URL 或表单参数。
  3. App 通过 Custom Tabs、系统浏览器或原生支付会话打开支付页面。
  4. 支付结束后,网关重定向到预先配置的 returnUrl 或 cancelUrl。
  5. 通过 Universal Link、App Link 或自定义 URL Scheme 唤起 App。
  6. App 返回前台后,向业务后端查询并确认最终支付状态。

Custom Tabs 可以用于打开支付页面,但 React Native 通常无法在付款完成后直接且可靠地将其关闭。支付成功页应重定向回 App。能否自动关闭,或能否从历史记录中移除,取决于浏览器、操作系统以及使用的 Custom Tabs 库,不能把它当作支付流程的基本保障。

推荐的集成结构

支付逻辑应放在服务端,不要直接写进 React Native:

React Native App
       |
       | 1. 创建订单
       v
业务后端
       |
       | 2. 使用商户密钥调用 ADCB
       v
ADCB Payment Gateway
       |
       | 3. 返回支付页面地址
       v
React Native App 打开 Custom Tab
       |
       | 4. 用户付款
       v
returnUrl / webhook
       |
       | 5. 后端验证支付结果
       v
App 查询订单并显示结果

这种结构可以避免把商户密钥、签名密钥等敏感信息放进 App,也能防止客户端伪造支付成功状态。

不同的 ADCB 商户方案可能接入不同的支付平台。创建交易、签名和查询订单需要哪些字段,应以商户后台或 ADCB 提供的接入文档为准。

第一步:由后端创建支付会话

React Native 只需把订单编号传给业务后端,不应自行生成网关签名。

下面是一个简化的 Node.js 示例。ADCB 接口地址和字段仅用于展示代码结构,需要按照实际接入文档替换:

app.post('/api/payments/create', async (req, res) => {
  const { orderId } = req.body;

  const order = await findOrder(orderId);

  if (!order || order.userId !== req.user.id) {
    return res.status(404).json({ error: 'Order not found' });
  }

  const paymentRequest = {
    merchantReference: order.id,
    amount: order.amount,
    currency: 'AED',
    returnUrl: 'https://pay.example.com/payment/return',
    cancelUrl: 'https://pay.example.com/payment/cancel'
  };

  // 必须在服务端按照 ADCB 文档完成鉴权和签名。
  const gatewayResponse = await createAdcbPayment(paymentRequest);

  res.json({
    paymentUrl: gatewayResponse.paymentUrl
  });
});

金额、币种和订单号应从数据库读取,不能直接采用客户端传来的值。

第二步:从 React Native 打开支付页面

先创建订单,再打开后端返回的可信支付地址:

async function startPayment(orderId) {
  const response = await fetch('https://api.example.com/api/payments/create', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${accessToken}`
    },
    body: JSON.stringify({ orderId })
  });

  if (!response.ok) {
    throw new Error('Unable to create payment');
  }

  const { paymentUrl } = await response.json();

  await CustomTabs.openURL(paymentUrl, {
    toolbarColor: '#607D8B',
    enableUrlBarHiding: true,
    showPageTitle: true,
    enableDefaultShare: false,
    animations: ANIMATIONS_SLIDE
  });
}

支付 URL 可能带有短期有效的会话标识,因此不建议开启分享按钮。

原代码直接拼接查询参数,还可能产生编码问题。如果确实需要构造 URL,应使用 URL 和 URLSearchParams:

const paymentUrl = new URL(
  'https://www.example.com/newsagepay/newtest.php'
);

paymentUrl.searchParams.set('customerSage', userId);
paymentUrl.searchParams.set('checkSage', '1');

await CustomTabs.openURL(paymentUrl.toString(), options);

不过,更安全的做法仍是由后端返回完整的支付地址,不让 App 自行组合支付参数。

第三步:付款后返回 App

可以为 App 配置这样的回调地址:

myshop://payment/result?orderId=ORDER_123

经过域名验证的 HTTPS 链接通常更合适:

https://app.example.com/payment/result?orderId=ORDER_123

HTTPS Universal Link 或 Android App Link 通常具有更好的安全性和兼容性。App 未安装时,也可以回退到网页。

支付网关完成流程后,先重定向到业务服务器:

https://pay.example.com/payment/return

业务服务器接收网关返回的信息,然后跳转到 App:

HTTP/1.1 302 Found
Location: https://app.example.com/payment/result?orderId=ORDER_123

如果 ADCB 只允许配置 HTTPS 回调地址,服务端中转会很合适。

第四步:在 React Native 中处理回调链接

React Native 的 Linking 可以同时处理 App 冷启动和已经运行的情况:

import { useEffect } from 'react';
import { Linking } from 'react-native';

function PaymentLinkHandler({ navigation }) {
  useEffect(() => {
    const handleUrl = ({ url }) => {
      processPaymentLink(url);
    };

    const subscription = Linking.addEventListener('url', handleUrl);

    Linking.getInitialURL().then((url) => {
      if (url) {
        processPaymentLink(url);
      }
    });

    return () => subscription.remove();
  }, []);

  async function processPaymentLink(url) {
    const parsedUrl = new URL(url);

    const isPaymentCallback =
      parsedUrl.hostname === 'app.example.com' &&
      parsedUrl.pathname === '/payment/result';

    if (!isPaymentCallback) {
      return;
    }

    const orderId = parsedUrl.searchParams.get('orderId');

    if (!orderId) {
      return;
    }

    navigation.navigate('PaymentResult', { orderId });
  }

  return null;
}

使用自定义 Scheme 时,URL 的 hostname 和 pathname 结构可能不同。例如:

myshop://payment/result

这里的 payment 通常会被解析为 hostname,/result 则是 pathname,处理时要按照实际的 URL 结构判断。

Android 需要配置对应的 intent filter。iOS 使用自定义 Scheme 时需要注册 URL Types,使用 Universal Links 时还要配置 Associated Domains。具体配置取决于 React Native 工程结构和使用的导航库。

Custom Tab 能否自动关闭

不能假定所有 Custom Tabs 实现都支持程序化关闭。openURL() 成功返回通常只代表页面已经打开,不代表支付流程已经结束,也不能把它的 Promise 当作付款结果通知。

部分 React Native Custom Tabs 库提供类似以下接口:

CustomTabs.close();

只有当前使用的库明确提供并支持该方法,才能调用它。App 收到深链后,可以尝试关闭 Custom Tab:

async function processPaymentLink(url) {
  try {
    if (typeof CustomTabs.close === 'function') {
      await CustomTabs.close();
    }
  } catch (error) {
    console.warn('Unable to close Custom Tab', error);
  }

  // 随后向后端查询订单状态。
}

即使没有 close(),深链通常也能把 App 带回前台。不过,用户之后通过系统返回操作,仍可能看到原来的浏览器页面。支付结果页应避免重复提交,并显示”正在返回应用”或”可以关闭此页面”等提示。

如果需要更一致的关闭体验,可以评估原生认证会话类组件,例如 iOS 的 ASWebAuthenticationSession。前提是支付网关允许这种打开方式,而且所选的 React Native 库能够正确处理回调 URL。不要为了强制关闭页面而直接改用 WebView。部分支付网关或银行卡验证流程会禁止 WebView,而且 WebView 的安全边界通常更弱。

支付结果必须由后端确认

回调链接中的以下内容都不能直接证明支付成功:

?status=success
?paid=true
?result=approved

任何人都可以手动构造这类链接。App 收到回调后,只应提取订单号并请求后端:

async function getPaymentStatus(orderId) {
  const response = await fetch(
    `https://api.example.com/api/payments/${encodeURIComponent(orderId)}`,
    {
      headers: {
        Authorization: `Bearer ${accessToken}`
      }
    }
  );

  if (!response.ok) {
    throw new Error('Unable to verify payment');
  }

  return response.json();
}

服务端应通过以下一种或多种方式确认支付结果:

  • 验证 ADCB 回调的签名。
  • 接收网关的服务端 webhook。
  • 主动调用网关的交易查询接口。
  • 核对订单号、商户号、币种和金额。
  • 检查交易是否已经处理,防止重复记账。

如果用户返回 App 时 webhook 还未到达,可以暂时显示”支付确认中”,并在有限时间内轮询后端。不能仅凭 Custom Tab 已关闭、用户返回 App 或页面显示成功就立即发货。

其他注意事项

  • 商户密钥、签名密钥和网关管理凭证只能保存在服务端。
  • 不要在日志中记录银行卡信息、CVV、完整支付令牌或包含敏感参数的 URL。
  • 必须使用 HTTPS,并验证支付页面 URL 的域名,避免后端数据异常导致 App 打开任意地址。
  • 订单创建接口应支持幂等处理,防止用户连续点击后生成多笔交易。
  • 支付成功、失败、取消和待确认应分别作为不同状态处理。
  • 回调可能重复到达,服务端更新订单时也要保持幂等。
  • 需要测试 App 冷启动、App 位于后台、用户取消、网络中断、3-D Secure 跳转,以及支付完成后未返回 App 等场景。
  • 正式选择 Custom Tabs、Universal Links 或 WebView 前,应先确认 ADCB 接入文档对跳转方式、回调 URL、3-D Secure 和浏览器环境的具体要求。

备注:内容仅供参考。