前端集成

从 SDK 初始化与 authorize 开始,逐步完成状态读取、账单地址与信用卡添加、询价与下单,包括处理支付流程与异常处理。

目录

1. 初始化 SDK

const legend = new LegendBase({
  mode: "sandbox",
	normalizeEvents: true,
  /**
   * 错误处理回调
   * @param {Object} error - 错误对象
   * @param {string} error.code - 错误代码
   * @param {string} error.message - 错误信息
   */
  onError: (error) => {
    alert(error.message)
  },

  /**
  * 支付流程回调
  * @param {string} status - 支付状态
  *   终态:success | pending | failed | cancelled
  *   中间态(仅 Apple Pay / Google Pay 原生按钮流程):clicked | authorizing | processing
  */
  onPay: (status, paymentData) => {
    // 在这里处理支付逻辑
    console.log('支付状态:', status);
    console.log('相关支付数据', paymentData)
  },
})

// 使用 SDK
try {
  await legend.someMethod();
} catch (error) {
  // 错误会通过 onError 回调处理,这里可以不用重复处理
}

Options

参数类型默认值说明
mode'sandbox' | 'production'-SDK 运行模式
normalizeEventsbooleanfalse是否标准化事件数据
onPay(status, paymentData) => void-支付流程回调,详见 8.onPay 支付回调
onError(error) => void-错误处理回调,详见 9.onError 错误处理回调

onPay

// —— 终态 ——

// success
{ status: "success", data: any }

// pending
{ status: "pending" }

// failed
{
  status: "failed",
  code: string,
  message: string,
  errors: object
}

// cancelled —— 用户主动取消支付
{
  status: "cancelled",
  provider: "applepay" | "googlepay",
  reason?: string   // 目前为 "user_abort"
}

// —— 中间态(仅 Apple Pay / Google Pay 原生按钮流程触发)——

// clicked —— 用户点击了 Apple Pay / Google Pay 按钮
{ status: "clicked", provider: "applepay" | "googlepay" }

// authorizing —— 支付 sheet 即将弹起,等待用户授权
{ status: "authorizing", provider: "applepay" | "googlepay" }

// processing —— 用户已授权,正在向 Legend 下单
{ status: "processing", provider: "applepay" | "googlepay" }

onError

{
  status: "error",
  code: string,
  message: string,
  errors: object
}

2. 授权

authorize 方法是整个 SDK 的核心授权接口,用于建立与 KYC 服务的安全连接。该方法通过验证服务端返回的签名数据,完成应用的身份认证和用户会话初始化。

const resp = await legend.authorize(authConfig));

参数说明

authConfig 是一个包含认证信息的配置对象,所有字段均来自服务端加签返回的数据:

字段名类型必填描述来源
timestampstring时间戳,用于防止重放攻击服务端生成
signaturestring服务端生成的数字签名,用于验证请求的完整性服务端生成
appIdstring应用唯一标识符服务端生成 APP-ID
appKeystring应用密钥,用于身份验证服务端生成 APP-KEY
appUidstring用户在应用系统中的唯一标识服务端生成 APP-UID
appUrlstring应用回调地址服务端生成APP-URL
appEmailstring用户邮箱地址服务端生成 APP-EMAIL
appPhonestring用户手机号码服务端生成 APP-PHONE

⚠️ 如何获取 authConfig ,参考 获取签名

返回值

  • Promise<Object> - 授权结果对象,包含会话状态和连接信息

使用示例

// 使用服务端返回的加签数据调用授权方法
const resp = await legend.authorize({
  timestamp: data.timestamp, // 来自服务端的时间戳
  signature: data.signature, // 来自服务端的数字签名
  appId: data["APP-ID"], // 应用ID
  appKey: data["APP-KEY"], // 应用密钥
  appUid: data["APP-UID"], // 用户ID
  appUrl: data["APP-URL"], // 回调URL
  appEmail: data["APP-EMAIL"], // 用户邮箱
  appPhone: data["APP-PHONE"], // 用户手机号
});

console.log("授权响应:", resp);
console.log("KYC 状态", resp.kyc_status);

返回值

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjdjNjk0NzBlYzM0NzA4MTM2YmZiMjEwYjZmYWYwOTk4YTYzMThmMDkwNDdhNTg0MDU3NzNlYmRlMDUwM2M4ZGViZDJhMTBmNDliMjczN2NlIn0...", // 完整的JWT令牌
  "user_preference": {
    "default_fiat": "EUR",
    "skip_bank_disclaimer": true,
    "skip_wire_disclaimer": true,
    "banner": []
  },
  "kyc_status": "Approved", // KYC认证状态
  "account_type": "Individual", // 账户类型
  "account_email": "[email protected]", // 账户邮箱
  "account_status": "active", // 账户状态
  "expires_at": "2023-03-21 09:09:51", // 令牌过期时间
  "next_kyc_step": "questionnaire", // 下一步KYC步骤
  "exchange_config": {
    "auto_withdrawal": true,
    "trade_confirmation_popup": "The purchased coins will arrive in your exchange wallet in 30mins - 1hour.",
    "minimum_trading_size": "40",
    "exchange_name": "PHEMEX",
    "logo_url": "https://legendtrading.com/img/logo-partners/phemex.png",
    "allowed_payment_methods": ["wire", "creditcard"],
    "user_max_ach_deposit": "1000",
    "allowed_fiat_deposit_methods": ["wire", "ach"],
    "allowed_fiat_withdraw_methods": ["wire"],
    "disable_modify_wallet": false,
    "no_sell": false,
    "app_type": "CEX",
    "allow_usd_wire_transfer": "off"
  },
  "user_info": {
    "edd": {}, // 增强尽职调查信息
    "rfi": {}  // 信息请求信息
  }
}

3. 获取应用状态 (getState)

getState 方法用于获取当前用户的完整应用状态信息,包括 KYC 状态、账户信息、余额、交易所配置等。该方法返回一个包含丰富状态信息的对象。

方法签名

const state = legend.getState();

返回值

返回 StateObject 类型,包含以下分类信息:

1. KYC 认证状态

字段名类型描述
kycStatusstringKYC 认证状态 "Pending": 审核中 "Approved": 已通过 "Rejected": 已拒绝
nextKycStepstring下一步 KYC 步骤示例:"enter_bank" - 需要完善银行信息
originalNextKycStepstring原始下一步 KYC 步骤用于回退或重置流程

2. 账户基本信息

字段名类型描述
accountEmailstring用户注册邮箱地址
accountEmailHashstring邮箱地址的哈希值(用于隐私保护)
accountTypestring账户类型 
- "individual": 个人账户 
- "institutional": 机构账户

3. 会话与安全信息

