@fat-zebra/sdk 1.0.1 → 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/dist/index.d.ts +1 -1
- package/dist/index.js +2 -5
- package/dist/react/VerifyCard.js +11 -17
- package/dist/react/index.js +2 -8
- package/dist/react/url.js +10 -14
- package/dist/react/useFatZebra.js +23 -63
- package/dist/sca/cardinal.d.ts +2 -1
- package/dist/sca/cardinal.js +22 -61
- package/dist/sca/eci-mappings.js +4 -8
- package/dist/sca/index.js +182 -296
- package/dist/sca/scenarios/enrollment.d.ts +2 -1
- package/dist/sca/scenarios/enrollment.js +25 -28
- package/dist/sca/scenarios/index.d.ts +4 -2
- package/dist/sca/scenarios/index.js +2 -7
- package/dist/sca/scenarios/validation.d.ts +2 -1
- package/dist/sca/scenarios/validation.js +16 -19
- package/dist/sca/types.d.ts +2 -1
- package/dist/sca/types.js +6 -8
- package/dist/shared/api-gateway-client.js +37 -81
- package/dist/shared/bridge-client.js +5 -8
- package/dist/shared/constants.js +6 -13
- package/dist/shared/event-manager.d.ts +2 -1
- package/dist/shared/event-manager.js +3 -9
- package/dist/shared/post-message-client.d.ts +2 -1
- package/dist/shared/post-message-client.js +38 -75
- package/dist/shared/types.d.ts +2 -1
- package/dist/shared/types.js +5 -7
- package/dist/shared/util.js +18 -31
- package/fat-zebra-sdk-1.0.2.tgz +0 -0
- package/package.json +2 -2
- package/tsconfig.json +6 -4
- package/dist/index.js.map +0 -1
- package/dist/react/VerifyCard.js.map +0 -1
- package/dist/react/index.js.map +0 -1
- package/dist/react/url.js.map +0 -1
- package/dist/react/useFatZebra.js.map +0 -1
- package/dist/sca/cardinal.js.map +0 -1
- package/dist/sca/eci-mappings.js.map +0 -1
- package/dist/sca/index.js.map +0 -1
- package/dist/sca/scenarios/enrollment.js.map +0 -1
- package/dist/sca/scenarios/index.js.map +0 -1
- package/dist/sca/scenarios/validation.js.map +0 -1
- package/dist/sca/types.js.map +0 -1
- package/dist/shared/api-gateway-client.js.map +0 -1
- package/dist/shared/bridge-client.js.map +0 -1
- package/dist/shared/constants.js.map +0 -1
- package/dist/shared/event-manager.js.map +0 -1
- package/dist/shared/post-message-client.js.map +0 -1
- package/dist/shared/types.js.map +0 -1
- package/dist/shared/util.js.map +0 -1
- package/src/index.ts +0 -9
- package/src/react/VerifyCard.tsx +0 -36
- package/src/react/index.ts +0 -3
- package/src/react/url.ts +0 -97
- package/src/react/useFatZebra.ts +0 -154
- package/src/sca/__tests__/eci-mappings.test.ts +0 -35
- package/src/sca/__tests__/index.test.ts +0 -514
- package/src/sca/cardinal.ts +0 -136
- package/src/sca/eci-mappings.ts +0 -63
- package/src/sca/index.ts +0 -329
- package/src/sca/scenarios/enrollment.ts +0 -164
- package/src/sca/scenarios/index.ts +0 -2
- package/src/sca/scenarios/validation.ts +0 -142
- package/src/sca/types.ts +0 -206
- package/src/shared/api-gateway-client.ts +0 -71
- package/src/shared/bridge-client.ts +0 -26
- package/src/shared/constants.ts +0 -19
- package/src/shared/event-manager.ts +0 -44
- package/src/shared/post-message-client.test.ts +0 -73
- package/src/shared/post-message-client.ts +0 -125
- package/src/shared/types.test.ts +0 -65
- package/src/shared/types.ts +0 -182
- package/src/shared/util.test.ts +0 -164
- package/src/shared/util.ts +0 -98
- package/tests/helpers/api-gateway-mock.ts +0 -46
package/src/sca/cardinal.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
// https://cardinaldocs.atlassian.net/wiki/spaces/CC/pages/1409568/Configurations
|
|
2
|
-
interface CardinalConfig {
|
|
3
|
-
timeout?: number
|
|
4
|
-
maxRequestRetries?: number
|
|
5
|
-
logging?: {
|
|
6
|
-
level: 'on' | 'off' | 'verbose'
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface CardinalContinueObject {
|
|
11
|
-
AcsUrl: string
|
|
12
|
-
Payload: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface CardinalOrderObject {
|
|
16
|
-
OrderDetails: {
|
|
17
|
-
TransactionId: string
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface CardinalSetupCompleteResponseData {
|
|
22
|
-
sessionId: string
|
|
23
|
-
modules: { module: string, loaded: boolean }[]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// https://cardinaldocs.atlassian.net/wiki/spaces/CC/pages/98315/Response+Objects
|
|
27
|
-
interface PaymentValidatedResponseData {
|
|
28
|
-
Validated: boolean
|
|
29
|
-
ErrorNumber: number
|
|
30
|
-
ErrorDescription: string
|
|
31
|
-
ActionCode: 'SUCCESS' | 'NOACTION' | 'FAILURE' | 'ERROR'
|
|
32
|
-
Payment: {
|
|
33
|
-
OrderId: string
|
|
34
|
-
OrderNumber: string
|
|
35
|
-
ProcessorTransactionId: string
|
|
36
|
-
ReasonDescription: string
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface PaymentValidatedDTO {
|
|
41
|
-
processorTransactionId: string
|
|
42
|
-
jwt: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
enum CardinalWorkflow {
|
|
46
|
-
CCA = 'cca',
|
|
47
|
-
INIT = 'init',
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
enum CardinalPaymentEvent {
|
|
51
|
-
SETUP_COMPLETE = 'payments.setupComplete',
|
|
52
|
-
VALIDATED = 'payments.validated',
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface Cardinal {
|
|
56
|
-
configure(config: CardinalConfig): void
|
|
57
|
-
continue(
|
|
58
|
-
workflow: CardinalWorkflow,
|
|
59
|
-
data: CardinalContinueObject,
|
|
60
|
-
order: CardinalOrderObject
|
|
61
|
-
): void
|
|
62
|
-
on(event: string, callback: (...data: any[]) => void): void
|
|
63
|
-
setup(workflow: CardinalWorkflow, data: any): void
|
|
64
|
-
trigger(event: string, cardBin: string): Promise<any>
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
interface ScaResult {
|
|
68
|
-
success: boolean
|
|
69
|
-
type: 'frictionless' | 'challenge' | 'none'
|
|
70
|
-
error: string
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const defaultConfig = {
|
|
74
|
-
timeout: 8000,
|
|
75
|
-
maxRequestRetries: 3,
|
|
76
|
-
logging: {
|
|
77
|
-
level: 'on'
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export default class CardinalManager {
|
|
82
|
-
private cardinal: Cardinal
|
|
83
|
-
|
|
84
|
-
constructor(config?: CardinalConfig) {
|
|
85
|
-
window.Cardinal.configure((config ? config : defaultConfig as CardinalConfig))
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
setup(jwt: any): void {
|
|
89
|
-
window.Cardinal.setup(CardinalWorkflow.INIT, { jwt })
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
onPaymentValidated(handler: (data: PaymentValidatedDTO, error: string) => void): void {
|
|
93
|
-
window.Cardinal.on('payments.validated', (data: PaymentValidatedResponseData, jwt: string) => {
|
|
94
|
-
if (data.ErrorDescription.toLowerCase() === 'success') {
|
|
95
|
-
handler({
|
|
96
|
-
processorTransactionId: data.Payment.ProcessorTransactionId,
|
|
97
|
-
jwt
|
|
98
|
-
}, null)
|
|
99
|
-
} else {
|
|
100
|
-
handler(null, data.ErrorDescription)
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
onPaymentSetupComplete(): Promise<CardinalSetupCompleteResponseData> {
|
|
106
|
-
return new Promise((resolve, reject) => {
|
|
107
|
-
window.Cardinal.on(CardinalPaymentEvent.SETUP_COMPLETE, (data: CardinalSetupCompleteResponseData) => {
|
|
108
|
-
resolve(data)
|
|
109
|
-
})
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async processBin(bin: string): Promise<any> {
|
|
114
|
-
await window.Cardinal.trigger('bin.process', bin)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
continue(acsUrl: string, pareq: string, transactionId: string) {
|
|
118
|
-
window.Cardinal.continue(
|
|
119
|
-
CardinalWorkflow.CCA,
|
|
120
|
-
{
|
|
121
|
-
AcsUrl: acsUrl,
|
|
122
|
-
Payload: pareq
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
OrderDetails: {
|
|
126
|
-
TransactionId: transactionId
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export {
|
|
134
|
-
CardinalManager,
|
|
135
|
-
PaymentValidatedDTO,
|
|
136
|
-
}
|
package/src/sca/eci-mappings.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// FatZebra only supports SLI 05, 06, 07 & 09.
|
|
2
|
-
// https://docs.fatzebra.com/docs/3d-secure
|
|
3
|
-
// Cardinal returns ECI(or SLI for MC) in the range of 01~07
|
|
4
|
-
// https://cardinaldocs.atlassian.net/wiki/spaces/CCen/pages/903577725/EMV+3DS+2.0+Test+Cases
|
|
5
|
-
const eciMappings: { vendorEci: string, fatzebraSli: string }[] = [
|
|
6
|
-
{
|
|
7
|
-
// Cards: Mastercard, CB Mastercard
|
|
8
|
-
// Test Case 2: Failed Frictionless Authentication
|
|
9
|
-
// Test Case 4: Unavailable Frictionless Authentication from the Issuer
|
|
10
|
-
// Test Case 5: Rejected Frictionless Authentication by the Issuer
|
|
11
|
-
// Test Case 6: Authentication Not Available on Lookup
|
|
12
|
-
// Test Case 7: Error on Lookup
|
|
13
|
-
// Test Case 9: Bypassed Authentication
|
|
14
|
-
// Test Case 11: Failed Step Up Authentication
|
|
15
|
-
// Test Case 12: Step Up Authentication is Unavailable
|
|
16
|
-
// Test Case 13: Error on Authentication
|
|
17
|
-
vendorEci: '00',
|
|
18
|
-
// Non-authenticated transaction - issuer not support. CAVV not present.
|
|
19
|
-
fatzebraSli: '07',
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
// Cards: Mastercard, CB Mastercard
|
|
23
|
-
// Test Case 3: Attempts Stand-In Frictionless Authentication
|
|
24
|
-
vendorEci: '01',
|
|
25
|
-
// Cards: Visa, American Express, Discover (Diners Club), CB Visa, ELO
|
|
26
|
-
// Secure, Non-authenticated transaction (XID present, CAVV not present)
|
|
27
|
-
fatzebraSli: '06',
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
// Cards: Mastercard, CB Mastercard
|
|
31
|
-
// Test Case 1: Successful Frictionless Authentication
|
|
32
|
-
// Test Case 10: Successful Step Up Authentication
|
|
33
|
-
vendorEci: '02',
|
|
34
|
-
// Cards: Visa, American Express, Discover (Diners Club), CB Visa, ELO
|
|
35
|
-
// Secure, Authenticated transaction with XID and CAVV present
|
|
36
|
-
fatzebraSli: '05',
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
vendorEci: '05',
|
|
40
|
-
// Secure, Authenticated transaction with XID and CAVV present
|
|
41
|
-
fatzebraSli: '05',
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
vendorEci: '06',
|
|
45
|
-
// Secure, Non-authenticated transaction (XID present, CAVV not present)
|
|
46
|
-
fatzebraSli: '06',
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
vendorEci: '07',
|
|
50
|
-
// Secure, Non-authenticated transaction (XID and CAVV not present). This is the default value.
|
|
51
|
-
fatzebraSli: '07',
|
|
52
|
-
},
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
const toFzSli = (vendorEci: string): string => {
|
|
56
|
-
const matched = eciMappings.find((item) => item.vendorEci == vendorEci)
|
|
57
|
-
return matched ? matched.fatzebraSli : vendorEci
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export {
|
|
61
|
-
eciMappings,
|
|
62
|
-
toFzSli,
|
|
63
|
-
}
|
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
|
-
EnrollmentScenario,
|
|
163
|
-
enrollmentScenarios
|
|
164
|
-
}
|