Apple Pay 用户旅程与页面归属

本文档面向交易所(Exchange)对接方的产品经理,补充说明 Apple Pay 集成参考页 中未展开的用户交互归属问题:在整个 Apple Pay 购买链路中,哪一步在交易所自有页面上完成,哪一步会跳到 Legend Trading (otcsb.legendtrading.com/applepay/*) 承载页,哪一步交给 Apple 原生 sheet。

集成方式比较

Legend 提供两条 JS SDK 集成路径与一条移动端 App 集成路径,它们对 Apple Pay 的处理方式不同:

集成方式承载环境Apple Pay 是否跳转到 legendtrading 域名Legend Trading 品牌页是否可见
LG Base SDK交易所 Web 页面✅ 打开 legendtrading 承载页✅ 显示
LG Widget SDK(Web Component)交易所 Web 页面✅ 默认跳转到 legendtrading 承载页✅ 显示
Legend Gateway in App(WebView)交易所移动 App✅ 通过 WebView 加载 legendtrading 承载页视 WebView 尺寸而定

后文分别按用户旅程逐步说明。

1. LG Base SDK(Web 方案)

调用 startApplePay({..., layout: 'worldpay', redirect: location.origin}),SDK 返回一个 url,交易所侧用 window.open(url) 或整页跳转打开 legendtrading 承载页完成支付。

用户旅程分步

A. 交易所域名下({exchange}.com

  1. 选择加密币种(USDT / USDC / BTC / ETH),选择买入 / 卖出、输入法币金额、查看预估换算,点击 Review Order

    📷

  2. 订单预览页 —— 点 Add Debit Cards 展示 Apple Pay / Google Pay 支付方式。

    📷

  3. 弹窗内选择 Apple Pay。

    📷

  4. 勾选 User Agreement,点 Confirm;交易所调用:

    const { url, payment_intent_id } = await sdk.startApplePay({
      app_state, network, address, pair, side, size,
      external_order_id,
      layout: 'worldpay',
      redirect: location.origin,
    });
    window.open(url); // 或 location.href = url;

➡️ 跳转发生:打开 https://otcsb.legendtrading.com/applepay/worldpay?intent={JWT}&redirect={callback}

B. Legend Trading 域名下

  1. 承载页显示 Buy {token} 金额、Network fees、Processing fees、Apple Pay 黑色按钮。

    📷

    桌面版(手机版会页面自适应)

  2. 点击 Apple Pay 按钮,分支:

    • 桌面无 Apple Pay → 弹出 "Scan Code with iPhone" QR 码模态(需要 iOS 18+)

    • 移动 Safari / 已注册 Apple Pay 的 macOS Safari → 直接唤起 Apple 原生页面

      📷

  3. 用户在 Apple 原生页内选择信用卡,Face ID / Touch ID 授权。

➡️ 回调发生:承载页 location = {redirect}?status=success|failed|pending&trade_id=xxx,同时 SDK 派发 legend:payment-success / legend:payment-failed / legend:payment-pending / legend:payment-error 事件。

完整录屏

🎬

完整录屏

三方职责一览

环节交易所Legend TradingApple
选币 / 选金额 / 支付方式选择
手续费展示✅ 承载页
Apple Pay 按钮 & QR 兜底触发跳转✅ 承载页渲染
Merchant Session / 卡片授权✅ 后端 Worldpay 收单✅ 原生 sheet
订单结果落地✅ 收到回调后展示成功页✅ 后端结算

2. LG Widget SDK — 默认跳转

LG Widget SDK 通过 Web Component(<legend-trade-full> / <legend-trade>)嵌入到交易所页面。交易所只提供外壳容器,组件内部渲染整套下单 UI。

产品视角要点:LG Widget SDK 里 Apple Pay 功能默认走跳转到外部 legendtrading.com 域名下的承载页执行支付。用户在组件内选完币种、金额、支付方式(Apple Pay)后,组件会 window.open 或整页跳转到 otcsb.legendtrading.com/applepay/…,完成支付后再回跳到交易所配置的回调地址。

集成示例

<script src="https://.../legend-gateway-entry.js"></script>
<legend-trade-full
  app-state="..."
  mode="light"
  lang="en"
  redirect="https://myexchange.com/order-callback">
</legend-trade-full>

完整录屏

🎬

Widget SDK 录屏

产品验收要点

  • 组件内 "Powered by Legend Trading" 标识始终可见
  • 语言(lang)、主题(mode / color)与交易所页融合度
  • 打开承载页时使用 window.open 还是整页跳转(弹窗拦截、返回栈体验)
  • 回跳后的成功页 / 失败重试 UI

3. Legend Gateway in App — 通过 WebView

移动 App(iOS / Android)里的 Apple Pay 集成通过 WebView 加载 legendtrading.com 承载页完成——App 原生代码不直接调用 Apple Pay API,而是把 Legend 承载页当作嵌入的网页来渲染。

3.1 全屏 WebView(标准形态)

App 内一个全屏 WebView 加载 https://otcsb.legendtrading.com/applepay/worldpay?intent=…,用户看到完整的 legendtrading 承载页(金额 + 手续费 + Apple Pay 按钮),点击后 WebView 内唤起 Apple Pay sheet。

移动端承载页效果:

📷

3.2 Mini 按钮 WebView(推荐 App 内嵌)

如果 App 希望在自己的下单页里嵌一个「原生按钮般」的 Apple Pay 入口,而不是打开一个全屏页面,可以:

  1. 在 App 下单页布局一个按钮大小的自定义尺寸 WebView(例如宽度撑满、高度 48pt)。
  2. 调用 LG Base SDK 的 startApplePay({ ..., layout: 'mini' }),SDK 返回的 URL 指向的承载页会渲染成充满整个 WebView 的一个 Apple Pay 按钮(除去按钮外没有任何内容)。
  3. 用户点击 WebView 内的 Apple Pay 按钮,Apple 原生 sheet 由 WebView 唤起。

这样在 App 内用户视觉上就是一个原生 Apple Pay 按钮,实际背后仍然是 legendtrading 承载页在 WebView 里工作。同一个 SDK 参数、同样的合规主体,只是换了个 layout

在 legend-demo 里驱动 startApplePay 的示例:

📷

Flutter 示例:按钮尺寸的 WebView 容器

以下是用 webview_flutter 在 Flutter App 里承载 layout: 'mini' 承载页的最小示例——把 WebView 限制在按钮大小的容器内,加圆角与边框,clipBehavior 保证网页内容跟着圆角裁剪:

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

class MiniatureWebViewButton extends StatefulWidget {
  const MiniatureWebViewButton({super.key});

  @override
  State<MiniatureWebViewButton> createState() => _MiniatureWebViewButtonState();
}

class _MiniatureWebViewButtonState extends State<MiniatureWebViewButton> {
  late final WebViewController _controller;

  @override
  void initState() {
    super.initState();
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse('https://flutter.dev'));
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        // 1. Define standard button dimensions
        width: 200,
        height: 50,
        // 2. Apply a clip behavior so the web content respects rounded corners
        clipBehavior: Clip.antiAlias,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(8),
          border: Border.all(color: Colors.blue, width: 2),
        ),
        // 3. Inject the webview into the restricted container
        child: WebViewWidget(controller: _controller),
      ),
    );
  }
}

实际集成时把示例中的 Uri.parse('https://flutter.dev') 换成 LG Base SDK startApplePay({..., layout: 'mini'}) 返回的 url,App 侧就得到一个「原生按钮」外观的 Apple Pay 入口——点击后 WebView 内的承载页会唤起 Apple Pay 原生 sheet 完成支付。

iOS 示例:按钮尺寸的 WKWebView 容器

iOS 原生等价实现——WKWebView 通过 Auto Layout 约束到按钮尺寸,layer.cornerRadius + layer.masksToBounds = true 保证网页内容跟着圆角裁剪:

import UIKit
import WebKit

final class MiniatureWebViewButton: UIView {
    private let webView: WKWebView = {
        let config = WKWebViewConfiguration()
        let prefs = WKWebpagePreferences()
        prefs.allowsContentJavaScript = true
        config.defaultWebpagePreferences = prefs
        return WKWebView(frame: .zero, configuration: config)
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupView()
    }

    private func setupView() {
        // 1. Define standard button dimensions
        translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            widthAnchor.constraint(equalToConstant: 200),
            heightAnchor.constraint(equalToConstant: 50),
        ])

        // 2. Rounded corners + border; masksToBounds clips web content to the shape
        layer.cornerRadius = 8
        layer.masksToBounds = true
        layer.borderColor = UIColor.systemBlue.cgColor
        layer.borderWidth = 2

        // 3. Inject the webview into the restricted container
        webView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(webView)
        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: topAnchor),
            webView.bottomAnchor.constraint(equalTo: bottomAnchor),
            webView.leadingAnchor.constraint(equalTo: leadingAnchor),
            webView.trailingAnchor.constraint(equalTo: trailingAnchor),
        ])

        // 4. Load the URL returned by startApplePay({ ..., layout: 'mini' })
        if let url = URL(string: "https://flutter.dev") {
            webView.load(URLRequest(url: url))
        }
    }
}

同样把 URL(string: "https://flutter.dev") 替换成 startApplePay({..., layout: 'mini'}) 返回的 url 即可。

layout 参数取值

用途承载页样式
worldpayWeb + 全屏 WebView完整承载页(金额、手续费、Apple Pay 按钮)
miniApp 按钮尺寸 WebView只渲染一个填满视口的 Apple Pay 按钮
legend备用模板特殊场景(详见 API 参考)

4. 支付完成后交易所要处理什么

无论哪种集成方式,最终交易所都会同时得到两类信号:

  1. redirect query 参数(有跳转 / Widget / WebView 模式)—— 承载页支付完成后 redirect 到交易所回调地址,附 ?status=success|failed|pending&trade_id=xxx
  2. SDK 事件(LG Base SDK / LG Widget SDK)—— 全部模式派发 legend:payment-success / legend:payment-failed / legend:payment-pending / legend:payment-error

建议:以 SDK 事件为主、query 参数为兜底。pending 状态建议交易所自身订单页轮询或订阅 Webhook(Apple Pay + Worldpay 存在异步确认窗口)。

5. FAQ

Q1. LG Widget SDK 能不跳转吗?
A. 默认走跳转。用户在 Web Component 内选完 Apple Pay 后一定会打开 legendtrading 承载页。

Q2. App 集成为什么要用 WebView 而不是原生调用?
A. 因为 Apple Pay 的 Merchant ID、Worldpay Merchant Session 都注册在 Legend Trading 名下。走 WebView + 承载页可以把这些合规主体与后端契约完全托管给 Legend,App 侧不需要接 Apple 原生 PassKit / PKPaymentAuthorizationController。

Q3. layout: 'mini' 的按钮大小怎么定?
A. 由 App 侧的 WebView 尺寸决定,mini 布局会 100% 填充 WebView 视口。推荐按 iOS/Android 的 Apple Pay 按钮设计规范(一般高度 44-56pt,圆角 8-10pt)来做 WebView 尺寸。

Q4. 桌面浏览器不支持 Apple Pay 时用户看到什么?
A. 承载页会弹出 "Scan Code with iPhone" QR 模态,要求用户用 iPhone 相机扫码在手机上继续(iOS 18+)。

Q5. 手续费在哪一步告知用户?
A. 在 legendtrading 承载页明确列出 Network fees 与 Processing fees。

Q6. 用户放弃支付、关闭 Apple sheet 会发生什么?
A. 承载页 / 组件保持在原位,可再次点击 Apple Pay 按钮;payment_intent 未消费,可复用直至过期。用户直接关闭承载页窗口,交易所不会收到 redirect,但 SDK 事件仍可能是 legend:payment-error 或超时后的 legend:payment-pending

参考