@tonder.io/ionic-lite-sdk 0.0.68 → 0.0.69-beta.TEC-192.ee4f7cd

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.
@@ -148,6 +148,14 @@ export interface IEventSecureInput {
148
148
  isEmpty: boolean;
149
149
  isFocused: boolean;
150
150
  isValid: boolean;
151
+ /**
152
+ * The current value of the field as reported by Skyflow.
153
+ * Available in non-PROD Skyflow environments (DEV/SANDBOX).
154
+ * In PROD, sensitive fields (card_number, cvv) return an empty string;
155
+ * non-sensitive fields (cardholder_name, expiration_month, expiration_year)
156
+ * may still return their value.
157
+ */
158
+ value?: string;
151
159
  }
152
160
  export interface IEvents extends ICardFormEvents {
153
161
  }
@@ -193,6 +201,22 @@ export interface IFormPlaceholder {
193
201
  }
194
202
  export interface IStyles {
195
203
  cardForm?: ILiteCardFormStyles;
204
+ /** Input-element styles applied only to the cardholder name field. Overrides `cardForm.inputStyles` for this field. */
205
+ cardholderName?: CollectInputStylesVariant;
206
+ /** Input-element styles applied only to the card number field. Overrides `cardForm.inputStyles` for this field. */
207
+ cardNumber?: CollectInputStylesVariant;
208
+ /** Input-element styles applied only to the CVV field. Overrides `cardForm.inputStyles` for this field. */
209
+ cvv?: CollectInputStylesVariant;
210
+ /** Input-element styles applied only to the expiration month field. Overrides `cardForm.inputStyles` for this field. */
211
+ expirationMonth?: CollectInputStylesVariant;
212
+ /** Input-element styles applied only to the expiration year field. Overrides `cardForm.inputStyles` for this field. */
213
+ expirationYear?: CollectInputStylesVariant;
214
+ /**
215
+ * Show the card-network icon inside the card number Skyflow Element.
216
+ * Corresponds to Skyflow's `CollectElementOptions.enableCardIcon`.
217
+ * @default true
218
+ */
219
+ enableCardIcon?: boolean;
196
220
  }
197
221
  export interface ILiteCardFormStyles extends StylesBaseVariant, IElementStyle {
198
222
  }
@@ -215,7 +239,7 @@ export interface CollectInputStylesVariant extends StylesBaseVariant, StylesFocu
215
239
  dropdownIcon?: Record<string, any>;
216
240
  dropdown?: Record<string, any>;
217
241
  dropdownListItem?: Record<string, any>;
218
- global: Record<string, any>;
242
+ global?: Record<string, any>;
219
243
  }