字段名类型描述
tokenstring用户访问令牌(JWT格式)
tokenTypestring令牌类型,通常为 "Bearer"
expiresInnumber令牌剩余有效时间(秒)
expiresAtstring令牌过期时间(格式:"YYYY-MM-DD HH:mm:ss")
isHighRiskIpboolean当前 IP 是否被识别为高风险
ipAddressInfoobjectIP 地址相关信息

4. 交易对与资产信息

字段名类型描述
pairsArray可交易的货币对列表
assetstring主计价货币,如 "USD"
currenciesArray支持的货币列表
currencyUnitListobject货币单位配置信息

5. 余额信息 (balance)

用户在各类型钱包中的资产余额:

balance: {
  crypto_exchange: {    // 交易所加密货币余额
    [asset: string]: string
  },
  crypto: {            // 主钱包加密货币余额  
    [asset: string]: string
  },
  fiat: {              // 法币余额
    [asset: string]: string
  }
}

6. 交易所配置 (exchangeConfig)

当前连接的交易所配置信息:

字段名类型描述
exchange_namestring交易所名称
logo_urlstring交易所 Logo URL
app_typestring应用类型,如 "CEX"(中心化交易所)
auto_withdrawalboolean是否启用自动提现
minimum_trading_sizestring最小交易金额
trade_confirmation_popupstring交易确认弹窗提示文案
allowed_payment_methodsstring[]允许的支付方式
- "wire": 电汇
- "ach": ACH 转账
- "creditcard": 信用卡
allowed_fiat_deposit_methodsstring[]允许的法币充值方式
allowed_fiat_withdraw_methodsstring[]允许的法币提现方式
user_max_ach_depositstring用户 ACH 最大存款限额
disable_modify_walletboolean是否禁用钱包修改
no_sellboolean是否禁止卖出操作
allow_usd_wire_transferstring是否允许美元电汇转账

7. 用户偏好设置 (user_preference)

字段名类型描述
default_fiatstring默认法币货币
skip_bank_disclaimerboolean是否跳过银行免责声明
skip_wire_disclaimerboolean是否跳过电汇免责声明
bannerArray用户界面横幅配置

8. 其他信息

字段名类型描述
operationTypestring当前操作类型,如 "credit_card"
minimumTradingSizenumber最小交易规模(数字格式)
exchangeNamestring交易所名称(简化版)
regionListTypestring区域列表类型

使用示例

基本调用

// 获取完整状态对象
const state = legend.getState();

// 检查 KYC 状态
console.log("KYC 状态:", state.kycStatus); // "Approved"

// 获取用户邮箱
console.log("用户邮箱:", state.accountEmail); // "[email protected]"

// 检查令牌过期时间
console.log("令牌过期:", state.expiresAt); // "2023-12-06 03:27:43"

检查余额

const state = legend.getState();

// 检查法币余额
console.log("法币余额:", state.balance.fiat);
// 输出: { USD: "1000.00", EUR: "500.00" }

// 检查加密货币余额
console.log("加密货币余额:", state.balance.crypto);
// 输出: { BTC: "0.5", ETH: "2.3" }

验证交易权限

const state = legend.getState();

// 检查是否支持信用卡支付
const canUseCreditCard = state.exchangeConfig.allowed_payment_methods.includes('creditcard');
console.log("支持信用卡:", canUseCreditCard); // true

// 检查最小交易金额
const minTradeSize = parseFloat(state.exchangeConfig.minimum_trading_size);
console.log("最小交易金额:", minTradeSize); // 40

处理 KYC 流程

const state = legend.getState();

switch (state.kycStatus) {
  case "Approved":
    console.log("KYC 已通过,可以正常交易");
    break;
  case "Pending":
    console.log("KYC 审核中,下一步:", state.nextKycStep);
    break;
  case "Rejected":
    console.log("KYC 被拒绝,请联系客服");
    break;
  default:
    console.log("未知 KYC 状态");
}

4. 读取和设置账单地址

提供账单地址的读取和设置功能,用于在交易过程中处理用户的账单信息。

4.1 获取 KYC 的用户地址 (getAddressFromKyc)

获取用户在 KYC 认证过程中填写的默认地址信息。该地址通常用于表单预填充,提升用户体验。

方法签名

const profile = await legend.getAddressFromKyc();

返回值

返回 Promise<BillingAddress>,包含完整的账单地址信息:

字段名类型必填描述
namestring用户全名
date_of_birthstring出生日期 (格式: YYYY-MM-DD)
address_line_1string地址行 1(街道地址)
address_line_2string地址行 2(公寓号、单元号等)
citystring城市
statestring州/省份
countrystring国家名称
country_iso2_codestring国家 ISO 2 位代码
zipcodestring邮政编码
registration_pathstring注册途径,如 "deposit"

使用示例

// 获取 KYC 中的主要地址信息
const profile = await legend.getAddressFromKyc();
console.log("从 KYC 获取的主要地址: \n%o", profile);

// 输出示例:
// {
//   "name": "Yao 1 Ji",
//   "date_of_birth": "1988-08-08",
//   "address_line_1": "1329 Wiley Oak Dr",
//   "address_line_2": null,
//   "city": "Jarrettsville",
//   "country": "United States",
//   "country_iso2_code": "US",
//   "state": "California",
//   "zipcode": "21084-1953",
//   "registration_path": "deposit"
// }

4.2 创建账单地址 (createBillingAddress)

为当前交易设置一个新的账单地址。重要:此操作会生成一个新的地址记录,不会修改用户原有的 KYC 地址信息。

方法签名

const resp = await legend.createBillingAddress(addressData);

参数说明

⚠️ addressData 需要包含与 getAddressFromKyc 返回对象相同结构的地址信息。

返回值

字段名类型描述
uuidstring新地址的唯一标识符,例如:“581bd72d-5091-4447-ad63-40f388d5794d”
resultstring操作是否成功,例如:"success"
messagestring操作结果信息,例如:"New billing address added."

使用示例

// 使用从 KYC 获取的地址设置新账单地址
const profile = await legend.getAddressFromKyc();
const resp = await legend.createBillingAddress(profile);

console.log("设置新账单地址并获取 UUID: \n%o", resp.uuid);
// 输出示例: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"

// 或者创建自定义地址
const customAddress = {
  name: "Zhang San",
  date_of_birth: "1990-01-01",
  address_line_1: "123 Main Street",
  address_line_2: "Apt 4B",
  city: "New York",
  state: "New York",
  country: "United States",
  country_iso2_code: "US",
  zipcode: "10001",
  registration_path: "deposit"
};

const customResp = await legend.createBillingAddress(customAddress);
console.log("自定义地址设置结果:", customResp);
// 输出示例:
// {
//  "result": "success",
//   "message": "New billing address added.",
//   "uuid": "581bd72d-5091-4447-ad63-40f388d5794d"
// }

