@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.
- package/README.md +1157 -677
- package/dist/classes/liteCheckout.d.ts +24 -4
- package/dist/helpers/skyflow.d.ts +9 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/types/card.d.ts +95 -0
- package/dist/types/checkout.d.ts +10 -1
- package/dist/types/commons.d.ts +25 -1
- package/dist/types/liteInlineCheckout.d.ts +49 -8
- package/dist/types/requests.d.ts +1 -0
- package/package.json +1 -1
- package/src/classes/liteCheckout.ts +88 -39
- package/src/helpers/skyflow.ts +144 -41
- package/src/index.ts +8 -0
- package/src/types/card.ts +96 -0
- package/src/types/checkout.ts +10 -1
- package/src/types/commons.ts +25 -1
- package/src/types/liteInlineCheckout.ts +50 -8
- package/src/types/requests.ts +1 -0
package/dist/types/commons.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
|
54
|
-
*
|
|
55
|
-
*
|
|
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(
|
|
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
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
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
|
}
|
package/dist/types/requests.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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 {
|
|
131
|
+
const { business } = this.merchantData!;
|
|
132
132
|
const cardOnFileEnabled = this._hasCardOnFileKeys();
|
|
133
133
|
|
|
134
|
-
const
|
|
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?:
|
|
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 {
|
|
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
|
-
|
|
541
|
-
|
|
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) {
|
package/src/helpers/skyflow.ts
CHANGED
|
@@ -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
|
-
*
|
|
30
|
-
*
|
|
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:
|
|
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
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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:
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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,
|