@akinon/pz-masterpass 1.19.0

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.
@@ -0,0 +1,141 @@
1
+ 'use client';
2
+
3
+ import { useLocalization } from '@akinon/next/hooks';
4
+ import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
5
+ import Script from 'next/script';
6
+ import { useEffect } from 'react';
7
+ import { checkMasterpass } from './utils/check-masterpass';
8
+ import { init } from './utils/init';
9
+ import {
10
+ setAccountStatus,
11
+ setCredentials,
12
+ setError,
13
+ setIsDirectPurchase,
14
+ setLanguage,
15
+ setMsisdn,
16
+ setReferenceNo,
17
+ setToken
18
+ } from './redux/reducer';
19
+ import { MasterpassStatus } from './types';
20
+ import { Image, Modal } from '@akinon/next/components';
21
+ import masterpassLogo from '../assets/img/mp_masterpass-logo.png';
22
+ import { checkoutApi } from '@akinon/next/data/client/checkout';
23
+
24
+ interface MasterpassProviderProps {
25
+ children: React.ReactNode;
26
+ translations?: {
27
+ genericErrorMessage?: string;
28
+ };
29
+ }
30
+
31
+ const getLanguage = (locale: string) => {
32
+ switch (locale) {
33
+ case 'en':
34
+ return 'eng';
35
+ case 'tr':
36
+ return 'tur';
37
+ default:
38
+ return 'eng';
39
+ }
40
+ };
41
+
42
+ export const MasterpassProvider = (props: MasterpassProviderProps) => {
43
+ const { children } = props;
44
+ const { preOrder } = useAppSelector((state) => state.checkout);
45
+ const { otp, error } = useAppSelector((state) => state.masterpass);
46
+ const { locale } = useLocalization();
47
+ const dispatch = useAppDispatch();
48
+
49
+ const prepareState = async () => {
50
+ dispatch(setLanguage(getLanguage(locale)));
51
+
52
+ const data = await init();
53
+
54
+ if (!data) {
55
+ return;
56
+ }
57
+
58
+ dispatch(setCredentials(data));
59
+
60
+ const { status, response, msisdn, token, referenceNo } =
61
+ await checkMasterpass();
62
+
63
+ if (status !== 200) {
64
+ dispatch(
65
+ setError(
66
+ props.translations?.genericErrorMessage ??
67
+ 'An error occurred while initializing Masterpass.'
68
+ )
69
+ );
70
+ return;
71
+ }
72
+
73
+ const accountStatus = response.accountStatus.substring(
74
+ 0,
75
+ 6
76
+ ) as MasterpassStatus;
77
+
78
+ dispatch(setToken(token));
79
+ dispatch(setMsisdn(msisdn));
80
+ dispatch(setReferenceNo(referenceNo));
81
+ dispatch(setAccountStatus(accountStatus));
82
+
83
+ if (accountStatus === MasterpassStatus.ListCards) {
84
+ dispatch(setIsDirectPurchase(false));
85
+ }
86
+ };
87
+
88
+ useEffect(() => {
89
+ if (preOrder?.payment_option?.payment_type !== 'masterpass') {
90
+ return;
91
+ }
92
+
93
+ prepareState();
94
+ }, [preOrder?.payment_option?.payment_type]);
95
+
96
+ useEffect(() => {
97
+ if (!otp.response) {
98
+ return;
99
+ }
100
+
101
+ if (
102
+ otp.response.token &&
103
+ (otp.response.responseCode === '0000' || otp.response.responseCode === '')
104
+ ) {
105
+ dispatch(
106
+ checkoutApi.endpoints.completeMasterpassPayment.initiate({
107
+ token: otp.response.token
108
+ }) as any
109
+ );
110
+ }
111
+ }, [otp.response]);
112
+
113
+ if (preOrder?.payment_option?.payment_type !== 'masterpass') {
114
+ return <>{children}</>;
115
+ }
116
+
117
+ return (
118
+ <>
119
+ <Script src="/mfs-client.min.js" strategy="afterInteractive" />
120
+ <Script src="/zepto.min.js" strategy="afterInteractive" />
121
+
122
+ <Modal
123
+ portalId="masterpass-error-modal"
124
+ title={
125
+ <Image
126
+ width={140}
127
+ height={25}
128
+ src={masterpassLogo.src}
129
+ alt="Masterpass Logo"
130
+ />
131
+ }
132
+ open={!!error}
133
+ setOpen={() => dispatch(setError(undefined))}
134
+ >
135
+ <div className="p-6">{error}</div>
136
+ </Modal>
137
+
138
+ {children}
139
+ </>
140
+ );
141
+ };
@@ -0,0 +1,126 @@
1
+ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
2
+ import {
3
+ MasterpassCredentials,
4
+ MasterpassResponse,
5
+ MasterpassResponseCardType,
6
+ MasterpassStatus
7
+ } from '../types';
8
+
9
+ export interface MasterpassState {
10
+ error?: string;
11
+ token?: string;
12
+ msisdn?: string;
13
+ referenceNo?: string;
14
+ language: string;
15
+ credentials?: MasterpassCredentials;
16
+ accountStatus?: MasterpassStatus;
17
+ cards: MasterpassResponseCardType[];
18
+ selectedCard?: MasterpassResponseCardType;
19
+ isDirectPurchase?: boolean;
20
+ otp: {
21
+ isModalVisible: boolean;
22
+ modalTitle?: string;
23
+ response?: MasterpassResponse;
24
+ };
25
+ deletion: {
26
+ isModalVisible: boolean;
27
+ cardAliasName?: string;
28
+ };
29
+ }
30
+
31
+ const initialState: MasterpassState = {
32
+ language: 'eng',
33
+ cards: [],
34
+ otp: {
35
+ isModalVisible: false
36
+ },
37
+ isDirectPurchase: true,
38
+ deletion: {
39
+ isModalVisible: false
40
+ }
41
+ };
42
+
43
+ const rootSlice = createSlice({
44
+ name: 'masterpass',
45
+ initialState,
46
+ reducers: {
47
+ setError: (state, { payload }: PayloadAction<string>) => {
48
+ state.error = payload;
49
+ },
50
+ setToken: (state, { payload }: PayloadAction<string>) => {
51
+ state.token = payload;
52
+ },
53
+ setMsisdn: (state, { payload }: PayloadAction<string>) => {
54
+ state.msisdn = payload;
55
+ },
56
+ setReferenceNo: (state, { payload }: PayloadAction<string>) => {
57
+ state.referenceNo = payload;
58
+ },
59
+ setLanguage: (state, { payload }: PayloadAction<string>) => {
60
+ state.language = payload;
61
+ },
62
+ setCredentials: (
63
+ state,
64
+ { payload }: PayloadAction<MasterpassCredentials>
65
+ ) => {
66
+ state.credentials = payload;
67
+ },
68
+ setAccountStatus: (state, { payload }: PayloadAction<MasterpassStatus>) => {
69
+ state.accountStatus = payload;
70
+ },
71
+ setCards: (
72
+ state,
73
+ { payload }: PayloadAction<MasterpassResponseCardType[]>
74
+ ) => {
75
+ state.cards = payload;
76
+ },
77
+ setSelectedCard: (
78
+ state,
79
+ { payload }: PayloadAction<MasterpassResponseCardType>
80
+ ) => {
81
+ state.selectedCard = payload;
82
+ },
83
+ setIsDirectPurchase: (state, { payload }: PayloadAction<boolean>) => {
84
+ state.isDirectPurchase = payload;
85
+
86
+ if (payload) {
87
+ state.selectedCard = undefined;
88
+ }
89
+ },
90
+ setOtpModalVisible: (state, { payload }: PayloadAction<boolean>) => {
91
+ state.otp.isModalVisible = payload;
92
+ },
93
+ setOtpModalTitle: (state, { payload }: PayloadAction<string>) => {
94
+ state.otp.modalTitle = payload;
95
+ },
96
+ setOtpResponse: (state, { payload }: PayloadAction<MasterpassResponse>) => {
97
+ state.otp.response = payload;
98
+ },
99
+ setDeletionModalVisible: (state, { payload }: PayloadAction<boolean>) => {
100
+ state.deletion.isModalVisible = payload;
101
+ },
102
+ setDeletionCardAliasName: (state, { payload }: PayloadAction<string>) => {
103
+ state.deletion.cardAliasName = payload;
104
+ }
105
+ }
106
+ });
107
+
108
+ export const {
109
+ setError,
110
+ setToken,
111
+ setMsisdn,
112
+ setReferenceNo,
113
+ setLanguage,
114
+ setCredentials,
115
+ setAccountStatus,
116
+ setCards,
117
+ setSelectedCard,
118
+ setIsDirectPurchase,
119
+ setOtpModalVisible,
120
+ setOtpModalTitle,
121
+ setOtpResponse,
122
+ setDeletionModalVisible,
123
+ setDeletionCardAliasName
124
+ } = rootSlice.actions;
125
+
126
+ export default rootSlice.reducer;
@@ -0,0 +1,111 @@
1
+ declare global {
2
+ interface Window {
3
+ // TODO: Add types for MFS
4
+ MFS: {
5
+ addCardToMasterPass: (a: any, b: any) => void;
6
+ checkMasterPass: (a: any, b: MasterpassResponseCallback) => void;
7
+ commit: (a: any) => void;
8
+ completeRegistration: (a: any, b: any, c: any) => void;
9
+ deleteCard: MasterpassAction;
10
+ directPurchase: (a: any, b: any) => void;
11
+ forgotPassword: (a: any, b: any) => void;
12
+ getLastToken: () => string;
13
+ initiateRecurringPayment: (a: any, b: any) => void;
14
+ linkCardToClient: MasterpassAction;
15
+ listCards: (a: any, b: any, c: any) => void;
16
+ parseQrCode: (a: any, b: any) => void;
17
+ purchase: (a: any, b: any) => void;
18
+ purchaseAndRegister: (a: any, b: any) => void;
19
+ register: MasterpassAction;
20
+ resendOtp: (a: any, b: any, c: any) => void;
21
+ setAdditionalParameters: (a: any) => void;
22
+ setAddress: (address: string) => void;
23
+ setClientId: (clientId: string) => void;
24
+ setFingerprint: (a: any) => void;
25
+ setToken: (a: any) => void;
26
+ updateUser: (a: any, b: any) => void;
27
+ validateTransaction: MasterpassAction;
28
+ verifyPin: (a: any, b: any) => void;
29
+ };
30
+ $: any;
31
+ }
32
+ }
33
+
34
+ export enum MasterpassStatus {
35
+ NoAccount = '000000',
36
+ ListCards = '011100',
37
+ ShowLinkModal = '011000'
38
+ }
39
+
40
+ export type MasterpassResponse = {
41
+ responseCode: string;
42
+ responseDescription: string;
43
+ referenceNo?: string;
44
+ token?: string;
45
+ transactionId?: string;
46
+ cardUniqueId?: string;
47
+ };
48
+
49
+ export type MasterpassResponseCallback<T = MasterpassResponse> = (
50
+ statusCode: number,
51
+ response: T
52
+ ) => Promise<void> | void;
53
+
54
+ export type MasterpassAction = (
55
+ a: JQuery<Element>,
56
+ b: MasterpassResponseCallback
57
+ ) => Promise<void> | void;
58
+
59
+ export type MasterpassCredentials = {
60
+ client_id: string;
61
+ js: string;
62
+ merchant_id: string;
63
+ osessionid: string;
64
+ s3d_return_url: string;
65
+ };
66
+
67
+ export type MasterpassTokenResponse = {
68
+ msisdn?: string;
69
+ req_ref_no?: string;
70
+ token?: string;
71
+ };
72
+
73
+ export type CheckMasterpassResponse = {
74
+ accountStatus: string;
75
+ amount: string;
76
+ cards: Array<MasterpassResponseCardType>;
77
+ installmentCount: string;
78
+ internalResponseCode: string;
79
+ internalResponseDescription: string;
80
+ newMsisdn: string;
81
+ orderNo: string;
82
+ referenceNo: string;
83
+ responseCode: string;
84
+ responseDescription: string;
85
+ token: string;
86
+ transactionId: string;
87
+ url3D: string;
88
+ url3DError: string;
89
+ url3DSuccess: string;
90
+ };
91
+
92
+ export type MasterpassResponseCardType = {
93
+ BankIca: string;
94
+ CardStatus: string;
95
+ Name: string;
96
+ ProductName: string;
97
+ UniqueId: string;
98
+ Value1: string;
99
+ };
100
+
101
+ export interface RemoveModalProps {
102
+ isOpen: boolean;
103
+ setOpen: (arg: boolean) => void;
104
+ onConfirm: () => void;
105
+ isLoading: boolean;
106
+ }
107
+
108
+ export interface OtpModalProps {
109
+ title?: string;
110
+ isOpen?: boolean;
111
+ }
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+
3
+ import { formCreator } from '.';
4
+ import { CheckMasterpassResponse, MasterpassTokenResponse } from '../types';
5
+ import { buildClientRequestUrl } from '@akinon/next/utils';
6
+
7
+ export const checkMasterpass = () =>
8
+ new Promise<{
9
+ status: number;
10
+ response?: CheckMasterpassResponse;
11
+ msisdn?: string;
12
+ token?: string;
13
+ referenceNo?: string;
14
+ }>(async (resolve) => {
15
+ let data: MasterpassTokenResponse;
16
+
17
+ try {
18
+ const response = await fetch(
19
+ buildClientRequestUrl('/orders/masterpass-token')
20
+ );
21
+
22
+ if (response.status !== 200) {
23
+ resolve({
24
+ status: response.status
25
+ });
26
+
27
+ return;
28
+ }
29
+
30
+ data = await response.json();
31
+
32
+ const fields = [
33
+ {
34
+ name: 'token',
35
+ value: data.token ?? ''
36
+ },
37
+ {
38
+ name: 'referenceNo',
39
+ value: data.req_ref_no ?? ''
40
+ },
41
+ { name: 'userId', value: data.msisdn ?? '' },
42
+ { name: 'sendSmsLanguage', value: 'eng' },
43
+ { name: 'sendSms', value: 'N' }
44
+ ];
45
+
46
+ const form = formCreator({
47
+ id: 'check-masterpass-form',
48
+ fields
49
+ });
50
+
51
+ window.MFS.checkMasterPass(
52
+ $(form),
53
+ (status: number, response: CheckMasterpassResponse) => {
54
+ resolve({
55
+ status,
56
+ response,
57
+ msisdn: data.msisdn ?? '',
58
+ token: data.token ?? '',
59
+ referenceNo: data.req_ref_no ?? ''
60
+ });
61
+ }
62
+ );
63
+ } catch (error) {
64
+ console.error(error);
65
+ }
66
+ });
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+
3
+ import { CheckMasterpassResponse, MasterpassResponseCardType } from '../types';
4
+
5
+ export const getMasterpassCards = ({
6
+ msisdn,
7
+ token
8
+ }: {
9
+ msisdn: string;
10
+ token: string;
11
+ }) =>
12
+ new Promise<{
13
+ statusCode: number;
14
+ cards: Array<MasterpassResponseCardType>;
15
+ description: string;
16
+ }>((resolve) => {
17
+ try {
18
+ window.MFS?.listCards(
19
+ msisdn,
20
+ token,
21
+ (statusCode: number, response: CheckMasterpassResponse) => {
22
+ resolve({
23
+ statusCode,
24
+ cards: response.cards,
25
+ description: response.responseDescription
26
+ });
27
+ }
28
+ );
29
+ } catch (error) {
30
+ console.error(error);
31
+ }
32
+ });
@@ -0,0 +1,177 @@
1
+ import { CompleteCreditCardParams } from '@akinon/next/data/client/checkout';
2
+ import { MasterpassResponseCardType } from '../types';
3
+ import { getSession } from 'next-auth/react';
4
+
5
+ type Field = {
6
+ name: string;
7
+ value: string;
8
+ };
9
+
10
+ interface FromCreator {
11
+ id: string;
12
+ fields: Field[];
13
+ }
14
+
15
+ const setMFSParams = ({ amount }: { amount: string }) => {
16
+ window.MFS.setAdditionalParameters({
17
+ order_product_name_arr: ['TEST'],
18
+ order_product_code_arr: ['TST'],
19
+ order_price_arr: [amount],
20
+ order_qty_arr: ['1'],
21
+ order_product_info_arr: ['test']
22
+ });
23
+ };
24
+
25
+ export const formCreator = (args: FromCreator) => {
26
+ const { id, fields } = args;
27
+
28
+ const form = document.createElement('form');
29
+ form.setAttribute('method', 'POST');
30
+ form.setAttribute('id', id);
31
+
32
+ fields.forEach((field) => {
33
+ const input = document.createElement('input');
34
+ input.setAttribute('type', 'hidden');
35
+ input.setAttribute('name', field.name);
36
+ input.setAttribute('value', field.value);
37
+ form.appendChild(input);
38
+ });
39
+
40
+ return form;
41
+ };
42
+
43
+ export const getCreditCardType = (number: string) => {
44
+ if (/^5[1-5]/.test(number)) {
45
+ return 'mastercard';
46
+ }
47
+
48
+ if (/^4/.test(number)) {
49
+ return 'visa';
50
+ }
51
+
52
+ if (/^3[47]/.test(number)) {
53
+ return 'amex';
54
+ }
55
+
56
+ if (/^(?:9792|65\d{2}|36|2205)/.test(number)) {
57
+ return 'troy';
58
+ }
59
+
60
+ return 'other';
61
+ };
62
+
63
+ export const buildPurchaseForm = async ({
64
+ token,
65
+ orderNo,
66
+ msisdn,
67
+ referenceNo,
68
+ merchantId,
69
+ selectedCard,
70
+ amount
71
+ }: {
72
+ token: string;
73
+ orderNo: string;
74
+ msisdn: string;
75
+ referenceNo: string;
76
+ merchantId: string;
77
+ selectedCard: MasterpassResponseCardType;
78
+ amount: string;
79
+ }) => {
80
+ const session = await getSession();
81
+
82
+ setMFSParams({ amount });
83
+
84
+ const fields = [
85
+ {
86
+ name: 'token',
87
+ value: token
88
+ },
89
+ {
90
+ name: 'orderNo',
91
+ value: orderNo
92
+ },
93
+ {
94
+ name: 'referenceNo',
95
+ value: referenceNo
96
+ },
97
+ { name: 'amount', value: amount },
98
+ { name: 'userId', value: String(session?.user?.pk ?? '') },
99
+ { name: 'msisdn', value: msisdn },
100
+ { name: 'installmentCount', value: '' },
101
+ { name: 'macroMerchantId', value: merchantId },
102
+ { name: 'sendSmsLanguage', value: 'eng' },
103
+ { name: 'sendSms', value: 'N' },
104
+ { name: 'cardName', value: selectedCard?.Name ?? '' },
105
+ { name: 'listAccountName', value: selectedCard?.Name ?? '' },
106
+ { name: 'cardAliasName', value: selectedCard?.Name ?? '' }
107
+ ];
108
+
109
+ const form = formCreator({
110
+ id: 'mp-purchase-form',
111
+ fields
112
+ });
113
+
114
+ return form;
115
+ };
116
+
117
+ export const buildDirectPurchaseForm = async (
118
+ {
119
+ token,
120
+ orderNo,
121
+ msisdn,
122
+ referenceNo,
123
+ merchantId,
124
+ amount
125
+ }: {
126
+ token: string;
127
+ orderNo: string;
128
+ msisdn: string;
129
+ referenceNo: string;
130
+ merchantId: string;
131
+ amount: string;
132
+ },
133
+ params?: CompleteCreditCardParams
134
+ ) => {
135
+ const session = await getSession();
136
+
137
+ setMFSParams({ amount });
138
+
139
+ const rtaPan = params.card_number;
140
+ const expiryDate =
141
+ params.card_year.slice(-2) + params.card_month.padStart(2, '0');
142
+ const cardHolderName = params.card_holder;
143
+ const cvc = params.card_cvv;
144
+
145
+ const fields = [
146
+ {
147
+ name: 'token',
148
+ value: token
149
+ },
150
+ {
151
+ name: 'orderNo',
152
+ value: orderNo
153
+ },
154
+ {
155
+ name: 'referenceNo',
156
+ value: referenceNo
157
+ },
158
+ { name: 'amount', value: amount },
159
+ { name: 'userId', value: String(session?.user?.pk ?? '') },
160
+ { name: 'msisdn', value: msisdn },
161
+ { name: 'installmentCount', value: '' },
162
+ { name: 'macroMerchantId', value: merchantId },
163
+ { name: 'sendSmsLanguage', value: 'eng' },
164
+ { name: 'sendSms', value: 'N' },
165
+ { name: 'rtaPan', value: rtaPan },
166
+ { name: 'expiryDate', value: expiryDate },
167
+ { name: 'cardHolderName', value: cardHolderName },
168
+ { name: 'cvc', value: cvc }
169
+ ];
170
+
171
+ const form = formCreator({
172
+ id: 'mp-direct-purchase-form',
173
+ fields
174
+ });
175
+
176
+ return form;
177
+ };
@@ -0,0 +1,17 @@
1
+ import { buildClientRequestUrl } from '@akinon/next/utils';
2
+ import { MasterpassCredentials } from '../types';
3
+
4
+ export const init = () =>
5
+ new Promise<MasterpassCredentials>(async (resolve) => {
6
+ try {
7
+ const response = await fetch(buildClientRequestUrl('/masterpass-js'));
8
+ const data: MasterpassCredentials = await response.json();
9
+
10
+ window.MFS.setClientId(data.client_id);
11
+ window.MFS.setAddress(data.js);
12
+
13
+ resolve(data);
14
+ } catch (error) {
15
+ console.error(error);
16
+ }
17
+ });