@fat-zebra/sdk 2.0.8-beta.3 → 2.1.0-beta.3

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/CHANGELOG.md CHANGED
@@ -5,103 +5,142 @@ All notable changes to the Fat Zebra JS SDK will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.1.0] - 2026-06-02
9
+
10
+ > **Upgrade recommended.** This release fixes a critical issue where the React SDK could silently fall back to the legacy 3DS API, which is being decommissioned. Merchants should upgrade to avoid potential production failures.
11
+
12
+ ### Changed
13
+
14
+ - Removed feature flag to conditionally enable the new ThreeDSecure flow. The ThreeDSecure flow is now always used for authenticating 3DS transactions.
15
+
16
+ ### Fixed
17
+
18
+ - Fixed the ThreeDSecure flow failing silently when `accessToken` is supplied via config rather than stored in `localStorage`
19
+ - Fixed an issue where 3DS processing could silently fall back to the legacy 3DS API, which would cause failures once the legacy API is decommissioned
20
+
8
21
  ## [2.0.6] - 2026-05-27
9
22
 
10
23
  ### Added
24
+
11
25
  - Improved reliability of the ThreeDSecure flow to prevent duplicate processing when a payment is retried or initiated more than once
12
26
 
13
27
  ### Changed
28
+
14
29
  - React SDK ThreeDSecure flow brought to parity with the core SDK
15
30
 
16
31
  ## [2.0.5] - 2026-05-21
17
32
 
18
33
  ### Fixed
34
+
19
35
  - Fixed an issue where the device profiling step in the ThreeDSecure flow could trigger card enrollment multiple times if the device profile event fired more than once
20
36
 
21
37
  ## [2.0.4] - 2026-05-20
22
38
 
23
39
  ### Fixed
40
+
24
41
  - Fixed an issue where triggering the ThreeDSecure flow multiple times (e.g. in a single-page application) could register duplicate event listeners, leading to multiple enrollment attempts
25
42
 
26
43
  ## [2.0.3] - 2026-05-19
27
44
 
28
45
  ### Fixed
46
+
29
47
  - Fixed an issue in SPA environments where the Cardinal SCA `payments.validated` handler could accumulate across payment flows, causing stale callbacks to fire on subsequent transactions
30
48
 
31
49
  ## [2.0.2] - 2026-05-14
32
50
 
33
51
  ### Changed
52
+
34
53
  - Default ThreeDSecure challenge window size changed from full-page to 500×600px
35
54
 
36
55
  ### Fixed
56
+
37
57
  - ThreeDSecure challenge window iframe errors are now correctly reported rather than silently ignored
38
58
 
39
59
  ## [2.0.1] - 2026-05-11
40
60
 
41
61
  ### Fixed
62
+
42
63
  - Resolved an issue causing delayed rendering of the payment form in `renderPaymentsPage`
43
64
  - Payment source is now correctly included in SDK tokenisation request events
44
65
 
45
66
  ### Security
67
+
46
68
  - Bumped axios from 1.13.5 to 1.16.0 to address known vulnerabilities
47
69
 
48
70
  ## [2.0.0] - 2026-04-02
49
71
 
50
72
  ### Changed
73
+
51
74
  - **Upgraded 3DS2 authentication flow** — replaced the previous CyberSource Cardinal/Songbird library with a new SDK-native implementation backed directly by Fat Zebra's infrastructure. The new flow supports a configurable challenge window (250×400, 390×400, 500×600, 600×400, or full-page) and handles device fingerprinting internally. No changes are required to existing integrations; the correct flow is selected automatically.
52
75
 
53
76
  ### Security
77
+
54
78
  - Bumped handlebars from 4.7.8 to 4.7.9 to address known vulnerabilities
55
79
  - Bumped axios to 1.15.0
56
80
 
57
81
  ## [1.5.14] - 2026-03-10
58
82
 
59
83
  ### Security
84
+
60
85
  - Addressed known vulnerabilities in transitive dependencies (axios, minimatch, glob)