5. 支付方式管理

提供支付方式的查询、添加、删除和事件监听功能,支持信用卡等多种支付方式。

5.1 获取支付方式

5.1.1 获取所有支付方式

获取用户当前可用的所有支付方式列表。

// 获取全部支付方式
const paymentMethods = await legend.getAllPaymentMethods();
console.log("所有支付方式: \n%o", paymentMethods);

5.1.2 根据交易对获取支付方式

根据特定交易对和交易方向获取适用的支付方式。

// 根据交易对获取支付方式
const tradePaymentMethods = await legend.getPaymentMethodsByTrade({
  pair: "USDTUSD",  // 交易对
  side: "buy",      // 交易方向: "buy" 或 "sell"
});
console.log("交易相关支付方式: \n%o", tradePaymentMethods);

参数说明:

字段名类型必填描述
pairstring交易货币对,如 "USDTUSD"
sidestring交易方向:"buy"(买入)或 "sell"(卖出)

5.2 添加支付方式(信用卡)

通过 iframe 方式安全地添加信用卡支付方式,支持 3DS 认证。

5.2.1 HTML 结构要求

<!-- 信用卡号输入框 -->
<div class="frame-another-input"></div>

<!-- 支付表单容器 -->
<section class="frame-wrapper">
  <div class="frame-card-number"></div>   <!-- 卡号 -->
  <div class="frame-expiry-date"></div>   <!-- 有效期 -->
  <div class="frame-cvv"></div>           <!-- CVV -->
  <button class="btn-submit">提交</button> <!-- 提交按钮 -->
</section>

<!-- 3DS 认证 iframe -->
<iframe class="frame-3ds" title="3D Secure 认证"></iframe>

5.2.2 JavaScript 实现

// 首先设置账单地址(必需)
const addressResult = await legend.createBillingAddress(profile);
const billingUuid = addressResult.uuid;

// 事件处理函数
const handleFrameEvents = (type) => (...args) => {
  console.log(`事件类型: ${type}`, ...args);
  
  // 处理表单验证状态
  if (type === "FRAME_VALIDATION_CHANGED") {
    const $frame = document.querySelector(`.frame-${args[0].element}`);
    if (args[0].isValid || args[0].isEmpty) {
      $frame.classList.remove("error");
    } else {
      $frame.classList.add("error");
    }
  }
  
  // 处理特定事件
  switch (type) {
    case "CARD_TOKENIZED":
      console.log("卡片令牌化成功");
      break;
    case "CARD_TOKENIZATION_FAILED":
      console.error("卡片令牌化失败");
      break;
    case "READY":
      console.log("支付框架准备就绪");
      break;
  }
};

try {
  const result = await legend.addPaymentMethod({
    // 必需参数
    uuidAddress: billingUuid, // 使用 setBillingAddress() 返回的 UUID
    btnSubmitSelector: ".btn-submit",
    frame3DsSelector: ".frame-3ds",
    
    // 3DS 认证回调
    showFrame3Ds: () => {
      console.log("显示 3DS 认证窗口");
      document.querySelector(".frame-3ds").style.display = "block";
    },
    hideFrame3Ds: () => {
      console.log("隐藏 3DS 认证窗口");
      document.querySelector(".frame-3ds").style.display = "none";
    },
    
    // 卡片字段配置
    cardNumber: {
      frameSelector: ".frame-another-input",
    },
    expiryDate: {
      // 使用默认配置
    },
    cvv: {
      // 使用默认配置
    },
    // 样式定制-推荐 styles和style配置一个即可
    styles: {
       "input": {
          "color": "black",
          "font-weight": "bold",
          "font-size": "20px",
          "letter-spacing": "3px"
        },
        "input#pan": {
          "font-size": "24px"
        },
        "input.is-valid": {
          "color": "green"
        },
        "input.is-invalid": {
          "color": "red"
        },
        "input.is-onfocus": {
          "color": "black"
        }
    },
    // 样式定制-已过时但兼容 
    style: {
      placeholder: {
        base: {
          color: "#ff2052",
          fontSize: "15px"
        }
      }
    },
    
    // 事件监听
    events: {
      CARD_BIN_CHANGED: handleFrameEvents("CARD_BIN_CHANGED"),
      CARD_SUBMITTED: handleFrameEvents("CARD_SUBMITTED"),
      CARD_TOKENIZED: handleFrameEvents("CARD_TOKENIZED"),
      CARD_TOKENIZATION_FAILED: handleFrameEvents("CARD_TOKENIZATION_FAILED"),
      CARD_VALIDATION_CHANGED: handleFrameEvents("CARD_VALIDATION_CHANGED"),
      FRAME_ACTIVATED: handleFrameEvents("FRAME_ACTIVATED"),
      FRAME_FOCUS: handleFrameEvents("FRAME_FOCUS"),
      FRAME_BLUR: handleFrameEvents("FRAME_BLUR"),
      FRAME_VALIDATION_CHANGED: handleFrameEvents("FRAME_VALIDATION_CHANGED"),
      PAYMENT_METHOD_CHANGED: handleFrameEvents("PAYMENT_METHOD_CHANGED"),
      READY: handleFrameEvents("READY"),
    },
  });
  
  console.log("添加支付方式成功: \n%o", result);
  
} catch (error) {
  console.error("添加支付方式失败: ", error);
}

5.2.3 配置参数说明

必需参数:

字段名类型描述
uuidAddressstringsetBillingAddress() 返回的 UUID
btnSubmitSelectorstring提交按钮的 CSS 选择器
frame3DsSelectorstring3DS iframe 的 CSS 选择器

卡片字段配置:

字段名类型描述
cardNumberobject卡号输入框配置
expiryDateobject有效期输入框配置
cvvobjectCVV输入框配置

3DS 回调函数:

函数名描述
showFrame3Ds()显示 3DS 认证窗口时调用
hideFrame3Ds()隐藏 3DS 认证窗口时调用

5.3 删除支付方式

删除指定的支付方式。

// 删除支付方式
const result = await legend.removePaymentMethod({ 
  payment_method_id: "pm_123456789"  // 支付方式ID
});
console.log("删除支付方式结果: \n%o", result);

参数说明:

字段名类型必填描述
payment_method_idstring支付方式的唯一标识符

5.4 支付事件监听

在最新版本中,我们强烈推荐使用构造函数回调配置的方式来处理 SDK 的各种状态和交互。相关文档查看 8. onPay 支付回调

const legend = new LegendBase({
 /**
  * 支付流程回调
  * @param {string} status - 支付状态(success|pending|failed)
  */
  onPay: (status, paymentData) => {
    // 在这里处理支付逻辑
    console.log('支付状态:', status);
    console.log(‘相关支付数据’,paymentData)
  },
});

⚠️所以对于原先事件,我们依然支持 但不推荐。

