@triplinkintl/embedded-card 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TripLink
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # TripLink Embedded Card
2
+
3
+ ![NPM Version](https://img.shields.io/npm/v/@triplinkintl/embedded-card?style=flat-square)
4
+
5
+ [TripLink](https://www.triplinkintl.com/) Embedded Card lets you show virtual card details in your own product, without taking on PCI scope. [TripLink](https://www.triplinkintl.com/) remains the regulated issuer and PCI payment provider — issuing, funding, and protecting card data on your behalf. A few lines of JavaScript is enough to embed a TripLink-hosted card into any page.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @triplinkintl/embedded-card
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import TripLink from '@triplinkintl/embedded-card';
17
+
18
+ const { destroy } = TripLink.renderCard({
19
+ container: 'card-root',
20
+ ephemeralKey: auth.ephemeralKey,
21
+ cardLid: auth.cardLogId,
22
+ customerId: auth.customerId,
23
+ source: 'blink',
24
+ locale: 'zh-HK',
25
+ env: 'FAT',
26
+ styles: {
27
+ root: { backgroundColor: '#0b1f3a' },
28
+ },
29
+ onSuccess: ({ requestId }) => {
30
+ console.log('card rendered', requestId);
31
+ },
32
+ onError: ({ resultCode, message, requestId }) => {
33
+ console.error(resultCode, message, requestId);
34
+ },
35
+ });
36
+
37
+ // later
38
+ destroy();
39
+ ```
40
+
41
+ ```html
42
+ <div id="card-root" style="width: 360px; height: 220px;"></div>
43
+ ```
44
+
45
+ Named export `renderCard` is equivalent to `TripLink.renderCard`.
46
+
47
+ ## Options
48
+
49
+
50
+ | Field | Type | Required | Default | Description |
51
+ | -------------- | ---------------------------------------------- | -------- | ------- | ------------------------------------------- |
52
+ | `container` | `string` | yes | — | DOM container id |
53
+ | `ephemeralKey` | `string` | yes | — | Ephemeral key from TripLink |
54
+ | `cardLid` | `string` | yes | — | Card id (`cardLogId`) from TripLink |
55
+ | `customerId` | `string` | yes | — | Customer id from TripLink |
56
+ | `source` | `string` | yes | — | Partner source. Apply to TripLink first. |
57
+ | `locale` | `string` | no | `en-US` | BCP 47 language tag, e.g. `en-HK`, `zh-HK` |
58
+ | `env` | `FAT` / `PROD` | no | `PROD` | Environment. `FAT` or `PROD`. |
59
+ | `styles` | `{ root? }` | no | — | Card styles sent to the iframe via CardInit |
60
+ | `onSuccess` | `({ requestId }) => void` | no | — | Fired when the iframe reports init success |
61
+ | `onError` | `({ resultCode, message, requestId }) => void` | no | — | Fired on validation / load / init errors |
62
+
63
+
64
+
65
+
66
+ ## Result codes
67
+
68
+
69
+ | `resultCode` | When |
70
+ | --------------------- | ----------------------------------------- |
71
+ | `BROWSER_VERSION_LOW` | Browser is too old |
72
+ | `INVALID_PARAMS` | Missing or invalid SDK arguments |
73
+ | `CONTAINER_NOT_FOUND` | No DOM node with the given `container` id |
74
+ | `IFRAME_LOAD_FAILED` | Iframe failed to load |
75
+ | `CARD_INIT_FAILED` | Iframe reported card init failure |
76
+
77
+
package/dist/index.cjs ADDED
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: !0 });
9
+ }, __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from == "object" || typeof from == "function")
11
+ for (let key of __getOwnPropNames(from))
12
+ !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ return to;
14
+ };
15
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod);
16
+
17
+ // src/index.ts
18
+ var index_exports = {};
19
+ __export(index_exports, {
20
+ DEFAULT_ENV: () => DEFAULT_ENV,
21
+ DEFAULT_LOCALE: () => DEFAULT_LOCALE,
22
+ IFRAME_BASE_URLS: () => IFRAME_BASE_URLS,
23
+ ResultCode: () => ResultCode,
24
+ TripLink: () => TripLink,
25
+ default: () => index_default,
26
+ renderCard: () => renderCard
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/renderCard.ts
31
+ var import_uuid = require("uuid");
32
+
33
+ // src/constants.ts
34
+ var DEFAULT_LOCALE = "en-US", DEFAULT_ENV = "PROD", IFRAME_BASE_URLS = {
35
+ FAT: "https://vcc-hosting.fat.ctripqa.com/embedded-card/cardInfo",
36
+ PROD: "https://vcc-hosting.fat.ctripqa.com/embedded-card/cardInfo"
37
+ };
38
+
39
+ // src/enums/resultCode.ts
40
+ var ResultCode = /* @__PURE__ */ ((ResultCode2) => (ResultCode2.INVALID_PARAMS = "INVALID_PARAMS", ResultCode2.CONTAINER_NOT_FOUND = "CONTAINER_NOT_FOUND", ResultCode2.IFRAME_LOAD_FAILED = "IFRAME_LOAD_FAILED", ResultCode2.CARD_INIT_FAILED = "CARD_INIT_FAILED", ResultCode2.BROWSER_VERSION_LOW = "BROWSER_VERSION_LOW", ResultCode2))(ResultCode || {});
41
+
42
+ // src/utils/validators.ts
43
+ var REQUIRED_STRING_KEYS = ["container", "ephemeralKey", "cardLid", "customerId", "source"];
44
+ function isNonEmptyString(value) {
45
+ return typeof value == "string" && value.trim().length > 0;
46
+ }
47
+ function isSupported() {
48
+ return [
49
+ "postMessage",
50
+ // Portal/SDK 需要支持 postMessage
51
+ "TextEncoder",
52
+ // Portal 需要支持 TextEncoder
53
+ "Uint8Array",
54
+ // Portal 需要支持 Uint8Array
55
+ "atob"
56
+ // Portal 需要支持 atob
57
+ ].every((key) => {
58
+ let isSupported3 = typeof window[key] == "function";
59
+ return isSupported3 || console.warn(`[TripLink SDK] ${key} is not supported`), isSupported3;
60
+ });
61
+ }
62
+ function validateOptions(options) {
63
+ if (!options || typeof options != "object")
64
+ return { ok: !1, code: "INVALID_PARAMS" /* INVALID_PARAMS */, message: "options is required" };
65
+ for (let key of REQUIRED_STRING_KEYS)
66
+ if (!isNonEmptyString(options[key]))
67
+ return { ok: !1, code: "INVALID_PARAMS" /* INVALID_PARAMS */, message: `${key} is required` };
68
+ return options.env != null && options.env !== "FAT" && options.env !== "PROD" ? { ok: !1, code: "INVALID_PARAMS" /* INVALID_PARAMS */, message: "env must be FAT or PROD" } : { ok: !0 };
69
+ }
70
+
71
+ // src/utils/url.ts
72
+ function appendQuery(base, params) {
73
+ let pairs = [];
74
+ for (let key in params) {
75
+ if (!Object.prototype.hasOwnProperty.call(params, key))
76
+ continue;
77
+ let value = params[key];
78
+ value && pairs.push(key + "=" + encodeURIComponent(value));
79
+ }
80
+ if (!pairs.length)
81
+ return base;
82
+ let hashIndex = base.indexOf("#"), hash = hashIndex >= 0 ? base.slice(hashIndex) : "", withoutHash = hashIndex >= 0 ? base.slice(0, hashIndex) : base, lastChar = withoutHash.charAt(withoutHash.length - 1), separator = "&";
83
+ return withoutHash.indexOf("?") === -1 ? separator = "?" : (lastChar === "?" || lastChar === "&") && (separator = ""), withoutHash + separator + pairs.join("&") + hash;
84
+ }
85
+ function getOrigin(url) {
86
+ let match = url.match(/^(https?:\/\/[^/?#]+)/i);
87
+ return match ? match[1] : "";
88
+ }
89
+ function buildIframeUrl(options) {
90
+ return appendQuery(IFRAME_BASE_URLS[options.env || DEFAULT_ENV], {
91
+ locale: options.locale,
92
+ source: options.source,
93
+ oid: options.oid
94
+ });
95
+ }
96
+
97
+ // src/renderCard.ts
98
+ function applyIframeStyle(iframe) {
99
+ iframe.setAttribute("title", "TripLink Card"), iframe.setAttribute("allow", "clipboard-write");
100
+ }
101
+ function renderCard(options) {
102
+ let noop = { destroy() {
103
+ } }, requestId = (0, import_uuid.v4)(), reportError = (code, message) => {
104
+ console.error(`[Triplink-embedded-card SDK] ${code}: ${message}`), options?.onError?.({ resultCode: code, message, requestId });
105
+ };
106
+ if (!isSupported())
107
+ return reportError("BROWSER_VERSION_LOW" /* BROWSER_VERSION_LOW */, "Browser version is too low"), noop;
108
+ let validated = validateOptions(options);
109
+ if (!validated.ok)
110
+ return reportError(validated.code, validated.message), noop;
111
+ let container = document.getElementById(options.container);
112
+ if (!container)
113
+ return reportError("CONTAINER_NOT_FOUND" /* CONTAINER_NOT_FOUND */, `Mount node not found: ${options.container}`), noop;
114
+ let locale = (options.locale || DEFAULT_LOCALE).trim(), env = options.env || DEFAULT_ENV, iframeSrc = buildIframeUrl({
115
+ env,
116
+ locale,
117
+ source: options.source.trim(),
118
+ oid: requestId
119
+ }), iframeOrigin = getOrigin(iframeSrc), iframe = document.createElement("iframe");
120
+ applyIframeStyle(iframe);
121
+ let didInit = !1, destroyed = !1, onComplete = () => didInit || destroyed ? !1 : didInit = !0, onError = (code, message) => {
122
+ onComplete() && reportError(code, message);
123
+ }, onSuccess = () => {
124
+ onComplete() && options.onSuccess?.({ requestId });
125
+ }, postCardInitMessage = () => {
126
+ destroyed || didInit || iframe.contentWindow?.postMessage(
127
+ {
128
+ requestId,
129
+ type: "CARD_INIT" /* CardInit */,
130
+ payload: {
131
+ ephemeralKey: options.ephemeralKey.trim(),
132
+ cardLogId: options.cardLid.trim(),
133
+ customerId: options.customerId.trim(),
134
+ ...options.styles ? { styles: options.styles } : {}
135
+ }
136
+ },
137
+ iframeOrigin
138
+ );
139
+ }, onMessage = (event) => {
140
+ if (destroyed || didInit || iframe.contentWindow && event.source !== iframe.contentWindow || event.origin !== iframeOrigin)
141
+ return;
142
+ let { requestId: messageRequestId, type: messageType, payload: messagePayload } = event.data || {};
143
+ if (!(!messageRequestId || messageRequestId !== requestId))
144
+ switch (messageType) {
145
+ case "READY" /* Ready */:
146
+ postCardInitMessage();
147
+ break;
148
+ case "CARD_INIT_SUCCESS" /* InitSuccess */:
149
+ onSuccess();
150
+ break;
151
+ case "CARD_INIT_FAILED" /* InitFailed */:
152
+ let message = messagePayload?.message || "Failed to initialize card information";
153
+ onError("CARD_INIT_FAILED" /* CARD_INIT_FAILED */, message);
154
+ break;
155
+ default:
156
+ onError("CARD_INIT_FAILED" /* CARD_INIT_FAILED */, `Unknown message type '${messageType}'`);
157
+ break;
158
+ }
159
+ }, onIframeError = () => {
160
+ onError("IFRAME_LOAD_FAILED" /* IFRAME_LOAD_FAILED */, "Failed to load iframe page");
161
+ };
162
+ return window.addEventListener("message", onMessage), iframe.addEventListener("error", onIframeError), iframe.src = iframeSrc, container.replaceChildren(iframe), {
163
+ /** 移除 iframe,并停止 message 监听。 */
164
+ destroy() {
165
+ destroyed || (destroyed = !0, window.removeEventListener("message", onMessage), iframe.removeEventListener("error", onIframeError), iframe.remove());
166
+ }
167
+ };
168
+ }
169
+
170
+ // src/index.ts
171
+ var TripLink = {
172
+ renderCard
173
+ }, index_default = TripLink;
174
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1,95 @@
1
+ /** 传给 `onError({ resultCode, message, requestId })` 的错误码。 */
2
+ declare enum ResultCode {
3
+ /** 无效的参数 */
4
+ INVALID_PARAMS = "INVALID_PARAMS",
5
+ /** 容器未找到 */
6
+ CONTAINER_NOT_FOUND = "CONTAINER_NOT_FOUND",
7
+ /** iframe 加载失败 */
8
+ IFRAME_LOAD_FAILED = "IFRAME_LOAD_FAILED",
9
+ /** 卡初始化失败 */
10
+ CARD_INIT_FAILED = "CARD_INIT_FAILED",
11
+ /** 浏览器版本低(不支持 postMessage) */
12
+ BROWSER_VERSION_LOW = "BROWSER_VERSION_LOW"
13
+ }
14
+
15
+ /** iframe 环境:FAT 或 PROD。 */
16
+ type Env = 'FAT' | 'PROD';
17
+ /** `ResultCode` 的字符串取值。 */
18
+ type ResultCodeValue = ResultCode;
19
+ /** `onError` 回调入参。 */
20
+ type OnErrorPayload = {
21
+ /** 错误码 */
22
+ resultCode: ResultCode;
23
+ /** 错误描述 */
24
+ message: string;
25
+ /** 本次 `renderCard` 的请求 id */
26
+ requestId: string;
27
+ };
28
+ /** `onSuccess` 回调入参。 */
29
+ type OnSuccessPayload = {
30
+ /** 本次 `renderCard` 的请求 id */
31
+ requestId: string;
32
+ };
33
+ type OnError = (payload: OnErrorPayload) => void;
34
+ type OnSuccess = (payload: OnSuccessPayload) => void;
35
+ /** 传给托管页的内联样式,使用驼峰 CSS 属性名,如 `backgroundColor`。 */
36
+ type CSSStyleProperties = Record<string, string | number>;
37
+ /** 卡片样式,通过 CardInit 下发给托管页。 */
38
+ type CardStyles = {
39
+ /** 根节点样式 */
40
+ root?: CSSStyleProperties;
41
+ };
42
+ type RenderCardOptions = {
43
+ /** DOM 容器 id */
44
+ container: string;
45
+ /** TripLink 签发的短期凭证,通过 postMessage 传递,不得出现在 iframe URL 上 */
46
+ ephemeralKey: string;
47
+ /** 卡 id(cardLogId) */
48
+ cardLid: string;
49
+ /** 客户 id */
50
+ customerId: string;
51
+ /**
52
+ * 接入方标识,作为非敏感 query 拼到 iframe URL,
53
+ * 供托管页拉取对应的 origin 白名单。
54
+ */
55
+ source: string;
56
+ /** BCP 47 语言标签,默认 `en-US` */
57
+ locale?: string;
58
+ /** FAT 或 PROD,默认 `PROD` */
59
+ env?: Env;
60
+ /** 卡片样式,通过 CardInit postMessage 下发给托管页 */
61
+ styles?: CardStyles;
62
+ /** iframe 初始化成功时回调 */
63
+ onSuccess?: OnSuccess;
64
+ /** 参数校验、iframe 加载或卡初始化失败时回调 */
65
+ onError?: OnError;
66
+ };
67
+ type RenderCardInstance = {
68
+ /** 移除 iframe,并停止 message 监听。 */
69
+ destroy: () => void;
70
+ };
71
+
72
+ /**
73
+ * 在宿主容器中挂载 TripLink 卡信息 iframe,并通过 postMessage 完成初始化。
74
+ *
75
+ * 流程:校验入参 → 定位容器 → 拼接不含敏感信息的 iframe URL →
76
+ * 挂载 iframe → 加载完成后下发 `{ ephemeralKey, cardLid, customerId }` →
77
+ * 等待 iframe 回传(`onSuccess` / `onError`)。
78
+ *
79
+ * @returns `{ destroy }` 用于移除 iframe 并停止监听
80
+ */
81
+ declare function renderCard(options: RenderCardOptions): RenderCardInstance;
82
+
83
+ /** 默认语言 */
84
+ declare const DEFAULT_LOCALE = "en-US";
85
+ /** 默认环境 */
86
+ declare const DEFAULT_ENV: Env;
87
+ /** 托管卡页地址 TODO */
88
+ declare const IFRAME_BASE_URLS: Record<Env, string>;
89
+
90
+ /** 对外 SDK 命名空间:`TripLink.renderCard(...)`。 */
91
+ declare const TripLink: {
92
+ renderCard: typeof renderCard;
93
+ };
94
+
95
+ export { type CSSStyleProperties, type CardStyles, DEFAULT_ENV, DEFAULT_LOCALE, type Env, IFRAME_BASE_URLS, type OnError, type OnErrorPayload, type OnSuccess, type OnSuccessPayload, type RenderCardInstance, type RenderCardOptions, ResultCode, type ResultCodeValue, TripLink, TripLink as default, renderCard };
@@ -0,0 +1,95 @@
1
+ /** 传给 `onError({ resultCode, message, requestId })` 的错误码。 */
2
+ declare enum ResultCode {
3
+ /** 无效的参数 */
4
+ INVALID_PARAMS = "INVALID_PARAMS",
5
+ /** 容器未找到 */
6
+ CONTAINER_NOT_FOUND = "CONTAINER_NOT_FOUND",
7
+ /** iframe 加载失败 */
8
+ IFRAME_LOAD_FAILED = "IFRAME_LOAD_FAILED",
9
+ /** 卡初始化失败 */
10
+ CARD_INIT_FAILED = "CARD_INIT_FAILED",
11
+ /** 浏览器版本低(不支持 postMessage) */
12
+ BROWSER_VERSION_LOW = "BROWSER_VERSION_LOW"
13
+ }
14
+
15
+ /** iframe 环境:FAT 或 PROD。 */
16
+ type Env = 'FAT' | 'PROD';
17
+ /** `ResultCode` 的字符串取值。 */
18
+ type ResultCodeValue = ResultCode;
19
+ /** `onError` 回调入参。 */
20
+ type OnErrorPayload = {
21
+ /** 错误码 */
22
+ resultCode: ResultCode;
23
+ /** 错误描述 */
24
+ message: string;
25
+ /** 本次 `renderCard` 的请求 id */
26
+ requestId: string;
27
+ };
28
+ /** `onSuccess` 回调入参。 */
29
+ type OnSuccessPayload = {
30
+ /** 本次 `renderCard` 的请求 id */
31
+ requestId: string;
32
+ };
33
+ type OnError = (payload: OnErrorPayload) => void;
34
+ type OnSuccess = (payload: OnSuccessPayload) => void;
35
+ /** 传给托管页的内联样式,使用驼峰 CSS 属性名,如 `backgroundColor`。 */
36
+ type CSSStyleProperties = Record<string, string | number>;
37
+ /** 卡片样式,通过 CardInit 下发给托管页。 */
38
+ type CardStyles = {
39
+ /** 根节点样式 */
40
+ root?: CSSStyleProperties;
41
+ };
42
+ type RenderCardOptions = {
43
+ /** DOM 容器 id */
44
+ container: string;
45
+ /** TripLink 签发的短期凭证,通过 postMessage 传递,不得出现在 iframe URL 上 */
46
+ ephemeralKey: string;
47
+ /** 卡 id(cardLogId) */
48
+ cardLid: string;
49
+ /** 客户 id */
50
+ customerId: string;
51
+ /**
52
+ * 接入方标识,作为非敏感 query 拼到 iframe URL,
53
+ * 供托管页拉取对应的 origin 白名单。
54
+ */
55
+ source: string;
56
+ /** BCP 47 语言标签,默认 `en-US` */
57
+ locale?: string;
58
+ /** FAT 或 PROD,默认 `PROD` */
59
+ env?: Env;
60
+ /** 卡片样式,通过 CardInit postMessage 下发给托管页 */
61
+ styles?: CardStyles;
62
+ /** iframe 初始化成功时回调 */
63
+ onSuccess?: OnSuccess;
64
+ /** 参数校验、iframe 加载或卡初始化失败时回调 */
65
+ onError?: OnError;
66
+ };
67
+ type RenderCardInstance = {
68
+ /** 移除 iframe,并停止 message 监听。 */
69
+ destroy: () => void;
70
+ };
71
+
72
+ /**
73
+ * 在宿主容器中挂载 TripLink 卡信息 iframe,并通过 postMessage 完成初始化。
74
+ *
75
+ * 流程:校验入参 → 定位容器 → 拼接不含敏感信息的 iframe URL →
76
+ * 挂载 iframe → 加载完成后下发 `{ ephemeralKey, cardLid, customerId }` →
77
+ * 等待 iframe 回传(`onSuccess` / `onError`)。
78
+ *
79
+ * @returns `{ destroy }` 用于移除 iframe 并停止监听
80
+ */
81
+ declare function renderCard(options: RenderCardOptions): RenderCardInstance;
82
+
83
+ /** 默认语言 */
84
+ declare const DEFAULT_LOCALE = "en-US";
85
+ /** 默认环境 */
86
+ declare const DEFAULT_ENV: Env;
87
+ /** 托管卡页地址 TODO */
88
+ declare const IFRAME_BASE_URLS: Record<Env, string>;
89
+
90
+ /** 对外 SDK 命名空间:`TripLink.renderCard(...)`。 */
91
+ declare const TripLink: {
92
+ renderCard: typeof renderCard;
93
+ };
94
+
95
+ export { type CSSStyleProperties, type CardStyles, DEFAULT_ENV, DEFAULT_LOCALE, type Env, IFRAME_BASE_URLS, type OnError, type OnErrorPayload, type OnSuccess, type OnSuccessPayload, type RenderCardInstance, type RenderCardOptions, ResultCode, type ResultCodeValue, TripLink, TripLink as default, renderCard };
package/dist/index.js ADDED
@@ -0,0 +1,154 @@
1
+ // src/renderCard.ts
2
+ import { v4 as uuidv4 } from "uuid";
3
+
4
+ // src/constants.ts
5
+ var DEFAULT_LOCALE = "en-US", DEFAULT_ENV = "PROD", IFRAME_BASE_URLS = {
6
+ FAT: "https://vcc-hosting.fat.ctripqa.com/embedded-card/cardInfo",
7
+ PROD: "https://vcc-hosting.fat.ctripqa.com/embedded-card/cardInfo"
8
+ };
9
+
10
+ // src/enums/resultCode.ts
11
+ var ResultCode = /* @__PURE__ */ ((ResultCode2) => (ResultCode2.INVALID_PARAMS = "INVALID_PARAMS", ResultCode2.CONTAINER_NOT_FOUND = "CONTAINER_NOT_FOUND", ResultCode2.IFRAME_LOAD_FAILED = "IFRAME_LOAD_FAILED", ResultCode2.CARD_INIT_FAILED = "CARD_INIT_FAILED", ResultCode2.BROWSER_VERSION_LOW = "BROWSER_VERSION_LOW", ResultCode2))(ResultCode || {});
12
+
13
+ // src/utils/validators.ts
14
+ var REQUIRED_STRING_KEYS = ["container", "ephemeralKey", "cardLid", "customerId", "source"];
15
+ function isNonEmptyString(value) {
16
+ return typeof value == "string" && value.trim().length > 0;
17
+ }
18
+ function isSupported() {
19
+ return [
20
+ "postMessage",
21
+ // Portal/SDK 需要支持 postMessage
22
+ "TextEncoder",
23
+ // Portal 需要支持 TextEncoder
24
+ "Uint8Array",
25
+ // Portal 需要支持 Uint8Array
26
+ "atob"
27
+ // Portal 需要支持 atob
28
+ ].every((key) => {
29
+ let isSupported3 = typeof window[key] == "function";
30
+ return isSupported3 || console.warn(`[TripLink SDK] ${key} is not supported`), isSupported3;
31
+ });
32
+ }
33
+ function validateOptions(options) {
34
+ if (!options || typeof options != "object")
35
+ return { ok: !1, code: "INVALID_PARAMS" /* INVALID_PARAMS */, message: "options is required" };
36
+ for (let key of REQUIRED_STRING_KEYS)
37
+ if (!isNonEmptyString(options[key]))
38
+ return { ok: !1, code: "INVALID_PARAMS" /* INVALID_PARAMS */, message: `${key} is required` };
39
+ return options.env != null && options.env !== "FAT" && options.env !== "PROD" ? { ok: !1, code: "INVALID_PARAMS" /* INVALID_PARAMS */, message: "env must be FAT or PROD" } : { ok: !0 };
40
+ }
41
+
42
+ // src/utils/url.ts
43
+ function appendQuery(base, params) {
44
+ let pairs = [];
45
+ for (let key in params) {
46
+ if (!Object.prototype.hasOwnProperty.call(params, key))
47
+ continue;
48
+ let value = params[key];
49
+ value && pairs.push(key + "=" + encodeURIComponent(value));
50
+ }
51
+ if (!pairs.length)
52
+ return base;
53
+ let hashIndex = base.indexOf("#"), hash = hashIndex >= 0 ? base.slice(hashIndex) : "", withoutHash = hashIndex >= 0 ? base.slice(0, hashIndex) : base, lastChar = withoutHash.charAt(withoutHash.length - 1), separator = "&";
54
+ return withoutHash.indexOf("?") === -1 ? separator = "?" : (lastChar === "?" || lastChar === "&") && (separator = ""), withoutHash + separator + pairs.join("&") + hash;
55
+ }
56
+ function getOrigin(url) {
57
+ let match = url.match(/^(https?:\/\/[^/?#]+)/i);
58
+ return match ? match[1] : "";
59
+ }
60
+ function buildIframeUrl(options) {
61
+ return appendQuery(IFRAME_BASE_URLS[options.env || DEFAULT_ENV], {
62
+ locale: options.locale,
63
+ source: options.source,
64
+ oid: options.oid
65
+ });
66
+ }
67
+
68
+ // src/renderCard.ts
69
+ function applyIframeStyle(iframe) {
70
+ iframe.setAttribute("title", "TripLink Card"), iframe.setAttribute("allow", "clipboard-write");
71
+ }
72
+ function renderCard(options) {
73
+ let noop = { destroy() {
74
+ } }, requestId = uuidv4(), reportError = (code, message) => {
75
+ console.error(`[Triplink-embedded-card SDK] ${code}: ${message}`), options?.onError?.({ resultCode: code, message, requestId });
76
+ };
77
+ if (!isSupported())
78
+ return reportError("BROWSER_VERSION_LOW" /* BROWSER_VERSION_LOW */, "Browser version is too low"), noop;
79
+ let validated = validateOptions(options);
80
+ if (!validated.ok)
81
+ return reportError(validated.code, validated.message), noop;
82
+ let container = document.getElementById(options.container);
83
+ if (!container)
84
+ return reportError("CONTAINER_NOT_FOUND" /* CONTAINER_NOT_FOUND */, `Mount node not found: ${options.container}`), noop;
85
+ let locale = (options.locale || DEFAULT_LOCALE).trim(), env = options.env || DEFAULT_ENV, iframeSrc = buildIframeUrl({
86
+ env,
87
+ locale,
88
+ source: options.source.trim(),
89
+ oid: requestId
90
+ }), iframeOrigin = getOrigin(iframeSrc), iframe = document.createElement("iframe");
91
+ applyIframeStyle(iframe);
92
+ let didInit = !1, destroyed = !1, onComplete = () => didInit || destroyed ? !1 : didInit = !0, onError = (code, message) => {
93
+ onComplete() && reportError(code, message);
94
+ }, onSuccess = () => {
95
+ onComplete() && options.onSuccess?.({ requestId });
96
+ }, postCardInitMessage = () => {
97
+ destroyed || didInit || iframe.contentWindow?.postMessage(
98
+ {
99
+ requestId,
100
+ type: "CARD_INIT" /* CardInit */,
101
+ payload: {
102
+ ephemeralKey: options.ephemeralKey.trim(),
103
+ cardLogId: options.cardLid.trim(),
104
+ customerId: options.customerId.trim(),
105
+ ...options.styles ? { styles: options.styles } : {}
106
+ }
107
+ },
108
+ iframeOrigin
109
+ );
110
+ }, onMessage = (event) => {
111
+ if (destroyed || didInit || iframe.contentWindow && event.source !== iframe.contentWindow || event.origin !== iframeOrigin)
112
+ return;
113
+ let { requestId: messageRequestId, type: messageType, payload: messagePayload } = event.data || {};
114
+ if (!(!messageRequestId || messageRequestId !== requestId))
115
+ switch (messageType) {
116
+ case "READY" /* Ready */:
117
+ postCardInitMessage();
118
+ break;
119
+ case "CARD_INIT_SUCCESS" /* InitSuccess */:
120
+ onSuccess();
121
+ break;
122
+ case "CARD_INIT_FAILED" /* InitFailed */:
123
+ let message = messagePayload?.message || "Failed to initialize card information";
124
+ onError("CARD_INIT_FAILED" /* CARD_INIT_FAILED */, message);
125
+ break;
126
+ default:
127
+ onError("CARD_INIT_FAILED" /* CARD_INIT_FAILED */, `Unknown message type '${messageType}'`);
128
+ break;
129
+ }
130
+ }, onIframeError = () => {
131
+ onError("IFRAME_LOAD_FAILED" /* IFRAME_LOAD_FAILED */, "Failed to load iframe page");
132
+ };
133
+ return window.addEventListener("message", onMessage), iframe.addEventListener("error", onIframeError), iframe.src = iframeSrc, container.replaceChildren(iframe), {
134
+ /** 移除 iframe,并停止 message 监听。 */
135
+ destroy() {
136
+ destroyed || (destroyed = !0, window.removeEventListener("message", onMessage), iframe.removeEventListener("error", onIframeError), iframe.remove());
137
+ }
138
+ };
139
+ }
140
+
141
+ // src/index.ts
142
+ var TripLink = {
143
+ renderCard
144
+ }, index_default = TripLink;
145
+ export {
146
+ DEFAULT_ENV,
147
+ DEFAULT_LOCALE,
148
+ IFRAME_BASE_URLS,
149
+ ResultCode,
150
+ TripLink,
151
+ index_default as default,
152
+ renderCard
153
+ };
154
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@triplinkintl/embedded-card",
3
+ "version": "1.0.0",
4
+ "description": "TripLink Embedded Card lets you show virtual card details in your own product, without taking on PCI scope.",
5
+ "license": "MIT",
6
+ "author": "q_yao",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "registry": "https://registry.npmjs.org"
10
+ },
11
+ "keywords": [
12
+ "triplink",
13
+ "embedded-card",
14
+ "vcc",
15
+ "iframe",
16
+ "PCI"
17
+ ],
18
+ "type": "module",
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js",
26
+ "require": "./dist/index.cjs"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "!dist/ares.2019",
32
+ "!dist/ares.2019/**",
33
+ "!dist/**/*.map",
34
+ "!dist/**/*.global.js",
35
+ "LICENSE",
36
+ "README.md"
37
+ ],
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest"
42
+ },
43
+ "sideEffects": false,
44
+ "devDependencies": {
45
+ "jsdom": "^26.1.0",
46
+ "tsup": "^8.5.0",
47
+ "typescript": "^5.9.2",
48
+ "vitest": "^3.2.4"
49
+ },
50
+ "dependencies": {
51
+ "uuid": "14.0.2"
52
+ }
53
+ }