61
86
 
62
87
  ## [1.5.12] - 2025-10-14
88
+
63
89
  ## Updated
90
+
64
91
  - Bump to react-dom and react 19 from react 18
65
- - Bump husky from 8.0.3 to 9.1.7
92
+ - Bump husky from 8.0.3 to 9.1.7
66
93
  - Bump jest-environment-jsdom from 29.6.1 to 30.2.0
67
- - Bump nock from 12.0.3 to 13.5.6
94
+ - Bump nock from 12.0.3 to 13.5.6
68
95
  - Bump brace-expansion from 1.1.11 to 1.1.12
69
96
  - Bump @testing-library/user-event from 14.5.2 to 14.6.1
70
97
 
71
98
  ## Improved
99
+
72
100
  - Replace webpack with esbuild
73
101
 
74
102
  ## [1.5.11] - 2025-09-25
103
+
75
104
  ## Updated
105
+
76
106
  - Bump ts-loader from 9.4.4 to 9.5.4
107
+
77
108
  ## Improved
109
+
78
110
  - added method decorator logging
79
111
 
80
112
  ## [1.5.10] - 2025-08-11
113
+
81
114
  ## Updated
115
+
82
116
  - bumped jsdom from ^16.2.1 to jsdom 26.1.0
83
117
  - bumped axios from ^1.6.4 to 1.11.0
84
118
 
85
119
  ## [1.5.9] - 2025-07-09
120
+
86
121
  ## Changed
122
+
87
123
  - VerifyExistingCard component will now always perform 3DS action. Previously, the VerifyExistingCard would only check if the card had been previously tokenized, unless sca_enabled: true was passed as an option. This update will run SCA regardless.
88
124
 
89
125
  ## [1.5.8] – 2025-05-27
126
+
90
127
  ### Changed
128
+
91
129
  - removed yarn.lock in favour of package-lock.json
92
130
 
93
131
  ## [1.5.7] – 2025-03-31
94
132
 
95
133
  ### Changed
134
+
96
135
  Support for 3-D Secure 2.x PARes Status: CHALLENGED
97
136
 
98
137
  - Introduced a new PARes.CHALLENGED enumeration value to represent a "challenge" status in 3-D Secure (3DS) 2.x authentication flows. This is a required change from Cybersouce
99
138
  as it will now explicitly handle challenged events. For more information: https://developer.cybersource.com/library/documentation/dev_guides/Payer_Authentication_SCMP_API/html/Topics/Response_FieldsTable23Response_Fields_Field_NameDescriptionReturned_ByData_Type_Lengthauthorizatio-6wc.htm
100
139
 
101
-
102
140
  ### Fixed
103
- - Pass through economic commerce indicator: https://developer.cybersource.com/docs/cybs/en-us/payments/developer/vital/sa/payments/payments-processing-pa-process-intro/payments-processing-pa-eci.html
104
141
 
142
+ - Pass through economic commerce indicator: https://developer.cybersource.com/docs/cybs/en-us/payments/developer/vital/sa/payments/payments-processing-pa-process-intro/payments-processing-pa-eci.html
105
143
 
106
144
  ### Security
145
+
107
146
  - Patched routine package updates