5.4.1 支付成功(不推荐)

legend.on("legend:payment-success", (event) => {
  console.log("支付成功: \n%o", event.data);
  // 处理成功逻辑,如更新UI、跳转页面等
});

5.4.2 支付失败(不推荐)

legend.on("legend:payment-failed", (event) => {
  console.error("支付失败:", event);
  // 示例事件数据:
  // {
  //   subject: "Unable to verify your card due to insufficient funds or credits.",
  //   message: "Please try a different card."
  // }
  alert(`支付失败: ${event.message}`);
});

5.4.3 支付处理中(不推荐)

legend.on("legend:payment-pending", (event) => {
  console.log("支付处理中: \n%o", event);
  // 显示处理中状态
});

5.4.4 支付错误(不推荐)

legend.on("legend:payment-error", (event) => {
  console.error("支付错误: \n%o", event);
  // 处理系统错误
});

5.5 获取推荐的银行列表

// recommended crypto friendly banks
const resp = await legend.queryRecommendedBanks({
  currency: "USD",
  country_iso2_code: "US"
});
console.log("recommended banks: \n%o", resp);

完整示例:添加信用卡支付

async function addCreditCard() {
  try {
    // 1. 设置账单地址
    const address = await legend.getAddressFromKyc();
    const addressResult = await legend.createBillingAddress(address);
    
    // 2. 添加支付方式
    const paymentResult = await legend.addPaymentMethod({
      uuidAddress: addressResult.uuid,
      btnSubmitSelector: ".btn-submit",
      frame3DsSelector: ".frame-3ds",
      showFrame3Ds: () => document.querySelector(".frame-3ds").style.display = "block",
      hideFrame3Ds: () => document.querySelector(".frame-3ds").style.display = "none",
      cardNumber: { frameSelector: ".frame-card-number" },
      expiryDate: {},
      cvv: {},
      events: {
        CARD_TOKENIZED: () => console.log("卡片添加成功"),
        CARD_TOKENIZATION_FAILED: (error) => console.error("卡片添加失败", error)
      }
    });
    
    console.log("支付方式添加成功:", paymentResult);
    return paymentResult;
    
  } catch (error) {
    console.error("添加支付方式失败:", error);
    throw error;
  }
}

6. 交易询价

提供基于法币金额的交易询价功能,用于在交易前获取实时的价格、手续费等交易详情。

方法说明

quoteByFiat 方法根据指定的法币金额,计算在特定交易对和支付方式下可获得的加密货币数量及相关费用。

方法签名

const quote = await legend.quoteByFiat(quoteParams);

参数说明

quoteParams 对象包含以下字段:

字段名类型必填描述示例
pairstring交易货币对 "USDTUSD", "BTCUSD"
payment_method_idstring支付方式 ID "pm_123456789"
sidestring交易方向 "buy"(买入), "sell"(卖出)
sizenumber法币金额 50(表示 50 USD)

返回值

返回 QuoteResponse 对象,包含完整的交易报价信息:

字段名类型描述
createdstring报价创建时间(ISO 8601 格式) "2025-10-09T08:43:36.785355Z"
pairstring交易货币对 "USDTUSD"
pricestring交易价格(1个加密货币对应的法币价格) "1.00530"
quantitynumber可获得的加密货币数量 47.4983
processing_feestring处理手续费 "0.75"
network_feenumber网络手续费 1.5
sidestring交易方向 "buy"
sizenumber输入的法币金额 50

示例代码

// 基于法币金额询价(买入 USDT)
const quote = await legend.quoteByFiat({
  pair: "USDTUSD",
  payment_method_id: "pm_123456789",
  side: "buy",
  size: 50,  // 50 USD
});

console.log("法币询价结果: \n%o", quote);

// 输出示例:
// {
//   created: "2025-10-09T08:43:36.785355Z",
//   pair: "USDTUSD",
//   price: "1.00530",
//   quantity: 47.4983,
//   processing_fee: "0.75",
//   network_fee: 1.5,
//   side: "buy",
//   size: 50
// }

7. 交易执行

提供基于法币金额的交易执行功能,支持信用卡支付和 3DS 安全认证。

方法说明

orderByFiat 方法用于执行加密货币交易,根据指定的法币金额和支付方式完成买入或卖出操作。

方法签名

const orderResult = await legend.orderByFiat(orderParams);

参数说明

orderParams 对象包含以下字段:

字段名类型必填描述
frame3DsSelectorstring3DS 验证容器的 CSS 选择器 ".frame-3ds"|
showFrame3Dsfunction显示 3DS 验证窗口的回调函数 () => console.log("显示3DS")|
hideFrame3Dsfunction隐藏 3DS 验证窗口的回调函数 () => console.log("隐藏3DS")|
app_statestring应用状态标识,用于关联订单(会在 webhook 的 reference 字段中传递) "user_123_session"|
networkstring数字货币使用的区块链网络 "ETH", "BTC", "TRX"|
pairstring交易货币对(最后三位为法币,之前为数字货币) "USDCUSD", "USDTUSD"|
payment_method_idstring支付方式 ID "pm_123456789"|
sidestring交易方向 "buy"(买入), "sell"(卖出)」
sizenumber法币交易金额 50|
cvvstring信用卡 CVV 安全码(需要从用户输入获取) "123"|

返回值

返回 OrderResponse 对象,包含交易执行结果:

字段名类型描述
trade_idstring交易唯一标识符 "e792990b-9b79-423e-824e-bf125bd90e2a"
quantitynumber实际获得的加密货币数量 47.498
pricenumber成交价格 1.00531
pairstring交易货币对 "USDTUSD"
sidestring交易方向 "buy"
createdstring交易创建时间(ISO 8601 格式) "2025-10-09T09:05:47.020480Z"
network_feenumber网络手续费 1.5
processing_feestring处理手续费 "0.75"
sizenumber输入的法币金额 50

使用示例

7.1 基本用法

// 执行法币交易(买入 USDT)
const resp = await legend.orderByFiat({
  frame3DsSelector: ".frame-3ds",
  showFrame3Ds: () => {
    console.log("显示 3DS 验证窗口");
    document.querySelector(".frame-3ds").style.display = "block";
  },
  hideFrame3Ds: () => {
    console.log("隐藏 3DS 验证窗口");
    document.querySelector(".frame-3ds").style.display = "none";
  },
  app_state: "user_123_session_001", // 用于关联订单的任意字符串
  network: "ETH",
  pair: "USDTUSD",
  payment_method_id: "pm_123456789",
  side: "buy",
  size: 50,
  cvv: "123", // 从用户输入获取
});

console.log("交易执行结果: \n%o", resp);

