@fat-zebra/sdk 1.0.2 → 1.0.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/src/sca/index.ts DELETED
@@ -1,329 +0,0 @@
1
- import * as t from './types'
2
- import {
3
- EnrollmentScenario,
4
- enrollmentScenarios,
5
- ValidationScenario,
6
- validationScenarios,
7
- } from './scenarios'
8
- import {
9
- Customer,
10
- CustomerSnakeCase,
11
- PaymentIntent,
12
- PublicEvent,
13
- } from '../shared/types'
14
- import {
15
- CardinalManager,
16
- PaymentValidatedDTO,
17
- } from './cardinal'
18
- import {
19
- emit,
20
- } from '../shared/event-manager'
21
- import { toFzSli } from './eci-mappings'
22
- import { ThreedsData } from './types'
23
- import GatewayClient from '../shared/api-gateway-client'
24
-
25
- export interface ScaRunProps {
26
- cardToken: string
27
- customer?: Customer
28
- paymentIntent: PaymentIntent
29
- bin: string
30
- challengeWindowSize?: t.ChallengeWindowSize
31
- }
32
-
33
- export interface ScaConfig {
34
- gatewayClient: GatewayClient,
35
- }
36
-
37
- class Sca {
38
- private _cardinal: CardinalManager
39
- private _eventEmitTarget: HTMLDivElement | Window
40
- private enrollmentResult: t.EnrollSCAResponse
41
- private paymentIntent: PaymentIntent
42
- private bin: string
43
- private cardToken: string
44
- private customer: Customer
45
- private challengeWindowSize: t.ChallengeWindowSize
46
- private sessionId: string
47
- private gatewayClient: GatewayClient
48
-
49
- constructor({ gatewayClient }: ScaConfig) {
50
- this._eventEmitTarget = window;
51
- this.sessionId = null;
52
- this.gatewayClient = gatewayClient;
53
-
54
- this.loadScript()
55
- }
56
-
57
- loadScript(): void {
58
- if (!document.getElementById("songbird-script")) {
59
- const script: HTMLScriptElement = document.createElement('script')
60
- script.type = 'text/javascript';
61
- script.id = "songbird-script"
62
- script.src = process.env.SONGBIRD_URL
63
- script.async = true;
64
- script.onload = () => {
65
- this._cardinal = new CardinalManager()
66
- };
67
- document.body.appendChild(script);
68
- } else {
69
- this._cardinal = new CardinalManager()
70
- }
71
- }
72
-
73
- get cardinal(): CardinalManager {
74
- return this._cardinal
75
- }
76
-
77
- set cardinal(cardinal: CardinalManager) {
78
- this._cardinal = cardinal
79
- }
80
-
81
- get eventEmitTarget(): HTMLDivElement | Window {
82
- return this._eventEmitTarget
83
- }
84
-
85
- // Specify the DOM element to which the SCA results are outputted.
86
- set eventEmitTarget(target: HTMLDivElement | Window) {
87
- this._eventEmitTarget = target
88
- }
89
-
90
- async run(config: ScaRunProps): Promise<void> {
91
- // Persist states until next 3DS run.
92
- this.paymentIntent = config.paymentIntent
93
- this.bin = config.bin
94
- this.cardToken = config.cardToken
95
- this.customer = config.customer
96
- this.challengeWindowSize = config.challengeWindowSize || t.ChallengeWindowSize.SIZE_FULL_PAGE
97
-
98
- let cardinalJwt: string
99
-
100
- // Generate Cardinal JWT
101
- try {
102
- cardinalJwt = await this.createCardinalJWT()
103
- } catch (error) {
104
- const message = 'FatZebra.3DS: JWT creation failed.'
105
- emit(PublicEvent.SCA_ERROR, {
106
- errors: [message],
107
- data: null
108
- })
109
- console.log(message)
110
- return
111
- }
112
-
113
- // Init cardinal
114
- this._cardinal.setup(cardinalJwt)
115
-
116
- if (!this.sessionId) {
117
- const paymentsSetupCompleteResponse = await this._cardinal.onPaymentSetupComplete()
118
- this.sessionId = paymentsSetupCompleteResponse.sessionId
119
-
120
- // Register handler. Called after OTP is entered on challenge prompt.
121
- this._cardinal.onPaymentValidated(async (data: PaymentValidatedDTO, error: string): Promise<void> => {
122
- if (typeof error == 'string') {
123
- const message = `FatZebra.3DS: Validation failed. ${error}.`
124
- emit(PublicEvent.SCA_ERROR, {
125
- errors: [message],
126
- data: null,
127
- })
128
- console.log(message)
129
- return
130
- }
131
-
132
- const decodeSCASessionResponse = (await this.gatewayClient.decodeSCASession({
133
- token: data.jwt
134
- })).data as t.DecodeSCASessionResponse
135
-
136
- if (data.processorTransactionId !== decodeSCASessionResponse.processor_transaction_id) {
137
- const message = 'FatZebra.3DS: Validation failed. Invalid process transaction id.'
138
- emit(PublicEvent.SCA_ERROR, {
139
- errors: [message],
140
- data: null
141
- })
142
- console.log(message)
143
- }
144
-
145
- let validateSCAResponse
146
-
147
- try {
148
- const requestParams: t.ValidateSCARequest = {
149
- amount: this.paymentIntent.payment.amount,
150
- authentication_transaction_id: this.enrollmentResult.authentication_transaction_id,
151
- card_token: this.cardToken,
152
- currency: this.paymentIntent.payment.currency,
153
- pareq: this.enrollmentResult.pareq,
154
- reference: this.paymentIntent.payment.reference,
155
- }
156
-
157
- validateSCAResponse = (await this.gatewayClient.validateSCA(requestParams)).data as t.ValidateSCAResponse
158
-
159
- } catch (errorResponse) {
160
- const message = 'FatZebra.3DS: Validation failed. Server error.'
161
- emit(PublicEvent.SCA_ERROR, {
162
- errors: [message],
163
- data: null,
164
- })
165
- console.log(message)
166
- return
167
- }
168
-
169
- const threedsData = threedsResponseData(validateSCAResponse)
170
- const scenario: ValidationScenario = getValidationResult(validateSCAResponse)
171
-
172
- if (scenario.outcome.success) {
173
- const message = `FatZebra.3DS: 3DS success - ${scenario.description}.`
174
- emit(PublicEvent.SCA_SUCCESS, {
175
- message,
176
- data: threedsData,
177
- })
178
- console.log(message)
179
- } else {
180
- const message = `FatZebra.3DS: 3DS error - ${scenario.description}`
181
- emit(PublicEvent.SCA_ERROR, {
182
- errors: [message],
183
- data: {
184
- errorCode: scenario.outcome.errorCode
185
- },
186
- })
187
- console.log(message)
188
- }
189
- })
190
- }
191
-
192
- // Cardinal processs BIN using directory server. This determines the 3DS version used.
193
- try {
194
- await this._cardinal.processBin(this.bin)
195
- } catch (err) {
196
- const message = 'FatZebra.3DS: BIN verification failed.'
197
- emit(PublicEvent.SCA_ERROR, {
198
- errors: [message],
199
- data: null
200
- })
201
- console.log(message)
202
- return
203
- }
204
-
205
- try {
206
- await this.check3DSEnrollment()
207
- } catch (err) {
208
- const message = 'FatZebra.3DS: Enrollment failed. Server error.'
209
- emit(PublicEvent.SCA_ERROR, {
210
- errors: [message],
211
- data: null
212
- })
213
- console.log(message)
214
- return
215
- }
216
-
217
- this.handleEnrollmentResult(this.enrollmentResult)
218
- }
219
-
220
- async createCardinalJWT(): Promise<string> {
221
- const { payment } = this.paymentIntent
222
- const response = (await this.gatewayClient.createSCASession({
223
- amount: payment.amount,
224
- currency: payment.currency,
225
- hide_card_holder: payment.hide_card_holder,
226
- })).data as t.CreateSCASessionResponse
227
-
228
- return response.jwt
229
- }
230
-
231
- async check3DSEnrollment(): Promise<void> {
232
- // Persist enrollResult state in memory for handlePaymentValidation handler
233
- this.enrollmentResult = (await this.gatewayClient.enrolSCA({
234
- amount: this.paymentIntent.payment.amount,
235
- card_token: this.cardToken,
236
- currency: this.paymentIntent.payment.currency,
237
- reference: this.paymentIntent.payment.reference,
238
- verification: this.paymentIntent.verification,
239
- session_id: this.sessionId,
240
- challenge_window_size: this.challengeWindowSize,
241
- hide_card_holder: this.paymentIntent.payment.hide_card_holder,
242
- customer: this.customerProperties(this.customer),
243
- })).data as t.EnrollSCAResponse
244
- }
245
-
246
- customerProperties(customer: Customer): CustomerSnakeCase {
247
- let properties: CustomerSnakeCase = {};
248
- if (!customer) return properties;
249
- if (customer.firstName) properties.first_name = customer.firstName
250
- if (customer.lastName) properties.last_name = customer.lastName
251
- if (customer.email) properties.email = customer.email
252
- if (customer.address) properties.address = customer.address
253
- if (customer.city) properties.city = customer.city
254
- if (customer.state) properties.state = customer.state
255
- if (customer.postcode) properties.postcode = customer.postcode
256
- if (customer.country) properties.country = customer.country
257
- return properties
258
- }
259
-
260
- handleEnrollmentResult(enrollSCAResponse: t.EnrollSCAResponse): void {
261
- const threedsData = threedsResponseData(enrollSCAResponse)
262
- const scenario: EnrollmentScenario = getEnrollmentResult(enrollSCAResponse)
263
-
264
- if (scenario.outcome.authenticationType === 'challenge') {
265
- this._cardinal.continue(
266
- enrollSCAResponse.acs_url,
267
- enrollSCAResponse.pareq,
268
- enrollSCAResponse.authentication_transaction_id
269
- )
270
- return
271
- }
272
-
273
- if (scenario.outcome.success) {
274
- const message = `FatZebra.3DS: 3DS success - ${scenario.description}.`
275
- emit(PublicEvent.SCA_SUCCESS, {
276
- message,
277
- data: threedsData,
278
- })
279
- console.log(message)
280
- } else {
281
- const message = `FatZebra.3DS: 3DS error - ${scenario.description}`
282
- emit(PublicEvent.SCA_ERROR, {
283
- errors: [message],
284
- data: {
285
- errorCode: scenario.outcome.errorCode
286
- },
287
- })
288
- console.log(message)
289
- }
290
- }
291
- }
292
-
293
- const threedsResponseData = (response: t.EnrollSCAResponse | t.ValidateSCAResponse): ThreedsData => {
294
- return {
295
- // CAVV: Visa & Amex only
296
- // AAV: Mastercard only, known as UCAF
297
- cavv: response.cavv || response.aav,
298
- par: response.pares,
299
- sli: toFzSli(response.eci),
300
- xid: response.xid,
301
- ver: response.enrolled,
302
- directoryServerTxnId: response.directory_server_txn_id,
303
- threedsVersion: response.version,
304
- }
305
- }
306
-
307
- const getEnrollmentResult = (enrollment: t.EnrollSCAResponse): EnrollmentScenario => {
308
- return enrollmentScenarios.find((item: EnrollmentScenario) => {
309
- return item.reasonCode === enrollment.reason_code &&
310
- item.veresEnrolled === enrollment.enrolled &&
311
- item.pares === enrollment.pares &&
312
- item.threedsVersion === getMajor3dsVersion(enrollment.version)
313
- })
314
- }
315
-
316
- const getValidationResult = (validation: t.ValidateSCAResponse): ValidationScenario => {
317
- return validationScenarios.find((item: ValidationScenario) => {
318
- return item.reasonCode === validation.reason_code &&
319
- item.pares === validation.pares &&
320
- item.threedsVersion === getMajor3dsVersion(validation.version)
321
- })
322
- }
323
-
324
- // 3DS versioning follows SEMVER format. The first charactor is the major version number.
325
- const getMajor3dsVersion = (original: string): string => {
326
- return original[0]
327
- }
328
-
329
- export default Sca;
@@ -1,164 +0,0 @@
1
- import { ScaErrorCode, VEResEnrolled, PARes } from '../types'
2
-
3
- interface EnrollmentScenario {
4
- description: string
5
- threedsVersion: '1' | '2'
6
- reasonCode: string
7
- veresEnrolled: VEResEnrolled
8
- pares: PARes | null // null is for challenge
9
- outcome: {
10
- authenticationType?: 'bypass' | 'frictionless' | 'challenge'
11
- success: boolean
12
- errorCode?: string
13
- }
14
- }
15
-
16
- const enrollmentScenarios: EnrollmentScenario[] = [
17
- /***************************************************************/
18
- /********************** 3DS2 scenarios *************************/
19
- /***************************************************************/
20
- {
21
- // Bypassed Authentication
22
- description: 'Bypassed Authentication',
23
- threedsVersion: '2',
24
- reasonCode: '100',
25
- veresEnrolled: VEResEnrolled.BYPASSED,
26
- pares: null,
27
- outcome: {
28
- success: false, // no liability shift
29
- authenticationType: 'bypass',
30
- errorCode: ScaErrorCode.BYPASSED_AUTHENTICATION
31
- },
32
- },
33
- {
34
- // Authentication not Available on Lookup
35
- description: 'Authentication not Available on Lookup ',
36
- threedsVersion: '2',
37
- reasonCode: '100',
38
- veresEnrolled: VEResEnrolled.UNABLE,
39
- pares: null,
40
- outcome: {
41
- success: false, // no liability shift
42
- errorCode: ScaErrorCode.AUTHENTICATION_NOT_AVAILABLE_ON_LOOKUP
43
- }
44
- },
45
- {
46
- // Attempts Processing Frictionless Authentication
47
- description: 'Attempts Processing Frictionless Authentication',
48
- threedsVersion: '2',
49
- reasonCode: '100',
50
- veresEnrolled: VEResEnrolled.ENROLLED,
51
- pares: PARes.ATTEMPTED,
52
- outcome: {
53
- authenticationType: 'frictionless',
54
- success: true
55
- }
56
- },
57
- {
58
- // Successful Frictionless Authentication
59
- description: 'Successful Frictionless Authentication',
60
- threedsVersion: '2',
61
- reasonCode: '100',
62
- veresEnrolled: VEResEnrolled.ENROLLED,
63
- pares: PARes.SUCCESS,
64
- outcome: {
65
- authenticationType: 'frictionless',
66
- success: true
67
- }
68
- },
69
- {
70
- // Unavailable Frictionless Authentication
71
- description: 'Unavailable Frictionless Authentication',
72
- threedsVersion: '2',
73
- reasonCode: '100',
74
- veresEnrolled: VEResEnrolled.ENROLLED,
75
- pares: PARes.NOT_COMPLETED,
76
- outcome: {
77
- authenticationType: 'frictionless',
78
- success: false,
79
- errorCode: ScaErrorCode.UNAVAILABLE_FRICTIONLESS_AUTHENTICATION
80
- }
81
- },
82
- {
83
- // Challenge. Continue with OTP prompt
84
- description: 'Challenge. Continue with OTP prompt',
85
- threedsVersion: '2',
86
- reasonCode: '475',
87
- veresEnrolled: VEResEnrolled.ENROLLED,
88
- pares: null,
89
- outcome: {
90
- authenticationType: 'challenge',
91
- success: true
92
- }
93
- },
94
- {
95
- // Unsuccessful Frictionless Authentication
96
- description: 'Unsuccessful Frictionless Authentication',
97
- threedsVersion: '2',
98
- reasonCode: '476',
99
- veresEnrolled: VEResEnrolled.ENROLLED,
100
- pares: PARes.CANCELED,
101
- outcome: {
102
- authenticationType: 'frictionless',
103
- success: false,
104
- errorCode: ScaErrorCode.UNSUCCESSFUL_FRICTIONLESS_AUTHENTICATION
105
- }
106
- },
107
- {
108
- // Rejected Frictionless Authentication
109
- description: 'Rejected Frictionless Authentication',
110
- threedsVersion: '2',
111
- reasonCode: '476',
112
- veresEnrolled: VEResEnrolled.ENROLLED,
113
- pares: PARes.REJECTED,
114
- outcome: {
115
- authenticationType: 'frictionless',
116
- success: false,
117
- errorCode: ScaErrorCode.REJECTED_FRICTIONLESS_AUTHENTICATION
118
- }
119
- },
120
- /***************************************************************/
121
- /********************** 3DS1 scenarios *************************/
122
- /***************************************************************/
123
- {
124
- // Proceed to challenge step
125
- description: '*****',
126
- threedsVersion: '1',
127
- reasonCode: '475',
128
- veresEnrolled: VEResEnrolled.ENROLLED,
129
- pares: null,
130
- outcome: {
131
- authenticationType: 'challenge',
132
- success: true
133
- }
134
- },
135
- {
136
- // Error, Unavailable Authentication
137
- description: 'Error; Unavailable Authentication',
138
- threedsVersion: '1',
139
- reasonCode: '100',
140
- veresEnrolled: VEResEnrolled.UNABLE,
141
- pares: null,
142
- outcome: {
143
- authenticationType: 'frictionless',
144
- success: false
145
- }
146
- },
147
- {
148
- // Not Enrolled
149
- description: 'Not Enrolled',
150
- threedsVersion: '1',
151
- reasonCode: '100',
152
- veresEnrolled: VEResEnrolled.NOT_ENROLLED,
153
- pares: null,
154
- outcome: {
155
- authenticationType: 'frictionless',
156
- success: true
157
- }
158
- },
159
- ]
160
-
161
- export {
162
- enrollmentScenarios
163
- }
164
- export type { EnrollmentScenario }
@@ -1,4 +0,0 @@
1
- export { enrollmentScenarios } from './enrollment'
2
- export type { EnrollmentScenario } from './enrollment'
3
- export { validationScenarios } from './validation'
4
- export type { ValidationScenario } from './validation'
@@ -1,142 +0,0 @@
1
- import { ScaErrorCode, VEResEnrolled, PARes } from '../types'
2
-
3
- interface ValidationScenario {
4
- description: string
5
- threedsVersion: '1' | '2',
6
- reasonCode: string
7
- veresEnrolled: VEResEnrolled
8
- pares: PARes | null // null is for challenge
9
- outcome: {
10
- authenticationType?: 'bypass' | 'frictionless' | 'challenge',
11
- success: boolean,
12
- errorCode?: string
13
- }
14
- }
15
-
16
- const validationScenarios: ValidationScenario[] = [
17
- /***************************************************************/
18
- /********************** 3DS2 scenarios *************************/
19
- /***************************************************************/
20
- {
21
- // Successful Step-Up Authentication
22
- description: 'Successful Step-Up Authentication',
23
- threedsVersion: '2',
24
- reasonCode: '100',
25
- veresEnrolled: null,
26
- pares: PARes.SUCCESS,
27
- outcome: {
28
- authenticationType: 'challenge',
29
- success: true // liability shift
30
- },
31
- },
32
- {
33
- // Unsuccessful Step-Up Authentication
34
- description: 'Unsuccessful Step-Up Authentication',
35
- threedsVersion: '2',
36
- reasonCode: '476',
37
- veresEnrolled: null,
38
- pares: PARes.CANCELED,
39
- outcome: {
40
- authenticationType: 'challenge',
41
- success: false, // no liability shift
42
- errorCode: ScaErrorCode.UNSUCCESSFUL_STEPUP_AUTHENTICATION
43
- }
44
- },
45
- {
46
- // Unavailable Step-Up Authentication
47
- description: 'Unavailable Step-Up Authentication',
48
- threedsVersion: '2',
49
- reasonCode: '100',
50
- veresEnrolled: null,
51
- pares: PARes.NOT_COMPLETED,
52
- outcome: {
53
- authenticationType: 'challenge',
54
- success: false, // no liability shift
55
- errorCode: ScaErrorCode.UNAVAILABLE_STEPUP_AUTHENTICATION
56
- }
57
- },
58
- /***************************************************************/
59
- /********************** 3DS1 scenarios *************************/
60
- /***************************************************************/
61
- {
62
- // Successful Step-Up Authentication
63
- description: 'Successful Authentication',
64
- threedsVersion: '1',
65
- reasonCode: '100',
66
- veresEnrolled: null,
67
- pares: PARes.SUCCESS,
68
- outcome: {
69
- authenticationType: 'challenge',
70
- success: true // liability shift
71
- },
72
- },
73
- {
74
- // Incomplete Authentication
75
- description: 'Incomplete Authentication',
76
- threedsVersion: '1',
77
- reasonCode: '100',
78
- veresEnrolled: null,
79
- pares: PARes.NOT_COMPLETED,
80
- outcome: {
81
- authenticationType: 'challenge',
82
- success: false,
83
- errorCode: ScaErrorCode.UNAVAILABLE_STEPUP_AUTHENTICATION
84
- }
85
- },
86
- {
87
- // Attempts Processing
88
- description: 'Attempts Processing',
89
- threedsVersion: '1',
90
- reasonCode: '100',
91
- veresEnrolled: null,
92
- pares: PARes.ATTEMPTED,
93
- outcome: {
94
- authenticationType: 'challenge',
95
- success: true
96
- }
97
- },
98
- {
99
- // Unsuccessful Authentication
100
- description: 'Unsuccessful Authentication',
101
- threedsVersion: '1',
102
- reasonCode: '476',
103
- veresEnrolled: null,
104
- pares: PARes.CANCELED,
105
- outcome: {
106
- authenticationType: 'challenge',
107
- success: false,
108
- errorCode: ScaErrorCode.UNSUCCESSFUL_STEPUP_AUTHENTICATION
109
- }
110
- },
111
- {
112
- // Successful Authentication But Invalid PARes
113
- description: 'Successful Authentication But Invalid PARes',
114
- threedsVersion: '1',
115
- reasonCode: '476',
116
- veresEnrolled: null,
117
- pares: null,
118
- outcome: {
119
- authenticationType: 'challenge',
120
- success: false,
121
- errorCode: ScaErrorCode.UNSUCCESSFUL_STEPUP_AUTHENTICATION
122
- }
123
- },
124
- {
125
- // Authentication Error
126
- description: 'Authentication Error',
127
- threedsVersion: '1',
128
- reasonCode: '476',
129
- veresEnrolled: null,
130
- pares: null,
131
- outcome: {
132
- authenticationType: 'challenge',
133
- success: false, // no liability shift
134
- errorCode: ScaErrorCode.UNSUCCESSFUL_STEPUP_AUTHENTICATION
135
- }
136
- },
137
- ]
138
-
139
- export {
140
- validationScenarios
141
- }
142
- export type { ValidationScenario }