# @exerp/odin-dropin 中文文档

规范地址：https://npmcn.com/package/%40exerp/odin-dropin/

版本：2\.1\.0
npm 元数据更新时间：2026-02-05T20:09:33.123Z
中文译文更新时间：2026-11-14T03:31:38.566Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40exerp/odin-dropin
原文对照：https://npmcn.com/package/%40exerp/odin-dropin/?lang=original
项目主页：https://github.com/exerpteam/odin-drop-in/tree/main/packages/odin-dropin#readme
源码仓库：https://github.com/exerpteam/odin-drop-in

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：6.4 MB，11 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- 运行依赖：0 个
- 首次发布：2025-05-15T20:40:17.190Z
- 最新版本发布：2026-02-05T20:09:33.006Z
- 近 30 天下载量：12703（2026-10-14 至 2026-11-12，npm 官方下载统计）
- 已发布版本数：8（近 12 个月 1 个）

## 中文简介

ODIN 支付

## 安装

```sh
npm install @exerp/odin-dropin
```

也可以使用：`pnpm add @exerp/odin-dropin`、`yarn add @exerp/odin-dropin`、`bun add @exerp/odin-dropin`

## README

[工作空间读取器](https://npmcn.com/package/README.md) - 说吧 [QQ 更改日志](https://npmcn.com/package/%40exerp/odin-dropin/CHANGELOG.md)

* * *

# ODIN 支付`@exerp/odin-dropin`(中文(简体) )

这个软件包为将ODIN Pay Drop-in组件整合到网络应用程序提供了公共外观. 它简化了与 Stencil 网络基本组件和 `OdinPay.js` 图书馆.

<table><tbody><tr><td align="center"><strong>示例:卡片支付表</strong><br><img alt="ODIN Drop-in Card Example" src="https://npmcn.com/package/docs/assets/images/dropin-card-example-fields.png" width="350" loading="lazy" decoding="async" referrerpolicy="no-referrer"></td><td align="center"><strong>示例:银行 支付表</strong><br><img alt="ODIN Drop-in BANK_ACCOUNT Example" src="https://npmcn.com/package/docs/assets/images/dropin-ach-example-fields.png" width="350" loading="lazy" decoding="async" referrerpolicy="no-referrer"></td></tr></tbody></table>

## 安装

```bash
# Using pnpm
pnpm add @exerp/odin-dropin

# Using npm
npm install @exerp/odin-dropin

# Using yarn
yarn add @exerp/odin-dropin
```

## API 文档

此套件的主要导出是 `OdinDropin` 课.

### 导入

```javascript
// ES Module import
import {
  OdinDropin,
  type LogLevel,
  type OdinDropinInitializationParams, // For typing params
  type OdinFieldValidationEvent,      // For the Validation callback
  type OdinV2ThemeConfig,           // For the theme config
  type OdinSubmitPayload,           // For onSubmit callback
  type OdinPayErrorPayload          // For onError callback
} from '@exerp/odin-dropin';

// For UMD builds (if included via <script> tag),
// it should be available as a global variable `OdinDropin`.
// const odinDropinInstance = new window.OdinDropin({...});
```

### `new OdinDropin(params)`

初始化 ODIN 滴入控制器的新实例 .

**参数:**

-   `params` (单位:千美元)`OdinDropinInitializationParams`, (中文(简体) ), **需求**: 包含配置参数的对象:
    -   `odinPublicToken` (单位:千美元)`string`, (中文(简体) ), **需求**:从你的后端安全地获得的短命公共标志(它从ODIN中获取) `/auth2/public-token` 结束点). 此符号用于验证和初始化基础 `OdinPay.js` 图书馆.
    -   `countryCode` (单位:千美元)`'US' | 'CA'`, (中文(简体) ), **需求**:指定应配置付款表的国家。 这影响到邮政编码域标注和验证规则 `OdinPay.js`。 。 。 .
    -   `paymentMethodType?` (单位:千美元)`'CARD' | 'BANK_ACCOUNT'`, (中文(简体) ), _可选_,默认 : `'CARD'`:指定要制作的付款表的类型.
        -   `'CARD'`:提交信用卡支付表.
        -   `'BANK_ACCOUNT'`: 提供银行账户支付表(美国ACH,加拿大EFT,由OdinPay支持).
    -   `billingFieldsConfig?` (单位:千美元)`BillingFieldsConfig`, (中文(简体) ), _可选_:一个用于配置收费地址字段和其他标准字段的能见度并自定义外观的对象.
        -   **信用卡使用:**
            -   对于可选字段(例如, `name`, (中文(简体) ), `addressLine1`, (中文(简体) ), `city`, (中文(简体) ), `state`, (中文(简体) ), `country`, (中文(简体) ), `emailAddress`, (中文(简体) ), `phoneNumber`:
                -   关闭字段密钥: 字段将不会显示 .
                -   设定为 `true`: 字段将显示其默认标签和占位符.
                -   设定为 `FieldCustomization` 对象(例如, `{ label: 'Your Name', placeholder: 'Full Name Here' }`:字段将显示指定的自定义标签和/或占位符。 标签/位置持有人的空字符串将有效使用默认值 .
            -   对于总是构成结构的一部分但其标签/位置持有者可定制的字段(`postalCode`, (中文(简体) ), `cardInformation` 标签 :
                -   关闭字段密钥或提供空键 `FieldCustomization` 对象:将使用默认标签/位置持有人.
                -   设定为 `FieldCustomization` 对象:将使用指定的自定义标签和/或占位符。 (说明: `cardInformation`其内部占位符无法通过此来配置).
        -   **示例**
            
            ```javascript
            billingFieldsConfig: {
                name: true, // Enable 'Name on Card' with default texts
                addressLine1: { label: 'Street Address', placeholder: 'E.g., 123 Main St' },
                city: { label: 'Town/City' }, // Custom label, default placeholder
                postalCode: { placeholder: 'ZIP / Postal Code' } // Custom placeholder for postal code
                // Other fields like addressLine2, state, country etc., will not be shown.
            }
            ```
            
        -   **银行用法:** 银行账户 `name` 输入 `billingFieldsConfig` (如果定制或启用为 `true`)指"账户持有者名". 其他可选计费字段( E)`addressLine1`, (中文(简体) ), `postalCode`也可以配置,并将与银行账户细节一起收集。 田园 `accountNumber`, (中文(简体) ), `bankAccountType`, (中文(简体) ), `routingNumber` (美国), `transitNumber` (加拿大),以及 `institutionNumber` (CA)是银行账户表格的核心,其标签/位置持有者也可以使用其各自的密钥进行定制 `billingFieldsConfig` (例如, `accountNumber: { label: 'Bank Account No.' }`) (中文(简体) ).
        -   **示例( 银行账户字段定制):**
            
            ```javascript
            billingFieldsConfig: {
                name: { label: 'Full Name on Account' }, // Custom label for Account Holder Name
                accountNumber: { placeholder: 'Enter your bank account number' },
                // addressLine1: true, // Optionally collect address
            }
            ```
            
        -   _(参见导出) `BillingFieldsConfig` 财务报告和审定财务报表 `FieldCustomization` 详细类型。 )_
    -   `logLevel?` (单位:千美元)`LogLevel`, (中文(简体) ), _可选_,默认 : `'WARN'`: 控制通过 Drop- in 组件(外观和核心部分)登录到浏览器控制台上的信件的动词. 设置一个关卡可以显示该关卡和更严重的信息.
        -   可能的值 : `'NONE'`, (中文(简体) ), `'ERROR'`, (中文(简体) ), `'WARN'`, (中文(简体) ), `'INFO'`, (中文(简体) ), `'DEBUG'`。 。 。 .
        -   `'NONE'`: 没有显示日志 .
        -   `'ERROR'`:仅显示关键错误.
        -   `'WARN'`: 显示错误和警告.
        -   `'INFO'`: 显示错误、警告和信息信息(例如生命周期事件).
        -   `'DEBUG'`:显示所有信件,包括详细的调试信息.
    -   `onChangeValidation?` (单位:千美元)`(event: OdinFieldValidationEvent) => void`, (中文(简体) ), _可选_: OdinPay.js v2在用户与输入字段互动时引用的可选回调函数. 它为单个字段提供了实时验证状态,允许动态UI更新或自定义逻辑.
        -   **有效载荷`OdinFieldValidationEvent`:**
            
            | 财产 | 类型 | 说明 |
            | --- | --- | --- |
            | `type` | `string` | 事件的类型,一般 `"FIELD_VALIDATION"`。 。 。 . |
            | `fieldName` | `string` | 正在验证的字段名称( 例如 ), `"cardNumber"`, (中文(简体) ), `"postalCode"`) (中文(简体) ). |
            | `selector` | `string` | 向OdinPay.js提供的外地集装箱CSS选择器. |
            | `isValid` | `boolean` | 表示根据 OdinPay.js 验证规则该字段目前是否有效 . |
            | `errorCode?` | `string` | 如果字段无效则错误代码( 例如, ), `"INVALID"`, (中文(简体) ), `"REQUIRED"`)可选. |
            
        -   **示例使用法:**
            
            ```javascript
            new OdinDropin({
              // ... other params
              onChangeValidation: (event) => {
                console.log('Field Validation:', event.fieldName, 'Is Valid:', event.isValid, 'Error Code:', event.errorCode);
                // Example: Add/remove a custom error class to the field's parent
                const fieldContainer = document.querySelector(event.selector)?.closest('.my-field-wrapper');
                if (fieldContainer) {
                  if (event.isValid) {
                    fieldContainer.classList.remove('has-error');
                  } else {
                    fieldContainer.classList.add('has-error');
                  }
                }
              }
            });
            ```
            
    -   `onSubmit` (单位:千美元)`(result: OdinSubmitPayload) => void`, (中文(简体) ), **需求**: 用户成功提交支付表时将引用的回调函数 `OdinPay.js` 返回付款方法符号.
        -   **有效载荷`OdinSubmitPayload`:** 包含:
            -   `paymentMethodId` (单位:千美元)`string`:由ODIN所生成的象征性支付方法标识符. 将此ID发送到您的后端服务器以进行支付动作 .
            -   `paymentMethodType` (单位:千美元)`'CARD' | 'BANK_ACCOUNT'`:一个字符串,表示已标出支付方法的类型. 这有助于解释 `details` 对象.
            -   `billingInformation?` (单位:千美元)`OdinPayBillingInformation`, (中文(简体) ), _可选_:一个包含用户输入的计费细节的对象,如果设置并提交了任何计费字段. 其结构是:
                
                ```typescript
                interface OdinPayBillingInformation {
                    name?: string;
                    emailAddress?: string;
                    phoneNumber?: string;
                    address?: {
                        addressLine1?: string;
                        addressLine2?: string;
                        city?: string;
                        state?: string;
                        postalCode?: string;
                        country?: string;
                    };
                }
                ```
                
                如果配置了相应的字段, 将显示密钥 。 空字符串值表示字段被配置但被用户留下空白. 如果不配置一个字段,其密钥将被省略 。 这个 `address` 如果没有配置地址字段,对象本身将被省略.
            -   `details?` (单位:千美元)`CardPaymentMethodDetails | AchPaymentMethodDetails | ...`, (中文(简体) ), _可选_: 包含特定细节的物体: `paymentMethodType`。 。 。 .
                -   若为 `paymentMethodType` 实值 `'CARD'`这将是一个 `CardPaymentMethodDetails` 对象 :
                    
                    ```typescript
                    interface CardPaymentMethodDetails {
                        cardBrand?: string;       // e.g., "VISA", "MASTERCARD"
                        last4?: string;           // Last four digits of the card number
                        maskedAccountNumber?: string; // The masked card number (e.g., "************1111")
                        expirationDate?: string;  // e.g., "12/2025"
                        binDetails?: any;         // Object containing BIN (Bank Identification Number) details
                    }
                    ```
                    
                -   若为 `paymentMethodType` 实值 `'BANK_ACCOUNT'`,这将是一个 `AchPaymentMethodDetails` 对象 :
                    
                    ```typescript
                    interface AchPaymentMethodDetails {
                      bankAccountType?: 'CHECKING' | 'SAVINGS'; // Type of the bank account
                      accountNumberLast4?: string;    // Last four digits of the bank account number
                      routingNumber?: string;         // The routing number (for US accounts)
                      transitNumber?: string;         // The transit number (for Canadian accounts)
                      institutionNumber?: string;     // The institution number (for Canadian accounts)
                      country?: 'US' | 'CA';          // The country context for the Bank Account details
                    }
                    ```
                    
    -   `onError` (单位:千美元)`(error: OdinPayErrorPayload) => void`, (中文(简体) ), **需求**: 发生错误时引用的回调函数 。 可在下列期间发生:
        -   滴入或基础 OdinPay.js v2 库的初始化 .
            
        -   如果 OdinPay.js v2 表示失败(例如验证错误,API错误),则支付表提交程序.
            
        -   入站部分内部设置问题.
            
        -   对于提交错误,信息输入 `OdinPayErrorPayload` 主要来源于 `result.errors` odinPay.js v2 提供的数组.
            
        -   **有效载荷`OdinPayErrorPayload`:**
            
            | 财产 | 类型 | 说明 |
            | --- | --- | --- |
            | `code` | `string` | 表示错误性质的错误代码字符串 。 参见下"错误代码"表,例和意义. |
            | `message` | `string` | 对错误的一般,人可读的描述. 用于校验错误 `fieldErrors`这可能是一个总结。 对于API错误,它可能反映来自OdinPay.js的信件. |
            | `fieldErrors?` | `Array<{ field: string, message: string }>` | 可选的 详细列出特定字段验证错误的一系列对象。 每个对象包含:
            -   `field` (单位:千美元)`string`: `fieldName` 从OdinPay.js v2(例如, `"cardInformation"`, (中文(简体) ), `"postalCode"`)或通用密钥.
            -   `message` (单位:千美元)`string`: 通常为: `errorCode` 字符串来自 OdinPay.js v2 用于该字段(例如, `"REQUIRED"`, (中文(简体) ), `"INVALID_CARD_INFORMATION"`) (中文(简体) ).
            
             |
            | `httpStatusCode?` | `number` | 可选的 如果错误来自API调用(例如, `401`, (中文(简体) ), `400`, (中文(简体) ), `500`) (中文(简体) ). |
            | `rawError?` | `any` | 可选的 OdinPay.js(例如: `result.errors` v2 提交错误的数组,或 `result.message`。用于调试. |
            
        -   **常见出错代码( E)`code` 属性 :**
            
            | 代码 | 含义 | 页:1 |
            | --- | --- | --- |
            | `VALIDATION_ERROR_FIELDS` | 提交时一个或多个特定字段的输入验证失败 . | 检查 `fieldErrors` 每个字段的细节的阵列. |
            | `VALIDATION_ERROR_GENERAL` | 在提交过程中发生了一般输入验证失败( 与v2 不太常见) `result.errors`) (中文(简体) ). | 如果 `result.errors` 结构不按预期. |
            | `API_ERROR` | OdinPay.js v2在提交文件时与其后端API通信时发生了一般错误. | 经常对应 `type: "BACKEND"` 从 OdinPay.js v2 调取 `result.errors`。 。 。 . |
            | `API_AUTH_ERROR` | ODIN API的认证错误( 如 HTTP 401). | 通常是因为过期或无效的公用标志. |
            | `API_CLIENT_ERROR` | ODIN API的客户端错误(例如其他HTTP 4xx错误). |  |
            | `API_SERVER_ERROR` | 使用 ODIN API 的服务器端错误( 如 HTTP 5xx 错误) . |  |
            | `GENERAL_PAYMENT_ERROR` | V1 风格字符串消息的支付处理中出现一般错误 . |  |
            | `INIT_NO_KEY_PROVIDED` | 初始化失败: ODIN Public Token 丢失或为空 . |  |
            | `INIT_BADLY_FORMATTED_KEY` | 初始化失败: ODIN Public Token 格式化不良 . |  |
            | `INIT_INVALID_KEY_STRUCTURE` | 初始化失败: ODIN 公共托肯结构无效 . | 从OdinPay.js内部验证. |
            | `INIT_UNSUPPORTED_COUNTRY` | 初始化失败: 已提供 `countryCode` 由OdinPay.js不支持. |  |
            | `INIT_BT_SDK_FAILURE` | 初始化失败: OdinPay.js 使用的 Basic Theory SDK 相关的内部错误 . |  |
            | `INIT_NO_COUNTRY_CODE` | 初始化失败 : `countryCode` 核心组件需要时缺少道具. | 内部为出入口设置. |
            | `INIT_NO_COUNTRY_CODE_FACADE` | 初始化失败 : `countryCode` 失踪在 `OdinDropinInitializationParams`。 。 。 . | 花脸级鉴定. |
            | `SDK_LOAD_ERROR` | OdinPay.js v2 SDK脚本未能从CDN装入. | 检查网络连接和CDN URL. |
            | `SDK_NOT_DEFINED_ERROR` | OdinPay.js v2 SDK 脚本已装入, 但是 `OdinPay` 对象没有在全球范围定义. |  |
            | `INSTANCE_NULL` | 内部错误: OdinPay 实例在试图创建窗体时无效 . |  |
            | `CREATE_FORM_ERROR_CARD` | 拨打 OdinPay.js 时出错 `createCardForm()`。 。 。 . | 检查 `rawError` 详细内容来自OdinPay.js. |
            | `CREATE_FORM_ERROR_BANK_ACCOUNT` | 拨打 OdinPay.js 时出错 `createBankAccountForm()`。 。 。 . | 检查 `rawError` 详细内容来自OdinPay.js. |
            | `SUBMIT_WHILE_INIT_ERROR` | 当组件处于初始化错误状态时尝试提交 . |  |
            | `FORM_NOT_READY` | 在OdinPay.js表格完全完成或准备就绪之前曾试图提交. |  |
            | `UNKNOWN_PAYMENT_METHOD_TYPE` | 在成功回话中从OdinPay.js收到了出乎意料的支付方法类型. |  |
            | `UNEXPECTED_CALLBACK_STRUCTURE` | OdinPay.js的成功/错误回调结构不如预期. |  |
            | `ODIN_CALLBACK_ERROR` | 从OdinPay.js召回的通用错误无法进一步分类. | (与v1 处理错误更为常见, 与 v2 较少) `result.errors`) (中文(简体) ). |
            | `MOUNT_POINT_NOT_FOUND` | 未找到用于挂起滴入的 DOM 元素 . | 表面级错误 . |
            | `COMPONENT_CREATION_FAILED` | 内部的 Stencil 网络组件无法创建 . | 表面级错误 . |
            | `INTERNAL_ERROR` | 滴入组件内出乎意料的内部出错(例如未找到隐藏的提交按钮). |  |
            | `UNKNOWN_ERROR` | 发生了其他解析规则无法具体识别的错误. | 终相倒地. 检查 `rawError`。 。 。 . |
            
    -   `theme?` (单位:千美元)`OdinV2ThemeConfig`, (中文(简体) ), _可选_: 自定义 OdinPay.js v2 提供的输入字段外观的可选主题配置对象. 此对象允许您控制字体,颜色,贴花等样式,以及伪元素/状态样式(如:, `::placeholder`, (中文(简体) ), `:hover`, (中文(简体) ), `:focus`, (中文(简体) ), `invalid`) (中文(简体) ).
        -   **结构 :** 这个 `theme` 对象必须跟随 **平面结构** 指定由 OdinPay.js v2. CSS 属性直接应用在主题对象的顶级.
            
            ```typescript
            // Example structure for OdinV2ThemeConfig
            interface OdinV2ThemeConfig {
              fontFamily?: string;
              fontSize?: string;
              color?: string;
              backgroundColor?: string;
              padding?: string;
              letterSpacing?: string;
              // ... other supported CSS properties ...
            
              // Pseudo-selectors as keys
              '::placeholder'?: { 
                color?: string; 
                fontStyle?: string;
                // ... other placeholder styles ...
              };
              ':hover'?: { 
                backgroundColor?: string; 
                color?: string;
                // ... other hover styles ...
              };
              ':focus'?: { 
                backgroundColor?: string; 
                color?: string;
                fontWeight?: string;
                // ... other focus styles ...
              };
              // Style for invalid fields (syntax may depend on OdinPay.js v2 specific implementation)
              'invalid'?: { // Or potentially ':invalid' if it's a pseudo-class
                color?: string;
                fontWeight?: string;
                // ... other invalid styles ...
              };
            }
            ```
            
        -   **关于样式范围的重要说明:**
            -   这个 `theme` 对象 **只样式 OdinPay.js 输入字段的内部部分** (例如,案文、背景) _在内部_ 输入,占位符).
            -   **它不控制边框、框阴影、整体尺寸或实地容器的结构布局.** 这些结构样式 **必须通过外部 CSS 应用** 目标 `div` odinPay.js 字段的主机元素。 我们的投放部件 使这些容器 具有一类 `.odin-input` 内置 a `.odin-field-container`,每个都有一个独特的ID.
        -   **默认行为 :** 若无 `theme` 对象提供, OdinPay.js v2 将为字段使用自己的默认内部样式.
        -   **示例 `theme` 对象 :**
            
            ```javascript
            new OdinDropin({
              // ... other params
              theme: {
                "fontFamily": "Verdana, sans-serif",
                "fontSize": "14px",
                "color": "#000080", // Navy text
                "backgroundColor": "#f0f8ff", // Aliceblue background for input area
                "padding": "12px 15px",
                "::placeholder": {
                  "color": "#778899",
                  "fontStyle": "italic"
                },
                ":focus": {
                  "backgroundColor": "#add8e6"
                }
              }
            });
            ```
            
        -   **示例外部 CSS( 用于边框/ 容器, 将放在应用程序的样式表中):**
            
            ```css
            /* Target the containers rendered by @exerp/odin-dropin */
            /* If your app's styles are scoped (e.g., Vue <style scoped>), you might need :deep() */
            
            /* Styles the div our component creates around each OdinPay field div */
            .odin-field-container {
              border: 1px solid #ced4da;
              border-radius: 0.25rem;
              background-color: #ffffff;
              padding: 8px 10px; /* Adjust padding around OdinPay's input area */
              margin-bottom: 15px;
            }
            
            /* Styles the direct div OdinPay uses (inside .odin-field-container) */
            /* Make it transparent so .odin-field-container's background shows */
            .odin-field-container .odin-input {
              background-color: transparent;
              min-height: 38px; /* Ensure it has some height */
            }
            ```
            