// 输出示例:
// {
//   trade_id: "e792990b-9b79-423e-824e-bf125bd90e2a",
//   quantity: 47.498,
//   price: 1.00531,
//   pair: "USDTUSD",
//   side: "buy",
//   created: "2025-10-09T09:05:47.020480Z",
//   network_fee: 1.5,
//   processing_fee: "0.75",
//   size: 50
// }

7.2 HTML 结构要求

<!-- 3DS 认证容器 -->
<div class="frame-3ds" style="display: none;">
  <!-- 3DS 认证 iframe 将在此渲染 -->
</div>

<!-- CVV 输入框(如果需要用户输入) -->
<div class="cvv-input-container">
  <label for="cvv">信用卡安全码 (CVV)</label>
  <input type="password" id="cvv" maxlength="4" placeholder="123">
</div>

7.3 完整交易流程

async function executeTrade(tradeConfig) {
  try {
    const {
      amount,
      paymentMethodId,
      cryptoAsset = "USDT",
      network = "ETH",
      cvv
    } = tradeConfig;

    // 1. 先进行询价
    const quote = await legend.quoteByFiat({
      pair: `${cryptoAsset}USD`,
      payment_method_id: paymentMethodId,
      side: "buy",
      size: amount
    });

    console.log("询价结果:", quote);

    // 2. 执行交易
    const orderResult = await legend.orderByFiat({
      frame3DsSelector: ".frame-3ds",
      showFrame3Ds: () => {
        console.log("显示 3DS 认证");
        document.querySelector(".frame-3ds").classList.add("active");
      },
      hideFrame3Ds: () => {
        console.log("隐藏 3DS 认证");
        document.querySelector(".frame-3ds").classList.remove("active");
      },
      app_state: `trade_${Date.now()}_${cryptoAsset}`,
      network: network,
      pair: `${cryptoAsset}USD`,
      payment_method_id: paymentMethodId,
      side: "buy",
      size: amount,
      cvv: cvv
    });

    console.log("交易成功:", orderResult);
    return orderResult;

  } catch (error) {
    console.error("交易执行失败:", error);
    throw error;
  }
}

// 使用示例
const tradeResult = await executeTrade({
  amount: 100,
  paymentMethodId: "pm_123456789",
  cryptoAsset: "USDT",
  network: "ETH",
  cvv: "123"
});

7.4 交易状态监听

const legend = new LegendBase({
 /**
  * 支付流程回调
  * @param {string} status - 支付状态(success|pending|failed)
  */
  onPay: (status, paymentData) => {
    // 在这里处理支付逻辑
    console.log('支付状态:', status);
    console.log(‘相关支付数据’,paymentData)
  },
});

8.onPay 支付回调

支付流程的状态回调函数,用于处理支付过程中的各种状态变化。

回调函数签名

/**
 * 支付流程回调
 * @param {string} status - 支付状态
 *   终态:'success' | 'pending' | 'failed' | 'cancelled'
 *   中间态:'clicked' | 'authorizing' | 'processing'
 * @param {Object} paymentData - 支付数据,根据状态不同数据结构有所差异
 */
onPay: (status, paymentData) => {
  // 处理支付状态变化
}

状态总览

onPaystatus 分为两类:终态表示本次支付流程已结束,中间态表示流程仍在进行中,用于驱动 loading UI。

status类别触发时机适用流程
clicked中间态用户点击 Apple Pay / Google Pay 按钮那一刻仅原生按钮流程
authorizing中间态支付 sheet 即将弹起,等待用户授权仅原生按钮流程
processing中间态用户已授权,SDK 正在向 Legend 下单仅原生按钮流程
success终态支付成功,返回完整交易信息全部
pending终态支付处理中,等待外部服务结果全部
failed终态明确的业务失败全部
cancelled终态用户主动取消支付仅原生按钮流程
⚠️

中间态(clicked / authorizing / processing仅在 Apple Pay / Google Pay 原生按钮流程中触发setupGooglePay() / setupApplePay())。Apple Pay 的重定向流程startApplePay())下,合作伙伴页面已经跳转离开,onPay 回调全程不会触发,支付结果只能通过回跳 URL 的 status 参数获取,详见 Apple Pay

状态时序合约

三个中间态都不是终态,收到之后保证会有一个终态跟随,可以据此安全地配对开关 loading:

[空闲] ── clicked ──> [authorizing] ──(sheet 弹起)──> [用户在 sheet 内操作]
                                                            │
                        ┌─── 用户取消 ──────────────────────┤
                        ↓                                    ↓
                  cancelled(终态)                       用户授权完成
                                                             │
                                                             ↓
                                                    processing(下单中)
                                                             │
                        ┌────────────┬─────────────┬─────────┤
                        ↓            ↓             ↓         ↓
                     success       failed       pending   (异常 → onError)
                     (终态)        (终态)        (终态)

Loading 生命周期建议

收到的 status建议动作
clickedsetLoading(true),同时 disable 按钮防重复点击
authorizing可选 setLoading(false) —— 支付 sheet 会遮住页面,此时页面 loading 意义不大
processingsetLoading(true) —— 这是最需要 loading 的阶段,下单接口在 Sandbox 环境实测可达 20–40 秒
success / pending / failed / cancelledsetLoading(false) + 展示对应结果 UI

状态详情

1、status = 'success' - 支付成功

支付已成功完成,返回完整的交易信息。

paymentData 结构:

{
  "target": "checkout-redirect",
  "status": "success",
  "message": "Card added successfully.",
  "data": {
    "trade_id": "0ad4c41d-c76c-4a25-8343-7a0ae96786b3", // 交易ID
    "quantity": 47.4961,     // 成交数量
    "price": 1.00535,        // 成交价格
    "pair": "USDTUSD",       // 交易对
    "side": "buy",           // 买卖方向 (buy/sell)
    "created": "2025-07-13T03:19:38.549313Z", // 创建时间
    "network_fee": 1.5,      // 网络费用
    "processing_fee": "0.75", // 处理费用
    "size": 50               // 订单大小
  }
}

使用示例:

onPay: (status, paymentData) => {
  if (status === 'success') {
    console.log('支付成功!');
    console.log('交易ID:', paymentData.data.trade_id);
    console.log('成交数量:', paymentData.data.quantity);
    console.log('总费用:', paymentData.data.network_fee + parseFloat(paymentData.data.processing_fee));
    
    // 显示成功页面,更新订单状态等
    showSuccessPage(paymentData.data);
  }
}

2、status = 'pending' - 支付处理中

支付正在处理中,需要等待进一步的结果。

paymentData 结构:

{
  "target": "checkout-redirect",
  "status": "pending",
  "subject": "Trade In Progress",
  "message": "Your order is being processed, please check again later.",
  "data": {
    "target": "checkout-redirect",
    "status": "pending"
  }
}

使用示例:

