# Masterpass REST - Usage Guide

## Table of Contents

- [Installation](#installation)
- [SDK Setup](#sdk-setup)
- [Basic Usage](#basic-usage)
- [Props Reference](#props-reference)
- [Custom Rendering](#custom-rendering)
  - [Available Custom Render Slots](#available-custom-render-slots)
  - [PaymentMethodSelector](#1-paymentmethodselector)
  - [CardList](#2-cardlist)
  - [CreditCardForm](#3-creditcardform)
  - [InstallmentList](#4-installmentlist)
  - [LinkModal](#5-linkmodal)
  - [OTPModal](#6-otpmodal)
  - [ConfirmationModal](#7-confirmationmodal)
  - [RewardSelectionModal](#7b-rewardselectionmodal)
  - [ErrorDisplay](#8-errordisplay)
  - [LoadingState](#9-loadingstate)
  - [EmptyState](#10-emptystate)
  - [Full Render](#11-fullrender)
- [Text Customization](#text-customization)
- [Exported Hooks](#exported-hooks)
- [Exported Utilities](#exported-utilities)
- [Type Definitions](#type-definitions)
- [Payment Flow](#payment-flow)

---

## Installation

```bash
npx @akinon/projectzero@latest --plugins
```

## SDK Setup

Copy the Masterpass JavaScript SDK to your project's `public` folder:

```bash
cp node_modules/@akinon/pz-masterpass-rest/assets/masterpass-javascript-sdk-web.min.js public/
```

The SDK file must be accessible at `/masterpass-javascript-sdk-web.min.js` in your application.

## Basic Usage

```tsx
// src/views/checkout/steps/payment/options/masterpass-rest.tsx
import { useLocalization } from '@akinon/next/hooks'
import PluginModule, { Component } from '@akinon/next/components/plugin-module'

const MasterpassRest = () => {
  const { locale, currency } = useLocalization()

  return (
    <PluginModule
      component={Component.MasterpassRest}
      props={{ locale, currency }}
    />
  )
}

export default MasterpassRest
```

## Props Reference

`MasterpassRestOption` is the main component. It accepts:

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `locale` | `string` | `'en'` | Language locale (`'en'`, `'tr'`) |
| `currency` | `string` | `'usd'` | Currency code (`'usd'`, `'try'`) |
| `sdkUrl` | `string` | `undefined` | Custom Masterpass SDK URL |
| `environment` | `'production' \| 'development'` | `undefined` | SDK environment |
| `texts` | `MasterpassRestOptionTexts` | `defaultTexts` | UI text overrides for localization |
| `customRender` | `MasterpassRestOptionCustomRender` | `undefined` | Custom render functions |
| `enableRewards` | `boolean` | `false` | Opt in to the card rewards (FBB/BNS) feature. Requires backend support for `MasterpassRestRewardListPage` and `MasterpassRestRewardSelectionPage`. |

```tsx
<PluginModule
  component={Component.MasterpassRest}
  props={{
    locale: 'tr',
    currency: 'try',
    environment: 'production',
    texts: { title: 'Masterpass ile Ode' },
    customRender: { /* ... */ }
  }}
/>
```

### Enabling the Rewards feature

The card rewards feature (FBB / BNS query + selection) is **disabled by default** to keep existing integrations unaffected. To opt in:

```tsx
<MasterpassRestOption
  locale="tr"
  currency="try"
  enableRewards
/>
```

When `enableRewards` is `true`:
- After a saved card is selected, the package automatically calls `MasterpassRestRewardListPage` to fetch available rewards.
- A "Use Rewards" CTA appears inline on the selected card (default `CardList`).
- A modal is mounted at the bottom of the view for selection (`RewardSelectionModal`).
- On confirm, the selection is sent to `MasterpassRestRewardSelectionPage`. The chosen rewards are then forwarded to Masterpass via `additional_fields.rewardList` server-side during `prepareMasterpassOrder` — no frontend wiring needed.

Both backend pages must exist on your environment. If they don't, the package silently falls back (no rewards shown, no UI errors), but you'll see failed network requests in devtools.

See [RewardSelectionModal](#7b-rewardselectionmodal) for custom render options including a no-modal inline pattern.

---

## Custom Rendering

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.

### Available Custom Render Slots

```typescript
type MasterpassRestOptionCustomRender = {
  paymentMethodSelector?: (props: PaymentMethodSelectorProps) => ReactElement
  cardList?: (props: CardListProps) => ReactElement
  creditCardForm?: (props: CreditCardFormProps) => ReactElement
  installmentList?: (props: InstallmentListProps) => ReactElement
  linkModal?: (props: LinkModalProps) => ReactElement
  otpModal?: (props: OTPModalProps) => ReactElement
  confirmationModal?: (props: ConfirmationModalProps) => ReactElement
  rewardSelectionModal?: (props: RewardSelectionModalProps) => ReactElement
  errorDisplay?: (props: ErrorDisplayProps) => ReactElement
  loadingState?: (props: LoadingStateProps) => ReactElement
  emptyState?: (props: EmptyStateProps) => ReactElement
  fullRender?: (props: MasterpassRestOptionRenderProps) => ReactElement
}
```

---

### 1. PaymentMethodSelector

Toggles between saved cards and new card entry. Only rendered when the user has stored cards.

**Props:**

```typescript
type PaymentMethodSelectorProps = {
  cards: any[]                                                // User's stored cards array
  selectedMethod: 'stored_card' | 'new_card'                  // Currently active method
  onMethodChange: (method: 'stored_card' | 'new_card') => void // Switch handler
  texts: MasterpassRestOptionTexts                            // Merged text strings
}
```

**Example:**

```tsx
<PluginModule
  component={Component.MasterpassRest}
  props={{
    locale,
    currency,
    customRender: {
      paymentMethodSelector: ({ cards, selectedMethod, onMethodChange, texts }) => (
        <div className="flex gap-4 border-b pb-4">
          <button
            className={selectedMethod === 'stored_card' ? 'font-bold border-b-2 border-primary' : 'text-gray-500'}
            onClick={() => onMethodChange('stored_card')}
          >
            {texts.savedCardsText} ({cards.length})
          </button>
          <button
            className={selectedMethod === 'new_card' ? 'font-bold border-b-2 border-primary' : 'text-gray-500'}
            onClick={() => onMethodChange('new_card')}
          >
            {texts.newCardText}
          </button>
        </div>
      )
    }
  }}
/>
```

---

### 2. CardList

Displays the user's saved cards with selection, CVC input, and remove functionality.

**Props:**

```typescript
type CardListProps = {
  cards: any[]
  onCardSelect: (card: any) => void
  selectedCard?: any | null
  onRemove?: (card: any) => void
  removingCardId?: string | null
  cvc?: string
  onCvcChange?: (cvc: string) => void
  cvcRequired?: boolean

  availableRewards?: RewardItem[]
  selectedRewards?: RewardItem[]
  isLoadingRewards?: boolean
  isConfirmingRewards?: boolean
  onOpenRewardModal?: () => void
  onConfirmRewards?: (selected: RewardItem[]) => Promise<void> | void
  rewardCurrency?: string
  rewardPayableAmount?: string | number | null

  texts: MasterpassRestOptionTexts
}
```

`cards`, `onCardSelect`, `selectedCard`, `onRemove`, `removingCardId`, `cvc`, `onCvcChange`, `cvcRequired` are the standard card props. The reward props are only populated when `enableRewards` is set on `MasterpassRestOption`; when disabled they are `undefined` and existing card-list themes keep working unchanged.

Reward prop behavior:
- `availableRewards` — list returned by `MasterpassRestRewardListPage` for the currently selected card.
- `selectedRewards` — what the user has confirmed so far (persisted in Redux).
- `isLoadingRewards` — true while the list is being fetched.
- `isConfirmingRewards` — true while the selection is being posted.
- `onOpenRewardModal` — opens the default `RewardSelectionModal`.
- `onConfirmRewards` — bypasses the modal entirely; posts a selection directly. The cumulative cap (special → general) is applied inside before the network call.
- `rewardCurrency` — display-only ISO code, e.g. `'TRY'`.
- `rewardPayableAmount` — the order's unpaid amount as a string. Pass this to `getCappedRewardTotal(selected, rewardPayableAmount)` if you render the redeemable total in your inline picker.

**CardModel structure (each item in `cards` array):**

```typescript
interface CardModel {
  cardAlias: string
  maskedCardNumber: string
  uniqueCardNumber: string
  cardType: 'Credit' | 'Debit' | 'Unknown' | ''
  cardBin: string
  isDefaultCard: boolean
  expireSoon: boolean
  isExpired: boolean
  cardValidationType: 'OTP' | 'RTA' | '_3D' | 'Unknown' | ''
}
```

`cardAlias` is the user-friendly name (e.g. `"My Visa Card"`); `cardBin` is the first 6 digits; `uniqueCardNumber` is the stable identifier. More fields are available — see `account.types.ts`.

**Example:**

```tsx
customRender: {
  cardList: ({ cards, onCardSelect, selectedCard, onRemove, cvc, onCvcChange, texts }) => (
    <div className="space-y-2">
      {cards.map((card) => (
        <div
          key={card.uniqueCardNumber}
          className={`p-4 border cursor-pointer ${
            selectedCard?.uniqueCardNumber === card.uniqueCardNumber
              ? 'border-primary bg-primary/5'
              : 'border-gray-200'
          }`}
          onClick={() => onCardSelect(card)}
        >
          <div className="flex justify-between items-center">
            <div>
              <span className="font-medium">{card.cardAlias}</span>
              <span className="ml-2 text-gray-500">{card.maskedCardNumber}</span>
              {card.isDefaultCard && (
                <span className="ml-2 text-xs bg-green-100 text-green-700 px-2 py-0.5">
                  {texts.defaultCardText}
                </span>
              )}
            </div>
            <button
              onClick={(e) => { e.stopPropagation(); onRemove?.(card) }}
              className="text-red-500 text-sm"
            >
              Remove
            </button>
          </div>

          {selectedCard?.uniqueCardNumber === card.uniqueCardNumber && (
            <input
              type="text"
              maxLength={4}
              value={cvc || ''}
              onChange={(e) => onCvcChange?.(e.target.value)}
              placeholder={texts.cvcPlaceholder3}
              className="mt-2 border px-2 py-1 w-20"
            />
          )}
        </div>
      ))}
    </div>
  )
}
```

---

### 3. CreditCardForm

The new card entry form with validation, BIN checking, and optional card saving.

**Props:**

```typescript
type CreditCardFormProps = {
  onSaveCard?: (data: CreditCardFormData) => void  // Submit handler
  isLoading?: boolean                              // Disables form during API calls
  showSaveOption?: boolean                         // Shows "save card" checkbox
  initialValues?: Partial<CreditCardFormData>      // Pre-fill form fields
  onBinChange?: (bin: string) => Promise<void>     // Called with first 6 digits for installment lookup
  texts: MasterpassRestOptionTexts
}
```

**CreditCardFormData (form submit payload):**

```typescript
interface CreditCardFormData {
  cardNumber: string       // '5342610000001234'
  cardholderName: string   // 'John Doe'
  expiryDate: string       // '12/27'
  cvv: string              // '123'
  saveCard: boolean        // true
  cardAlias: string        // 'My Card'
}
```

**Example:**

```tsx
customRender: {
  creditCardForm: ({ onSaveCard, isLoading, showSaveOption, onBinChange, texts }) => (
    <MyCustomCardForm
      onSubmit={(formData) => onSaveCard?.(formData)}
      disabled={isLoading}
      showSave={showSaveOption}
      onCardNumberChange={(number) => {
        // Trigger installment lookup when 6+ digits entered
        const digits = number.replace(/\D/g, '')
        if (digits.length >= 6) {
          onBinChange?.(digits.substring(0, 6))
        }
      }}
      labels={{
        cardNumber: texts.cardNumberLabel,
        name: texts.cardholderNameLabel,
        expiry: texts.expiryDateLabel,
        cvc: texts.cvcLabel,
        submit: texts.addCardButton
      }}
    />
  )
}
```

> **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.

---

### 4. InstallmentList

Shows available installment options after a card/BIN is selected, plus the "Proceed to Payment" button.

**Props:**

```typescript
type InstallmentListProps = {
  installments: Installment[]                         // Available installment options
  cardType: CardType | null                           // Detected card type info
  onInstallmentSelect: (installment: Installment) => void
  selectedInstallment?: Installment | null
  isLoading?: boolean                                 // Loading installments or preparing order
  onProceedToPayment?: () => void                     // Triggers payment flow
  paymentLoading?: boolean                            // Payment in progress
  texts: MasterpassRestOptionTexts
}
```

**Installment structure:**

```typescript
interface Installment {
  pk: number
  installment_count: number                    // 1 = single payment, 2+ = installments
  label: string
  price_with_accrued_interest: string          // Total price (e.g. '1500.00')
  monthly_price_with_accrued_interest: string  // Monthly amount (e.g. '500.00')
}
```

**CardType structure:**

```typescript
interface CardType {
  name: string   // 'Visa'
  slug: string   // 'visa'
  logo: string   // URL to card logo
}
```

**Example:**

```tsx
customRender: {
  installmentList: ({
    installments,
    cardType,
    onInstallmentSelect,
    selectedInstallment,
    isLoading,
    onProceedToPayment,
    paymentLoading,
    texts
  }) => (
    <div>
      {cardType && (
        <div className="mb-4 text-sm text-gray-500">
          {texts.cardTypeLabel} {cardType.name}
        </div>
      )}

      <div className="space-y-2">
        {installments.map((inst) => (
          <label
            key={inst.pk}
            className={`flex items-center justify-between p-3 border cursor-pointer ${
              selectedInstallment?.pk === inst.pk ? 'border-primary' : 'border-gray-200'
            }`}
          >
            <div className="flex items-center gap-2">
              <input
                type="radio"
                checked={selectedInstallment?.pk === inst.pk}
                onChange={() => onInstallmentSelect(inst)}
              />
              <span>
                {inst.installment_count === 1
                  ? texts.singlePaymentText
                  : texts.installmentsText?.replace('{count}', String(inst.installment_count))}
              </span>
            </div>
            <span className="font-medium">{inst.price_with_accrued_interest} TL</span>
          </label>
        ))}
      </div>

      <button
        onClick={onProceedToPayment}
        disabled={!selectedInstallment || isLoading || paymentLoading}
        className="w-full mt-4 py-3 bg-primary text-white disabled:opacity-50"
      >
        {paymentLoading ? texts.processingPaymentText : texts.proceedToPaymentText}
      </button>
    </div>
  )
}
```

---

### 5. LinkModal

Shown when the user has a Masterpass account but it's not linked to the current merchant.

**Props:**

```typescript
type LinkModalProps = {
  open: boolean
  onClose: () => void
  onConfirm: () => void     // Links account to merchant
  texts: MasterpassRestOptionTexts
}
```

**Example:**

```tsx
customRender: {
  linkModal: ({ open, onClose, onConfirm, texts }) => {
    if (!open) return null

    return (
      <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
        <div className="bg-white p-6 max-w-md">
          <p className="mb-4">{texts.linkModalDescription}</p>
          <div className="flex gap-3">
            <button onClick={onConfirm} className="flex-1 bg-primary text-white py-2">
              {texts.linkAccountButton}
            </button>
            <button onClick={onClose} className="flex-1 border py-2">
              {texts.linkModalCancelButton}
            </button>
          </div>
        </div>
      </div>
    )
  }
}
```

---

### 6. OTPModal

Handles 3 types of verification: RTA (security verification), OTP (bank SMS), and CVV.

**Props:**

```typescript
type OTPModalProps = {
  open: boolean
  onClose: () => void
  onSubmit: (otp: string) => Promise<{
    success: boolean
    requiresOTP?: boolean
    message?: string
  }>
  type: 'RTA' | 'OTP' | 'CVV'          // Determines labels/placeholders
  responseCode?: string
  description?: string
  texts: MasterpassRestOptionTexts
}
```

**Example:**

```tsx
customRender: {
  otpModal: ({ open, onClose, onSubmit, type, texts }) => {
    if (!open) return null

    const titles = {
      RTA: texts.rtaVerificationTitle,
      OTP: texts.bankOtpVerificationTitle,
      CVV: texts.cvvVerificationTitle
    }

    const descriptions = {
      RTA: texts.rtaVerificationDescription,
      OTP: texts.bankOtpVerificationDescription,
      CVV: texts.cvvVerificationDescription
    }

    let inputValue = ''

    const handleSubmit = async () => {
      const result = await onSubmit(inputValue)
      if (!result.success && result.message) {
        alert(result.message)
      }
    }

    return (
      <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
        <div className="bg-white p-6 max-w-sm w-full">
          <h3 className="font-bold text-lg mb-2">{titles[type]}</h3>
          <p className="text-sm text-gray-600 mb-4">{descriptions[type]}</p>
          <input
            type="text"
            maxLength={type === 'CVV' ? 4 : 6}
            onChange={(e) => { inputValue = e.target.value }}
            className="w-full border px-3 py-2 mb-4"
            placeholder={
              type === 'CVV'
                ? texts.cvvVerificationPlaceholder
                : texts.rtaVerificationPlaceholder
            }
          />
          <div className="flex gap-3">
            <button onClick={handleSubmit} className="flex-1 bg-primary text-white py-2">
              {texts.verifyButton}
            </button>
            <button onClick={onClose} className="flex-1 border py-2">
              {texts.otpModalCancelButton}
            </button>
          </div>
        </div>
      </div>
    )
  }
}
```

---

### 7. ConfirmationModal

Used for card removal confirmation. Receives the card alias in the message.

**Props:**

```typescript
type ConfirmationModalProps = {
  open: boolean
  onClose: () => void
  onConfirm: () => void
  title: React.ReactNode | string    // Can be JSX (default: Masterpass logo image)
  message: string                    // Pre-formatted with card alias
  confirmText?: string
  cancelText?: string
  isLoading?: boolean
  loadingText?: string
  texts: MasterpassRestOptionTexts
}
```

**Example:**

```tsx
customRender: {
  confirmationModal: ({ open, onClose, onConfirm, title, message, isLoading, texts }) => {
    if (!open) return null

    return (
      <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
        <div className="bg-white p-6 max-w-sm">
          <div className="mb-4">{title}</div>
          <p className="mb-4">{message}</p>
          <div className="flex gap-3">
            <button
              onClick={onConfirm}
              disabled={isLoading}
              className="flex-1 bg-red-600 text-white py-2"
            >
              {isLoading ? texts.removeCardLoadingText : texts.removeCardConfirmText}
            </button>
            <button onClick={onClose} className="flex-1 border py-2">
              {texts.removeCardCancelText}
            </button>
          </div>
        </div>
      </div>
    )
  }
}
```

---

### 7b. RewardSelectionModal

Modal that lets the customer pick which card rewards (FBB / BNS) to apply. Only mounted when `enableRewards={true}` is passed to `MasterpassRestOption`. Requires backend support for `MasterpassRestRewardListPage` and `MasterpassRestRewardSelectionPage`.

**Props:**

```typescript
type RewardSelectionModalProps = {
  open: boolean
  onClose: () => void
  onConfirm: (selected: RewardItem[]) => Promise<void> | void
  rewards: RewardItem[]
  selectedRewards: RewardItem[]
  isLoading?: boolean
  currency?: string
  payableAmount?: string | number | null
  texts: MasterpassRestOptionTexts
}

type RewardItem = {
  type: 'special' | 'general'
  amount: number | string
  name?: 'FBB' | 'BNS'
}
```

`RewardItem.type` is the category — `'special'` maps to `FBB`, `'general'` maps to `BNS`. `amount` is what the backend currently returns (string like `"180.00"`); both `string` and `number` are accepted. `name` is the optional raw code.

#### Cumulative cap behavior

When `payableAmount` is provided, the modal shows the **actual amount that will be redeemed**, not the full reward value. The cap is cumulative across both categories with a fixed priority — `special` first, then `general`:

```
payable = 100.00
special reward = 180.00  →  100.00 used   (capped, remaining = 0)
general reward =  50.00  →    0.00 used   (no budget left)
```

When a reward is selected and capped, the modal:
- Shows the full reward amount struck through.
- Displays a green note below it using `texts.rewardCappedNoticeText` with `{amount}` replaced by the actual redeemed value.

`confirmRewards` applies the same cap before sending the selection to `MasterpassRestRewardSelectionPage`, so the backend payload is always `special + general ≤ payable`.

**Example — override with your own modal:**

```tsx
{
  customRender: {
    rewardSelectionModal: ({ open, onClose, onConfirm, rewards, ...props }) => (
      <MyRewardModal
        isOpen={open}
        onDismiss={onClose}
        onApply={onConfirm}
        items={rewards}
        {...props}
      />
    )
  }
}
```

**Example — render rewards inline (no modal at all) via CardList:**

Brands that prefer an inline picker instead of a modal can use the new reward props on `CardListProps`:

```tsx
{
  customRender: {
    cardList: (props) => (
      <MyCardList
        cards={props.cards}
        onCardSelect={props.onCardSelect}
        selectedCard={props.selectedCard}
        renderRewards={(card) =>
          props.availableRewards?.length ? (
            <InlineRewardPicker
              rewards={props.availableRewards}
              selected={props.selectedRewards ?? []}
              loading={props.isLoadingRewards}
              confirming={props.isConfirmingRewards}
              payableAmount={props.rewardPayableAmount}
              onConfirm={props.onConfirmRewards}
            />
          ) : null
        }
        {...props}
      />
    )
  }
}
```

`onConfirmRewards` bypasses the default modal entirely — your inline picker calls it directly with the user's selection, the package applies the cumulative cap and posts to `MasterpassRestRewardSelectionPage`. For showing the capped redeemable total in your inline picker, use `getCappedRewardTotal(selected, props.rewardPayableAmount)` from `'@akinon/pz-masterpass-rest'`.

When you go this route, leave `customRender.rewardSelectionModal` unset — the default modal still won't show because your CardList handles selection via `onConfirmRewards` directly.

---

### 8. ErrorDisplay

Shown at the bottom of the payment area when an error occurs.

**Props:**

```typescript
type ErrorDisplayProps = {
  error: any
  onDismiss: () => void
  getErrorInfo: (error: any) => { message: string; type: string } | null
}
```

**Example:**

```tsx
customRender: {
  errorDisplay: ({ error, onDismiss, getErrorInfo }) => {
    const info = getErrorInfo(error)
    if (!info) return null

    return (
      <div className="mt-4 p-4 bg-red-50 border border-red-200 flex justify-between items-start">
        <div>
          <h4 className="font-semibold text-red-800">{info.type}</h4>
          <p className="text-sm text-red-700">{info.message}</p>
        </div>
        <button onClick={onDismiss} className="text-red-500 text-xl leading-none">&times;</button>
      </div>
    )
  }
}
```

---

### 9. LoadingState

Shown during initial token fetch and SDK loading.

**Props:**

```typescript
type LoadingStateProps = {
  message: string    // Either texts.loadingMessage or texts.scriptLoadingMessage
}
```

**Example:**

```tsx
customRender: {
  loadingState: ({ message }) => (
    <div className="flex items-center justify-center h-48">
      <div className="animate-pulse text-center">
        <div className="w-12 h-12 border-4 border-primary border-t-transparent rounded-full animate-spin mx-auto" />
        <p className="mt-3 text-gray-500">{message}</p>
      </div>
    </div>
  )
}
```

---

### 10. EmptyState

Shown in the installment area when no installments are available yet.

**Props:**

```typescript
type EmptyStateProps = {
  paymentMethod: 'stored_card' | 'new_card'
  cardNumberLength: number        // Number of digits entered (for new card)
  isCheckoutLoading: boolean
}
```

**Example:**

```tsx
customRender: {
  emptyState: ({ paymentMethod, cardNumberLength }) => (
    <div className="flex items-center justify-center h-64 border-2 border-dashed border-gray-200">
      <div className="text-center text-gray-400">
        {paymentMethod === 'stored_card'
          ? 'Select a card to view installment options'
          : cardNumberLength < 6
            ? `Enter ${6 - cardNumberLength} more digits to see installments`
            : 'No installment options available'}
      </div>
    </div>
  )
}
```

---

### 11. FullRender

Takes over the entire component rendering. You get all state, handlers, and default components as props.

**Props:**

```typescript
type MasterpassRestOptionRenderProps = {
  // State
  paymentMethod: 'stored_card' | 'new_card'
  hasStoredCards: boolean
  shouldShowDirectForm: boolean
  cvc: string
  error: any

  // Data
  accountData: AccountAccessSuccessResponse | null
  accountStatus: any
  modalState: ModalState
  paymentState: PaymentState

  // Handlers
  handlePaymentMethodChange: (method: 'stored_card' | 'new_card') => void
  handleCardSelect: (card: any) => Promise<void>
  updateModalState: (updates: Partial<ModalState>) => void
  handleRemoveCard: (card: any) => void
  confirmRemoveCard: () => Promise<void>
  handleCvcChange: (cvc: string) => void
  handleSaveCard: (cardData: CreditCardFormData) => Promise<void>
  handleBinChange: (bin: string) => Promise<void>
  handleInstallmentSelect: (installment: Installment) => Promise<void>
  handleProceedToPayment: () => Promise<void>
  handleLinkConfirm: () => Promise<void>
  handleOTPSubmit: (otp: string) => Promise<{ success: boolean; message?: string }>
  onCloseError: () => void

  isCheckoutLoading: boolean
  isInstallmentLoading: boolean
  isPrepareLoading: boolean
  isFinalizeLoading: boolean
  isProcessingPayment: boolean
  isRewardsQueryLoading: boolean
  isRewardsSelectLoading: boolean

  openRewardModal: () => void
  closeRewardModal: () => void
  handleConfirmRewards: (selected: RewardItem[]) => Promise<void>
  payableAmount: string | null

  texts: MasterpassRestOptionTexts

  components: {
    PaymentMethodSelector: React.ComponentType<PaymentMethodSelectorProps>
    CardList: React.ComponentType<CardListProps>
    CreditCardForm: React.ComponentType<CreditCardFormProps>
    InstallmentList: React.ComponentType<InstallmentListProps>
    LinkModal: React.ComponentType<LinkModalProps>
    OTPModal: React.ComponentType<OTPModalProps>
    ConfirmationModal: React.ComponentType<ConfirmationModalProps>
    RewardSelectionModal: React.ComponentType<RewardSelectionModalProps>
  }
}
```

`isProcessingPayment` is the orchestration-level flag that stays `true` for the entire payment flow (refresh → prepare → Masterpass SDK → finalize). If your `fullRender` exposes a "Proceed to Payment" button, OR it together with `isPrepareLoading` and `isFinalizeLoading` and pass that to your `InstallmentList.paymentLoading` — otherwise the button briefly re-enables between the prepare and finalize steps and double-clicks slip through.

#### Opting in to rewards from `fullRender`

If your `fullRender` replaces the package's default view, it also bypasses the default `RewardSelectionModal` mount and the inline reward CTA on the default `CardList`. To enable rewards in a `fullRender` integration:

1. Pass `enableRewards={true}` on `MasterpassRestOption` so the package auto-fetches rewards on card selection and exposes the modal helpers via render props.
2. Mount `components.RewardSelectionModal` alongside your other modals, wired to `modalState.showRewardModal`, `closeRewardModal`, `handleConfirmRewards`, `paymentState.availableRewards`, `paymentState.selectedRewards`, `isRewardsSelectLoading`, and `payableAmount`.
3. In whichever component renders the saved-card list, expose an "Use Rewards" trigger that calls `openRewardModal` once `paymentState.availableRewards.length > 0`. If you use `components.CardList`, pass the reward props through (`availableRewards`, `selectedRewards`, `isLoadingRewards`, `onOpenRewardModal`, `rewardCurrency`, `rewardPayableAmount`) and it will render the inline CTA for you.

Minimal sketch:

```tsx
<MasterpassRestOption
  enableRewards
  customRender={{
    fullRender: (props) => {
      const {
        paymentState,
        modalState,
        components: { CardList, RewardSelectionModal },
        openRewardModal,
        closeRewardModal,
        handleConfirmRewards,
        isRewardsQueryLoading,
        isRewardsSelectLoading,
        payableAmount,
        texts
      } = props

      return (
        <>
          <CardList
            {...cardListProps}
            availableRewards={paymentState.availableRewards}
            selectedRewards={paymentState.selectedRewards}
            isLoadingRewards={paymentState.isLoadingRewards || isRewardsQueryLoading}
            isConfirmingRewards={isRewardsSelectLoading}
            onOpenRewardModal={openRewardModal}
            rewardCurrency="TRY"
            rewardPayableAmount={payableAmount}
            texts={texts}
          />

          <RewardSelectionModal
            open={modalState.showRewardModal}
            onClose={closeRewardModal}
            onConfirm={handleConfirmRewards}
            rewards={paymentState.availableRewards}
            selectedRewards={paymentState.selectedRewards}
            isLoading={isRewardsSelectLoading}
            currency="TRY"
            payableAmount={payableAmount}
            texts={texts}
          />
        </>
      )
    }
  }}
/>
```

Without `enableRewards`, the package does not call `MasterpassRestRewardListPage`, no reward state is populated, and the helpers stay no-ops — existing `fullRender` brands keep behaving identically until they explicitly opt in.

**Example - Custom layout with default components:**

```tsx
customRender: {
  fullRender: (props) => {
    const {
      paymentMethod,
      hasStoredCards,
      shouldShowDirectForm,
      accountData,
      paymentState,
      modalState,
      handlePaymentMethodChange,
      handleCardSelect,
      handleCvcChange,
      handleSaveCard,
      handleBinChange,
      handleInstallmentSelect,
      handleProceedToPayment,
      handleLinkConfirm,
      handleOTPSubmit,
      handleRemoveCard,
      confirmRemoveCard,
      updateModalState,
      cvc,
      isCheckoutLoading,
      isInstallmentLoading,
      isPrepareLoading,
      isFinalizeLoading,
      isProcessingPayment,
      texts,
      components
    } = props

    const { CardList, CreditCardForm, InstallmentList, LinkModal, OTPModal } = components

    return (
      <div className="my-custom-layout">
        {/* Your custom header */}
        <h1 className="text-2xl mb-6">Secure Payment</h1>

        {/* Use default components with custom layout */}
        {hasStoredCards && !shouldShowDirectForm && (
          <div className="tabs mb-4">
            <button onClick={() => handlePaymentMethodChange('stored_card')}>
              Saved Cards
            </button>
            <button onClick={() => handlePaymentMethodChange('new_card')}>
              New Card
            </button>
          </div>
        )}

        <div className="flex gap-8">
          <div className="flex-1">
            {paymentMethod === 'stored_card' && hasStoredCards ? (
              <CardList
                cards={accountData?.result?.cards || []}
                onCardSelect={handleCardSelect}
                selectedCard={paymentState.selectedCard}
                onRemove={handleRemoveCard}
                removingCardId={modalState?.removingCardId}
                cvc={cvc}
                onCvcChange={handleCvcChange}
                texts={texts}
              />
            ) : (
              <CreditCardForm
                onSaveCard={handleSaveCard}
                isLoading={isCheckoutLoading}
                showSaveOption={true}
                onBinChange={handleBinChange}
                texts={texts}
              />
            )}
          </div>

          <div className="flex-1">
            {paymentState.installments.length > 0 && (
              <InstallmentList
                installments={paymentState.installments}
                cardType={paymentState.cardType}
                onInstallmentSelect={handleInstallmentSelect}
                selectedInstallment={paymentState.selectedInstallment}
                isLoading={isInstallmentLoading || isPrepareLoading}
                onProceedToPayment={handleProceedToPayment}
                paymentLoading={isProcessingPayment || isPrepareLoading || isFinalizeLoading}
                texts={texts}
              />
            )}
          </div>
        </div>

        <LinkModal
          open={modalState?.showLinkModal ?? false}
          onClose={() => updateModalState({ showLinkModal: false })}
          onConfirm={handleLinkConfirm}
          texts={texts}
        />
        <OTPModal
          open={modalState?.showOTPModal ?? false}
          onClose={() => updateModalState({ showOTPModal: false })}
          onSubmit={handleOTPSubmit}
          type={modalState?.otpType ?? 'OTP'}
          texts={texts}
        />
      </div>
    )
  }
}
```

---

## Text Customization

All user-facing strings can be overridden via the `texts` prop. Pass only the keys you want to change; the rest will use defaults.

```tsx
<PluginModule
  component={Component.MasterpassRest}
  props={{
    locale: 'tr',
    currency: 'try',
    texts: {
      title: 'Masterpass ile Ode',
      selectCardTitle: 'Kart Secin',
      newCardTitle: 'Yeni Kart ile Ode',
      installmentOptionsTitle: 'Taksit Secenekleri',
      enterCardDetailsTitle: 'Kart Bilgilerini Girin',

      cardNumberLabel: 'Kart Numarasi',
      cardholderNameLabel: 'Kart Uzerindeki Isim',
      expiryDateLabel: 'Son Kullanma Tarihi',
      cvcLabel: 'CVC/CVV',
      saveCardLabel: 'Bu karti gelecek odemeler icin kaydet',
      addCardButton: 'Kart Ekle',

      savedCardsText: 'Kayitli Kartlar',
      newCardText: 'Yeni Kart',

      singlePaymentText: 'Tek Cekim',
      installmentsText: '{count} Taksit',
      noInterestText: 'Faizsiz',
      proceedToPaymentText: 'Odemeye Devam Et',
      processingPaymentText: 'Odeme Isleniyor...',

      defaultCardText: 'Varsayilan',
      expiresSoonText: 'Suresi Yakinda Dolacak',

      linkModalDescription: '<PHONE_NUMBER> numarasi ile Masterpass hesabinizdaki kartlari gormek ister misiniz?',
      linkAccountButton: 'Evet, istiyorum',
      linkModalCancelButton: 'Hayir, istemiyorum',
      removeCardMessage: '<{cardAlias}> kartini Masterpass altyapisindan silmek istediginize emin misiniz?',

      rewardOpenButtonText: 'Puanlari Kullan',
      rewardModalTitle: 'Kart Puanlarini Kullan',
      rewardModalDescription: 'Bu siparise uygulamak istediginiz puanlari secin.',
      rewardModalEmptyMessage: 'Bu kart icin uygun puan yok.',
      rewardModalConfirmText: 'Uygula',
      rewardModalCancelText: 'Vazgec',
      rewardModalLoadingText: 'Uygulaniyor...',
      rewardSelectedSummaryText: '{count} puan uygulandi',
      rewardAvailableSummaryText: '{count} puan mevcut',
      rewardCategorySpecialText: 'Ozel Puanlar',
      rewardCategoryGeneralText: 'Genel Puanlar',
      rewardCappedNoticeText: '{amount} kullanilacak',
      rewardFailedToLoadText: 'Puanlar yuklenemedi',
      rewardFailedToSelectText: 'Puanlar uygulanamadi',

      rtaVerificationTitle: 'Guvenlik Dogrulamasi',
      bankOtpVerificationTitle: 'Banka Dogrulamasi',
      cvvVerificationTitle: 'CVV Dogrulamasi',
      verifyButton: 'Dogrula',

      paymentErrorTitle: 'Odeme Hatasi',
      cardNumberRequiredText: 'Kart numarasi zorunludur',
      cardNumberInvalidText: 'Gecerli bir kart numarasi girin',

      loadingMessage: 'Odeme sistemi hazirlaniyor...',
      scriptLoadingMessage: 'Odeme altyapisi yukleniyor...',

      sessionExpiredTitle: 'Oturum Suresi Doldu',
      sessionExpiredMessage: 'Oturumunuz zaman asimina ugradi. Devam etmek icin islemi yeniden baslatin.',
      sessionExpiredButton: 'Yeniden Basla'
    }
  }}
/>
```

### Dynamic Text Placeholders

Some text keys support placeholders:

| Key | Placeholder | Description |
|-----|------------|-------------|
| `installmentsText` | `{count}` | Installment count |
| `savedCardsDescription` | `{count}` | Number of saved cards |
| `removeCardMessage` | `{cardAlias}` | Name of the card being removed |
| `cvcHelpText` | `{length}`, `{side}` | CVC digit count and card side |
| `linkModalDescription` | `<PHONE_NUMBER>` | User's phone number (auto-replaced by SDK) |
| `rewardSelectedSummaryText` | `{count}` | Number of selected rewards |
| `rewardAvailableSummaryText` | `{count}` | Number of available rewards for the selected card |
| `rewardCappedNoticeText` | `{amount}` | Capped redeemable amount (e.g. `"100.00 TRY"`) |

---

## Exported Hooks

These hooks can be imported independently for advanced use cases:

### `useMasterpassScript`

Manages Masterpass SDK script loading and initialization.

```typescript
import { useMasterpassScript } from '@akinon/pz-masterpass-rest'

const { isScriptLoaded, isMasterpassInitialized } = useMasterpassScript({
  locale: 'tr',
  sdkUrl: 'https://mp-sdk.masterpassturkiye.com',
  environment: 'production',
  onScriptLoaded: () => console.log('SDK ready'),
  onScriptError: (error) => console.error('SDK failed', error)
})
```

### `useMasterpassToken`

Fetches and manages the JWT token for Masterpass API authentication.

```typescript
import { useMasterpassToken } from '@akinon/pz-masterpass-rest'

const { token, tokenData, isLoading, error, refetch } = useMasterpassToken({
  useThreeD: false,
  onTokenReady: (data) => console.log('Token ready', data)
})

// Token data includes: MerchantId, AccountKey, UserId, Hash, exp, etc.
```

### `useMasterpassAccount`

Manages account access, linking, card operations, and OTP verification.

```typescript
import { useMasterpassAccount } from '@akinon/pz-masterpass-rest'

const {
  accountData,       // User's account with cards
  accountStatus,     // Linked/unlinked/not found
  modalState,        // All modal visibility states
  initializeAccount,
  refreshAccountData,
  handleLinkConfirm,
  handleOTPSubmit,
  handleRemoveCard,
  confirmRemoveCard,
  handleAddCard
} = useMasterpassAccount()
```

### `useMasterpassPayment`

Handles the payment processing flow, including the rewards opt-in.

```typescript
import { useMasterpassPayment } from '@akinon/pz-masterpass-rest'

const {
  paymentState,
  isCheckoutLoading,
  isInstallmentLoading,
  isPrepareLoading,
  isFinalizeLoading,
  isRewardsQueryLoading,
  isRewardsSelectLoading,
  payableAmount,
  handleCardSelect,
  handleInstallmentSelect,
  processPayment,
  processDirectPayment,
  fetchRewardsForCard,
  openRewardModal,
  closeRewardModal,
  confirmRewards
} = useMasterpassPayment({ enableRewards: true })
```

`paymentState` carries the live selection state (`selectedCard`, `selectedInstallment`, `installments`, plus `availableRewards`, `selectedRewards`, `isLoadingRewards` when rewards are enabled).

`useMasterpassPayment` accepts a single options arg `{ enableRewards?: boolean }` — default `false`. When `false`, `handleCardSelect` skips the reward fetch, and the reward state stays empty. Pass `true` only when you are also using `<MasterpassRestOption enableRewards />` (or rendering the rewards UI yourself); otherwise the package never hits `MasterpassRestRewardListPage`.

`payableAmount` is read from `state.checkout.preOrder.unpaid_amount` (falling back to `total_amount_with_interest`). Pass it to `getCappedRewardTotal` / `getCappedRewardAmounts` if you are building a custom rewards UI.

`confirmRewards(selected)` applies the cumulative cap (`special` first, then `general`) and posts to `MasterpassRestRewardSelectionPage`. It returns `{ success: true }` on success or `{ success: false, message }` on failure.

---

## Exported Utilities

```typescript
import {
  formatCardNumber,
  formatExpiryDate,
  formatCVV,
  detectCardType,
  getCvcLength,
  maskCardNumber,
  validateCardNumber,
  getCardBIN,
  getCardIcon,

  isTokenExpired,
  getTransactionType,
  formatAmountForPayment,
  createPaymentRequest,
  createDirectPaymentRequest,

  parseRewardAmount,
  getCappedRewardAmounts,
  getCappedRewardTotal,
  REWARD_PRIORITY,

  createCreditCardFormSchema
} from '@akinon/pz-masterpass-rest'
```

**Card utilities** — `formatCardNumber` (`'5342610000001234'` → `'5342 6100 0000 1234'`), `formatExpiryDate` (`'1227'` → `'12/27'`), `formatCVV` (strips non-digits, max 4 chars), `detectCardType` (returns `'visa' | 'mastercard' | 'amex' | 'troy' | 'discover' | 'unknown'`), `getCvcLength` (returns `3`, or `4` for Amex), `maskCardNumber` (`'5342610000001234'` → `'****1234'`), `validateCardNumber` (Luhn), `getCardBIN` (first 6 digits), `getCardIcon` (logo image object by BIN).

**Payment utilities** — `isTokenExpired` checks the JWT expiry, `getTransactionType` returns `'PURCHASE'` / `'PURCHASE_3D'`, `formatAmountForPayment` (`'1500.00'` → `'150000'`), `createPaymentRequest` / `createDirectPaymentRequest` build the SDK request body.

**Reward utilities** — used internally and exported for custom UIs:

```typescript
parseRewardAmount(amount: number | string): number

getCappedRewardAmounts(
  selected: RewardItem[],
  payableAmount?: string | number | null
): Record<'special' | 'general', number>

getCappedRewardTotal(
  selected: RewardItem[],
  payableAmount?: string | number | null
): number

REWARD_PRIORITY: ['special', 'general']
```

- `parseRewardAmount` safely parses backend string amounts (e.g. `"180.00"`) to a number; returns `0` for invalid input.
- `getCappedRewardAmounts` applies the cumulative cap (`special` first, then `general`) and returns the actual amounts that will be redeemed per category.
- `getCappedRewardTotal` is the sum of the capped amounts — use this when rendering a "X TRY will be applied" total.
- `REWARD_PRIORITY` is the fixed order in which the cap is applied. Exported so custom UIs can iterate in the same order.

`payableAmount` is the order's unpaid amount. If `null` / `undefined` / unparseable, no cap is applied (returns the full reward amounts) — this matches the behavior in the package and is safe as a fallback.

**Validation** — `createCreditCardFormSchema` returns the Yup schema used by the default `CreditCardForm`.

---

## Type Definitions

All types are exported and can be imported for use in custom components:

```typescript
import type {
  PaymentMethodSelectorProps,
  CardListProps,
  CreditCardFormProps,
  InstallmentListProps,
  LinkModalProps,
  OTPModalProps,
  ConfirmationModalProps,
  RewardSelectionModalProps,
  ErrorDisplayProps,
  LoadingStateProps,
  EmptyStateProps,
  MasterpassRestOptionRenderProps,
  MasterpassRestOptionCustomRender,
  MasterpassRestOptionTexts,

  CardModel,
  CardType,
  Installment,
  PaymentState,
  ModalState,
  OrderData,
  OTPType,
  TransactionType,
  InformationModalData,
  RewardItem,
  RewardName,
  RewardCategory,

  AccountAccessRequest,
  AccountAccessResponse,
  AccountAccessSuccessResponse,
  AccountAccessErrorResponse,
  CardResponse,

  PaymentProcessRequest,
  DirectPaymentRequest,
  DirectPaymentResponse,
  MPResponse,

  MasterpassEnvironment
} from '@akinon/pz-masterpass-rest'
```

---

## Payment Flow

### Stored Card Payment

```
1. Component mounts
   -> Token fetched from backend
   -> Masterpass SDK loaded
   -> Account access requested

2. Account status determined:
   - Account found & linked -> Show saved cards
   - Account found & NOT linked -> Show LinkModal
   - Account not found -> Show new card form only

3. User selects a saved card
   -> BIN check sent to backend
   -> Installment options returned

4. User selects installment option

5. User clicks "Proceed to Payment"
   -> Order prepared (gets order number)
   -> Payment request sent via Masterpass SDK

6. Payment result:
   - Success -> Order finalized
   - 3D Secure required -> Redirect to bank page
   - OTP required -> Show OTP modal (RTA/OTP/CVV)
   - Error -> Show error display
```

### New Card Payment

```
1. User enters card number (6+ digits)
   -> BIN check sent automatically
   -> Installment options shown

2. User fills remaining card fields

3. User selects installment option

4. User clicks "Proceed to Payment"
   -> Direct payment request via Masterpass SDK

5. Same result handling as stored card flow
```

### OTP Verification

```
1. OTP modal shown with type-specific UI (RTA/OTP/CVV)
2. User enters verification code
3. Code submitted to Masterpass
4. Result:
   - Success -> Order finalized
   - Another OTP needed -> Modal updates
   - Session expired -> Information modal shown
   - 3D Secure needed -> Redirect
```
