本文档面向交易所(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)
-
选择加密币种(USDT / USDC / BTC / ETH),选择买入 / 卖出、输入法币金额、查看预估换算,点击 Review Order。

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

-
弹窗内选择 Apple Pay。

-
勾选 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 域名下
-
承载页显示 Buy {token} 金额、Network fees、Processing fees、Apple Pay 黑色按钮。
-
点击 Apple Pay 按钮,分支:
-
桌面无 Apple Pay → 弹出 "Scan Code with iPhone" QR 码模态(需要 iOS 18+)
-
移动 Safari / 已注册 Apple Pay 的 macOS Safari → 直接唤起 Apple 原生页面

-
-
用户在 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 Trading | Apple |
|---|---|---|---|
| 选币 / 选金额 / 支付方式选择 | ✅ | — | — |
| 手续费展示 | — | ✅ 承载页 | — |
| 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>完整录屏
产品验收要点
- 组件内 "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 入口,而不是打开一个全屏页面,可以:
- 在 App 下单页布局一个按钮大小的自定义尺寸 WebView(例如宽度撑满、高度 48pt)。
- 调用 LG Base SDK 的
startApplePay({ ..., layout: 'mini' }),SDK 返回的 URL 指向的承载页会渲染成充满整个 WebView 的一个 Apple Pay 按钮(除去按钮外没有任何内容)。 - 用户点击 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 参数取值
layout 参数取值| 值 | 用途 | 承载页样式 |
|---|---|---|
worldpay | Web + 全屏 WebView | 完整承载页(金额、手续费、Apple Pay 按钮) |
mini | App 按钮尺寸 WebView | 只渲染一个填满视口的 Apple Pay 按钮 |
legend | 备用模板 | 特殊场景(详见 API 参考) |
4. 支付完成后交易所要处理什么
无论哪种集成方式,最终交易所都会同时得到两类信号:
- redirect query 参数(有跳转 / Widget / WebView 模式)—— 承载页支付完成后
redirect到交易所回调地址,附?status=success|failed|pending&trade_id=xxx。 - 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。
参考
- Apple Pay reference
- legend-demo
- 相关 SDK 方法:
startApplePay/canSetupApplePay(详见 API 参考页参数表)