onPay: (status, paymentData) => {
  if (status === 'pending') {
    console.log('支付处理中...');
    
    // 显示加载状态,防止用户重复提交
    showLoadingState('支付处理中,请稍候...');
  }
}

3、status = 'failed' - 支付失败

支付失败,返回具体的错误信息。

paymentData 结构:

{
  "target": "checkout-redirect",
  "status": "failed",
  "subject": "Trade Failed", // 错误主题
  "message": "Please try again later.", // 错误详情
  "data": {
    "target": "checkout-redirect",
    "status": "failed",
    "subject": "Unable to verify your card.",
    "message": "There's an issue with your card. Please try another one."
  }
}
// message 列表

Please try again later.

Unable to verify your card.

Unable to fetch 3DS init data.

3DS verification failed.

Unable to fetch 3DS result data.

Unable to authenticate your payment.

Payment rejected

There's an issue with your card. Please check your card details or try a different card.

Your transaction was declined due to insufficient funds or spending limits. Please check your balance or contact your bank.

This transaction was declined for security reasons. Please contact your bank to verify and approve the transaction.

Your card issuer does not support crypto transaction. Please try a different card or payment method.

We're experiencing a temporary technical issue. Please try again in a few minutes.

Your transaction was blocked due to security and risk control policies. Please try again later or use a different payment method.

Card holder name do not match our record.

使用示例:

onPay: (status, paymentData) => {
  if (status === 'failed') {
    console.log('支付失败:', paymentData.subject);
    console.log('失败原因:', paymentData.message);
    
    // 显示错误提示给用户
    showErrorModal({
      title: paymentData.subject,
      message: paymentData.message
    });
  }
}

4、status = 'cancelled' - 用户主动取消

用户在 Apple Pay / Google Pay 支付 sheet 中主动关闭或点击取消。这是一个终态

🚨

行为变更:在旧版本中,用户主动取消会被当作异常派发到 onError(Apple Pay 抛 DOMException { name: "AbortError" },Google Pay 抛 { statusCode: "CANCELED" })。从本版本起,用户取消被单独识别并派发到 onPay('cancelled')onError 只保留真正的技术异常。如果你此前在 onError 里通过判断 err.name === "AbortError"err.statusCode === "CANCELED" 来识别取消,请迁移到 onPaycancelled 分支。

paymentData 结构:

{
  "status": "cancelled",
  "provider": "googlepay",
  "reason": "user_abort"
}
字段类型说明
providerstring"applepay" / "googlepay"
reasonstring取消原因,目前为 "user_abort"。未来可能扩展新值,建议用 default 兜底

使用示例:

onPay: (status, paymentData) => {
  if (status === 'cancelled') {
    // 用户主动取消不是错误,不要弹错误提示
    setLoading(false);
    showToast('支付已取消');
    enableRetryButton();
  }
}

5、status = 'clicked' - 用户点击支付按钮

用户物理点击 Apple Pay / Google Pay 按钮的那一刻触发,早于任何网络请求。用于立即给出 UI 反馈(点击到 sheet 弹出之间可能有数百毫秒到 1 秒的空档)。

paymentData 结构:

{
  "status": "clicked",
  "provider": "googlepay"
}

使用示例:

onPay: (status, paymentData) => {
  if (status === 'clicked') {
    setLoading(true);
    disablePayButton();  // 防止重复点击
  }
}

6、status = 'authorizing' - 支付 sheet 弹起中

SDK 即将调用原生 API 唤起支付 sheet 时触发。

  • Apple Paynew PaymentRequest(...) 构造完成、show() 调用之前
  • Google Pay:商户配置与订单参数准备完成、loadPaymentData(...) 调用之前

paymentData 结构:

{
  "status": "authorizing",
  "provider": "applepay"
}

使用示例:

onPay: (status, paymentData) => {
  if (status === 'authorizing') {
    // 支付 sheet 会遮住页面,此处可选择关闭页面 loading
    setLoading(false);
  }
}

7、status = 'processing' - 正在下单

用户已在支付 sheet 中完成授权,sheet 已关闭,SDK 正在向 Legend 提交订单。

💡

这是最需要 loading 的阶段。 sheet 关闭后页面回到合作伙伴自己的 UI,若不给提示,用户会以为页面卡死。该阶段对应的下单接口在 Sandbox 环境实测耗时 20–40 秒

paymentData 结构:

{
  "status": "processing",
  "provider": "googlepay"
}

使用示例:

onPay: (status, paymentData) => {
  if (status === 'processing') {
    setLoading(true, '正在处理支付,请稍候…');
  }
}

收到 processing 后,最终结果仍通过既有的 success / pending / failed 终态返回,SDK 不会额外派发 processing-success 之类的配对事件。

完整使用示例

const legend = new LegendBase({
  onPay: (status, paymentData) => {
    switch (status) {
      // ——— 中间态:仅 Apple Pay / Google Pay 原生按钮流程 ———
      case 'clicked':
        // 用户刚点下按钮,立刻给反馈
        setLoading(true);
        disablePayButton();
        break;

      case 'authorizing':
        // 支付 sheet 即将弹起,会遮住页面,可选关闭 loading
        setLoading(false);
        break;

      case 'processing':
        // 用户已授权,正在下单 —— Sandbox 环境可能耗时 20-40 秒
        setLoading(true, '正在处理支付,请稍候…');
        break;

      // ——— 终态 ———
      case 'success':
        // 支付成功处理
        setLoading(false);
        console.log('🎉 支付成功! 交易ID:', paymentData.data.trade_id);
        updateOrderStatus('completed', paymentData.data);
        redirectToSuccessPage();
        break;
        
      case 'pending':
        // 支付处理中
        setLoading(false);
        console.log('⏳ 支付处理中...');
        showProcessingOverlay();
        break;
        
      case 'failed':
        // 支付失败
        setLoading(false);
        console.log('❌ 支付失败:', paymentData.message);
        showErrorMessage(paymentData.subject, paymentData.message);
        enableRetryButton();
        break;

      case 'cancelled':
        // 用户主动取消 —— 不是错误,不要弹错误提示
        setLoading(false);
        console.log('用户取消了支付:', paymentData.provider);
        showToast('支付已取消');
        enableRetryButton();
        break;
        
      default:
        console.warn('未知的支付状态:', status);
    }
  }
});

9.onError 错误处理回调

统一错误处理回调函数,用于捕获和处理 SDK 运行过程中发生的所有错误。

🚨

行为变更:用户主动取消不再进入 onError

在旧版本中,用户在 Apple Pay / Google Pay 支付 sheet 中主动取消,会作为异常派发到 onError

  • Apple Pay:DOMException { name: "AbortError", code: 20 }
  • Google Pay:{ statusCode: "CANCELED" }