220
244
  export interface CollectLabelStylesVariant extends StylesBaseVariant, StylesFocusVariant {
221
245
  requiredAsterisk?: Record<string, any>;
@@ -1,5 +1,5 @@
1
1
  import { IConfigureCheckout } from "./commons";
2
- import { ICustomerCardsResponse, ISaveCardRequest, ISaveCardResponse } from "./card";
2
+ import { ICustomerCardsResponse, IRevealCardFieldsRequest, ISaveCardResponse } from "./card";
3
3
  import { IPaymentMethod } from "./paymentMethod";
4
4
  import { IProcessPaymentRequest, IStartCheckoutResponse } from "./checkout";
5
5
  import { ITransaction } from "./transaction";
@@ -50,16 +50,19 @@ export interface ILiteCheckout {
50
50
  */
51
51
  getCustomerCards(): Promise<ICustomerCardsResponse>;
52
52
  /**
53
- * Saves a card to a customer's account. This method can be used to add a new card
54
- * or update an existing one.
55
- * @param {import("./index").ISaveCardRequest} card - The card information to be saved.
53
+ * Saves a card to a customer's account using card data collected via Skyflow Elements.
54
+ *
55
+ * **Requires** that `mountCardFields()` was called first with all five fields
56
+ * (`cardholder_name`, `card_number`, `expiration_month`, `expiration_year`, `cvv`)
57
+ * and that the user has filled them in before calling this method.
58
+ *
56
59
  * @returns {Promise<import("./index").ISaveCardResponse>} A promise that resolves with the saved card data.
57
60
  *
58
61
  * @throws {import("./index").IPublicError} Throws an error object if the operation fails.
59
62
  *
60
63
  * @public
61
64
  */
62
- saveCustomerCard(card: ISaveCardRequest): Promise<ISaveCardResponse>;
65
+ saveCustomerCard(): Promise<ISaveCardResponse>;
63
66
  /**
64
67
  * Removes a card from a customer's account.
65
68
  * @param {string} skyflowId - The unique identifier of the card to be deleted.
@@ -158,9 +161,18 @@ export interface ILiteCheckout {
158
161
  */
159
162
  getOpenpayDeviceSessionID(merchant_id: string, public_key: string, is_sandbox: boolean): Promise<string | ErrorResponse>;
160
163
  /**
161
- * Displays and renders card input fields in the checkout.
162
- * Uses the provided configuration to show the required fields in the payment form.
163
- * @param {import("./card").IMountCardFieldsRequest} event - Configuration for the card fields to render.
164
+ * Mounts Skyflow Elements (secure iframes) into developer-provided `<div>` containers.
165
+ *
166
+ * **New card form** (omit `card_id`): mount all 5 fields before calling `payment()` or
167
+ * `saveCustomerCard()`. Place divs with default IDs: `collect_cardholder_name`,
168
+ * `collect_card_number`, `collect_expiration_month`, `collect_expiration_year`, `collect_cvv`.
169
+ *
170
+ * **Saved-card CVV** (provide `card_id`): mount only `cvv` for a specific saved card before
171
+ * calling `payment()`. Default div ID: `collect_cvv_<card_id>`.
172
+ *
173
+ * Custom container IDs can be set via `{ field, container_id }` object form per field entry.
174
+ *
175
+ * @param {import("./card").IMountCardFieldsRequest} event - Configuration for the fields to render.
164
176
  * @returns {Promise<void>} Resolves when the fields have been successfully rendered.
165
177
  * @public
166
178
  */
@@ -172,4 +184,33 @@ export interface ILiteCheckout {
172
184
  * @public
173
185
  */
174
186
  unmountCardFields(context?: string): void;
187
+ /**
188
+ * Reveals card data (from the last `saveCustomerCard()` or `payment()` with a new card)
189
+ * in developer-provided `<div>` containers using Skyflow Reveal Elements (secure iframes).
190
+ *
191
+ * Must be called **after** a successful `saveCustomerCard()` or `payment()` that processed
192
+ * a new card. The SDK stores the Skyflow tokens from that collect operation internally.
193
+ *
194
+ * **Default container IDs:** `#reveal_<field>` (e.g. `#reveal_card_number`).
195
+ *
196
+ * **Redaction by field (fixed, cannot be overridden):**
197
+ * - `card_number` → `MASKED` (e.g. `4111 11•• •••• 1234`)
198
+ * - `cardholder_name`, `expiration_month`, `expiration_year` → `PLAIN_TEXT`
199
+ *
200
+ * > CVV cannot be revealed — PCI DSS 3.2.1 prohibits storing or displaying CVV post-authorization.
201
+ *
202
+ * @param request - Fields to reveal, plus optional styles, redaction level, and altText per field.
203
+ * @returns {Promise<void>} Resolves when all Reveal Elements have been mounted and `reveal()` called.
204
+ * @public
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * // After successful saveCustomerCard():
209
+ * await liteCheckout.revealCardFields({
210
+ * fields: ['card_number', 'cardholder_name', 'expiration_month', 'expiration_year']
211
+ * });
212
+ * // → renders masked card info in #reveal_card_number, #reveal_cardholder_name, etc.
213
+ * ```
214
+ */
215
+ revealCardFields(request: IRevealCardFieldsRequest): Promise<void>;
175
216
  }
@@ -73,6 +73,7 @@ export type TokensSkyflowRequest = {
73
73
  apiKey: string;
74
74
  vault_id: string;
75
75
  vault_url: string;
76
+ mode?: string;
76
77
  data?: {
77
78
  [key: string]: any;
78
79
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonder.io/ionic-lite-sdk",
3
- "version": "0.0.68",
3
+ "version": "0.0.69-beta.TEC-192.ee4f7cd",
4
4
  "description": "Tonder ionic lite SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,7 +9,7 @@ import {
9
9
  } from "../helpers/utils";
10
10
  import {getCustomerAPMs} from "../data/api";
11
11
  import {BaseInlineCheckout} from "./BaseInlineCheckout";
12
- import {getSkyflowTokens, initSkyflowInstance, mountSkyflowFields} from "../helpers/skyflow";
12
+ import {getSkyflowTokens, initSkyflowInstance, mountRevealFields, mountSkyflowFields} from "../helpers/skyflow";
13
13
  import {startCheckoutRouter} from "../data/checkoutApi";
14
14
  import {getOpenpayDeviceSessionID} from "../data/openPayApi";
15
15
  import {getPaymentMethodDetails} from "../shared/catalog/paymentMethodsCatalog";
@@ -17,7 +17,7 @@ import {APM, IEvents, IInlineLiteCheckoutOptions, InCollectorContainer, TonderAP
17
17
  import {
18
18
  ICustomerCardsResponse,
19
19
  IMountCardFieldsRequest,
20
- ISaveCardRequest,
20
+ IRevealCardFieldsRequest,
21
21
  ISaveCardResponse,
22
22
  ISaveCardSkyflowRequest
23
23
  } from "../types/card";
@@ -39,7 +39,7 @@ import {
39
39
  StartCheckoutRequest,
40
40
  TokensRequest
41
41
  } from "../types/requests";
42
- import {ICardFields, IStartCheckoutResponse} from "../types/checkout";
42
+ import {IStartCheckoutResponse} from "../types/checkout";
43
43
  import {ILiteCheckout} from "../types/liteInlineCheckout";
44
44
  import CollectorContainer from "skyflow-js/types/core/external/collect/collect-container";
45
45
  import Skyflow from "skyflow-js";
@@ -61,6 +61,8 @@ export class LiteCheckout extends BaseInlineCheckout implements ILiteCheckout{
61
61
  // Store mounted elements by context: 'create' or 'update:card_id'
62
62
  private mountedElementsByContext: Map<string, { elements: any[], container: InCollectorContainer | null }> = new Map();
63
63
  private customerCardsCache: ICustomerCardsResponse | null = null;
64
+ // Tokens from the last collectCreateCardTokens() call — used by revealCardFields()
65
+ private lastCollectedTokens: Record<string, string> | null = null;
64
66
 
65
67
  constructor({ apiKey, mode, returnUrl, callBack, apiKeyTonder, baseUrlTonder, customization, collectorIds, events }: IInlineLiteCheckoutOptions) {
66
68
  super({
@@ -120,32 +122,16 @@ export class LiteCheckout extends BaseInlineCheckout implements ILiteCheckout{
120
122
  }
121
123
  }
122
124
 
123
- public async saveCustomerCard(
124
- card: ISaveCardRequest,
125
- ): Promise<ISaveCardResponse> {
125
+ public async saveCustomerCard(): Promise<ISaveCardResponse> {
126
126
  let cardId: string | null = null;
127
127
  try {
128
128
  await this._fetchMerchantData();
129
129
  const customerResponse = await this._getCustomer() as CustomerRegisterResponse;
130
130
  const { auth_token, first_name = "", last_name = "", email = "" } = customerResponse;
131
- const { vault_id, vault_url, business } = this.merchantData!;
131
+ const { business } = this.merchantData!;
132
132
  const cardOnFileEnabled = this._hasCardOnFileKeys();
133
133
 
134
- const sanitizedCard = {
135
- card_number: card.card_number.replace(/\s+/g, ""),
136
- expiration_month: card.expiration_month.replace(/\s+/g, ""),
137
- expiration_year: card.expiration_year.replace(/\s+/g, ""),
138
- cvv: card.cvv.replace(/\s+/g, ""),
139
- cardholder_name: card.cardholder_name.replace(/\s+/g, ""),
140
- };
141
-
142
- const skyflowTokens: any = await getSkyflowTokens({
143
- vault_id: vault_id,
144
- vault_url: vault_url,
145
- data: sanitizedCard,
146
- baseUrl: this.baseUrl,
147
- apiKey: this.apiKeyTonder,
148
- });
134
+ const skyflowTokens: any = await this.collectCreateCardTokens();
149
135
 
150
136
  const saveResponse = await this._saveCustomerCard(
151
137
  auth_token,
@@ -353,6 +339,81 @@ export class LiteCheckout extends BaseInlineCheckout implements ILiteCheckout{
353
339
  }
354
340
  }
355
341
 
342
+ /**
343
+ * Collects card tokens from Skyflow Elements mounted for a new card (the 'create' context).
344
+ * Requires that `mountCardFields()` was called without a `card_id` beforehand.
345
+ */
346
+ private async collectCreateCardTokens(): Promise<Record<string, any>> {
347
+ const contextData = this.mountedElementsByContext.get('create');
348
+ const container = contextData?.container?.container as CollectorContainer | null;
349
+ if (!container) {
350
+ throw new TonderError({
351
+ code: ErrorKeyEnum.MOUNT_COLLECT_ERROR,
352
+ details: {
353
+ message: 'No card fields are mounted. Call mountCardFields() with the required fields before proceeding.',
354
+ },
355
+ });
356
+ }
357
+ try {
358
+ const collectResponse: any = await container.collect();
359
+ const fields: Record<string, any> = collectResponse?.records?.[0]?.fields || {};
360
+ // Store tokens so revealCardFields() can use them after save/payment
361
+ this.lastCollectedTokens = fields;
362
+ return fields;
363
+ } catch (e: any) {
364
+ const errorDescription = e?.error?.description;
365
+ this.reportSdkError(e, {
366
+ feature: "collect-create-card-tokens",
367
+ metadata: {
368
+ step: "collectCreateCardTokens",
369
+ },
370
+ });
371
+ throw new TonderError({
372
+ code: ErrorKeyEnum.MOUNT_COLLECT_ERROR,
373
+ details: {
374
+ message: errorDescription,
375
+ },
376
+ });
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Reveals card data collected in the last `saveCustomerCard()` or `payment()` call
382
+ * (with a new card) in developer-provided `<div>` containers using Skyflow Reveal Elements.
383
+ *
384
+ * Must be called **after** a successful `saveCustomerCard()` or `payment()` that processed
385
+ * a new card (i.e., without a saved-card `skyflow_id`). Skyflow Reveal Elements render the
386
+ * actual (or masked) values inside secure iframes without exposing them to the application.
387
+ *
388
+ * Default container IDs: `#reveal_<field>` (e.g. `#reveal_card_number`).
389
+ * Default redaction: `MASKED` for card_number, `REDACTED` for cvv, `PLAIN_TEXT` for others.
390
+ *
391
+ * @param request - Fields to reveal and optional per-field styles/redaction/altText.
392
+ */
393
+ public async revealCardFields(request: IRevealCardFieldsRequest): Promise<void> {
394
+ if (!this.lastCollectedTokens) {
395
+ throw buildPublicAppError({
396
+ errorCode: ErrorKeyEnum.MOUNT_COLLECT_ERROR,
397
+ details: {
398
+ message: 'No card tokens available. Call saveCustomerCard() or payment() with a new card before calling revealCardFields().',
399
+ },
400
+ });
401
+ }
402
+ if (!this.skyflowInstance) {
403
+ throw buildPublicAppError({
404
+ errorCode: ErrorKeyEnum.MOUNT_COLLECT_ERROR,
405
+ details: {
406
+ message: 'Skyflow instance not initialized. Ensure mountCardFields() was called before saveCustomerCard() or payment().',
407
+ },
408
+ });
409
+ }
410
+ await mountRevealFields({
411
+ skyflowInstance: this.skyflowInstance,
412
+ tokens: this.lastCollectedTokens,
413
+ request,
414
+ });
415
+ }
416
+
356
417
  public unmountCardFields(context: string = 'all'): void {
357
418
  if (context === 'all') {
358
419
  this.mountedElementsByContext.forEach((contextData, ctx) => {
@@ -427,6 +488,7 @@ export class LiteCheckout extends BaseInlineCheckout implements ILiteCheckout{
427
488
  vault_url: vault_url,
428
489
  baseUrl: this.baseUrl,
429
490
  apiKey: this.apiKeyTonder,
491
+ mode: this.mode,
430
492
  })
431
493
  }
432
494
 
@@ -484,14 +546,14 @@ export class LiteCheckout extends BaseInlineCheckout implements ILiteCheckout{
484
546
  // TODO: DEPRECATED
485
547
  returnUrl: returnUrlData
486
548
  }: {
487
- card?: ICardFields | string;
549
+ card?: string;
488
550
  payment_method?: string;
489
551
  isSandbox?: boolean;
490
552
  returnUrl?: string;
491
553
  }) {
492
554
  await this._fetchMerchantData();
493
555
  const customer = await this._getCustomer(this.abortController.signal) as CustomerRegisterResponse;
494
- const { vault_id, vault_url, business } = this.merchantData!;
556
+ const { business } = this.merchantData!;
495
557
  const { auth_token, first_name = "", last_name = "", email = "" } = customer;
496
558
  const cardOnFileEnabled = this._hasCardOnFileKeys();
497
559
  let skyflowTokens;
@@ -537,21 +599,8 @@ export class LiteCheckout extends BaseInlineCheckout implements ILiteCheckout{
537
599
  await this.collectCardTokens(card);
538
600
  }
539
601
  } else {
540
- const sanitizedCard = {
541
- ...card!,
542
- card_number: card!.card_number.replace(/\s+/g, ""),
543
- expiration_month: card!.expiration_month.replace(/\s+/g, ""),
544
- expiration_year: card!.expiration_year.replace(/\s+/g, ""),
545
- cvv: card!.cvv.replace(/\s+/g, ""),
546
- cardholder_name: card!.cardholder_name.replace(/\s+/g, ""),
547
- };
548
- skyflowTokens = await getSkyflowTokens({
549
- vault_id: vault_id,
550
- vault_url: vault_url,
551
- data: sanitizedCard,
552
- baseUrl: this.baseUrl,
553
- apiKey: this.apiKeyTonder,
554
- });
602
+ // New card: collect tokens from mounted Skyflow Elements (mountCardFields must be called first)
603
+ skyflowTokens = await this.collectCreateCardTokens();
555
604
  skyflowId = skyflowTokens.skyflow_id;
556
605
 
557
606
  if (cardOnFileEnabled) {
@@ -1,13 +1,15 @@
1
1
  import Skyflow from "skyflow-js";
2
2
  import CollectContainer from "skyflow-js/types/core/external/collect/collect-container";
3
3
  import CollectElement from "skyflow-js/types/core/external/collect/collect-element";
4
+ import RevealContainer from "skyflow-js/types/core/external/reveal/reveal-container";
4
5
  import { getVaultToken } from "../data/skyflowApi";
5
6
  import { TokensSkyflowRequest } from "../types/requests";
6
- import { CardFieldEnum, IMountCardFieldsRequest } from "../types/card";
7
+ import { CardFieldEnum, IMountCardFieldsRequest, IRevealCardField, IRevealCardFieldsRequest } from "../types/card";
7
8
  import {
8
9
  IEvents,
9
10
  IInputEvents,
10
11
  ILiteCustomizationOptions,
12
+ IStyles,
11
13
  StylesBaseVariant,
12
14
  } from "../types/commons";
13
15
  import {
@@ -26,9 +28,8 @@ import { buildPublicAppError } from "../shared/utils/appError";
26
28
  import { ErrorKeyEnum } from "../shared/enum/ErrorKeyEnum";
27
29
 
28
30
  /**
29
- * [DEPRECATION WARNING]
30
- * This function should be deprecated in favor of using mountSkyflowFields for security,
31
- * to prevent users from creating their own inputs.
31
+ * @deprecated This function is deprecated and will be removed in a future release.
32
+ * Use `mountCardFields()` to render Skyflow Elements and collect card data securely.
32
33
  */
33
34
  export async function getSkyflowTokens({
34
35
  baseUrl,
@@ -135,14 +136,16 @@ export async function initSkyflowInstance({
135
136
  apiKey,
136
137
  vault_id,
137
138
  vault_url,
139
+ mode,
138
140
  }: TokensSkyflowRequest): Promise<Skyflow> {
141
+ const skyflowEnv = mode === 'production' ? Skyflow.Env.PROD : Skyflow.Env.DEV;
139
142
  return Skyflow.init({
140
143
  vaultID: vault_id,
141
144
  vaultURL: vault_url,
142
145
  getBearerToken: async () => await getVaultToken(baseUrl, apiKey),
143
146
  options: {
144
147
  logLevel: Skyflow.LogLevel.ERROR,
145
- env: Skyflow.Env.DEV,
148
+ env: skyflowEnv,
146
149
  },
147
150
  });
148
151
  }
@@ -176,19 +179,42 @@ export async function mountSkyflowFields(event: {
176
179
  [CardFieldEnum.EXPIRATION_YEAR]: [regexMatchRule],
177
180
  [CardFieldEnum.CARDHOLDER_NAME]: [lengthMatchRule, regexMatchRule],
178
181
  };
179
- const customStyles = {
180
- errorStyles:
181
- customization?.styles?.cardForm?.errorStyles ||
182
- DEFAULT_SKYFLOW_ERROR_TEXT_STYLES,
183
- inputStyles:
184
- customization?.styles?.cardForm?.inputStyles ||
185
- DEFAULT_SKYFLOW_INPUT_STYLES,
186
- labelStyles:
187
- customization?.styles?.cardForm?.labelStyles ||
188
- DEFAULT_SKYFLOW_lABEL_STYLES,
182
+ const fieldToStyleKey: Record<CardFieldEnum, keyof Omit<IStyles, 'cardForm' | 'enableCardIcon'>> = {
183
+ [CardFieldEnum.CARDHOLDER_NAME]: 'cardholderName',
184
+ [CardFieldEnum.CARD_NUMBER]: 'cardNumber',
185
+ [CardFieldEnum.CVV]: 'cvv',
186
+ [CardFieldEnum.EXPIRATION_MONTH]: 'expirationMonth',
187
+ [CardFieldEnum.EXPIRATION_YEAR]: 'expirationYear',
189
188
  };
189
+
190
+ const getFieldStyles = (field: CardFieldEnum) => {
191
+ const perFieldInputStyles = customization?.styles?.[fieldToStyleKey[field]];
192
+ const form = customization?.styles?.cardForm;
193
+ const resolvedInputStyles = perFieldInputStyles ?? form?.inputStyles ?? DEFAULT_SKYFLOW_INPUT_STYLES;
194
+
195
+ // For card_number: inject paddingLeft default so text doesn't overlap the card-network icon.
196
+ // Applied only when the icon is visible (enableCardIcon !== false) and the developer
197
+ // hasn't already set paddingLeft in their base styles.
198
+ const iconVisible = field === CardFieldEnum.CARD_NUMBER && customization?.styles?.enableCardIcon !== false;
199
+ const inputStyles = iconVisible
200
+ ? {
201
+ ...resolvedInputStyles,
202
+ base: {
203
+ paddingLeft: '15px', // default — gives room for the card icon
204
+ ...(resolvedInputStyles as any)?.base, // developer's base overrides, including their own paddingLeft
205
+ },
206
+ }
207
+ : resolvedInputStyles;
208
+
209
+ return {
210
+ inputStyles,
211
+ labelStyles: form?.labelStyles ?? DEFAULT_SKYFLOW_lABEL_STYLES,
212
+ errorStyles: form?.errorStyles ?? DEFAULT_SKYFLOW_ERROR_TEXT_STYLES,
213
+ };
214
+ };
215
+
190
216
  const labels: Record<string, string> = {
191
- name: customization?.labels?.name || DEFAULT_SKYFLOW_lABELS.name,
217
+ cardholder_name: customization?.labels?.name || DEFAULT_SKYFLOW_lABELS.name,
192
218
  card_number:
193
219
  customization?.labels?.card_number || DEFAULT_SKYFLOW_lABELS.card_number,
194
220
  cvv: customization?.labels?.cvv || DEFAULT_SKYFLOW_lABELS.cvv,
@@ -203,7 +229,7 @@ export async function mountSkyflowFields(event: {
203
229
  DEFAULT_SKYFLOW_lABELS.expiration_year,
204
230
  };
205
231
  const placeholders: Record<string, string> = {
206
- name:
232
+ cardholder_name:
207
233
  customization?.placeholders?.name || DEFAULT_SKYFLOW_PLACEHOLDERS.name,
208
234
  card_number:
209
235
  customization?.placeholders?.card_number ||
@@ -225,21 +251,26 @@ export async function mountSkyflowFields(event: {
225
251
  };
226
252
 
227
253
  if ("fields" in data && Array.isArray(data.fields)) {
254
+ const cardIconOption = { enableCardIcon: customization?.styles?.enableCardIcon ?? true };
255
+
228
256
  if (data.fields.length > 0 && typeof data.fields[0] === "string") {
229
257
  for (const field of data.fields as CardFieldEnum[]) {
230
- const element = collectContainer.create({
231
- table: "cards",
232
- column: field,
233
- type: typeByField[field],
234
- validations: validationsByField[field],
235
- ...customStyles,
236
- label: labels[field],
237
- placeholder: placeholders[field],
238
- ...(data.card_id ? { skyflowID: data.card_id } : {}),
239
- });
258
+ const element = collectContainer.create(
259
+ {
260
+ table: "cards",
261
+ column: field,
262
+ type: typeByField[field],
263
+ validations: validationsByField[field],
264
+ ...getFieldStyles(field),
265
+ label: labels[field],
266
+ placeholder: placeholders[field],
267
+ ...(data.card_id ? { skyflowID: data.card_id } : {}),
268
+ },
269
+ field === CardFieldEnum.CARD_NUMBER ? cardIconOption : undefined,
270
+ );
240
271
  handleSkyflowElementEvents({
241
272
  element,
242
- errorStyles: customStyles.errorStyles,
273
+ errorStyles: getFieldStyles(field).errorStyles,
243
274
  fieldMessage: [
244
275
  CardFieldEnum.CVV,
245
276
  CardFieldEnum.EXPIRATION_MONTH,
@@ -261,16 +292,19 @@ export async function mountSkyflowFields(event: {
261
292
  field: CardFieldEnum;
262
293
  }[]) {
263
294
  const key = fieldObj.field;
264
- const element = collectContainer.create({
265
- table: "cards",
266
- column: key,
267
- type: typeByField[key],
268
- validations: validationsByField[key],
269
- ...customStyles,
270
- label: labels[key],
271
- placeholder: placeholders[key],
272
- ...(data.card_id ? { skyflowID: data.card_id } : {}),
273
- });
295
+ const element = collectContainer.create(
296
+ {
297
+ table: "cards",
298
+ column: key,
299
+ type: typeByField[key],
300
+ validations: validationsByField[key],
301
+ ...getFieldStyles(key),
302
+ label: labels[key],
303
+ placeholder: placeholders[key],
304
+ ...(data.card_id ? { skyflowID: data.card_id } : {}),
305
+ },
306
+ key === CardFieldEnum.CARD_NUMBER ? cardIconOption : undefined,
307
+ );
274
308
  const containerId =
275
309
  fieldObj.container_id ||
276
310
  `#collect_${String(key)}` + (data.card_id ? `_${data.card_id}` : "");
@@ -371,14 +405,83 @@ const executeEvent = (event: {
371
405
  if (typeof eventHandler === "function") {
372
406
  eventHandler({
373
407
  elementType: get(data, "elementType", ""),
374
- isEmpty: get(data, "isEmpty", ""),
375
- isFocused: get(data, "isFocused", ""),
376
- isValid: get(data, "isValid", ""),
408
+ isEmpty: get(data, "isEmpty", false),
409
+ isFocused: get(data, "isFocused", false),
410
+ isValid: get(data, "isValid", false),
411
+ value: get(data, "value", ""),
377
412
  });
378
413
  }
379
414
  }
380
415
  };
381
416
 
417
+ export async function mountRevealFields(event: {
418
+ skyflowInstance: Skyflow;
419
+ tokens: Record<string, string>;
420
+ request: IRevealCardFieldsRequest;
421
+ }): Promise<void> {
422
+ const { skyflowInstance, tokens, request } = event;
423
+ const revealContainer = skyflowInstance.container(
424
+ Skyflow.ContainerType.REVEAL,
425
+ ) as RevealContainer;
426
+
427
+ // Redaction levels are fixed per PCI DSS — not configurable by the caller.
428
+ // CVV (PCI DSS req. 3.2.1) must never be revealed after authorisation.
429
+ const pciRedaction: Partial<Record<CardFieldEnum, string>> = {
430
+ [CardFieldEnum.CARD_NUMBER]: 'MASKED', // first-6 / last-4 only
431
+ [CardFieldEnum.CARDHOLDER_NAME]: 'PLAIN_TEXT',
432
+ [CardFieldEnum.EXPIRATION_MONTH]: 'PLAIN_TEXT',
433
+ [CardFieldEnum.EXPIRATION_YEAR]: 'PLAIN_TEXT',
434
+ };
435
+
436
+ for (const entry of request.fields) {
437
+ const isString = typeof entry === 'string';
438
+ const field = (isString ? entry : (entry as IRevealCardField).field) as CardFieldEnum;
439
+
440
+ // CVV must never be revealed — skip silently with a warning.
441
+ if (field === CardFieldEnum.CVV) {
442
+ console.warn('[revealCardFields] CVV cannot be revealed (PCI DSS req. 3.2.1). Skipping.');
443
+ continue;
444
+ }
445
+
446
+ const token = tokens[field];
447
+ if (!token) {
448
+ console.warn(`[revealCardFields] No token found for field "${field}", skipping.`);
449
+ continue;
450
+ }
451
+
452
+ const cfg: Partial<IRevealCardField> = isString ? {} : (entry as IRevealCardField);
453
+ const containerId = cfg.container_id ?? `#reveal_${field}`;
454
+ const redactionKey = pciRedaction[field]!;
455
+ const fieldStyles = cfg.styles ?? {};
456
+ const globalStyles = request.styles ?? {};
457
+
458
+ const element = revealContainer.create({
459
+ token,
460
+ redaction: Skyflow.RedactionType[redactionKey as keyof typeof Skyflow.RedactionType], // fixed by SDK
461
+ ...(cfg.altText !== undefined && { altText: cfg.altText }),
462
+ ...(cfg.label !== undefined && { label: cfg.label }),
463
+ ...(fieldStyles.inputStyles || globalStyles.inputStyles
464
+ ? { inputStyles: fieldStyles.inputStyles ?? globalStyles.inputStyles }
465
+ : {}),
466
+ ...(fieldStyles.labelStyles || globalStyles.labelStyles
467
+ ? { labelStyles: fieldStyles.labelStyles ?? globalStyles.labelStyles }
468
+ : {}),
469
+ ...(fieldStyles.errorTextStyles || globalStyles.errorTextStyles
470
+ ? { errorTextStyles: fieldStyles.errorTextStyles ?? globalStyles.errorTextStyles }
471
+ : {}),
472
+ });
473
+
474
+ await tryMountElement({ element, containerId });
475
+ }
476
+
477
+ try {
478
+ await (revealContainer as any).reveal();
479
+ } catch (e) {
480
+ // reveal() returns partial success/error — not critical to throw
481
+ console.warn('[revealCardFields] reveal completed with errors:', e);
482
+ }
483
+ }
484
+
382
485
  async function tryMountElement(event: {
383
486
  element: any;
384
487
  containerId: string;
package/src/index.ts CHANGED
@@ -4,6 +4,14 @@ import { SdkTelemetryClient } from './helpers/SdkTelemetryClient'
4
4
  import { AppError } from './shared/utils/appError'
5
5
  import { validateCVV, validateCardNumber, validateExpirationMonth, validateCardholderName, validateExpirationYear } from './helpers/validations'
6
6
 
7
+ export type {
8
+ IRevealCardFieldsRequest,
9
+ IRevealCardField,
10
+ IRevealElementStyles,
11
+ IRevealElementInputStyles,
12
+ RevealableCardField,
13
+ } from './types/card'
14
+
7
15
  export {
8
16
  LiteCheckout,
9
17
  BaseInlineCheckout,