@fat-zebra/sdk 2.0.1-beta.1 → 2.0.1-beta.10

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/dist/hpp/hpp.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import Sca from '../sca';
2
2
  import { ChallengeWindowSize } from '../sca/types';
3
3
  import { Customer, PaymentIntent } from '../shared/types';
4
+ import ThreeDSecure from "../three_d_secure";
4
5
  declare const HPP_DEFAULT_OPTIONS: {
5
6
  [key: string]: boolean | string;
6
7
  };
@@ -11,6 +12,8 @@ interface HppModuleConfig {
11
12
  customer: Customer;
12
13
  username: string;
13
14
  sca: Sca;
15
+ threeDSecure: ThreeDSecure;
16
+ requestThreeDSEnabled: () => Promise<boolean>;
14
17
  test?: boolean;
15
18
  }
16
19
  interface HppLoadParams {
@@ -42,7 +45,7 @@ declare class Hpp {
42
45
  private customer;
43
46
  private username;
44
47
  private sca;
45
- private ThreeDSecure;
48
+ private threeDSecure;
46
49
  private cardToken;
47
50
  private postMessageClient;
48
51
  private test;
@@ -50,11 +53,14 @@ declare class Hpp {
50
53
  private headlessLoaded;
51
54
  private headlessPreviouslyLoaded;
52
55
  private iframeLoaded;
53
- private threeDSecureEnabled;
56
+ private isThreeDSecureEnabled;
57
+ private requestThreeDSEnabled;
54
58
  private challengeWindowSize;
55
59
  private scaHandler;
60
+ private crossFrameListenersBound;
61
+ private publicEventListenersBound;
56
62
  constructor(config: HppModuleConfig);
57
- setListenersAndEmitReady(): void;
63
+ setListenersAndEmitReady(): Promise<void>;
58
64
  load(config: HppLoadParams): void;
59
65
  purchase(): void;
60
66
  getPayNowUrl(options?: {
@@ -62,6 +68,7 @@ declare class Hpp {
62
68
  }): string;
63
69
  setCrossFramesEventListeners(): void;
64
70
  setPublicEventListeners(): void;
71
+ destroy(): void;
65
72
  createPurchase(extra?: {
66
73
  [key: string]: boolean | string;
67
74
  }): void;
package/dist/hpp/hpp.js CHANGED
@@ -4,6 +4,15 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
8
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
9
+ return new (P || (P = Promise))(function (resolve, reject) {
10
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
11
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
12
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
13
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
14
+ });
15
+ };
7
16
  import * as bridge from '../shared/bridge-client';
8
17
  import { LocalStorageAccessTokenKey } from '../shared/constants';
9
18
  import { emit, off, on } from '../shared/event-manager';
@@ -12,7 +21,8 @@ import { BridgeEvent, PublicEvent, } from '../shared/types';
12
21
  import * as util from '../shared/util';
13
22
  import { setTransactionReference } from "../logging/logger-context";
14
23
  import { logMethod } from "../logging/logMethod";
15
- import ThreeDSecure from "../three_d_secure";
24
+ import env from "../shared/env";
25
+ import { configureLogger } from "../logging/instrument";
16
26
  const HPP_DEFAULT_OPTIONS = {
17
27
  enableSca: false,
18
28
  hideButton: false,
@@ -20,7 +30,7 @@ const HPP_DEFAULT_OPTIONS = {
20
30
  };
21
31
  class Hpp {
22
32
  constructor(config) {
23
- this.threeDSecureEnabled = false;
33
+ this.isThreeDSecureEnabled = false;
24
34
  this.paymentIntent = config.paymentIntent;
25
35
  this.customer = config.customer;
26
36
  this.username = config.username;
@@ -30,58 +40,66 @@ class Hpp {
30
40
  this.headlessLoaded = false;
31
41
  this.headlessPreviouslyLoaded = false;
32
42
  this.iframeLoaded = false;
33
- this.postMessageClient = new PostMessageClient({
34
- channel: 'sca',
35
- target: this.iframe
43
+ this.threeDSecure = config.threeDSecure;
44
+ this.requestThreeDSEnabled = config.requestThreeDSEnabled;
45
+ this.crossFrameListenersBound = false;
46
+ this.publicEventListenersBound = false;
47
+ configureLogger({
48
+ baseUrl: env[process.env.API_ENV].payNowUrl
36
49
  });
37
50
  }
38
51
  setListenersAndEmitReady() {
39
- const message = {
40
- channel: 'sca',
41
- subject: BridgeEvent.READY,
42
- data: {}
43
- };
44
- if (this.headlessLoaded && this.iframeLoaded) {
45
- // initial headless load
46
- this.ThreeDSecure = new ThreeDSecure({
47
- bridge: this.headless,
48
- environment: process.env.API_ENV,
49
- iframe: this.iframe
50
- });
51
- this.setCrossFramesEventListeners();
52
- this.setPublicEventListeners();
53
- emit(PublicEvent.HPP_READY);
54
- this.iframe.contentWindow.postMessage(message, '*');
55
- }
56
- else if (this.headlessPreviouslyLoaded && this.iframeLoaded) {
57
- this.ThreeDSecure = new ThreeDSecure({
58
- bridge: this.headless,
59
- environment: process.env.API_ENV,
60
- iframe: this.iframe
61
- });
62
- this.iframe.contentWindow.postMessage(message, '*');
63
- // subsequent iframe loads after headless has been previously loaded
64
- // this caters for the SPA scenario
65
- emit(PublicEvent.HPP_READY);
66
- }
52
+ return __awaiter(this, void 0, void 0, function* () {
53
+ if (!this.crossFrameListenersBound) {
54
+ this.setCrossFramesEventListeners();
55
+ this.crossFrameListenersBound = true;
56
+ }
57
+ if (this.headlessLoaded && this.iframeLoaded) {
58
+ this.isThreeDSecureEnabled = yield this.requestThreeDSEnabled();
59
+ if (!this.publicEventListenersBound && !this.isThreeDSecureEnabled) {
60
+ this.setPublicEventListeners();
61
+ this.publicEventListenersBound = true;
62
+ }
63
+ emit(PublicEvent.HPP_READY);
64
+ }
65
+ else if (this.headlessPreviouslyLoaded && this.iframeLoaded) {
66
+ this.isThreeDSecureEnabled = yield this.requestThreeDSEnabled();
67
+ // subsequent iframe loads after headless has been previously loaded
68
+ // this caters for the SPA scenario
69
+ if (!this.publicEventListenersBound && !this.isThreeDSecureEnabled) {
70
+ this.setPublicEventListeners();
71
+ this.publicEventListenersBound = true;
72
+ }
73
+ emit(PublicEvent.HPP_READY);
74
+ }
75
+ });
67
76
  }
68
77
  load(config) {
69
- this.challengeWindowSize = config.options.challengeWindowSize;
78
+ var _a;
79
+ this.challengeWindowSize = (_a = config.options) === null || _a === void 0 ? void 0 : _a.challengeWindowSize;
70
80
  setTransactionReference(config.paymentIntent.payment.reference);
71
- this.hppOptions = config.options;
81
+ this.hppOptions = config.options || {};
72
82
  const { el, isExisting } = bridge.load2(process.env.PAYNOW_BRIDGE_URL);
73
83
  this.headless = el;
74
84
  this.headlessPreviouslyLoaded = isExisting;
75
85
  this.iframe = document.createElement('iframe');
76
86
  document.getElementById(config.containerId).appendChild(this.iframe);
77
- this.headless.onload = () => {
87
+ this.postMessageClient = new PostMessageClient({
88
+ channel: 'sca',
89
+ target: this.iframe
90
+ });
91
+ this.headless.addEventListener('load', () => {
78
92
  this.headlessLoaded = true;
79
93
  this.setListenersAndEmitReady();
80
- };
94
+ });
81
95
  this.iframe.onload = () => {
82
96
  this.iframeLoaded = true;
83
97
  this.setListenersAndEmitReady();
84
98
  };
99
+ if (this.headlessPreviouslyLoaded) {
100
+ this.headlessLoaded = true;
101
+ this.setListenersAndEmitReady();
102
+ }
85
103
  const payNowUrl = this.getPayNowUrl(config.options);
86
104
  this.iframe.setAttribute("src", payNowUrl);
87
105
  this.iframe.setAttribute('allow', 'payment');
@@ -141,13 +159,15 @@ class Hpp {
141
159
  if (this.hppOptions.tokenizeOnly)
142
160
  return;
143
161
  if (this.hppOptions.enableSca) {
144
- if (this.threeDSecureEnabled) {
145
- this.ThreeDSecure.run({
162
+ if (this.isThreeDSecureEnabled) {
163
+ this.threeDSecure.run({
146
164
  paymentIntent: this.paymentIntent,
147
165
  merchantUsername: this.username,
148
166
  cardToken: this.cardToken,
149
167
  test: this.test,
150
- challengeWindowSize: this.challengeWindowSize
168
+ challengeWindowSize: this.challengeWindowSize,
169
+ iframe: this.iframe,
170
+ tokenizeOnly: false
151
171
  });
152
172
  return; // do not continue execution to old 3DS
153
173
  }
@@ -210,16 +230,7 @@ class Hpp {
210
230
  data: data,
211
231
  });
212
232
  };
213
- handlers[BridgeEvent.THREE_D_SECURE_ENABLED] = (data) => {
214
- this.threeDSecureEnabled = typeof data === "boolean" ? data : false;
215
- if (this.threeDSecureEnabled) {
216
- // setPublicEventListeners is set before this feature flag is returned.
217
- // we need to therefore unset the scaHandler (for the old implementation or we will get two purchase requests
218
- off(PublicEvent.SCA_SUCCESS, this.scaHandler);
219
- }
220
- };
221
- const handlersIncludingThreeDSecure = Object.assign(Object.assign({}, handlers), this.ThreeDSecure.messageHandlers());
222
- this.postMessageClient.setEventListeners(handlersIncludingThreeDSecure);
233
+ this.postMessageClient.setEventListeners(handlers);
223
234
  }
224
235
  setPublicEventListeners() {
225
236
  this.scaHandler = (event) => {
@@ -231,6 +242,12 @@ class Hpp {
231
242
  };
232
243
  on(PublicEvent.SCA_SUCCESS, this.scaHandler);
233
244
  }
245
+ destroy() {
246
+ if (this.publicEventListenersBound && this.scaHandler) {
247
+ off(PublicEvent.SCA_SUCCESS, this.scaHandler);
248
+ this.publicEventListenersBound = false;
249
+ }
250
+ }
234
251
  createPurchase(extra = null) {
235
252
  const message = {
236
253
  channel: 'sca',
@@ -1,7 +1,11 @@
1
- type LogOptions = {
2
- endpoint?: string;
3
- mapArgs?: (args: unknown[]) => unknown;
1
+ export type LogOptions<A extends unknown[]> = {
2
+ endpoint?: string | ((...args: A) => string);
3
+ mapArgs?: (args: A) => unknown;
4
4
  enabled?: () => boolean;
5
5
  };
6
- export declare function instrumentFunction<A extends any[], R>(methodName: string, fn: (...args: A) => R, opts?: LogOptions): (...args: A) => R;
6
+ type LoggerConfig = {
7
+ baseUrl?: string;
8
+ };
9
+ export declare function configureLogger(config: LoggerConfig): void;
10
+ export declare function instrumentFunction<A extends unknown[], R>(methodName: string, fn: (...args: A) => R, opts?: LogOptions<A>): (...args: A) => R;
7
11
  export {};
@@ -1,7 +1,13 @@
1
1
  import axios from "axios";
2
2
  import { getLoggerUsername, getTransactionReference } from "./logger-context";
3
+ let loggerConfig = {};
4
+ export function configureLogger(config) {
5
+ loggerConfig = config;
6
+ }
3
7
  function defaultEndpoint() {
4
- return `${process.env.PAYNOW_BASE_URL}/log_sdk`;
8
+ return loggerConfig.baseUrl
9
+ ? `${loggerConfig.baseUrl}/log_sdk`
10
+ : `${process.env.PAYNOW_BASE_URL}/log_sdk`;
5
11
  }
6
12
  function sendLog(endpoint, payload) {
7
13
  try {
@@ -20,12 +26,15 @@ function sendLog(endpoint, payload) {
20
26
  }
21
27
  }
22
28
  export function instrumentFunction(methodName, fn, opts = {}) {
23
- var _a, _b, _c;
24
- const endpoint = (_a = opts.endpoint) !== null && _a !== void 0 ? _a : defaultEndpoint();
25
- const enabled = (_b = opts.enabled) !== null && _b !== void 0 ? _b : (() => true);
26
- const mapArgs = (_c = opts.mapArgs) !== null && _c !== void 0 ? _c : ((a) => a);
29
+ var _a, _b;
30
+ const enabled = (_a = opts.enabled) !== null && _a !== void 0 ? _a : (() => true);
31
+ const mapArgs = (_b = opts.mapArgs) !== null && _b !== void 0 ? _b : ((a) => a);
27
32
  return function instrumented(...args) {
33
+ var _a;
28
34
  if (enabled()) {
35
+ const endpoint = typeof opts.endpoint === "function"
36
+ ? opts.endpoint(...args)
37
+ : (_a = opts.endpoint) !== null && _a !== void 0 ? _a : defaultEndpoint();
29
38
  const payload = {
30
39
  username: getLoggerUsername(),
31
40
  reference: getTransactionReference(),
@@ -1,7 +1,7 @@
1
- type LogOptions = {
2
- endpoint?: string;
3
- mapArgs?: (args: unknown[]) => unknown;
1
+ type LogOptions<A extends unknown[]> = {
2
+ endpoint?: string | ((args: A) => string);
3
+ mapArgs?: (args: A) => unknown;
4
4
  enabled?: () => boolean;
5
5
  };
6
- export declare function logMethod(opts?: LogOptions): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
6
+ export declare function logMethod(opts?: LogOptions<unknown[]>): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
7
7
  export {};
@@ -35,6 +35,7 @@ export function logMethod(opts = {}) {
35
35
  const original = descriptor.value;
36
36
  if (typeof original !== "function") {
37
37
  console.error(`@logMethod can only decorate methods (${propertyKey})`);
38
+ return descriptor;
38
39
  }
39
40
  descriptor.value = instrumentFunction(propertyKey, original, Object.assign(Object.assign({}, opts), { mapArgs: (_a = opts.mapArgs) !== null && _a !== void 0 ? _a : ((args) => args.map((a) => {
40
41
  if (!a || typeof a !== "object")
package/dist/main.d.ts CHANGED
@@ -27,13 +27,22 @@ export default class FatZebra {
27
27
  private sca;
28
28
  private fzConfig;
29
29
  private gatewayClient;
30
+ private headless;
31
+ private threeDSecure;
32
+ private threeDSecureListenersBound;
33
+ private bridgeReady;
30
34
  constructor(config: FZConfig);
31
- tokenizeCard(card: Card, headless: HTMLIFrameElement): void;
32
- cardDidTokenize(headless: HTMLIFrameElement, callback: (data: any) => void): void;
35
+ tokenizeCard(card: Card): void;
36
+ cardDidTokenize(callback: (data: any) => void): void;
37
+ private requestThreeDSEnabled;
33
38
  verifyCard(params: VerifyCardParams): Promise<void>;
39
+ setThreeDSecureListeners(): void;
34
40
  renderPaymentsPage(params: HppLoadParams): void;
35
41
  renderApplePayButton(params: ApplePayParams): void;
36
42
  renderClickToPay(params: HppClickToPayParams): void;
43
+ reportThreeDSecureFetchedOnInit(data: any): void;
44
+ reportThreeDSecureFetched(data: any, paymentIntent?: PaymentIntent): void;
45
+ reportRequestThreedsEnabledTimeout(paymentIntent?: PaymentIntent): void;
37
46
  checkout(): void;
38
47
  on(event: PublicEvent, callback: (e: any) => void): void;
39
48
  off(event: PublicEvent, callback: (e: any) => void): void;
package/dist/main.js CHANGED
@@ -23,12 +23,14 @@ import { toHumanizedErrors, validateClickToPayLoadParams, validateHppLoadParams,
23
23
  import GatewayClient from './shared/api-gateway-client';
24
24
  import { Hpp } from './hpp';
25
25
  import ClickToPay from './click_to_pay';
26
- import { validateApplePayLoadParams } from "./validation/validators/apple-pay-load-params-button-validator";
27
- import { ApplePay } from "./applepay";
28
- import { setLoggerUsername } from "./logging/logger-context";
29
- import { logMethod } from "./logging/logMethod";
26
+ import { validateApplePayLoadParams } from './validation/validators/apple-pay-load-params-button-validator';
27
+ import { ApplePay } from './applepay';
28
+ import { setLoggerUsername } from './logging/logger-context';
29
+ import { logMethod } from './logging/logMethod';
30
+ import ThreeDSecure from './three_d_secure';
30
31
  export default class FatZebra {
31
32
  constructor(config) {
33
+ this.threeDSecureListenersBound = false;
32
34
  setLoggerUsername(config.username);
33
35
  this.fzConfig = config;
34
36
  window.MerchantUsername = config.username;
@@ -40,29 +42,57 @@ export default class FatZebra {
40
42
  gatewayClient: this.gatewayClient,
41
43
  });
42
44
  this.sca.loadScript();
45
+ const { el, isExisting } = bridge.load2(process.env.PAYNOW_BRIDGE_URL);
46
+ this.headless = el;
47
+ this.threeDSecure = new ThreeDSecure({
48
+ bridge: this.headless,
49
+ environment: process.env.API_ENV,
50
+ });
51
+ // bridgeReady is a single promise created once.
52
+ // After it resolves (bridge loaded), every subsequent "await this.bridgeReady"
53
+ // returns instantly — there's no re-evaluation.
54
+ // So for a SPA where the bridge is already loaded, there's zero overhead.
55
+ if (isExisting) {
56
+ console.log('headless exists');
57
+ this.bridgeReady = Promise.resolve();
58
+ this.setThreeDSecureListeners();
59
+ }
60
+ else {
61
+ this.bridgeReady = new Promise((resolve) => {
62
+ this.headless.addEventListener('load', () => {
63
+ console.log('headless loaded');
64
+ this.setThreeDSecureListeners();
65
+ resolve();
66
+ });
67
+ });
68
+ }
69
+ const message = {
70
+ channel: 'sca',
71
+ subject: BridgeEvent.READY,
72
+ data: {},
73
+ };
74
+ window.top.postMessage(message, '*');
43
75
  }
44
- tokenizeCard(card, headless) {
76
+ tokenizeCard(card) {
45
77
  const channel = 'sca';
46
- headless.onload = () => {
47
- const message = {
48
- channel,
49
- subject: BridgeEvent.TOKENIZE_CARD_REQUEST,
50
- data: {
51
- access_token: window.localStorage.getItem(LocalStorageAccessTokenKey),
52
- card_holder: card.holder,
53
- card_number: card.number,
54
- card_expiry: `${card.expiryMonth}/${card.expiryYear}`,
55
- cvv: card.cvv,
56
- }
57
- };
58
- headless.contentWindow.postMessage(message, '*');
78
+ const message = {
79
+ channel,
80
+ subject: BridgeEvent.TOKENIZE_CARD_REQUEST,
81
+ data: {
82
+ access_token: window.localStorage.getItem(LocalStorageAccessTokenKey),
83
+ card_holder: card.holder,
84
+ card_number: card.number,
85
+ card_expiry: `${card.expiryMonth}/${card.expiryYear}`,
86
+ cvv: card.cvv,
87
+ },
59
88
  };
89
+ this.headless.contentWindow.postMessage(message, '*');
60
90
  }
61
- cardDidTokenize(headless, callback) {
91
+ cardDidTokenize(callback) {
62
92
  const channel = 'sca';
63
93
  const postMessageClient = new PostMessageClient({
64
94
  channel,
65
- target: headless
95
+ target: this.headless,
66
96
  });
67
97
  const handlers = {};
68
98
  handlers[BridgeEvent.TOKENIZE_CARD_RESPONSE] = (data) => {
@@ -70,57 +100,154 @@ export default class FatZebra {
70
100
  };
71
101
  postMessageClient.setEventListeners(handlers);
72
102
  }
103
+ requestThreeDSEnabled(timeout, paymentIntent) {
104
+ return __awaiter(this, void 0, void 0, function* () {
105
+ console.log('requestThreeDSEnabled');
106
+ // If the bridge loads in time, bridgeReady wins and we can continue requesting the flag.
107
+ // If not, the timeout resolves the race and we fall through
108
+ // — the postMessage is sent to an unready bridge, the response never arrives,
109
+ // and the existing window.setTimeout at the bottom fires and resolves false.
110
+ // The total worst-case wait is still bounded by timeout.
111
+ yield Promise.race([
112
+ this.bridgeReady,
113
+ new Promise((resolve) => window.setTimeout(resolve, timeout)),
114
+ ]);
115
+ return new Promise((resolve) => {
116
+ const channel = 'sca';
117
+ const postMessageClient = new PostMessageClient({
118
+ channel,
119
+ target: this.headless,
120
+ });
121
+ let resolved = false;
122
+ postMessageClient.setEventListeners({
123
+ [BridgeEvent.THREE_D_SECURE_ENABLED]: (data) => {
124
+ const enabled = typeof data === 'boolean' ? data : false;
125
+ if (!resolved) {
126
+ this.reportThreeDSecureFetched(data, paymentIntent);
127
+ resolved = true;
128
+ resolve(enabled);
129
+ }
130
+ },
131
+ });
132
+ const message = {
133
+ channel,
134
+ subject: 'fzi.is-enabled-three-d-secure-req',
135
+ data: {
136
+ merchant: this.fzConfig.username,
137
+ access_token: window.localStorage.getItem(LocalStorageAccessTokenKey),
138
+ },
139
+ };
140
+ postMessageClient.send(message);
141
+ window.setTimeout(() => {
142
+ if (!resolved) {
143
+ this.reportRequestThreedsEnabledTimeout(paymentIntent);
144
+ resolved = true;
145
+ resolve(false);
146
+ }
147
+ }, timeout);
148
+ });
149
+ });
150
+ }
73
151
  verifyCard(params) {
74
152
  return __awaiter(this, void 0, void 0, function* () {
75
- var _a;
153
+ var _a, _b;
154
+ console.log('verifyCard');
76
155
  const valid = validateVerifyCardParams(params);
77
156
  if (!valid) {
78
157
  emit(PublicEvent.VALIDATION_ERROR, {
79
158
  errors: toHumanizedErrors(validateVerifyCardParams.errors),
80
- data: null
159
+ data: null,
81
160
  });
82
161
  return;
83
162
  }
163
+ const threeDSEnabled = yield this.requestThreeDSEnabled(1000, params.paymentIntent);
84
164
  switch (params.paymentMethod.type) {
85
- case PaymentMethodType.CARD:
86
- const headless = bridge.load(process.env.PAYNOW_BRIDGE_URL);
165
+ case PaymentMethodType.CARD: {
166
+ console.log('Verify card (new card)');
87
167
  const card = params.paymentMethod.data;
88
- this.cardDidTokenize(headless, (data) => __awaiter(this, void 0, void 0, function* () {
89
- var _a;
90
- const bin = card.number.substr(0, 6);
168
+ this.cardDidTokenize((data) => __awaiter(this, void 0, void 0, function* () {
169
+ var _a, _b;
170
+ if (threeDSEnabled) {
171
+ this.threeDSecure.run({
172
+ paymentIntent: params.paymentIntent,
173
+ cardToken: data.token,
174
+ merchantUsername: this.fzConfig.username,
175
+ challengeWindowSize: (_a = params.options) === null || _a === void 0 ? void 0 : _a.challengeWindowSize,
176
+ test: this.fzConfig.test,
177
+ tokenizeOnly: true,
178
+ });
179
+ }
180
+ else {
181
+ const bin = card.number.substr(0, 6);
182
+ this.sca.run({
183
+ cardToken: data.token,
184
+ customer: params.customer,
185
+ paymentIntent: params.paymentIntent,
186
+ bin,
187
+ challengeWindowSize: (_b = params.options) === null || _b === void 0 ? void 0 : _b.challengeWindowSize,
188
+ });
189
+ }
190
+ }));
191
+ this.tokenizeCard(card);
192
+ break;
193
+ }
194
+ case PaymentMethodType.CARD_ON_FILE: {
195
+ console.log('Verify card (card on file)');
196
+ const cardToken = params.paymentMethod.data.token;
197
+ if (threeDSEnabled) {
198
+ this.threeDSecure.run({
199
+ paymentIntent: params.paymentIntent,
200
+ cardToken,
201
+ merchantUsername: this.fzConfig.username,
202
+ challengeWindowSize: (_a = params.options) === null || _a === void 0 ? void 0 : _a.challengeWindowSize,
203
+ test: this.fzConfig.test,
204
+ tokenizeOnly: true,
205
+ });
206
+ }
207
+ else {
208
+ const bin = (yield this.gatewayClient.getCard({
209
+ card_token: cardToken,
210
+ })).data.bin;
91
211
  this.sca.run({
92
- cardToken: data.token,
212
+ cardToken,
93
213
  customer: params.customer,
94
214
  paymentIntent: params.paymentIntent,
95
215
  bin,
96
- challengeWindowSize: (_a = params.options) === null || _a === void 0 ? void 0 : _a.challengeWindowSize,
216
+ challengeWindowSize: (_b = params.options) === null || _b === void 0 ? void 0 : _b.challengeWindowSize,
97
217
  });
98
- }));
99
- this.tokenizeCard(card, headless);
100
- break;
101
- case PaymentMethodType.CARD_ON_FILE:
102
- const cardToken = params.paymentMethod.data.token;
103
- const bin = (yield this.gatewayClient.getCard({ card_token: cardToken })).data.bin;
104
- this.sca.run({
105
- cardToken,
106
- customer: params.customer,
107
- paymentIntent: params.paymentIntent,
108
- bin,
109
- challengeWindowSize: (_a = params.options) === null || _a === void 0 ? void 0 : _a.challengeWindowSize,
110
- });
218
+ }
111
219
  break;
220
+ }
112
221
  }
113
222
  });
114
223
  }
224
+ setThreeDSecureListeners() {
225
+ console.log('setThreeDSecureListeners');
226
+ if (this.threeDSecureListenersBound)
227
+ return;
228
+ const channel = 'sca';
229
+ const postMessageClient = new PostMessageClient({
230
+ channel,
231
+ target: this.headless,
232
+ });
233
+ const handlers = this.threeDSecure.messageHandlers();
234
+ postMessageClient.setEventListeners(handlers);
235
+ this.threeDSecureListenersBound = true;
236
+ }
115
237
  renderPaymentsPage(params) {
238
+ var _a;
239
+ console.log('renderPaymentsPage');
116
240
  const valid = validateHppLoadParams(params);
117
241
  if (!valid) {
118
242
  emit(PublicEvent.VALIDATION_ERROR, {
119
243
  errors: toHumanizedErrors(validateHppLoadParams.errors),
120
- data: null
244
+ data: null,
121
245
  });
122
246
  return;
123
247
  }
248
+ if ((_a = window.HPP) === null || _a === void 0 ? void 0 : _a.destroy) {
249
+ window.HPP.destroy();
250
+ }
124
251
  window.HPP = new Hpp({
125
252
  version: params.version,
126
253
  paymentIntent: params.paymentIntent,
@@ -128,6 +255,8 @@ export default class FatZebra {
128
255
  username: this.fzConfig.username,
129
256
  sca: this.sca,
130
257
  test: this.fzConfig.test,
258
+ threeDSecure: this.threeDSecure,
259
+ requestThreeDSEnabled: () => this.requestThreeDSEnabled(1000, params.paymentIntent),
131
260
  });
132
261
  window.HPP.load(params);
133
262
  }
@@ -136,19 +265,19 @@ export default class FatZebra {
136
265
  if (!valid) {
137
266
  emit(PublicEvent.VALIDATION_ERROR, {
138
267
  errors: toHumanizedErrors(validateApplePayLoadParams.errors),
139
- data: null
268
+ data: null,
140
269
  });
141
270
  return;
142
271
  }
143
272
  window.ApplePayButton = new ApplePay({
144
273
  environment: params.environment,
145
274
  paymentIntent: params.paymentIntent,
146
- username: this.fzConfig.username
275
+ username: this.fzConfig.username,
147
276
  });
148
277
  window.ApplePayButton.load({
149
278
  containerId: params.containerId,
150
279
  paymentIntent: params.paymentIntent,
151
- options: params.options
280
+ options: params.options,
152
281
  });
153
282
  }
154
283
  renderClickToPay(params) {
@@ -156,7 +285,7 @@ export default class FatZebra {
156
285
  if (!valid) {
157
286
  emit(PublicEvent.VALIDATION_ERROR, {
158
287
  errors: toHumanizedErrors(validateClickToPayLoadParams.errors),
159
- data: null
288
+ data: null,
160
289
  });
161
290
  return;
162
291
  }
@@ -167,6 +296,15 @@ export default class FatZebra {
167
296
  });
168
297
  window.HPP.load(params);
169
298
  }
299
+ reportThreeDSecureFetchedOnInit(data) {
300
+ console.log("three_d_secure fetched on init", data);
301
+ }
302
+ reportThreeDSecureFetched(data, paymentIntent) {
303
+ console.log("three_d_secure fetched", data);
304
+ }
305
+ reportRequestThreedsEnabledTimeout(paymentIntent) {
306
+ console.log("3DS enabled check timed out");
307
+ }
170
308
  checkout() {
171
309
  window.HPP.purchase();
172
310
  }
@@ -180,6 +318,18 @@ export default class FatZebra {
180
318
  onOnce(event, callback);
181
319
  }
182
320
  }
321
+ __decorate([
322
+ logMethod({
323
+ mapArgs: (args) => {
324
+ const params = args[0];
325
+ return [
326
+ {
327
+ payment_intent: params === null || params === void 0 ? void 0 : params.paymentIntent
328
+ },
329
+ ];
330
+ },
331
+ })
332
+ ], FatZebra.prototype, "requestThreeDSEnabled", null);
183
333
  __decorate([
184
334
  logMethod({
185
335
  mapArgs: (args) => {
@@ -195,4 +345,53 @@ __decorate([
195
345
  },
196
346
  })
197
347
  ], FatZebra.prototype, "verifyCard", null);
348
+ __decorate([
349
+ logMethod({
350
+ mapArgs: (args) => {
351
+ const params = args[0];
352
+ return [
353
+ {
354
+ payment_intent: params.paymentIntent,
355
+ version: params.version
356
+ },
357
+ ];
358
+ },
359
+ })
360
+ ], FatZebra.prototype, "renderPaymentsPage", null);
361
+ __decorate([
362
+ logMethod({
363
+ mapArgs: (args) => {
364
+ const data = args[0];
365
+ return [
366
+ {
367
+ data
368
+ },
369
+ ];
370
+ },
371
+ })
372
+ ], FatZebra.prototype, "reportThreeDSecureFetchedOnInit", null);
373
+ __decorate([
374
+ logMethod({
375
+ mapArgs: (args) => {
376
+ const data = args[0];
377
+ return [
378
+ {
379
+ data
380
+ },
381
+ ];
382
+ },
383
+ })
384
+ ], FatZebra.prototype, "reportThreeDSecureFetched", null);
385
+ __decorate([
386
+ logMethod({
387
+ mapArgs: (args) => {
388
+ const data = args[0];
389
+ return [
390
+ {
391
+ data
392
+ },
393
+ ];
394
+ },
395
+ })
396
+ ], FatZebra.prototype, "reportRequestThreedsEnabledTimeout", null);
198
397
  export { FatZebra, };
@@ -5,8 +5,12 @@ import Sca from "../sca";
5
5
  import GatewayClient from "../shared/api-gateway-client";
6
6
  import { generateVerifyURL } from "./verifyUrl";
7
7
  import useMessage from "./useMessage";
8
- import { instrumentFunction } from "../logging/instrument";
9
- const logUseFatZebra = instrumentFunction("useFatZebra", (meta) => meta, { mapArgs: ([meta]) => [meta] });
8
+ import { configureLogger, instrumentFunction } from "../logging/instrument";
9
+ import env from "../shared/env";
10
+ const logUseFatZebra = instrumentFunction("useFatZebra", (meta) => meta, {
11
+ mapArgs: ([meta]) => meta,
12
+ endpoint: (meta) => `${env[meta.environment].payNowUrl}/log_sdk`,
13
+ });
10
14
  const useFatZebra = ({ config, handlers, cardToken }) => {
11
15
  const { options, accessToken, paymentIntent, username } = config;
12
16
  const sca = useMemo(() => {
@@ -25,6 +29,9 @@ const useFatZebra = ({ config, handlers, cardToken }) => {
25
29
  config
26
30
  });
27
31
  useEffect(() => {
32
+ configureLogger({
33
+ baseUrl: env[config.environment].payNowUrl
34
+ });
28
35
  logUseFatZebra({
29
36
  environment: config.environment,
30
37
  reference: config.paymentIntent.payment.reference,
@@ -40,11 +40,14 @@ const useMessage = ({ paymentIntent, options, handlers, sca, config }) => {
40
40
  });
41
41
  }
42
42
  if (options && options.sca_enabled && threeDSecureEnabled) {
43
+ const iframe = document.querySelector('[name="renderIframe"]');
43
44
  threeDSecureRef.current.run({
44
45
  paymentIntent,
45
46
  cardToken: data.token,
46
47
  merchantUsername: config.username,
47
- test: config.environment !== Environment.production
48
+ test: config.environment !== Environment.production,
49
+ tokenizeOnly: config.options.tokenize_only,
50
+ iframe: iframe
48
51
  });
49
52
  return;
50
53
  }
@@ -66,13 +69,11 @@ const useMessage = ({ paymentIntent, options, handlers, sca, config }) => {
66
69
  const headless = el;
67
70
  let onMessage;
68
71
  headless.onload = () => {
69
- const iframe = document.querySelector('[name="renderIframe"]');
70
72
  const threeDS = new ThreeDSecure({
71
73
  successCallback,
72
74
  failureCallback,
73
75
  bridge: headless,
74
76
  environment: config.environment,
75
- iframe: iframe
76
77
  });
77
78
  threeDSecureRef.current = threeDS;
78
79
  const messages = threeDS.messageHandlers(); // { [subject]: (payload) => void }
package/dist/sca/index.js CHANGED
@@ -58,6 +58,7 @@ class Sca {
58
58
  }
59
59
  run(config) {
60
60
  return __awaiter(this, void 0, void 0, function* () {
61
+ console.log('Running 3DS (SCA)');
61
62
  try {
62
63
  if (!this._cardinal) {
63
64
  this._cardinal = new CardinalManager();
@@ -1,7 +1,7 @@
1
- declare const bridgeUrl = "https://paynow.test/sdk/bridge";
1
+ declare const bridgeUrl = "http://localhost:3006/sdk/bridge";
2
2
  declare const apiUrl = "https://api.test/sdk";
3
3
  declare const payNowUrl = "https://paynow.test";
4
4
  declare const songbirdUrl = "https://songbirdstag.cardinalcommerce.com/edge/v1/songbird.js";
5
5
  declare const cybersourceUrl = "https://centinelapistag.cardinalcommerce.com";
6
- declare const returnCybersourceUrl = "https://paynow.test/return";
6
+ declare const returnCybersourceUrl = "https://48e5-115-130-65-211.ngrok-free.app/return";
7
7
  export { bridgeUrl, apiUrl, payNowUrl, songbirdUrl, cybersourceUrl, returnCybersourceUrl };
@@ -1,7 +1,7 @@
1
- const bridgeUrl = "https://paynow.test/sdk/bridge";
1
+ const bridgeUrl = "http://localhost:3006/sdk/bridge";
2
2
  const apiUrl = "https://api.test/sdk";
3
3
  const payNowUrl = "https://paynow.test";
4
4
  const songbirdUrl = "https://songbirdstag.cardinalcommerce.com/edge/v1/songbird.js";
5
5
  const cybersourceUrl = "https://centinelapistag.cardinalcommerce.com";
6
- const returnCybersourceUrl = "https://paynow.test/return";
6
+ const returnCybersourceUrl = "https://48e5-115-130-65-211.ngrok-free.app/return";
7
7
  export { bridgeUrl, apiUrl, payNowUrl, songbirdUrl, cybersourceUrl, returnCybersourceUrl };
@@ -20,14 +20,17 @@ declare class ThreeDSecure {
20
20
  private challengeWindowSize;
21
21
  private successCallback;
22
22
  private failureCallback;
23
+ private tokenizeOnly;
23
24
  private iframe?;
24
- constructor({ successCallback, failureCallback, environment, bridge, iframe }: ThreeDSecureConfig);
25
- run({ paymentIntent, cardToken, merchantUsername, challengeWindowSize, test }: {
25
+ constructor({ successCallback, failureCallback, environment, bridge }: ThreeDSecureConfig);
26
+ run({ paymentIntent, cardToken, merchantUsername, challengeWindowSize, test, tokenizeOnly, iframe }: {
26
27
  paymentIntent: PaymentIntent;
27
28
  cardToken: string;
28
29
  merchantUsername: string;
29
30
  challengeWindowSize?: ChallengeWindowSize;
30
31
  test: boolean;
32
+ tokenizeOnly?: boolean;
33
+ iframe?: HTMLIFrameElement;
31
34
  }): void;
32
35
  setupResponse(data: DeviceDataCollectionMessage): void;
33
36
  messageHandlers(): {
@@ -41,6 +44,7 @@ declare class ThreeDSecure {
41
44
  private validateAuthentication;
42
45
  private validationAuthenticationResponse;
43
46
  private createPurchase;
47
+ private listenDeviceCollectionReady;
44
48
  private setLoading;
45
49
  }
46
50
  export default ThreeDSecure;
@@ -18,15 +18,19 @@ import { getThreeDSecureValidationResult } from "./utility/getValidationResult";
18
18
  import { ChallengeWindowSize } from "../sca/types";
19
19
  import * as util from "../shared/util";
20
20
  class ThreeDSecure {
21
- constructor({ successCallback, failureCallback, environment, bridge, iframe }) {
21
+ constructor({ successCallback, failureCallback, environment, bridge }) {
22
+ this.tokenizeOnly = false;
22
23
  this.headlessBridge = bridge;
23
24
  this.environment = environment;
24
25
  this.successCallback = successCallback;
25
26
  this.failureCallback = failureCallback;
26
- this.iframe = iframe;
27
27
  DeviceDataCollection.listenDataCollectionResponse(environment);
28
+ this.listenDeviceCollectionReady();
28
29
  }
29
- run({ paymentIntent, cardToken, merchantUsername, challengeWindowSize, test }) {
30
+ run({ paymentIntent, cardToken, merchantUsername, challengeWindowSize, test, tokenizeOnly, iframe }) {
31
+ console.log('Running 3DS (ThreeDSecure)');
32
+ this.iframe = iframe;
33
+ this.tokenizeOnly = tokenizeOnly;
30
34
  this.setLoading();
31
35
  this.paymentIntent = paymentIntent;
32
36
  this.test = test;
@@ -45,12 +49,6 @@ class ThreeDSecure {
45
49
  handlers[BridgeEvent.SETUP_THREE_D_SECURE_SUCCESS_RESPONSE] = (data) => {
46
50
  this.setupResponse(data);
47
51
  };
48
- on(PublicEvent.DEVICE_PROFILE_READY_THREE_D_SECURE_EVENT, () => this.checkEnrollment({
49
- paymentIntent: this.paymentIntent,
50
- cardToken: this.cardToken,
51
- merchantUsername: this.merchantUsername,
52
- cybersourceReferenceId: this.cybersourceReferenceId,
53
- }));
54
52
  handlers[BridgeEvent.ENROL_THREE_D_SECURE_RESPONSE] = (data) => {
55
53
  this.enrolmentResponse(data);
56
54
  };
@@ -61,7 +59,7 @@ class ThreeDSecure {
61
59
  this.validationAuthenticationResponse(data);
62
60
  };
63
61
  handlers[BridgeEvent.THREE_D_SECURE_FAILURE_RESPONSE] = (data) => {
64
- emit(PublicEvent.SCA_ERROR, { errors: [data.errors], data: data });
62
+ emit(PublicEvent.SCA_ERROR, { errors: [data.errors], data });
65
63
  };
66
64
  return handlers;
67
65
  }
@@ -123,6 +121,8 @@ class ThreeDSecure {
123
121
  }
124
122
  if (scenario.outcome.success) {
125
123
  this.reportSuccess(`FatZebra.3DS: 3DS success - ${scenario.description}.`, threeDSecureData);
124
+ if (this.tokenizeOnly)
125
+ return; // DO NO process purchase if tokenizing only.
126
126
  const extra = util.toObjectWithSnakeCaseKeys(threeDSecureData);
127
127
  this.createPurchase(extra);
128
128
  return;
@@ -149,6 +149,8 @@ class ThreeDSecure {
149
149
  const extra = util.toObjectWithSnakeCaseKeys(threeDSecureData);
150
150
  if (scenario.outcome.success) {
151
151
  this.reportSuccess(`FatZebra.3DS: 3DS success - ${scenario.description}.`, threeDSecureData);
152
+ if (this.tokenizeOnly)
153
+ return; // DO NO process purchase if tokenizing only.
152
154
  this.createPurchase(extra);
153
155
  }
154
156
  else {
@@ -176,6 +178,14 @@ class ThreeDSecure {
176
178
  message.data.extra = Object.assign(Object.assign({}, extra), { sli: toFzSli(extra.eci) });
177
179
  this.headlessBridge.contentWindow.postMessage(message, '*');
178
180
  }
181
+ listenDeviceCollectionReady() {
182
+ on(PublicEvent.DEVICE_PROFILE_READY_THREE_D_SECURE_EVENT, () => this.checkEnrollment({
183
+ paymentIntent: this.paymentIntent,
184
+ cardToken: this.cardToken,
185
+ merchantUsername: this.merchantUsername,
186
+ cybersourceReferenceId: this.cybersourceReferenceId,
187
+ }));
188
+ }
179
189
  setLoading(loading = true) {
180
190
  var _a;
181
191
  if ((_a = this === null || this === void 0 ? void 0 : this.iframe) === null || _a === void 0 ? void 0 : _a.contentWindow) {
@@ -12,8 +12,8 @@ export default class DeviceDataCollection {
12
12
  private static readonly INPUT_ID;
13
13
  createIframe(): void;
14
14
  static listenDataCollectionResponse(environment: Environment): void;
15
- private static hasEmittedProfileReady;
16
15
  private static handleDataCollectionResponse;
16
+ private static handleCollectionResponse;
17
17
  static collectDeviceData(): DeviceDataType;
18
18
  static setIframeUrl(url: string, jwt: string): void;
19
19
  static getForm(): HTMLFormElement | null;
@@ -1,6 +1,13 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
1
7
  import { PublicEvent } from "../../shared/types";
2
8
  import { emit } from "../../shared/event-manager";
3
9
  import env from "../../shared/env";
10
+ import { logMethod } from "../../logging/logMethod";
4
11
  class DeviceDataCollection {
5
12
  // Create elements if they don't already exist
6
13
  createIframe() {
@@ -43,17 +50,17 @@ class DeviceDataCollection {
43
50
  // https://developer.cybersource.com/docs/cybs/en-us/payer-authentication/developer/all/so/payer-auth/pa2-ccdc-ddc-intro.html#reference_qmk_jrl_xpb
44
51
  if (event.origin !== env[environment].cybersourceUrl)
45
52
  return;
46
- if (this.hasEmittedProfileReady)
47
- return;
53
+ this.handleCollectionResponse(event);
54
+ }
55
+ static handleCollectionResponse(event) {
48
56
  const response = JSON.parse(event.data);
49
57
  if (response["MessageType"] == "profile.completed") {
50
58
  emit(PublicEvent.DEVICE_PROFILE_READY_THREE_D_SECURE_EVENT, { message: null, data: null });
51
59
  }
52
- // Still proceed, even if status is false (as per recommendations from cybersource support team.
53
- if (response["Status"] === false) {
60
+ else if (response["Status"] == false) {
61
+ // Still proceed, even if status is false (as per recommendations from cybersource support team.
54
62
  emit(PublicEvent.DEVICE_PROFILE_READY_THREE_D_SECURE_EVENT, { message: null, data: null });
55
63
  }
56
- this.hasEmittedProfileReady = true;
57
64
  }
58
65
  static collectDeviceData() {
59
66
  var _a, _b, _c, _d;
@@ -115,5 +122,12 @@ DeviceDataCollection.IFRAME_ID = "cardinal_collection_iframe";
115
122
  DeviceDataCollection.IFRAME_NAME = "collectionIframe";
116
123
  DeviceDataCollection.FORM_ID = "cardinal_collection_form";
117
124
  DeviceDataCollection.INPUT_ID = "cardinal_collection_form_input";
118
- DeviceDataCollection.hasEmittedProfileReady = false;
119
125
  export default DeviceDataCollection;
126
+ __decorate([
127
+ logMethod({
128
+ mapArgs: (args) => {
129
+ const params = args[0]; // grab the first argument.
130
+ return JSON.parse(params.data);
131
+ },
132
+ })
133
+ ], DeviceDataCollection, "handleCollectionResponse", null);
@@ -66,11 +66,6 @@ class ThreeDSecureChallengeWindow {
66
66
  });
67
67
  content.appendChild(frameWrap);
68
68
  overlay.appendChild(content);
69
- // Backdrop click to close (optional)
70
- overlay.addEventListener("click", (e) => {
71
- if (e.target === overlay)
72
- ThreeDSecureChallengeWindow.hide();
73
- });
74
69
  document.body.appendChild(overlay);
75
70
  return { overlay, content, frameWrap };
76
71
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fat-zebra/sdk",
3
- "version": "2.0.1-beta.1",
3
+ "version": "2.0.1-beta.10",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -63,7 +63,7 @@
63
63
  "ajv": "^8.17.1",
64
64
  "ajv-formats": "^3.0.1",
65
65
  "ajv-keywords": "^5.1.0",
66
- "axios": "1.15.0",
66
+ "axios": "1.16.0",
67
67
  "custom-event-polyfill": "^1.0.7",
68
68
  "glob": "^13.0.6",
69
69
  "ts-polyfill": "^3.8.2",