从本版本起,用户主动取消由 SDK 单独识别,改为派发到 onPay('cancelled', { provider, reason })onError 从此只包含真正的技术异常(网络错误、接口报错、SDK 内部异常等)。

迁移方式:把 onError 里判断 err.name === "AbortError" / err.statusCode === "CANCELED" 的分支删掉,改为在 onPay 中新增一个 case 'cancelled': 分支。详见 8.onPay 支付回调status = 'cancelled' 小节。

回调函数签名

/**
 * 错误处理回调
 * @param {Object} error - 错误对象
 * @param {string|number} error.code - 错误代码
 * @param {string} error.message - 错误信息
 * @param {Object} [error.errors] - 错误详情对象
 * @param {Object} [error.metadata] - 错误元数据
 * @param {string} [error.success] - 请求成功状态
 */
onError: (error) => {
  // 处理错误逻辑
}

错误格式示例

422

{
  "code":422,
  "message": "Unauthenticated",
  "errors": {
    "signature": "Unauthenticated"
  }
}
{
  "code":422,
  "message": "Timestamp is not valid.",
  "errors": {
    "timestamp": "Timestamp is not valid."
  }
}
{
  "code": 422,
  "message": "The parent id field is required.",
  "errors": {
    "parent_id": ["The parent id field is required."]
  }
}
{
  "code": 422,
  "message": "The selected residential is invalid.",
  "errors": {
    "residential": ["The selected residential is invalid."]
  }
}
{
  "code": 422,
  "message": "The city is not allowed.",
  "errors": {
    "city": ["The city is not allowed."]
  }
}
{
  "code": 422,
  "message": "The country field is required when country iso2 code is not present.",
  "errors": {
    "country": [
      "The country field is required when country iso2 code is not present."
    ]
  }
}
{
  "code": 422,
  "message": "The country iso2 code must be 2 characters.",
  "errors": {
    "country_iso2_code": ["The country iso2 code must be 2 characters."]
  }
}
{
  "code": 422,
  "message": "The address line 1 field is required.",
  "errors": {
    "address_line_1": ["The address line 1 field is required."]
  }
}
{
  "code": 422,
  "message": "The state field is required.",
  "errors": {
    "state": ["The state field is required."]
  }
}
{
  "code": 422,
  "message": "The data field is required.",
  "errors": {
    "data": ["The data field is required."]
  }
}
{
  "code": 422,
  "message": "The data.token field is required.",
  "errors": {
    "data.token": ["The data.token field is required."]
  }
}
{
  "code": 422,
  "message": "The currency field is required.",
  "errors": {
    "currency": ["The currency field is required."]
  }
}
{
  "code": 422,
  "message": "The billing address field is required.",
  "errors": {
    "billing_address": ["You are not the owner of this model."]
  }
}
{
  "code": 422,
  "message": "The selected billing address is invalid.",
  "errors": {
    "billing_address": ["The selected billing address is invalid."]
  }
}

403

{
  "code": 403,
  "message": "To complete the verification process for your account, please check your email. We have sent instructions on how to finalize the verification of your latest deposits/withdrawals.",
  "error_code": "RFI_REQUIRED",
  "errors": {
    "rfi": ["RFI required"]
  },
  "data": {
    "url": "",
    "email": "",
    "grace_period_end_timestamp": null
  }
}
{
"code": 403,  
"message": "To complete the verification process for your account, please check your email. We have sent instructions on how to finalize the verification of your latest deposits/withdrawals.",
  "error_code": "EDD_REQUIRED",
  "errors": {
    "edd": ["EDD required"]
  },
  "data": {
    "email": ""
  }
}

50X

{
  "code":500,
  "message": "Card rejected.",
  "error_code": "card_rejected",
  "errors": {
    "trade": ["Card rejected."]
  }
}
{
  "code":503,
  "message": "Account suspended - Please contact [email protected]",
  "error": "Account suspended - Please contact [email protected]"
}

101100

{
  "success": "false",
  "code": "101100",
  "message": "Due to compliance reasons, we are unable to offer services to users from this region.",
  "metadata": {
    "ip": "1.1.1.1",
    "country": "AF"
  }
}

305xx

// 触发场景: WorldPay createTokenBySession API 调用失败
{
  "code": "305101",
  "message": "Please try again later."
}
// 触发场景: 绑卡时 CVV 验证失败
{
  "code": "305102",
  "message": "Unable to verify your card."
}
// 触发场景: init3DS API 调用失败
{
  "code": "305103",
  "message": "Unable to fetch 3DS init data."
}
// 触发场景: 3DS 验证失败(枚举定义)
{
  "code": "305104",
  "message": "3DS verification failed."
}
// 触发场景: get3DSResult API 调用失败
{
  "code": "305105",
  "message": "Unable to fetch 3DS result data."
}
// 触发场景: authenticate3DS / processChallenged3DS / get3DSResult 认证结果为失败
{
  "code": "305106",
  "message": "Unable to authenticate your payment."
}
// 触发场景: 超出信用卡交易限额
{
  "code": "305100",
  "message": "If you'd like to request for an increase in your trading limit please contact [email protected]"
}
// 触发场景: 支付时无具体结果码的兜底失败
{
  "code": "305108",
  "message": "Payment rejected"
}
// 触发场景: CVV 错误、卡号无效、卡已过期等卡信息问题
{
  "code": "305110",
  "message": "There's an issue with your card. Please check your card details or try a different card."
}
// 触发场景: 余额不足或超出消费限额
{
  "code": "305111",
  "message": "Your transaction was declined due to insufficient funds or spending limits. Please check your balance or contact your bank."
}
// 触发场景: 银行欺诈拦截或安全风险
{
  "code": "305112",
  "message": "This transaction was declined for security reasons. Please contact your bank to verify and approve the transaction."
}
// 触发场景: 发卡行不支持加密货币交易
{
  "code": "305113",
  "message": "Your card issuer does not support crypto transaction. Please try a different card or payment method."
}
// 触发场景: WorldPay 网关技术错误或超时
{
  "code": "305114",
  "message": "We're experiencing a temporary technical issue. Please try again in a few minutes."
}
// 触发场景: FraudSight 风险评分过高
{
  "code": "305115",
  "message": "Your transaction was blocked due to security and risk control policies. Please try again later or use a different payment method."
}
// 触发场景: 持卡人姓名与 KYC 记录不匹配
{
  "code": "305116",
  "message": "Card holder name do not match our record."
}
// 触发场景: getBinData API 调用失败
{
  "code": "305107",
  "message": "Unable to fetch BIN data."
}
// 触发场景: getDisputeAccessToken API 调用失败
{
  "code": "305109",
  "message": "Unable to get dispute access token"
}
// 触发场景: 手动维护开关启用
{
  "code": "305199",
  "message": "The service is currently under maintenance. Please try again later."
}