### `instance.mount(selectorOrElement)`

将 ODIN Drop- in UI 组件挂入指定的 DOM 元素.

**参数:**

-   `selectorOrElement` (单位:千美元)`string | HTMLElement`, (中文(简体) ), **需求**:
    -   如果字符串,应该是一个CSS选择器(例如, `'#my-odin-container'`指出应交付的集装箱要素.
    -   如果一个 `HTMLElement`,这是直接参考 容器元素.
-   **回返情况:** `void`

### `instance.unmount()`

从 DOM 中删除 ODIN Drop- in UI 组件,并清理关联事件听众.

-   **参数:** 无
-   **回返情况:** `void`

## 用法示例

```html
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Odin Drop-in Test</title>
    <style>
        /* Basic button styling for this example */
        .odin-submit-button { /* Target class from internal component */
            padding: 10px 15px; background-color: #007bff; color: white;
            border: none; border-radius: 4px; cursor: pointer; margin-top: 10px;
        }
        .odin-submit-button:disabled { opacity: 0.6; cursor: not-allowed; }
        #odin-container { max-width: 400px; padding: 20px; border: 1px solid #ccc; margin: 20px; min-height: 150px; }
        .error-message { color: red; margin-top: 10px; font-size: 0.9em; }

        /*
        IMPORTANT for OdinPay.js v2:
        Border styling and other structural styles for the input field containers
        are NOT controlled by the 'theme' object. Apply them using external CSS
        targeting the containers rendered by @exerp/odin-dropin.
        Our drop-in renders <div class="odin-field-container"> which wraps
        a <div class="odin-input"> where OdinPay.js mounts its field.
        */
        .odin-field-container { /* Or use :deep() if styles are scoped in your framework */
        border: 1px solid #ccc;
        border-radius: 4px;
        padding: 5px 8px; /* Example padding around OdinPay's input area */
        margin-bottom: 10px;
        background-color: #fff;
        }
        .odin-field-container .odin-input {
        background-color: transparent; /* Let container background show through */
        min-height: 36px; /* Ensure space for OdinPay field */
        }

    </style>
</head>
<body>
    <h1>Odin Payment Form Example</h1>
    <div id="odin-container">
        <!-- Drop-in will mount here -->
    </div>
    <div id="result-display"></div>
    <div id="error-display" class="error-message"></div>
    <div id="validation-event-display" style="margin-top:10px; font-size:0.9em; color: blue;"></div>

    <script type="module" src="app.js"></script>
</body>
</html>
```

```javascript
// app.js
import {
  OdinDropin,
  type BillingFieldsConfig, // Keep if used in example logic
  type OdinFieldValidationEvent,
  type OdinV2ThemeConfig,
  type OdinSubmitPayload,
  type OdinPayErrorPayload,
  type LogLevel
} from '@exerp/odin-dropin';

const odinContainer = document.getElementById('odin-container');
const resultDisplay = document.getElementById('result-display');
const errorDisplay = document.getElementById('error-display');
const validationEventDisplay = document.getElementById('validation-event-display');
let odinDropinInstance = null; // To hold the instance

// --- Fetch Public Token (Replace with your actual backend call) ---
async function getPublicToken() {
    // Simulate fetching from backend
    console.log("Fetching ODIN Public Token...");
    // In a real app, replace this with:
    // const response = await fetch('/api/get-odin-public-token');
    // if (!response.ok) throw new Error('Failed to fetch token');
    // const data = await response.json();
    // return data.publicToken;
    // For example purposes:
    return prompt("Enter ODIN Public Token:"); // DO NOT use prompt in production
}


function handleFieldValidation(event: OdinFieldValidationEvent) {
  console.log('[Example App] onChangeValidation:', event);
  if (validationEventDisplay) {
    validationEventDisplay.textContent = `Last Validation: ${event.fieldName} - Valid: ${event.isValid}, Code: ${event.errorCode || 'N/A'}`;
  }
  // Add custom logic here, e.g., dynamic styling based on event.isValid
}

// --- Initialize and Mount ---
async function initializeOdin() {
    if (!odinContainer) {
        console.error('Mount point not found');
        return;
    }

    try {
        const token = await getPublicToken();
        if (!token) {
            throw new Error('ODIN Public Token is required.');
        }

        const country = prompt("Enter Country Code (US or CA):"); // Example, get this from your app's context
        if (country !== 'US' && country !== 'CA') {
            throw new Error('Valid Country Code (US or CA) is required.');
        }

        const pmtType = confirm("Use BANK_ACCOUNT? (Cancel for Card)") ? 'BANK_ACCOUNT' : 'CARD'; // Example of selecting payment method type

        const enableNameField = confirm("Enable 'Name on Card' field?"); // Example
        const currentBillingFieldsConfig: BillingFieldsConfig = {
            name: enableNameField,
            // addressLine1: { label: "Street Address", placeholder: "123 Main St" },
            // addressLine2: false,
        };

        const exampleTheme: OdinV2ThemeConfig = {
          "fontFamily": "Arial, sans-serif",
          "fontSize": "16px",
          "color": "#333",
          "::placeholder": {
            "color": "#888"
          }
        };

        // Clean previous state if re-initializing
        if (odinDropinInstance) {
            odinDropinInstance.unmount();
        }
        if (resultDisplay) resultDisplay.textContent = '';
        if (errorDisplay) errorDisplay.textContent = '';
        if (validationEventDisplay) validationEventDisplay.textContent = '';

        odinDropinInstance = new OdinDropin({
            odinPublicToken: token,
            countryCode: country as 'US' | 'CA',
            paymentMethodType: pmtType as 'CARD' | 'BANK_ACCOUNT',
            billingFieldsConfig: currentBillingFieldsConfig,
            logLevel: 'DEBUG', // Example: set a log level
            onChangeValidation: handleFieldValidation,
            onSubmit: (result) => {
                console.log('onSubmit received:', result);
                let message = `Success! PM ID: ${result.paymentMethodId}\nType: ${result.paymentMethodType}`;

                if (result.paymentMethodType === 'CARD' && result.details) {
                    const cardDetails = result.details; // Type assertion might be needed in TS, not in JS example
                    message += `\nCard Brand: ${cardDetails.cardBrand || 'N/A'}`;
                    message += `\nLast 4: ${cardDetails.last4 || 'N/A'}`;
                    message += `\nExpires: ${cardDetails.expirationDate || 'N/A'}`;
                    // You might want to display maskedAccountNumber or binDetails too
                    // console.log('Masked Account Number:', cardDetails.maskedAccountNumber);
                    // console.log('BIN Details:', cardDetails.binDetails);
                } else if (result.paymentMethodType === 'BANK_ACCOUNT' && result.details) {
                    const achDetails = result.details as AchPaymentMethodDetails; // Type assertion for clarity
                    message += `\nAccount Type: ${achDetails.bankAccountType || 'N/A'}`;
                    message += `\nAccount Last4: ${achDetails.accountNumberLast4 || 'N/A'}`;
                    if (achDetails.routingNumber) message += `\nRouting: ${achDetails.routingNumber}`;
                    if (achDetails.transitNumber) message += `\nTransit: ${achDetails.transitNumber}`;
                    if (achDetails.institutionNumber) message += `\nInstitution: ${achDetails.institutionNumber}`;
                }

                if (result.billingInformation) {
                    message += `\nBilling Info: ${JSON.stringify(result.billingInformation, null, 2)}`;
                }

                if (resultDisplay) resultDisplay.textContent = message;
                if (errorDisplay) errorDisplay.textContent = '';
                
                alert(`Payment Method ID: ${result.paymentMethodId} - Review console for all details. Send ID to backend.`);
                // Optionally unmount after success:
                // odinDropinInstance.unmount();
            },
            onError: (error) => {
                console.error('onError received:', error);
                if (errorDisplay) errorDisplay.textContent = `Error: ${error.message}`;
                if (resultDisplay) resultDisplay.textContent = '';
            },
        });

        odinDropinInstance.mount(odinContainer); // Mount into the div element

    } catch (error) {
        console.error('Initialization or mounting failed:', error);
        if (errorDisplay) errorDisplay.textContent = `Error: ${error.message || error}`;
    }
}

// --- Run Initialization ---
initializeOdin();

// Example of how to unmount if needed later (e.g., on component destroy in a framework)
// function cleanup() {
//   if (odinDropinInstance) {
//     odinDropinInstance.unmount();
//     odinDropinInstance = null;
//     console.log('Odin Drop-in unmounted.');
//   }
// }
// window.addEventListener('beforeunload', cleanup); // Example cleanup trigger
```

## 常见问题

### @exerp/odin-dropin 是什么？

ODIN 支付

### 如何安装 @exerp/odin-dropin？

在项目目录运行 npm install @exerp/odin-dropin，或使用 pnpm add / yarn add / bun add。

### @exerp/odin-dropin 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @exerp/odin-dropin 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @exerp/odin-dropin 可以商用吗？

@exerp/odin-dropin 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @exerp/odin-dropin 的下载量有多少？

根据 npm 官方下载统计，@exerp/odin-dropin 在 2026/10/14 – 2026/11/12 这 30 天内被下载 12,703 次，日均约 423 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @exerp/odin-dropin 还在维护吗？

最新版本 2\.1\.0 发布于 2026/02/06（9 个月前）；至今共发布 8 个版本，其中过去 12 个月发布了 1 个；首次发布于 2025/05/16。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

