@akinon/pz-masterpass-rest 2.0.0-beta.12 → 2.0.0-beta.13
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 +21 -0
- package/README.md +11 -1
- package/assets/masterpass-javascript-sdk-web.min.js +1 -0
- package/docs/USAGE.md +1176 -0
- package/package.json +3 -3
- package/src/components/credit-card-form.tsx +1 -1
- package/src/components/information-modal.tsx +139 -0
- package/src/components/otp-modal.tsx +94 -12
- package/src/hooks/useMasterpassAccount.ts +77 -37
- package/src/hooks/useMasterpassPayment.ts +248 -123
- package/src/hooks/useMasterpassToken.ts +61 -16
- package/src/redux/api.ts +16 -7
- package/src/redux/reducer.ts +14 -0
- package/src/services/account.ts +73 -64
- package/src/services/payment.ts +131 -93
- package/src/services/verify.ts +44 -44
- package/src/types/custom-render.types.ts +22 -2
- package/src/types/payment.types.ts +14 -0
- package/src/utils/payment-constants.ts +3 -3
- package/src/utils/payment-utils.ts +20 -4
- package/src/utils/response-handler.ts +35 -6
- package/src/utils/session-handler.ts +60 -0
- package/src/utils/validation-schemas.ts +1 -1
- package/src/views/masterpass-rest-option.tsx +55 -30
package/docs/USAGE.md
ADDED
|
@@ -0,0 +1,1176 @@
|
|
|
1
|
+
# Masterpass REST - Usage Guide
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Installation](#installation)
|
|
6
|
+
- [SDK Setup](#sdk-setup)
|
|
7
|
+
- [Basic Usage](#basic-usage)
|
|
8
|
+
- [Props Reference](#props-reference)
|
|
9
|
+
- [Custom Rendering](#custom-rendering)
|
|
10
|
+
- [Available Custom Render Slots](#available-custom-render-slots)
|
|
11
|
+
- [PaymentMethodSelector](#1-paymentmethodselector)
|
|
12
|
+
- [CardList](#2-cardlist)
|
|
13
|
+
- [CreditCardForm](#3-creditcardform)
|
|
14
|
+
- [InstallmentList](#4-installmentlist)
|
|
15
|
+
- [LinkModal](#5-linkmodal)
|
|
16
|
+
- [OTPModal](#6-otpmodal)
|
|
17
|
+
- [ConfirmationModal](#7-confirmationmodal)
|
|
18
|
+
- [ErrorDisplay](#8-errordisplay)
|
|
19
|
+
- [LoadingState](#9-loadingstate)
|
|
20
|
+
- [EmptyState](#10-emptystate)
|
|
21
|
+
- [Full Render](#11-fullrender)
|
|
22
|
+
- [Text Customization](#text-customization)
|
|
23
|
+
- [Exported Hooks](#exported-hooks)
|
|
24
|
+
- [Exported Utilities](#exported-utilities)
|
|
25
|
+
- [Type Definitions](#type-definitions)
|
|
26
|
+
- [Payment Flow](#payment-flow)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx @akinon/projectzero@latest --plugins
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## SDK Setup
|
|
37
|
+
|
|
38
|
+
Copy the Masterpass JavaScript SDK to your project's `public` folder:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cp node_modules/@akinon/pz-masterpass-rest/assets/masterpass-javascript-sdk-web.min.js public/
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The SDK file must be accessible at `/masterpass-javascript-sdk-web.min.js` in your application.
|
|
45
|
+
|
|
46
|
+
## Basic Usage
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
// src/views/checkout/steps/payment/options/masterpass-rest.tsx
|
|
50
|
+
import { useLocalization } from '@akinon/next/hooks'
|
|
51
|
+
import PluginModule, { Component } from '@akinon/next/components/plugin-module'
|
|
52
|
+
|
|
53
|
+
const MasterpassRest = () => {
|
|
54
|
+
const { locale, currency } = useLocalization()
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<PluginModule
|
|
58
|
+
component={Component.MasterpassRest}
|
|
59
|
+
props={{ locale, currency }}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default MasterpassRest
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Props Reference
|
|
68
|
+
|
|
69
|
+
`MasterpassRestOption` is the main component. It accepts:
|
|
70
|
+
|
|
71
|
+
| Prop | Type | Default | Description |
|
|
72
|
+
|------|------|---------|-------------|
|
|
73
|
+
| `locale` | `string` | `'en'` | Language locale (`'en'`, `'tr'`) |
|
|
74
|
+
| `currency` | `string` | `'usd'` | Currency code (`'usd'`, `'try'`) |
|
|
75
|
+
| `sdkUrl` | `string` | `undefined` | Custom Masterpass SDK URL |
|
|
76
|
+
| `environment` | `'production' \| 'development'` | `undefined` | SDK environment |
|
|
77
|
+
| `texts` | `MasterpassRestOptionTexts` | `defaultTexts` | UI text overrides for localization |
|
|
78
|
+
| `customRender` | `MasterpassRestOptionCustomRender` | `undefined` | Custom render functions |
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<PluginModule
|
|
82
|
+
component={Component.MasterpassRest}
|
|
83
|
+
props={{
|
|
84
|
+
locale: 'tr',
|
|
85
|
+
currency: 'try',
|
|
86
|
+
environment: 'production',
|
|
87
|
+
texts: { title: 'Masterpass ile Ode' },
|
|
88
|
+
customRender: { /* ... */ }
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Custom Rendering
|
|
96
|
+
|
|
97
|
+
The `customRender` prop lets you override individual UI sections or the entire component. Each render function receives the same props as the default component, so you have full access to state and handlers.
|
|
98
|
+
|
|
99
|
+
### Available Custom Render Slots
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
type MasterpassRestOptionCustomRender = {
|
|
103
|
+
paymentMethodSelector?: (props: PaymentMethodSelectorProps) => ReactElement
|
|
104
|
+
cardList?: (props: CardListProps) => ReactElement
|
|
105
|
+
creditCardForm?: (props: CreditCardFormProps) => ReactElement
|
|
106
|
+
installmentList?: (props: InstallmentListProps) => ReactElement
|
|
107
|
+
linkModal?: (props: LinkModalProps) => ReactElement
|
|
108
|
+
otpModal?: (props: OTPModalProps) => ReactElement
|
|
109
|
+
confirmationModal?: (props: ConfirmationModalProps) => ReactElement
|
|
110
|
+
errorDisplay?: (props: ErrorDisplayProps) => ReactElement
|
|
111
|
+
loadingState?: (props: LoadingStateProps) => ReactElement
|
|
112
|
+
emptyState?: (props: EmptyStateProps) => ReactElement
|
|
113
|
+
fullRender?: (props: MasterpassRestOptionRenderProps) => ReactElement
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### 1. PaymentMethodSelector
|
|
120
|
+
|
|
121
|
+
Toggles between saved cards and new card entry. Only rendered when the user has stored cards.
|
|
122
|
+
|
|
123
|
+
**Props:**
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
type PaymentMethodSelectorProps = {
|
|
127
|
+
cards: any[] // User's stored cards array
|
|
128
|
+
selectedMethod: 'stored_card' | 'new_card' // Currently active method
|
|
129
|
+
onMethodChange: (method: 'stored_card' | 'new_card') => void // Switch handler
|
|
130
|
+
texts: MasterpassRestOptionTexts // Merged text strings
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Example:**
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
<PluginModule
|
|
138
|
+
component={Component.MasterpassRest}
|
|
139
|
+
props={{
|
|
140
|
+
locale,
|
|
141
|
+
currency,
|
|
142
|
+
customRender: {
|
|
143
|
+
paymentMethodSelector: ({ cards, selectedMethod, onMethodChange, texts }) => (
|
|
144
|
+
<div className="flex gap-4 border-b pb-4">
|
|
145
|
+
<button
|
|
146
|
+
className={selectedMethod === 'stored_card' ? 'font-bold border-b-2 border-primary' : 'text-gray-500'}
|
|
147
|
+
onClick={() => onMethodChange('stored_card')}
|
|
148
|
+
>
|
|
149
|
+
{texts.savedCardsText} ({cards.length})
|
|
150
|
+
</button>
|
|
151
|
+
<button
|
|
152
|
+
className={selectedMethod === 'new_card' ? 'font-bold border-b-2 border-primary' : 'text-gray-500'}
|
|
153
|
+
onClick={() => onMethodChange('new_card')}
|
|
154
|
+
>
|
|
155
|
+
{texts.newCardText}
|
|
156
|
+
</button>
|
|
157
|
+
</div>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
}}
|
|
161
|
+
/>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### 2. CardList
|
|
167
|
+
|
|
168
|
+
Displays the user's saved cards with selection, CVC input, and remove functionality.
|
|
169
|
+
|
|
170
|
+
**Props:**
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
type CardListProps = {
|
|
174
|
+
cards: any[] // Array of CardModel objects
|
|
175
|
+
onCardSelect: (card: any) => void // Called when a card is selected
|
|
176
|
+
selectedCard?: any | null // Currently selected card
|
|
177
|
+
onRemove?: (card: any) => void // Called to initiate card removal
|
|
178
|
+
removingCardId?: string | null // Card being removed (shows loader)
|
|
179
|
+
cvc?: string // Current CVC input value
|
|
180
|
+
onCvcChange?: (cvc: string) => void // CVC input change handler
|
|
181
|
+
cvcRequired?: boolean // Whether CVC field is shown
|
|
182
|
+
texts: MasterpassRestOptionTexts
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**CardModel structure (each item in `cards` array):**
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
interface CardModel {
|
|
190
|
+
cardAlias: string // 'My Visa Card'
|
|
191
|
+
maskedCardNumber: string // '534261******1234'
|
|
192
|
+
uniqueCardNumber: string // Unique identifier
|
|
193
|
+
cardType: 'Credit' | 'Debit' | 'Unknown' | ''
|
|
194
|
+
cardBin: string // First 6 digits
|
|
195
|
+
isDefaultCard: boolean
|
|
196
|
+
expireSoon: boolean
|
|
197
|
+
isExpired: boolean
|
|
198
|
+
cardValidationType: 'OTP' | 'RTA' | '_3D' | 'Unknown' | ''
|
|
199
|
+
// ...more fields
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Example:**
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
customRender: {
|
|
207
|
+
cardList: ({ cards, onCardSelect, selectedCard, onRemove, cvc, onCvcChange, texts }) => (
|
|
208
|
+
<div className="space-y-2">
|
|
209
|
+
{cards.map((card) => (
|
|
210
|
+
<div
|
|
211
|
+
key={card.uniqueCardNumber}
|
|
212
|
+
className={`p-4 border cursor-pointer ${
|
|
213
|
+
selectedCard?.uniqueCardNumber === card.uniqueCardNumber
|
|
214
|
+
? 'border-primary bg-primary/5'
|
|
215
|
+
: 'border-gray-200'
|
|
216
|
+
}`}
|
|
217
|
+
onClick={() => onCardSelect(card)}
|
|
218
|
+
>
|
|
219
|
+
<div className="flex justify-between items-center">
|
|
220
|
+
<div>
|
|
221
|
+
<span className="font-medium">{card.cardAlias}</span>
|
|
222
|
+
<span className="ml-2 text-gray-500">{card.maskedCardNumber}</span>
|
|
223
|
+
{card.isDefaultCard && (
|
|
224
|
+
<span className="ml-2 text-xs bg-green-100 text-green-700 px-2 py-0.5">
|
|
225
|
+
{texts.defaultCardText}
|
|
226
|
+
</span>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
<button
|
|
230
|
+
onClick={(e) => { e.stopPropagation(); onRemove?.(card) }}
|
|
231
|
+
className="text-red-500 text-sm"
|
|
232
|
+
>
|
|
233
|
+
Remove
|
|
234
|
+
</button>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
{/* CVC input for selected card */}
|
|
238
|
+
{selectedCard?.uniqueCardNumber === card.uniqueCardNumber && (
|
|
239
|
+
<input
|
|
240
|
+
type="text"
|
|
241
|
+
maxLength={4}
|
|
242
|
+
value={cvc || ''}
|
|
243
|
+
onChange={(e) => onCvcChange?.(e.target.value)}
|
|
244
|
+
placeholder={texts.cvcPlaceholder3}
|
|
245
|
+
className="mt-2 border px-2 py-1 w-20"
|
|
246
|
+
/>
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
))}
|
|
250
|
+
</div>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
### 3. CreditCardForm
|
|
258
|
+
|
|
259
|
+
The new card entry form with validation, BIN checking, and optional card saving.
|
|
260
|
+
|
|
261
|
+
**Props:**
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
type CreditCardFormProps = {
|
|
265
|
+
onSaveCard?: (data: CreditCardFormData) => void // Submit handler
|
|
266
|
+
isLoading?: boolean // Disables form during API calls
|
|
267
|
+
showSaveOption?: boolean // Shows "save card" checkbox
|
|
268
|
+
initialValues?: Partial<CreditCardFormData> // Pre-fill form fields
|
|
269
|
+
onBinChange?: (bin: string) => Promise<void> // Called with first 6 digits for installment lookup
|
|
270
|
+
texts: MasterpassRestOptionTexts
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**CreditCardFormData (form submit payload):**
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
interface CreditCardFormData {
|
|
278
|
+
cardNumber: string // '5342610000001234'
|
|
279
|
+
cardholderName: string // 'John Doe'
|
|
280
|
+
expiryDate: string // '12/27'
|
|
281
|
+
cvv: string // '123'
|
|
282
|
+
saveCard: boolean // true
|
|
283
|
+
cardAlias: string // 'My Card'
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Example:**
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
customRender: {
|
|
291
|
+
creditCardForm: ({ onSaveCard, isLoading, showSaveOption, onBinChange, texts }) => (
|
|
292
|
+
<MyCustomCardForm
|
|
293
|
+
onSubmit={(formData) => onSaveCard?.(formData)}
|
|
294
|
+
disabled={isLoading}
|
|
295
|
+
showSave={showSaveOption}
|
|
296
|
+
onCardNumberChange={(number) => {
|
|
297
|
+
// Trigger installment lookup when 6+ digits entered
|
|
298
|
+
const digits = number.replace(/\D/g, '')
|
|
299
|
+
if (digits.length >= 6) {
|
|
300
|
+
onBinChange?.(digits.substring(0, 6))
|
|
301
|
+
}
|
|
302
|
+
}}
|
|
303
|
+
labels={{
|
|
304
|
+
cardNumber: texts.cardNumberLabel,
|
|
305
|
+
name: texts.cardholderNameLabel,
|
|
306
|
+
expiry: texts.expiryDateLabel,
|
|
307
|
+
cvc: texts.cvcLabel,
|
|
308
|
+
submit: texts.addCardButton
|
|
309
|
+
}}
|
|
310
|
+
/>
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
> **Important:** When implementing a custom card form, you must call `onBinChange` with the first 6 digits of the card number to trigger installment options loading. The default form does this automatically.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
### 4. InstallmentList
|
|
320
|
+
|
|
321
|
+
Shows available installment options after a card/BIN is selected, plus the "Proceed to Payment" button.
|
|
322
|
+
|
|
323
|
+
**Props:**
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
type InstallmentListProps = {
|
|
327
|
+
installments: Installment[] // Available installment options
|
|
328
|
+
cardType: CardType | null // Detected card type info
|
|
329
|
+
onInstallmentSelect: (installment: Installment) => void
|
|
330
|
+
selectedInstallment?: Installment | null
|
|
331
|
+
isLoading?: boolean // Loading installments or preparing order
|
|
332
|
+
onProceedToPayment?: () => void // Triggers payment flow
|
|
333
|
+
paymentLoading?: boolean // Payment in progress
|
|
334
|
+
texts: MasterpassRestOptionTexts
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Installment structure:**
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
interface Installment {
|
|
342
|
+
pk: number
|
|
343
|
+
installment_count: number // 1 = single payment, 2+ = installments
|
|
344
|
+
label: string
|
|
345
|
+
price_with_accrued_interest: string // Total price (e.g. '1500.00')
|
|
346
|
+
monthly_price_with_accrued_interest: string // Monthly amount (e.g. '500.00')
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**CardType structure:**
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
interface CardType {
|
|
354
|
+
name: string // 'Visa'
|
|
355
|
+
slug: string // 'visa'
|
|
356
|
+
logo: string // URL to card logo
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**Example:**
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
customRender: {
|
|
364
|
+
installmentList: ({
|
|
365
|
+
installments,
|
|
366
|
+
cardType,
|
|
367
|
+
onInstallmentSelect,
|
|
368
|
+
selectedInstallment,
|
|
369
|
+
isLoading,
|
|
370
|
+
onProceedToPayment,
|
|
371
|
+
paymentLoading,
|
|
372
|
+
texts
|
|
373
|
+
}) => (
|
|
374
|
+
<div>
|
|
375
|
+
{cardType && (
|
|
376
|
+
<div className="mb-4 text-sm text-gray-500">
|
|
377
|
+
{texts.cardTypeLabel} {cardType.name}
|
|
378
|
+
</div>
|
|
379
|
+
)}
|
|
380
|
+
|
|
381
|
+
<div className="space-y-2">
|
|
382
|
+
{installments.map((inst) => (
|
|
383
|
+
<label
|
|
384
|
+
key={inst.pk}
|
|
385
|
+
className={`flex items-center justify-between p-3 border cursor-pointer ${
|
|
386
|
+
selectedInstallment?.pk === inst.pk ? 'border-primary' : 'border-gray-200'
|
|
387
|
+
}`}
|
|
388
|
+
>
|
|
389
|
+
<div className="flex items-center gap-2">
|
|
390
|
+
<input
|
|
391
|
+
type="radio"
|
|
392
|
+
checked={selectedInstallment?.pk === inst.pk}
|
|
393
|
+
onChange={() => onInstallmentSelect(inst)}
|
|
394
|
+
/>
|
|
395
|
+
<span>
|
|
396
|
+
{inst.installment_count === 1
|
|
397
|
+
? texts.singlePaymentText
|
|
398
|
+
: texts.installmentsText?.replace('{count}', String(inst.installment_count))}
|
|
399
|
+
</span>
|
|
400
|
+
</div>
|
|
401
|
+
<span className="font-medium">{inst.price_with_accrued_interest} TL</span>
|
|
402
|
+
</label>
|
|
403
|
+
))}
|
|
404
|
+
</div>
|
|
405
|
+
|
|
406
|
+
<button
|
|
407
|
+
onClick={onProceedToPayment}
|
|
408
|
+
disabled={!selectedInstallment || isLoading || paymentLoading}
|
|
409
|
+
className="w-full mt-4 py-3 bg-primary text-white disabled:opacity-50"
|
|
410
|
+
>
|
|
411
|
+
{paymentLoading ? texts.processingPaymentText : texts.proceedToPaymentText}
|
|
412
|
+
</button>
|
|
413
|
+
</div>
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
### 5. LinkModal
|
|
421
|
+
|
|
422
|
+
Shown when the user has a Masterpass account but it's not linked to the current merchant.
|
|
423
|
+
|
|
424
|
+
**Props:**
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
type LinkModalProps = {
|
|
428
|
+
open: boolean
|
|
429
|
+
onClose: () => void
|
|
430
|
+
onConfirm: () => void // Links account to merchant
|
|
431
|
+
texts: MasterpassRestOptionTexts
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Example:**
|
|
436
|
+
|
|
437
|
+
```tsx
|
|
438
|
+
customRender: {
|
|
439
|
+
linkModal: ({ open, onClose, onConfirm, texts }) => {
|
|
440
|
+
if (!open) return null
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
444
|
+
<div className="bg-white p-6 max-w-md">
|
|
445
|
+
<p className="mb-4">{texts.linkModalDescription}</p>
|
|
446
|
+
<div className="flex gap-3">
|
|
447
|
+
<button onClick={onConfirm} className="flex-1 bg-primary text-white py-2">
|
|
448
|
+
{texts.linkAccountButton}
|
|
449
|
+
</button>
|
|
450
|
+
<button onClick={onClose} className="flex-1 border py-2">
|
|
451
|
+
{texts.linkModalCancelButton}
|
|
452
|
+
</button>
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
### 6. OTPModal
|
|
464
|
+
|
|
465
|
+
Handles 3 types of verification: RTA (security verification), OTP (bank SMS), and CVV.
|
|
466
|
+
|
|
467
|
+
**Props:**
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
type OTPModalProps = {
|
|
471
|
+
open: boolean
|
|
472
|
+
onClose: () => void
|
|
473
|
+
onSubmit: (otp: string) => Promise<{
|
|
474
|
+
success: boolean
|
|
475
|
+
requiresOTP?: boolean
|
|
476
|
+
message?: string
|
|
477
|
+
}>
|
|
478
|
+
type: 'RTA' | 'OTP' | 'CVV' // Determines labels/placeholders
|
|
479
|
+
responseCode?: string
|
|
480
|
+
description?: string
|
|
481
|
+
texts: MasterpassRestOptionTexts
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**Example:**
|
|
486
|
+
|
|
487
|
+
```tsx
|
|
488
|
+
customRender: {
|
|
489
|
+
otpModal: ({ open, onClose, onSubmit, type, texts }) => {
|
|
490
|
+
if (!open) return null
|
|
491
|
+
|
|
492
|
+
const titles = {
|
|
493
|
+
RTA: texts.rtaVerificationTitle,
|
|
494
|
+
OTP: texts.bankOtpVerificationTitle,
|
|
495
|
+
CVV: texts.cvvVerificationTitle
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const descriptions = {
|
|
499
|
+
RTA: texts.rtaVerificationDescription,
|
|
500
|
+
OTP: texts.bankOtpVerificationDescription,
|
|
501
|
+
CVV: texts.cvvVerificationDescription
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
let inputValue = ''
|
|
505
|
+
|
|
506
|
+
const handleSubmit = async () => {
|
|
507
|
+
const result = await onSubmit(inputValue)
|
|
508
|
+
if (!result.success && result.message) {
|
|
509
|
+
alert(result.message)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return (
|
|
514
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
515
|
+
<div className="bg-white p-6 max-w-sm w-full">
|
|
516
|
+
<h3 className="font-bold text-lg mb-2">{titles[type]}</h3>
|
|
517
|
+
<p className="text-sm text-gray-600 mb-4">{descriptions[type]}</p>
|
|
518
|
+
<input
|
|
519
|
+
type="text"
|
|
520
|
+
maxLength={type === 'CVV' ? 4 : 6}
|
|
521
|
+
onChange={(e) => { inputValue = e.target.value }}
|
|
522
|
+
className="w-full border px-3 py-2 mb-4"
|
|
523
|
+
placeholder={
|
|
524
|
+
type === 'CVV'
|
|
525
|
+
? texts.cvvVerificationPlaceholder
|
|
526
|
+
: texts.rtaVerificationPlaceholder
|
|
527
|
+
}
|
|
528
|
+
/>
|
|
529
|
+
<div className="flex gap-3">
|
|
530
|
+
<button onClick={handleSubmit} className="flex-1 bg-primary text-white py-2">
|
|
531
|
+
{texts.verifyButton}
|
|
532
|
+
</button>
|
|
533
|
+
<button onClick={onClose} className="flex-1 border py-2">
|
|
534
|
+
{texts.otpModalCancelButton}
|
|
535
|
+
</button>
|
|
536
|
+
</div>
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
### 7. ConfirmationModal
|
|
547
|
+
|
|
548
|
+
Used for card removal confirmation. Receives the card alias in the message.
|
|
549
|
+
|
|
550
|
+
**Props:**
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
type ConfirmationModalProps = {
|
|
554
|
+
open: boolean
|
|
555
|
+
onClose: () => void
|
|
556
|
+
onConfirm: () => void
|
|
557
|
+
title: React.ReactNode | string // Can be JSX (default: Masterpass logo image)
|
|
558
|
+
message: string // Pre-formatted with card alias
|
|
559
|
+
confirmText?: string
|
|
560
|
+
cancelText?: string
|
|
561
|
+
isLoading?: boolean
|
|
562
|
+
loadingText?: string
|
|
563
|
+
texts: MasterpassRestOptionTexts
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**Example:**
|
|
568
|
+
|
|
569
|
+
```tsx
|
|
570
|
+
customRender: {
|
|
571
|
+
confirmationModal: ({ open, onClose, onConfirm, title, message, isLoading, texts }) => {
|
|
572
|
+
if (!open) return null
|
|
573
|
+
|
|
574
|
+
return (
|
|
575
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
576
|
+
<div className="bg-white p-6 max-w-sm">
|
|
577
|
+
<div className="mb-4">{title}</div>
|
|
578
|
+
<p className="mb-4">{message}</p>
|
|
579
|
+
<div className="flex gap-3">
|
|
580
|
+
<button
|
|
581
|
+
onClick={onConfirm}
|
|
582
|
+
disabled={isLoading}
|
|
583
|
+
className="flex-1 bg-red-600 text-white py-2"
|
|
584
|
+
>
|
|
585
|
+
{isLoading ? texts.removeCardLoadingText : texts.removeCardConfirmText}
|
|
586
|
+
</button>
|
|
587
|
+
<button onClick={onClose} className="flex-1 border py-2">
|
|
588
|
+
{texts.removeCardCancelText}
|
|
589
|
+
</button>
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
</div>
|
|
593
|
+
)
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
### 8. ErrorDisplay
|
|
601
|
+
|
|
602
|
+
Shown at the bottom of the payment area when an error occurs.
|
|
603
|
+
|
|
604
|
+
**Props:**
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
type ErrorDisplayProps = {
|
|
608
|
+
error: any
|
|
609
|
+
onDismiss: () => void
|
|
610
|
+
getErrorInfo: (error: any) => { message: string; type: string } | null
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Example:**
|
|
615
|
+
|
|
616
|
+
```tsx
|
|
617
|
+
customRender: {
|
|
618
|
+
errorDisplay: ({ error, onDismiss, getErrorInfo }) => {
|
|
619
|
+
const info = getErrorInfo(error)
|
|
620
|
+
if (!info) return null
|
|
621
|
+
|
|
622
|
+
return (
|
|
623
|
+
<div className="mt-4 p-4 bg-red-50 border border-red-200 flex justify-between items-start">
|
|
624
|
+
<div>
|
|
625
|
+
<h4 className="font-semibold text-red-800">{info.type}</h4>
|
|
626
|
+
<p className="text-sm text-red-700">{info.message}</p>
|
|
627
|
+
</div>
|
|
628
|
+
<button onClick={onDismiss} className="text-red-500 text-xl leading-none">×</button>
|
|
629
|
+
</div>
|
|
630
|
+
)
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
### 9. LoadingState
|
|
638
|
+
|
|
639
|
+
Shown during initial token fetch and SDK loading.
|
|
640
|
+
|
|
641
|
+
**Props:**
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
type LoadingStateProps = {
|
|
645
|
+
message: string // Either texts.loadingMessage or texts.scriptLoadingMessage
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
**Example:**
|
|
650
|
+
|
|
651
|
+
```tsx
|
|
652
|
+
customRender: {
|
|
653
|
+
loadingState: ({ message }) => (
|
|
654
|
+
<div className="flex items-center justify-center h-48">
|
|
655
|
+
<div className="animate-pulse text-center">
|
|
656
|
+
<div className="w-12 h-12 border-4 border-primary border-t-transparent rounded-full animate-spin mx-auto" />
|
|
657
|
+
<p className="mt-3 text-gray-500">{message}</p>
|
|
658
|
+
</div>
|
|
659
|
+
</div>
|
|
660
|
+
)
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
### 10. EmptyState
|
|
667
|
+
|
|
668
|
+
Shown in the installment area when no installments are available yet.
|
|
669
|
+
|
|
670
|
+
**Props:**
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
type EmptyStateProps = {
|
|
674
|
+
paymentMethod: 'stored_card' | 'new_card'
|
|
675
|
+
cardNumberLength: number // Number of digits entered (for new card)
|
|
676
|
+
isCheckoutLoading: boolean
|
|
677
|
+
}
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
**Example:**
|
|
681
|
+
|
|
682
|
+
```tsx
|
|
683
|
+
customRender: {
|
|
684
|
+
emptyState: ({ paymentMethod, cardNumberLength }) => (
|
|
685
|
+
<div className="flex items-center justify-center h-64 border-2 border-dashed border-gray-200">
|
|
686
|
+
<div className="text-center text-gray-400">
|
|
687
|
+
{paymentMethod === 'stored_card'
|
|
688
|
+
? 'Select a card to view installment options'
|
|
689
|
+
: cardNumberLength < 6
|
|
690
|
+
? `Enter ${6 - cardNumberLength} more digits to see installments`
|
|
691
|
+
: 'No installment options available'}
|
|
692
|
+
</div>
|
|
693
|
+
</div>
|
|
694
|
+
)
|
|
695
|
+
}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
### 11. FullRender
|
|
701
|
+
|
|
702
|
+
Takes over the entire component rendering. You get all state, handlers, and default components as props.
|
|
703
|
+
|
|
704
|
+
**Props:**
|
|
705
|
+
|
|
706
|
+
```typescript
|
|
707
|
+
type MasterpassRestOptionRenderProps = {
|
|
708
|
+
// State
|
|
709
|
+
paymentMethod: 'stored_card' | 'new_card'
|
|
710
|
+
hasStoredCards: boolean
|
|
711
|
+
shouldShowDirectForm: boolean
|
|
712
|
+
cvc: string
|
|
713
|
+
error: any
|
|
714
|
+
|
|
715
|
+
// Data
|
|
716
|
+
accountData: AccountAccessSuccessResponse | null
|
|
717
|
+
accountStatus: any
|
|
718
|
+
modalState: ModalState
|
|
719
|
+
paymentState: PaymentState
|
|
720
|
+
|
|
721
|
+
// Handlers
|
|
722
|
+
handlePaymentMethodChange: (method: 'stored_card' | 'new_card') => void
|
|
723
|
+
handleCardSelect: (card: any) => Promise<void>
|
|
724
|
+
updateModalState: (updates: Partial<ModalState>) => void
|
|
725
|
+
handleRemoveCard: (card: any) => void
|
|
726
|
+
confirmRemoveCard: () => Promise<void>
|
|
727
|
+
handleCvcChange: (cvc: string) => void
|
|
728
|
+
handleSaveCard: (cardData: CreditCardFormData) => Promise<void>
|
|
729
|
+
handleBinChange: (bin: string) => Promise<void>
|
|
730
|
+
handleInstallmentSelect: (installment: Installment) => Promise<void>
|
|
731
|
+
handleProceedToPayment: () => Promise<void>
|
|
732
|
+
handleLinkConfirm: () => Promise<void>
|
|
733
|
+
handleOTPSubmit: (otp: string) => Promise<{ success: boolean; message?: string }>
|
|
734
|
+
onCloseError: () => void
|
|
735
|
+
|
|
736
|
+
// Loading States
|
|
737
|
+
isCheckoutLoading: boolean
|
|
738
|
+
isInstallmentLoading: boolean
|
|
739
|
+
isPrepareLoading: boolean
|
|
740
|
+
isFinalizeLoading: boolean
|
|
741
|
+
|
|
742
|
+
// Texts
|
|
743
|
+
texts: MasterpassRestOptionTexts
|
|
744
|
+
|
|
745
|
+
// Default Components (use these to avoid reimplementing everything)
|
|
746
|
+
components: {
|
|
747
|
+
PaymentMethodSelector: React.ComponentType<PaymentMethodSelectorProps>
|
|
748
|
+
CardList: React.ComponentType<CardListProps>
|
|
749
|
+
CreditCardForm: React.ComponentType<CreditCardFormProps>
|
|
750
|
+
InstallmentList: React.ComponentType<InstallmentListProps>
|
|
751
|
+
LinkModal: React.ComponentType<LinkModalProps>
|
|
752
|
+
OTPModal: React.ComponentType<OTPModalProps>
|
|
753
|
+
ConfirmationModal: React.ComponentType<ConfirmationModalProps>
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
**Example - Custom layout with default components:**
|
|
759
|
+
|
|
760
|
+
```tsx
|
|
761
|
+
customRender: {
|
|
762
|
+
fullRender: (props) => {
|
|
763
|
+
const {
|
|
764
|
+
paymentMethod,
|
|
765
|
+
hasStoredCards,
|
|
766
|
+
shouldShowDirectForm,
|
|
767
|
+
accountData,
|
|
768
|
+
paymentState,
|
|
769
|
+
modalState,
|
|
770
|
+
handlePaymentMethodChange,
|
|
771
|
+
handleCardSelect,
|
|
772
|
+
handleCvcChange,
|
|
773
|
+
handleSaveCard,
|
|
774
|
+
handleBinChange,
|
|
775
|
+
handleInstallmentSelect,
|
|
776
|
+
handleProceedToPayment,
|
|
777
|
+
handleLinkConfirm,
|
|
778
|
+
handleOTPSubmit,
|
|
779
|
+
handleRemoveCard,
|
|
780
|
+
confirmRemoveCard,
|
|
781
|
+
updateModalState,
|
|
782
|
+
cvc,
|
|
783
|
+
isCheckoutLoading,
|
|
784
|
+
isInstallmentLoading,
|
|
785
|
+
isPrepareLoading,
|
|
786
|
+
isFinalizeLoading,
|
|
787
|
+
texts,
|
|
788
|
+
components
|
|
789
|
+
} = props
|
|
790
|
+
|
|
791
|
+
const { CardList, CreditCardForm, InstallmentList, LinkModal, OTPModal } = components
|
|
792
|
+
|
|
793
|
+
return (
|
|
794
|
+
<div className="my-custom-layout">
|
|
795
|
+
{/* Your custom header */}
|
|
796
|
+
<h1 className="text-2xl mb-6">Secure Payment</h1>
|
|
797
|
+
|
|
798
|
+
{/* Use default components with custom layout */}
|
|
799
|
+
{hasStoredCards && !shouldShowDirectForm && (
|
|
800
|
+
<div className="tabs mb-4">
|
|
801
|
+
<button onClick={() => handlePaymentMethodChange('stored_card')}>
|
|
802
|
+
Saved Cards
|
|
803
|
+
</button>
|
|
804
|
+
<button onClick={() => handlePaymentMethodChange('new_card')}>
|
|
805
|
+
New Card
|
|
806
|
+
</button>
|
|
807
|
+
</div>
|
|
808
|
+
)}
|
|
809
|
+
|
|
810
|
+
<div className="flex gap-8">
|
|
811
|
+
<div className="flex-1">
|
|
812
|
+
{paymentMethod === 'stored_card' && hasStoredCards ? (
|
|
813
|
+
<CardList
|
|
814
|
+
cards={accountData?.result?.cards || []}
|
|
815
|
+
onCardSelect={handleCardSelect}
|
|
816
|
+
selectedCard={paymentState.selectedCard}
|
|
817
|
+
onRemove={handleRemoveCard}
|
|
818
|
+
removingCardId={modalState?.removingCardId}
|
|
819
|
+
cvc={cvc}
|
|
820
|
+
onCvcChange={handleCvcChange}
|
|
821
|
+
texts={texts}
|
|
822
|
+
/>
|
|
823
|
+
) : (
|
|
824
|
+
<CreditCardForm
|
|
825
|
+
onSaveCard={handleSaveCard}
|
|
826
|
+
isLoading={isCheckoutLoading}
|
|
827
|
+
showSaveOption={true}
|
|
828
|
+
onBinChange={handleBinChange}
|
|
829
|
+
texts={texts}
|
|
830
|
+
/>
|
|
831
|
+
)}
|
|
832
|
+
</div>
|
|
833
|
+
|
|
834
|
+
<div className="flex-1">
|
|
835
|
+
{paymentState.installments.length > 0 && (
|
|
836
|
+
<InstallmentList
|
|
837
|
+
installments={paymentState.installments}
|
|
838
|
+
cardType={paymentState.cardType}
|
|
839
|
+
onInstallmentSelect={handleInstallmentSelect}
|
|
840
|
+
selectedInstallment={paymentState.selectedInstallment}
|
|
841
|
+
isLoading={isInstallmentLoading || isPrepareLoading}
|
|
842
|
+
onProceedToPayment={handleProceedToPayment}
|
|
843
|
+
paymentLoading={isFinalizeLoading}
|
|
844
|
+
texts={texts}
|
|
845
|
+
/>
|
|
846
|
+
)}
|
|
847
|
+
</div>
|
|
848
|
+
</div>
|
|
849
|
+
|
|
850
|
+
{/* Modals - use default components */}
|
|
851
|
+
<LinkModal
|
|
852
|
+
open={modalState?.showLinkModal ?? false}
|
|
853
|
+
onClose={() => updateModalState({ showLinkModal: false })}
|
|
854
|
+
onConfirm={handleLinkConfirm}
|
|
855
|
+
texts={texts}
|
|
856
|
+
/>
|
|
857
|
+
<OTPModal
|
|
858
|
+
open={modalState?.showOTPModal ?? false}
|
|
859
|
+
onClose={() => updateModalState({ showOTPModal: false })}
|
|
860
|
+
onSubmit={handleOTPSubmit}
|
|
861
|
+
type={modalState?.otpType ?? 'OTP'}
|
|
862
|
+
texts={texts}
|
|
863
|
+
/>
|
|
864
|
+
</div>
|
|
865
|
+
)
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
---
|
|
871
|
+
|
|
872
|
+
## Text Customization
|
|
873
|
+
|
|
874
|
+
All user-facing strings can be overridden via the `texts` prop. Pass only the keys you want to change; the rest will use defaults.
|
|
875
|
+
|
|
876
|
+
```tsx
|
|
877
|
+
<PluginModule
|
|
878
|
+
component={Component.MasterpassRest}
|
|
879
|
+
props={{
|
|
880
|
+
locale: 'tr',
|
|
881
|
+
currency: 'try',
|
|
882
|
+
texts: {
|
|
883
|
+
// Titles
|
|
884
|
+
title: 'Masterpass ile Ode',
|
|
885
|
+
selectCardTitle: 'Kart Secin',
|
|
886
|
+
newCardTitle: 'Yeni Kart ile Ode',
|
|
887
|
+
installmentOptionsTitle: 'Taksit Secenekleri',
|
|
888
|
+
enterCardDetailsTitle: 'Kart Bilgilerini Girin',
|
|
889
|
+
|
|
890
|
+
// Form labels
|
|
891
|
+
cardNumberLabel: 'Kart Numarasi',
|
|
892
|
+
cardholderNameLabel: 'Kart Uzerindeki Isim',
|
|
893
|
+
expiryDateLabel: 'Son Kullanma Tarihi',
|
|
894
|
+
cvcLabel: 'CVC/CVV',
|
|
895
|
+
saveCardLabel: 'Bu karti gelecek odemeler icin kaydet',
|
|
896
|
+
addCardButton: 'Kart Ekle',
|
|
897
|
+
|
|
898
|
+
// Payment method
|
|
899
|
+
savedCardsText: 'Kayitli Kartlar',
|
|
900
|
+
newCardText: 'Yeni Kart',
|
|
901
|
+
|
|
902
|
+
// Installments
|
|
903
|
+
singlePaymentText: 'Tek Cekim',
|
|
904
|
+
installmentsText: '{count} Taksit', // {count} is replaced dynamically
|
|
905
|
+
noInterestText: 'Faizsiz',
|
|
906
|
+
proceedToPaymentText: 'Odemeye Devam Et',
|
|
907
|
+
processingPaymentText: 'Odeme Isleniyor...',
|
|
908
|
+
|
|
909
|
+
// Card list
|
|
910
|
+
defaultCardText: 'Varsayilan',
|
|
911
|
+
expiresSoonText: 'Suresi Yakinda Dolacak',
|
|
912
|
+
|
|
913
|
+
// Modals
|
|
914
|
+
linkModalDescription: '<PHONE_NUMBER> numarasi ile Masterpass hesabinizdaki kartlari gormek ister misiniz?',
|
|
915
|
+
linkAccountButton: 'Evet, istiyorum',
|
|
916
|
+
linkModalCancelButton: 'Hayir, istemiyorum',
|
|
917
|
+
removeCardMessage: '<{cardAlias}> kartini Masterpass altyapisindan silmek istediginize emin misiniz?',
|
|
918
|
+
|
|
919
|
+
// Verification
|
|
920
|
+
rtaVerificationTitle: 'Guvenlik Dogrulamasi',
|
|
921
|
+
bankOtpVerificationTitle: 'Banka Dogrulamasi',
|
|
922
|
+
cvvVerificationTitle: 'CVV Dogrulamasi',
|
|
923
|
+
verifyButton: 'Dogrula',
|
|
924
|
+
|
|
925
|
+
// Errors
|
|
926
|
+
paymentErrorTitle: 'Odeme Hatasi',
|
|
927
|
+
cardNumberRequiredText: 'Kart numarasi zorunludur',
|
|
928
|
+
cardNumberInvalidText: 'Gecerli bir kart numarasi girin',
|
|
929
|
+
|
|
930
|
+
// Loading
|
|
931
|
+
loadingMessage: 'Odeme sistemi hazirlaniyor...',
|
|
932
|
+
scriptLoadingMessage: 'Odeme altyapisi yukleniyor...',
|
|
933
|
+
|
|
934
|
+
// Session
|
|
935
|
+
sessionExpiredTitle: 'Oturum Suresi Doldu',
|
|
936
|
+
sessionExpiredMessage: 'Oturumunuz zaman asimina ugradi. Devam etmek icin islemi yeniden baslatin.',
|
|
937
|
+
sessionExpiredButton: 'Yeniden Basla'
|
|
938
|
+
}
|
|
939
|
+
}}
|
|
940
|
+
/>
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
### Dynamic Text Placeholders
|
|
944
|
+
|
|
945
|
+
Some text keys support placeholders:
|
|
946
|
+
|
|
947
|
+
| Key | Placeholder | Description |
|
|
948
|
+
|-----|------------|-------------|
|
|
949
|
+
| `installmentsText` | `{count}` | Installment count |
|
|
950
|
+
| `savedCardsDescription` | `{count}` | Number of saved cards |
|
|
951
|
+
| `removeCardMessage` | `{cardAlias}` | Name of the card being removed |
|
|
952
|
+
| `cvcHelpText` | `{length}`, `{side}` | CVC digit count and card side |
|
|
953
|
+
| `linkModalDescription` | `<PHONE_NUMBER>` | User's phone number (auto-replaced by SDK) |
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## Exported Hooks
|
|
958
|
+
|
|
959
|
+
These hooks can be imported independently for advanced use cases:
|
|
960
|
+
|
|
961
|
+
### `useMasterpassScript`
|
|
962
|
+
|
|
963
|
+
Manages Masterpass SDK script loading and initialization.
|
|
964
|
+
|
|
965
|
+
```typescript
|
|
966
|
+
import { useMasterpassScript } from '@akinon/pz-masterpass-rest'
|
|
967
|
+
|
|
968
|
+
const { isScriptLoaded, isMasterpassInitialized } = useMasterpassScript({
|
|
969
|
+
locale: 'tr',
|
|
970
|
+
sdkUrl: 'https://mp-sdk.masterpassturkiye.com',
|
|
971
|
+
environment: 'production',
|
|
972
|
+
onScriptLoaded: () => console.log('SDK ready'),
|
|
973
|
+
onScriptError: (error) => console.error('SDK failed', error)
|
|
974
|
+
})
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
### `useMasterpassToken`
|
|
978
|
+
|
|
979
|
+
Fetches and manages the JWT token for Masterpass API authentication.
|
|
980
|
+
|
|
981
|
+
```typescript
|
|
982
|
+
import { useMasterpassToken } from '@akinon/pz-masterpass-rest'
|
|
983
|
+
|
|
984
|
+
const { token, tokenData, isLoading, error, refetch } = useMasterpassToken({
|
|
985
|
+
useThreeD: false,
|
|
986
|
+
onTokenReady: (data) => console.log('Token ready', data)
|
|
987
|
+
})
|
|
988
|
+
|
|
989
|
+
// Token data includes: MerchantId, AccountKey, UserId, Hash, exp, etc.
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
### `useMasterpassAccount`
|
|
993
|
+
|
|
994
|
+
Manages account access, linking, card operations, and OTP verification.
|
|
995
|
+
|
|
996
|
+
```typescript
|
|
997
|
+
import { useMasterpassAccount } from '@akinon/pz-masterpass-rest'
|
|
998
|
+
|
|
999
|
+
const {
|
|
1000
|
+
accountData, // User's account with cards
|
|
1001
|
+
accountStatus, // Linked/unlinked/not found
|
|
1002
|
+
modalState, // All modal visibility states
|
|
1003
|
+
initializeAccount,
|
|
1004
|
+
refreshAccountData,
|
|
1005
|
+
handleLinkConfirm,
|
|
1006
|
+
handleOTPSubmit,
|
|
1007
|
+
handleRemoveCard,
|
|
1008
|
+
confirmRemoveCard,
|
|
1009
|
+
handleAddCard
|
|
1010
|
+
} = useMasterpassAccount()
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
### `useMasterpassPayment`
|
|
1014
|
+
|
|
1015
|
+
Handles the payment processing flow.
|
|
1016
|
+
|
|
1017
|
+
```typescript
|
|
1018
|
+
import { useMasterpassPayment } from '@akinon/pz-masterpass-rest'
|
|
1019
|
+
|
|
1020
|
+
const {
|
|
1021
|
+
paymentState, // selectedCard, selectedInstallment, installments, etc.
|
|
1022
|
+
isCheckoutLoading,
|
|
1023
|
+
isInstallmentLoading,
|
|
1024
|
+
isPrepareLoading,
|
|
1025
|
+
isFinalizeLoading,
|
|
1026
|
+
handleCardSelect, // Select a saved card (triggers BIN check)
|
|
1027
|
+
handleInstallmentSelect,
|
|
1028
|
+
processPayment, // Stored card payment
|
|
1029
|
+
processDirectPayment // New card payment
|
|
1030
|
+
} = useMasterpassPayment()
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
---
|
|
1034
|
+
|
|
1035
|
+
## Exported Utilities
|
|
1036
|
+
|
|
1037
|
+
```typescript
|
|
1038
|
+
import {
|
|
1039
|
+
// Card utilities
|
|
1040
|
+
formatCardNumber, // '5342610000001234' -> '5342 6100 0000 1234'
|
|
1041
|
+
formatExpiryDate, // '1227' -> '12/27'
|
|
1042
|
+
formatCVV, // Strips non-digits, max 4 chars
|
|
1043
|
+
detectCardType, // Returns 'visa' | 'mastercard' | 'amex' | 'troy' | 'discover' | 'unknown'
|
|
1044
|
+
getCvcLength, // Returns 3 (or 4 for Amex)
|
|
1045
|
+
maskCardNumber, // '5342610000001234' -> '****1234'
|
|
1046
|
+
validateCardNumber, // Luhn algorithm validation
|
|
1047
|
+
getCardBIN, // Returns first 6 digits
|
|
1048
|
+
getCardIcon, // Returns logo image object by BIN
|
|
1049
|
+
|
|
1050
|
+
// Payment utilities
|
|
1051
|
+
isTokenExpired, // Check JWT token expiration
|
|
1052
|
+
getTransactionType, // Returns 'PURCHASE' or 'PURCHASE_3D'
|
|
1053
|
+
formatAmountForPayment, // '1500.00' -> '150000'
|
|
1054
|
+
createPaymentRequest, // Builds stored card payment request
|
|
1055
|
+
createDirectPaymentRequest, // Builds new card payment request
|
|
1056
|
+
|
|
1057
|
+
// Validation
|
|
1058
|
+
createCreditCardFormSchema // Returns Yup validation schema
|
|
1059
|
+
} from '@akinon/pz-masterpass-rest'
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
---
|
|
1063
|
+
|
|
1064
|
+
## Type Definitions
|
|
1065
|
+
|
|
1066
|
+
All types are exported and can be imported for use in custom components:
|
|
1067
|
+
|
|
1068
|
+
```typescript
|
|
1069
|
+
import type {
|
|
1070
|
+
// Component props
|
|
1071
|
+
PaymentMethodSelectorProps,
|
|
1072
|
+
CardListProps,
|
|
1073
|
+
CreditCardFormProps,
|
|
1074
|
+
InstallmentListProps,
|
|
1075
|
+
LinkModalProps,
|
|
1076
|
+
OTPModalProps,
|
|
1077
|
+
ConfirmationModalProps,
|
|
1078
|
+
ErrorDisplayProps,
|
|
1079
|
+
LoadingStateProps,
|
|
1080
|
+
EmptyStateProps,
|
|
1081
|
+
MasterpassRestOptionRenderProps,
|
|
1082
|
+
MasterpassRestOptionCustomRender,
|
|
1083
|
+
MasterpassRestOptionTexts,
|
|
1084
|
+
|
|
1085
|
+
// Data types
|
|
1086
|
+
CardModel,
|
|
1087
|
+
CardType,
|
|
1088
|
+
Installment,
|
|
1089
|
+
PaymentState,
|
|
1090
|
+
ModalState,
|
|
1091
|
+
OrderData,
|
|
1092
|
+
OTPType,
|
|
1093
|
+
TransactionType,
|
|
1094
|
+
InformationModalData,
|
|
1095
|
+
|
|
1096
|
+
// Account types
|
|
1097
|
+
AccountAccessRequest,
|
|
1098
|
+
AccountAccessResponse,
|
|
1099
|
+
AccountAccessSuccessResponse,
|
|
1100
|
+
AccountAccessErrorResponse,
|
|
1101
|
+
CardResponse,
|
|
1102
|
+
|
|
1103
|
+
// Payment types
|
|
1104
|
+
PaymentProcessRequest,
|
|
1105
|
+
DirectPaymentRequest,
|
|
1106
|
+
DirectPaymentResponse,
|
|
1107
|
+
MPResponse,
|
|
1108
|
+
|
|
1109
|
+
// Environment
|
|
1110
|
+
MasterpassEnvironment
|
|
1111
|
+
} from '@akinon/pz-masterpass-rest'
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
---
|
|
1115
|
+
|
|
1116
|
+
## Payment Flow
|
|
1117
|
+
|
|
1118
|
+
### Stored Card Payment
|
|
1119
|
+
|
|
1120
|
+
```
|
|
1121
|
+
1. Component mounts
|
|
1122
|
+
-> Token fetched from backend
|
|
1123
|
+
-> Masterpass SDK loaded
|
|
1124
|
+
-> Account access requested
|
|
1125
|
+
|
|
1126
|
+
2. Account status determined:
|
|
1127
|
+
- Account found & linked -> Show saved cards
|
|
1128
|
+
- Account found & NOT linked -> Show LinkModal
|
|
1129
|
+
- Account not found -> Show new card form only
|
|
1130
|
+
|
|
1131
|
+
3. User selects a saved card
|
|
1132
|
+
-> BIN check sent to backend
|
|
1133
|
+
-> Installment options returned
|
|
1134
|
+
|
|
1135
|
+
4. User selects installment option
|
|
1136
|
+
|
|
1137
|
+
5. User clicks "Proceed to Payment"
|
|
1138
|
+
-> Order prepared (gets order number)
|
|
1139
|
+
-> Payment request sent via Masterpass SDK
|
|
1140
|
+
|
|
1141
|
+
6. Payment result:
|
|
1142
|
+
- Success -> Order finalized
|
|
1143
|
+
- 3D Secure required -> Redirect to bank page
|
|
1144
|
+
- OTP required -> Show OTP modal (RTA/OTP/CVV)
|
|
1145
|
+
- Error -> Show error display
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
### New Card Payment
|
|
1149
|
+
|
|
1150
|
+
```
|
|
1151
|
+
1. User enters card number (6+ digits)
|
|
1152
|
+
-> BIN check sent automatically
|
|
1153
|
+
-> Installment options shown
|
|
1154
|
+
|
|
1155
|
+
2. User fills remaining card fields
|
|
1156
|
+
|
|
1157
|
+
3. User selects installment option
|
|
1158
|
+
|
|
1159
|
+
4. User clicks "Proceed to Payment"
|
|
1160
|
+
-> Direct payment request via Masterpass SDK
|
|
1161
|
+
|
|
1162
|
+
5. Same result handling as stored card flow
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
### OTP Verification
|
|
1166
|
+
|
|
1167
|
+
```
|
|
1168
|
+
1. OTP modal shown with type-specific UI (RTA/OTP/CVV)
|
|
1169
|
+
2. User enters verification code
|
|
1170
|
+
3. Code submitted to Masterpass
|
|
1171
|
+
4. Result:
|
|
1172
|
+
- Success -> Order finalized
|
|
1173
|
+
- Another OTP needed -> Modal updates
|
|
1174
|
+
- Session expired -> Information modal shown
|
|
1175
|
+
- 3D Secure needed -> Redirect
|
|
1176
|
+
```
|