完整使用示例

const legend = new LegendBase({
  onError: (error) => {
    console.error('SDK 错误:', error);
    
    // 根据错误代码进行分类处理
    switch (error.code) {
      case '101100':
        // 区域限制错误
        showBlockingError({
          title: '服务不可用',
          message: error.message,
          type: 'regional_restriction'
        });
        break;
        
      case 401:
      case 422:
        if (error.message.includes('Unauthenticated')) {
          // 认证错误
          handleAuthenticationError();
        } else if (error.errors) {
          // 参数验证错误
          handleValidationError(error.errors);
        } else {
          // 其他 4xx 错误
          showWarningToast(error.message);
        }
        break;
        
      case 500:
      case 502:
      case 503:
        // 服务器错误
        showServerError(error.message);
        break;
        
      default:
        // 未知错误
        showGenericError(error.message);
    }
  }
});

// 处理参数验证错误的辅助函数
function handleValidationErrors(errors) {
  const errorMessages = [];
  
  for (const [field, messages] of Object.entries(errors)) {
    if (Array.isArray(messages)) {
      errorMessages.push(...messages);
    } else {
      errorMessages.push(messages);
    }
  }
  
  if (errorMessages.length > 0) {
    showValidationErrorModal(errorMessages.join('\n'));
  }
}

10. 事件(不推荐)

🎯 推荐使用回调配置

在最新版本中,我们强烈推荐使用构造函数回调配置的方式来处理 SDK 的各种状态和交互。

const legend = new LegendBase({
  onError: (error) => {
    console.error('SDK 错误:', error);
  },
  onPay: (status, paymentData) => {
    console.log('支付状态:', status);
    console.log('支付数据:', paymentData);
  },
  onAuthChange: (status) => {
    console.log('KYC认证状态:', status);
  }
});

⚠️ 事件模型:依然支持但不推荐

该事件罗列了 SDK 中所有包含事件,用于参考

// bind events
legend.on("*", (type, e) => {
  console.log("custom event dispatched: \n[%o], %o", type, e);
});

legend.on("legend:payment-success", (e) => {
  // 在 addPaymentMethod() 方法或者 orderByFiat() 方法运行时触发。
  // 表示支付成功,添加卡片时的支付验证成功。
  console.log("legend:payment-success: \n%o", e);
  // {
  //   data: {
  //     "trade_id": "0ad4c41d-c76c-4a25-8343-7a0ae96786b3",
  //     "quantity": 47.4961,
  //     "price": 1.00535,
  //     "pair": "USDTUSD",
  //     "side": "buy",
  //     "created": "2025-07-13T03:19:38.549313Z",
  //     "network_fee": 1.5,
  //     "processing_fee": "0.75",
  //     "size": 50
  //   }
  // }

  // {
  //   "target": "checkout-redirect",
  //   "status": "success",
  //   "message": "Card added successfully.",
  //   "data": {}
  // }
});
legend.on("legend:payment-failed", (e) => {
  // 在 addPaymentMethod() 方法或者 orderByFiat() 方法运行时触发。
  // 表示支付失败,添加卡片时的支付验证失败。
  console.log("legend:payment-failed: \n%o", e);
  // {
  //   "target": "checkout-redirect",
  //   "status": "failed",
  //   "subject": "Unable to verify your card.",
  //   "message": "There’s an issue with your card. Please try another one."
  // }
});
legend.on("legend:payment-pending", (e) => {
  // 在 addPaymentMethod() 方法或者 orderByFiat() 方法运行时触发。
  // 表示支付或者添加卡片时的支付验证没有完成,等待外部服务的响应
  console.log("legend:payment-pending: \n%o", e);
});
legend.on("legend:payment-error", (e) => {
  // 在 addPaymentMethod() 方法或者 orderByFiat() 方法运行时触发。
  // 表示支付或者添加卡片时的支付验证发生错误。
  console.log("legend:payment-error: \n%o", e);
  // {
  //   "target": "checkout-redirect",
  //   "status": "failed",
  //   "subject": "Unable to verify your card.",
  //   "message": "There’s an issue with your card. Please try another one."
  // }
  // 也有另一种字符串格式的错误消息,需要检查数据类型
  // "Card form invalid"
});

legend.on("legend:payment-cancelled", (e) => {
  // 在 Apple Pay / Google Pay 原生按钮流程中,用户主动关闭支付 sheet 时触发。
  // 属于终态事件。旧版本中该场景走 legend:payment-error,现已拆分独立。
  console.log("legend:payment-cancelled: \n%o", e);
  // { provider: "googlepay", reason: "user_abort" }
});

legend.on("legend:payment-clicked", (e) => {
  // 用户点击 Apple Pay / Google Pay 按钮时触发(中间态)。
  console.log("legend:payment-clicked: \n%o", e);
  // { provider: "googlepay" }
});

legend.on("legend:payment-authorizing", (e) => {
  // 支付 sheet 即将弹起、等待用户授权时触发(中间态)。
  console.log("legend:payment-authorizing: \n%o", e);
  // { provider: "googlepay" }
});

legend.on("legend:payment-processing", (e) => {
  // 用户已在支付 sheet 中授权,SDK 正在向 Legend 下单时触发(中间态)。
  // Sandbox 环境该阶段实测耗时 20-40 秒,建议在此显示 loading。
  console.log("legend:payment-processing: \n%o", e);
  // { provider: "googlepay" }
});

legend.on("legend:add-payment-method", (e) => {
  // 在 addPaymentMethod() 方法执行完成,添加成功之后触发。
  console.log("legend:add-payment-method: \n%o", e);
  // {
  //   "type": "CREDIT",
  //   "entry": "",
  // }
});

legend.on("legend:frame-3ds", (e) => {
  // 在 addPaymentMethod() 方法或者 orderByFiat() 方法运行中遇到 3DS 验证时触发。
  // 表示需要显示或隐藏 3DS 验证的弹窗元素。
  // 也可以在调用方法时传入 showFrame3Ds 和 hideFrame3Ds 参数完成。
  console.log("legend:frame-3ds: \n%o", e);
  // { visible: true }
});

legend.on("legend:kyc-connect", (e) => {
  // 在实例调用 authorize() 方法之后触发,可以用来确认当前用户的 KYC 状态。
  // 也可以手动调用实例中的 getState() 方法,读取 kycStatus 确认 KYC 状态。
  console.log("legend:kyc-connect: \n%o", e);
  // { status: "Approved" }
});

legend.on("cancel-payment-method", (e) => {
  // 通常不需要处理这个取消事件,SDK 内部用于集成时兼容不同交互流程。
  // 表示取消了上一次的 addPaymentMethod() 方法的调用。
  console.log("cancel-payment-method: \n%o", e);
  // { status: "cancel" }
});