package/README.md CHANGED
@@ -7,4 +7,5 @@ A modern JavaScript SDK for integrating with [Fat Zebra](https://docs.fatzebra.c
7
7
  ```bash
8
8
  npm install @fat-zebra/sdk
9
9
  # or
10
- yarn add @fat-zebra/sdk
10
+ yarn add @fat-zebra/sdk
11
+ ```
@@ -1,5 +1,5 @@
1
1
  import { Environment } from "../shared/env";
2
- import type { TokenizeCardResponse, PaymentIntent } from "../shared/types";
2
+ import type { TokenizeCardResponse } from "../shared/types";
3
3
  type UseFatZebraLogMeta = {
4
4
  environment: Environment;
5
5
  reference: string;
@@ -8,6 +8,4 @@ type UseFatZebraLogMeta = {
8
8
  };
9
9
  declare const logUseFatZebra: (args_0: UseFatZebraLogMeta) => UseFatZebraLogMeta;
10
10
  declare const logReportTokenizeCardResponse: (args_0: TokenizeCardResponse) => void;
11
- declare const logReportThreeDSecureFetched: (data: boolean, paymentIntent?: PaymentIntent) => void;
12
- declare const logReportRequestThreedsEnabledTimeout: (paymentIntent?: PaymentIntent) => void;
13
- export { logUseFatZebra, logReportTokenizeCardResponse, logReportThreeDSecureFetched, logReportRequestThreedsEnabledTimeout, };
11
+ export { logUseFatZebra, logReportTokenizeCardResponse, };
@@ -18,19 +18,4 @@ const logReportTokenizeCardResponse = instrumentFunction("reportTokenizeCardResp
18
18
  mapArgs: ([data]) => ({ token: data.token, bin: data.bin }),
19
19
  className: "useMessage",
20
20
  });
21
- const logReportThreeDSecureFetched = instrumentFunction("reportThreeDSecureFetched", (data) => {
22
- console.log("three_d_secure fetched", data);
23
- }, {
24
- mapArgs: ([data, paymentIntent]) => ({
25
- data,
26
- payment_intent: paymentIntent,
27
- }),
28
- className: "useMessage",
29
- });
30
- const logReportRequestThreedsEnabledTimeout = instrumentFunction("reportRequestThreedsEnabledTimeout", () => {
31
- console.log("3DS enabled check timed out");
32
- }, {
33
- mapArgs: ([paymentIntent]) => ({ payment_intent: paymentIntent }),
34
- className: "useMessage",
35
- });
36
- export { logUseFatZebra, logReportTokenizeCardResponse, logReportThreeDSecureFetched, logReportRequestThreedsEnabledTimeout, };
21
+ export { logUseFatZebra, logReportTokenizeCardResponse, };
@@ -1,8 +1,5 @@
1
- import { useEffect, useMemo } from "react";
2
- import * as FatZebra from "../shared/types";
1
+ import { useEffect } from "react";
3
2
  import { generatePaymentURL } from "./paymentUrl";
4
- import Sca from "../sca";
5
- import GatewayClient from "../shared/api-gateway-client";
6
3
  import { generateVerifyURL } from "./verifyUrl";
7
4
  import useMessage from "./useMessage";
8
5
  import { configureLogger } from "../logging/instrument";
@@ -10,19 +7,10 @@ import env from "../shared/env";
10
7
  import { logUseFatZebra } from "./logging";
11
8
  const useFatZebra = ({ config, handlers, cardToken }) => {
12
9
  const { options, accessToken, paymentIntent, username } = config;
13
- const sca = useMemo(() => {
14
- const gatewayClient = new GatewayClient({ accessToken, username, environment: config.environment });
15
- const successCallback = handlers[FatZebra.PublicEvent.SCA_SUCCESS];
16
- const failureCallback = handlers[FatZebra.PublicEvent.SCA_ERROR];
17
- const scaObj = new Sca({ gatewayClient, successCallback, failureCallback, environment: config.environment });
18
- scaObj.loadScript();
19
- return scaObj;
20
- }, [config, username]);
21
10
  useMessage({
22
11
  paymentIntent,
23
12
  options,
24
13
  handlers,
25
- sca,
26
14
  config
27
15
  });
28
16
  useEffect(() => {
@@ -1,11 +1,9 @@
1
1
  import * as FatZebra from "../shared/types";
2
- import Sca from "../sca";
3
2
  type UseMessageProps = {
4
3
  paymentIntent: FatZebra.PaymentIntent;
5
4
  handlers: FatZebra.Handlers;
6
5
  options?: FatZebra.OptionalUrlValues;
7
- sca?: InstanceType<typeof Sca>;
8
6
  config?: FatZebra.PaymentConfig;
9
7
  };
10
- declare const useMessage: ({ paymentIntent, options, handlers, sca, config }: UseMessageProps) => void;
8
+ declare const useMessage: ({ paymentIntent, options, handlers, config }: UseMessageProps) => void;
11
9
  export default useMessage;
@@ -15,13 +15,10 @@ import env, { Environment } from "../shared/env";
15
15
  import { PostMessageClient } from "../shared/post-message-client";
16
16
  import { BridgeEvent } from "../shared/types";
17
17
  import { setFlowId } from '../logging/logger-context';
18
- import { LocalStorageAccessTokenKey } from '../shared/constants';
19
- import { logReportRequestThreedsEnabledTimeout, logReportThreeDSecureFetched, logReportTokenizeCardResponse } from './logging';
20
- const useMessage = ({ paymentIntent, options, handlers, sca, config }) => {
21
- const isThreeDSecureEnabled = useRef(false);
18
+ import { logReportTokenizeCardResponse } from './logging';
19
+ const useMessage = ({ paymentIntent, options, handlers, config }) => {
22
20
  const threeDSecureRef = useRef(null);
23
21
  const headlessRef = useRef(null);
24
- const initAuthClientRef = useRef(null);
25
22
  const listenersClientRef = useRef(null);
26
23
  // defaults to a resolved promise so handleTokenizeCardResponse doesn't hang if initAuth never runs (no config)
27
24
  const authReadyRef = useRef(Promise.resolve());
@@ -56,7 +53,7 @@ const useMessage = ({ paymentIntent, options, handlers, sca, config }) => {
56
53
  });
57
54
  if (options && options.sca_enabled) {
58
55
  yield authReadyRef.current;
59
- if (threeDSecureRef.current && isThreeDSecureEnabled.current && config) {
56
+ if (threeDSecureRef.current) {
60
57
  const iframe = document.querySelector('[name="renderIframe"]');
61
58
  const flowId = crypto.randomUUID();
62
59
  setFlowId(flowId);
@@ -70,9 +67,6 @@ const useMessage = ({ paymentIntent, options, handlers, sca, config }) => {
70
67
  flowId
71
68
  });
72
69
  }
73
- else if (sca) {
74
- yield sca.run({ paymentIntent, bin: data.bin, cardToken: data.token });
75
- }
76
70
  }
77
71
  });
78
72
  const messageHandler = (event) => __awaiter(void 0, void 0, void 0, function* () {
@@ -107,58 +101,21 @@ const useMessage = ({ paymentIntent, options, handlers, sca, config }) => {
107
101
  const { el, isExisting } = bridge.load2(env[config.environment].bridgeUrl);
108
102
  headlessRef.current = el;
109
103
  const initAuth = () => {
110
- const postMessageClient = new PostMessageClient({
104
+ const threeDS = new ThreeDSecure({
105
+ successCallback,
106
+ failureCallback,
107
+ bridge: el,
108
+ environment: config.environment,
109
+ accessToken: config.accessToken,
110
+ });
111
+ threeDSecureRef.current = threeDS;
112
+ const listenersClient = new PostMessageClient({
111
113
  channel: 'sca',
112
114
  target: el,
113
115
  environment: config.environment,
114
116
  });
115
- initAuthClientRef.current = postMessageClient;
116
- let resolved = false;
117
- let resolveAuthReady;
118
- authReadyRef.current = new Promise((resolve) => { resolveAuthReady = resolve; });
119
- postMessageClient.setEventListeners({
120
- [BridgeEvent.THREE_D_SECURE_ENABLED]: (data) => {
121
- const enabled = typeof data === 'boolean' ? data : false;
122
- if (!resolved) {
123
- logReportThreeDSecureFetched(enabled, paymentIntent);
124
- resolved = true;
125
- isThreeDSecureEnabled.current = enabled;
126
- resolveAuthReady();
127
- if (enabled) {
128
- const threeDS = new ThreeDSecure({
129
- successCallback,
130
- failureCallback,
131
- bridge: el,
132
- environment: config.environment,
133
- });
134
- threeDSecureRef.current = threeDS;
135
- const listenersClient = new PostMessageClient({
136
- channel: 'sca',
137
- target: el,
138
- environment: config.environment,
139
- });
140
- listenersClient.setEventListeners(threeDS.messageHandlers());
141
- listenersClientRef.current = listenersClient;
142
- }
143
- }
144
- },
145
- }, { once: true });
146
- const message = {
147
- channel: 'sca',
148
- subject: 'fzi.is-enabled-three-d-secure-req',
149
- data: {
150
- merchant: config.username,
151
- access_token: window.localStorage.getItem(LocalStorageAccessTokenKey),
152
- },
153
- };
154
- postMessageClient.send(message);
155
- window.setTimeout(() => {
156
- if (!resolved) {
157
- logReportRequestThreedsEnabledTimeout(paymentIntent);
158
- resolved = true;
159
- resolveAuthReady();
160
- }
161
- }, 500);
117
+ listenersClient.setEventListeners(threeDS.messageHandlers());
118
+ listenersClientRef.current = listenersClient;
162
119
  };
163
120
  if (isExisting) {
164
121
  initAuth();
@@ -179,10 +136,9 @@ const useMessage = ({ paymentIntent, options, handlers, sca, config }) => {
179
136
  };
180
137
  }
181
138
  return () => {
182
- var _a, _b;
139
+ var _a;
183
140
  el.removeEventListener('load', initAuth);
184
- (_a = initAuthClientRef.current) === null || _a === void 0 ? void 0 : _a.removeEventListeners();
185
- (_b = listenersClientRef.current) === null || _b === void 0 ? void 0 : _b.removeEventListeners();
141
+ (_a = listenersClientRef.current) === null || _a === void 0 ? void 0 : _a.removeEventListeners();
186
142
  };
187
143
  }, []);
188
144
  useEffect(() => {
@@ -8,6 +8,7 @@ interface ThreeDSecureConfig {
8
8
  environment?: Environment;
9
9
  bridge: HTMLIFrameElement;
10
10
  iframe?: HTMLIFrameElement;
11
+ accessToken?: string;
11
12
  }
12
13
  interface ThreeDSecureRunProps {
13
14
  paymentIntent: PaymentIntent;
@@ -35,7 +36,9 @@ declare class ThreeDSecure {
35
36
  private iframe?;
36
37
  private deviceProfileReadyHandler;
37
38
  private flowId;
38
- constructor({ successCallback, failureCallback, environment, bridge }: ThreeDSecureConfig);
39
+ private accessToken;
40
+ constructor({ successCallback, failureCallback, environment, bridge, accessToken }: ThreeDSecureConfig);
41
+ private getAccessToken;
39
42
  run({ paymentIntent, cardToken, merchantUsername, challengeWindowSize, test, tokenizeOnly, iframe, flowId }: ThreeDSecureRunProps): void;
40
43
  setupResponse(data: DeviceDataCollectionMessage): void;
41
44
  messageHandlers(): {
@@ -19,15 +19,21 @@ import { ChallengeWindowSize } from "../sca/types";
19
19
  import * as util from "../shared/util";
20
20
  import { setFlowId } from "../logging/logger-context";
21
21
  class ThreeDSecure {
22
- constructor({ successCallback, failureCallback, environment, bridge }) {
22
+ constructor({ successCallback, failureCallback, environment, bridge, accessToken }) {
23
23
  this.tokenizeOnly = false;
24
24
  this.deviceProfileReadyHandler = null;
25
+ this.accessToken = null;
25
26
  this.headlessBridge = bridge;
26
27
  this.environment = environment;
27
28
  this.successCallback = successCallback;
28
29
  this.failureCallback = failureCallback;
30
+ this.accessToken = accessToken !== null && accessToken !== void 0 ? accessToken : null;
29
31
  DeviceDataCollection.listenDataCollectionResponse(environment);
30
32
  }
33
+ getAccessToken() {
34
+ var _a;
35
+ return (_a = this.accessToken) !== null && _a !== void 0 ? _a : window.localStorage.getItem(LocalStorageAccessTokenKey);
36
+ }
31
37
  run({ paymentIntent, cardToken, merchantUsername, challengeWindowSize, test, tokenizeOnly, iframe, flowId }) {
32
38
  console.log('Running 3DS (ThreeDSecure)');
33
39
  this.flowId = flowId;
@@ -102,7 +108,7 @@ class ThreeDSecure {
102
108
  subject: BridgeEvent.SETUP_THREE_D_SECURE_REQUEST,
103
109
  data: {
104
110
  flow_id: this.flowId,
105
- access_token: window.localStorage.getItem(LocalStorageAccessTokenKey),
111
+ access_token: this.getAccessToken(),
106
112
  reference: this.paymentIntent.payment.reference,
107
113
  amount: this.paymentIntent.payment.amount,
108
114
  currency: this.paymentIntent.payment.currency,
@@ -116,7 +122,7 @@ class ThreeDSecure {
116
122
  const message = {
117
123
  channel: 'sca',
118
124
  subject: BridgeEvent.ENROL_THREE_D_SECURE_REQUEST,
119
- data: Object.assign(Object.assign(Object.assign({ flow_id: this.flowId, access_token: window.localStorage.getItem(LocalStorageAccessTokenKey), merchant_username: merchantUsername, card_token: cardToken }, paymentIntent.payment), { verification: paymentIntent.verification, reference_id: cybersourceReferenceId, return_url: `${env[this.environment].returnCybersourceUrl}?flow_id=${this.flowId}`, acs_window_size: (_a = this.challengeWindowSize) !== null && _a !== void 0 ? _a : ChallengeWindowSize.SIZE_500X600, device_channel: "BROWSER" }), snakeCaseKeys(DeviceDataCollection.collectDeviceData()))
125
+ data: Object.assign(Object.assign(Object.assign({ flow_id: this.flowId, access_token: this.getAccessToken(), merchant_username: merchantUsername, card_token: cardToken }, paymentIntent.payment), { verification: paymentIntent.verification, reference_id: cybersourceReferenceId, return_url: `${env[this.environment].returnCybersourceUrl}?flow_id=${this.flowId}`, acs_window_size: (_a = this.challengeWindowSize) !== null && _a !== void 0 ? _a : ChallengeWindowSize.SIZE_500X600, device_channel: "BROWSER" }), snakeCaseKeys(DeviceDataCollection.collectDeviceData()))
120
126
  };
121
127
  this.headlessBridge.contentWindow.postMessage(message, '*');
122
128
  }
@@ -151,7 +157,7 @@ class ThreeDSecure {
151
157
  const message = {
152
158
  channel: 'sca',
153
159
  subject: BridgeEvent.VALIDATE_THREE_D_SECURE_REQUEST,
154
- data: Object.assign(Object.assign({ flow_id: this.flowId, access_token: window.localStorage.getItem(LocalStorageAccessTokenKey), merchant_username: this.merchantUsername, card_token: data.cardToken }, this.paymentIntent.payment), { authentication_transaction_id: data.transactionId })
160
+ data: Object.assign(Object.assign({ flow_id: this.flowId, access_token: this.getAccessToken(), merchant_username: this.merchantUsername, card_token: data.cardToken }, this.paymentIntent.payment), { authentication_transaction_id: data.transactionId })
155
161
  };
156
162
  this.headlessBridge.contentWindow.postMessage(message, '*');
157
163
  }
@@ -179,7 +185,7 @@ class ThreeDSecure {
179
185
  channel: 'sca',
180
186
  subject: BridgeEvent.CREATE_PAYMENT_REQUEST,
181
187
  data: {
182
- access_token: window.localStorage.getItem(LocalStorageAccessTokenKey),
188
+ access_token: this.getAccessToken(),
183
189
  amount: this.paymentIntent.payment.amount,
184
190
  card_token: this.cardToken,
185
191
  currency: this.paymentIntent.payment.currency,
package/dist/version.js CHANGED
@@ -1,5 +1,6 @@
1
+ /*! fatzebra-sdk-commit: 5bc4f3388b7a97acd62478a9bfdfa340a80c3847 */
1
2
  export const version = '1.5.9';
2
3
  // Placeholder replaced at build time by .github/scripts/stamp-version.js with the
3
4
  // deployed commit hash (`git rev-parse HEAD`). Stays as the placeholder for builds
4
5
  // that don't run the stamp step (e.g. local/package builds).
5
- export const commitHash = '__SDK_COMMIT_HASH__';
6
+ export const commitHash = '5bc4f3388b7a97acd62478a9bfdfa340a80c3847';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fat-zebra/sdk",
3
- "version": "2.0.8-beta.3",
3
+ "version": "2.1.0-beta.3",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/README.dev.md DELETED
@@ -1,157 +0,0 @@
1
- # FatZebra-JS
2
-
3
- **fatzebra.js** (a.k.a JS SDK)is an agent through which various FZ payment functionalies are made available to merchants.
4
-
5
-
6
- ## Architecture Overview
7
-
8
- ![Alt text](./diagrams/sdk_architecture.png)
9
-
10
- ### API Gateway
11
-
12
- JS SDk communicates to various FZ backend services from merchant websites's frontend. A valid `access token` needs to be provided to the SDK during initialisation. This grants temporary access to selected services such as Gateway and PayNow. See Obtain Access Token for details
13
-
14
- The only FZ backend system that the SDK directly interacts with is the API Gateway (AWS API Gateway). Other backend services are hidden behind the API Gateway.
15
-
16
- ### Cross-Frame Communications
17
-
18
- One of the functionalities provided by the SDK is to load the Hosted Payments Page (HPP) into merchant webpage so that customers can enter their credit card details and make a purchase subsequently. Upon calling the `renderPaymentsPage()` method, the SDK does the following things in order
19
-
20
- * Create an `<iframe>` DOM element and attach it to the `<div>` specified by the merchant.
21
- * Load the HPP into `<iframe>`
22
- * Upon HPP is successfully loaded, the SDK registers a number of event listeners to listen to events coming from the child frame (HPP).
23
- * Using the combination of the event listeners above as well as the SDK's event registration method `.on()`, events from the child frame can be bubbled up to the merchant page context.
24
-
25
-
26
- ## Development Environment Setup
27
-
28
- ### NPM install
29
- Install dependencies
30
-
31
- ```bash
32
- $ npm install
33
- ```
34
-
35
- ### Start PayNow
36
-
37
- ```bash
38
- $ bundle exec rails -p 3000
39
- ```
40
-
41
- Go to .env.local, ensure PAYNOW_BASE_URL and PAYNOW_BRIDGE_URL have the desired hostname and port number.
42
-
43
- Upon receiving a request from the SDK, PayNow first extracts the access token from the request header, decodes it, andloads the merchant using the username extracted from the token. Please also ensure that a corresponding merchant record is available in FatZebra Core DB. It must have the same merchant username as the one used for generating the access token previously.
44
-
45
-
46
- ### Start API Gateway
47
- **Skip this section if the SDK workflow you are dealing does not involve multiple FZ backend services.**
48
-
49
- > coming soon...
50
-
51
- ## ApplePay
52
- ### Ngrok tunnelling
53
-
54
- The setup above would not give us access to Apple Pay's functionalities just yet as they are only available to merchants who have gone through Apple Pay onboarding process, e.g. registering merchant website. FZ has already registered https://fzapplepay.ngrok.io with Apple and this should be used for development.
55
-
56
- Tunnel PayNow via https://fz-applepay-child.ngrok.io
57
-
58
- ```bash
59
- $ ngrok http 4000 -subdomain=fz-applepay-child
60
- ```
61
-
62
- Tunnel Merchant JS via https://fzapplepay.ngrok.io
63
-
64
- ```bash
65
- ngrok http -subdomain fzapplepay -host-header="localhost:8080" 8080
66
- ```
67
-
68
- _Remember to specify host header, otherwise webpack-dev-server will complain._
69
-
70
- ### Visit tunnelled link
71
-
72
- Visit https://fzapplepay.ngrok.io in Safari. That's all!
73
-
74
- Webpack-dev-server supports hot reloading, meaning that code changes are reflected on the web page without restarting the server.
75
-
76
- ## Build Javascript
77
-
78
- There are two npm build scripts in `package.json` for building javascript files.
79
-
80
- | Command | Output | Description |
81
- |---------|--------|-------------|
82
- | npm run build:local | dist/local/fatzebra.js | Used for local dev only. Not saved in S3. |
83
- | npm run build:staging | dist/staging/fatzebra.js | Used for E2E testing on staging. Saved in S3. Bundle is NOT minified nor uglified. |
84
- | npm run build:sandbox | dist/sandbox/fatzebra.js | Saved in S3. Bundle is minified and uglified. |
85
- | npm run build:production | dist/production/fatzebra.js | Saved in S3. Bundle is minified and uglified. |
86
-
87
- ## Deployment Pipeline
88
-
89
- The JS SDK gets built and delpoyed via github actions https://github.com/fatzebra/fatzebra-js/actions. The pipeline is defined in `.github/workflows/ci.yml`
90
-
91
- The deployed SDKs are available from the following URLs.
92
-
93
- * Test: https://cdn.pmnts-staging.io/sdk/v1/fatzebra.js
94
- * Sandbox: https://cdn.pmnts-sandbox.io/sdk/v1/fatzebra.js
95
- * Production: https://cdn.pmnts.io/sdk/v1/fatzebra.js
96
-
97
-
98
- ## Release Management
99
-
100
- ### NPM
101
-
102
- Releases are based on tags. There are two types of releases:
103
- - Production
104
- - Beta
105
-
106
- To perform a production release:
107
-
108
- ```shell
109
- git tag -a v1.5.10 -m "Production Release: 1.5.10"
110
- git push origin v1.5.10
111
- ```
112
-
113
- **NOTE:** Production releases will only work when on the `main` branch
114
-
115
- To perform a beta release
116
-
117
- ```shell
118
- git tag -a v1.5.10-beta.0 -m "Beta: 1.5.10-beta.0"
119
- git push origin v1.5.10-beta.0
120
- ```
121
-
122
- ### CDN
123
-
124
- ## Staging
125
-
126
- Staging is released automatically for each branch (on push)
127
-
128
- ## Sandbox
129
-
130
- Sandbox deployment will trigger when pushing through a tag with beta in it on the `main` branch.
131
-
132
- Running this on main will trigger sandbox deployment (as well as npm beta release):
133
-
134
- ```shell
135
- git tag -a v1.5.10-beta.0 -m "Beta: 1.5.10-beta.0"
136
- git push origin v1.5.10-beta.0
137
- ```
138
-
139
- Additionally, the pipeline will trigger a cypress integration spec on sandbox which will run here: https://github.com/fatzebra/gazelle/actions
140
- Ensure this pipeline is green before deploying to production and then post a link of the outcome
141
-
142
- ## Production
143
-
144
- Production deployment will _ONLY_ trigger on the main branch and when pushing through a tag WITHOUT beta in it.
145
-
146
- Running this on main will trigger production deployment:
147
-
148
- ```shell
149
- git tag -a v1.5.10 -m "Beta: 1.5.10"
150
- git push origin v1.5.10
151
- ```
152
-
153
- ## Rollback management
154
-
155
- The pipeline will upload a version to override the existing deployment, but also a version of the build to a folder in s3.
156
-
157
-