# Input Payment

A cross-platform React payment card input that automatically detects the card type from the entered number and displays relevant payment icons.
<!-- BEGIN:xui-mcp-instructions:input-payment -->
A specialised text input for payment data entry. Extends the standard input with a dedicated payment icons block — a row of accepted payment method logos (Visa, Mastercard, etc.) displayed inside the field — and an optional leading icon. Used exclusively in checkout flows, billing forms, and payment method configuration screens.

### When to use
- For collecting a card number, expiry date, CVV, or other payment-related values in a checkout or billing form
- When the field must visually communicate which payment methods are accepted — showing logos inside the input reassures the user before they start typing
- When the payment method icon should change dynamically based on detected card type (e.g. showing the Visa logo as soon as the user types a Visa prefix)
- As a pair: one InputPayment for the card number, one for the expiry, one for the CVV — in a standard payment card form layout

### When not to use
- For non-payment text inputs — use the standard Input component
- When no payment branding is needed — use standard Input with an optional left icon
- When accepting only one specific payment method with no ambiguity — the payment icons block may be omitted and a single logo shown via Icon left instead

### Content guidelines
- Placeholder text — use format hints, not instructions: 1234 5678 9012 3456 for card number, MM / YY for expiry, CVV or CVC for security code. Do not use *"Enter your card number"* — the field label already communicates this.
- Error messages — be specific:
- *"Invalid card number"* — after Luhn check fails
- *"Expiry date has passed"* — past month/year entered
- *"CVV must be 3 digits"* (or 4 for Amex) — wrong length
- *"Card number is required"* — required field left empty
- Field labels — always provide a visible label above the field: *"Card number"*, *"Expiry date"*, *"Security code"*. Do not rely on the placeholder alone.
- Payment icons — show only the logos for payment methods actually accepted by the product's payment provider. Do not show logos for unsupported methods — it creates false expectations.

### Behaviour guidelines
- Card number formatting — as the user types, format the card number with spaces every 4 digits: 4242 4242 4242 4242. Apply this formatting in real time without disrupting cursor position. Accept both numeric-only input and formatted strings with spaces.
- Card type detection — detect the card network from the first 1–6 digits (IIN/BIN range). When detected, highlight the matching logo in the payment icons block and dim or hide others. Update in real time as the user types.
- Expiry date formatting — auto-insert a / separator after the month digits: 12/26. Prevent the user from entering an invalid month (> 12) or a past expiry date.
- CVV masking — optionally mask CVV input as the user types (show dots •••). Provide a show/hide toggle via a trailing icon button if masking is enabled.
- Input masking — use inputmode=*"numeric"* and pattern=*"[0-9]*"* on numeric fields to trigger a numeric keyboard on mobile. Do not use type=*"number"* for card fields — it interferes with leading zeros and formatting.
- Validation timing — validate on blur (when the user leaves the field), not on every keystroke. Switch to State=Error with a specific error message when the value fails validation. Clear the error when the user starts typing again.
- Disabled state — use State=Disabled for saved payment methods being displayed in a non-editable summary. Always show the masked card value (e.g. •••• 4242) rather than an empty disabled field.
- Autofill — support browser and OS autofill for payment fields. Use standard autocomplete attributes: autocomplete=*"cc-number"* for card number, autocomplete=*"cc-exp"* for expiry, autocomplete=*"cc-csc"* for CVV. Autofill should trigger Filled=True and the detected card type should update the payment icons block.

### Accessibility
- Each InputPayment field must have a visible label associated via <label for> or aria-labelledby. Do not use placeholder as the only label.
- Use autocomplete attributes on all payment fields — they are required for WCAG 1.3.5 (Identify Input Purpose).
- Use inputmode=*"numeric"* on card number, expiry, and CVV fields to trigger the numeric keyboard on touch devices.
- When State=Error, the error message must be associated via aria-describedby so screen readers announce it when the field is focused.
- The payment icons block is decorative — wrap it in aria-hidden=*"true"* so screen readers do not attempt to read logo names. The accepted payment methods should instead be listed in a visible or screen-reader-only text near the form (e.g. *"We accept Visa, Mastercard, and American Express"*).
- The Icon left is decorative when a label is present — set aria-hidden=*"true"* on the icon element.
- When State=Disabled, set aria-disabled=*"true"* and communicate the card type and masked number via aria-label — e.g. aria-label=*"Visa card ending in 4242, disabled"*.
<!-- END:xui-mcp-instructions:input-payment -->

## Installation

```bash
npm install @xsolla/xui-input-payment
```

## Demo

### Basic Payment Input

```tsx
import * as React from "react";
import { InputPayment } from "@xsolla/xui-input-payment";

export default function BasicPayment() {
  const [cardNumber, setCardNumber] = React.useState("");

  return (
    <InputPayment
      value={cardNumber}
      onChangeText={setCardNumber}
      placeholder="Card number"
    />
  );
}
```

### With Auto-Detection Callback

```tsx
import * as React from "react";
import { InputPayment } from "@xsolla/xui-input-payment";

export default function WithDetection() {
  const [cardNumber, setCardNumber] = React.useState("");
  const [cardType, setCardType] = React.useState<string | null>(null);

  return (
    <div>
      <InputPayment
        value={cardNumber}
        onChangeText={setCardNumber}
        onRecognizedPaymentChange={(type) => setCardType(type)}
      />
      {cardType && <p>Detected: {cardType}</p>}
    </div>
  );
}
```

### Different Sizes

```tsx
import * as React from "react";
import { InputPayment } from "@xsolla/xui-input-payment";

export default function Sizes() {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      <InputPayment size="sm" placeholder="Small" />
      <InputPayment size="md" placeholder="Medium" />
      <InputPayment size="lg" placeholder="Large" />
    </div>
  );
}
```

## Anatomy

```jsx
import { InputPayment } from "@xsolla/xui-input-payment";

<InputPayment
  value={cardNumber} // Card number value
  onChangeText={setCardNumber} // Change handler
  size="md" // Input size
  placeholder="Card number" // Placeholder text
  possiblePayments={["visa", "mastercard"]} // Accepted cards
  maxVisiblePossiblePayments={5} // Max icons shown
  recognizedPayment="visa" // Force recognized type
  autoDetect={true} // Enable auto-detection
  errorMessage="Invalid card" // Error message
  disabled={false} // Disabled state
/>;
```

## Examples

### Custom Accepted Cards

```tsx
import * as React from "react";
import { InputPayment } from "@xsolla/xui-input-payment";

export default function CustomCards() {
  return (
    <InputPayment
      possiblePayments={["visa", "mastercard", "amex"]}
      maxVisiblePossiblePayments={3}
      placeholder="We accept Visa, Mastercard, Amex"
    />
  );
}
```

### With Error State

```tsx
import * as React from "react";
import { InputPayment } from "@xsolla/xui-input-payment";

export default function WithError() {
  const [cardNumber, setCardNumber] = React.useState("");
  const [error, setError] = React.useState("");

  const validate = (value: string) => {
    if (value.length > 0 && value.length < 13) {
      setError("Card number too short");
    } else {
      setError("");
    }
  };

  return (
    <InputPayment
      value={cardNumber}
      onChangeText={(text) => {
        setCardNumber(text);
        validate(text);
      }}
      errorMessage={error}
    />
  );
}
```

### Controlled Recognition

```tsx
import * as React from "react";
import { InputPayment } from "@xsolla/xui-input-payment";

export default function ControlledRecognition() {
  const [cardNumber, setCardNumber] = React.useState("");

  return (
    <InputPayment
      value={cardNumber}
      onChangeText={setCardNumber}
      autoDetect={false}
      recognizedPayment={cardNumber.startsWith("4") ? "visa" : undefined}
    />
  );
}
```

### With Icon

```tsx
import * as React from "react";
import { InputPayment } from "@xsolla/xui-input-payment";
import { CreditCard } from "@xsolla/xui-icons-base";

export default function WithIcon() {
  return <InputPayment icon={<CreditCard />} placeholder="Enter card number" />;
}
```

### In Payment Form

```tsx
import * as React from "react";
import { InputPayment } from "@xsolla/xui-input-payment";
import { Input } from "@xsolla/xui-input";
import { Button } from "@xsolla/xui-button";

export default function PaymentForm() {
  const [cardNumber, setCardNumber] = React.useState("");
  const [cardType, setCardType] = React.useState<string | null>(null);

  return (
    <form
      style={{
        display: "flex",
        flexDirection: "column",
        gap: 16,
        maxWidth: 400,
      }}
    >
      <InputPayment
        value={cardNumber}
        onChangeText={setCardNumber}
        onRecognizedPaymentChange={setCardType}
        placeholder="Card number"
      />
      <div style={{ display: "flex", gap: 16 }}>
        <Input placeholder="MM/YY" style={{ flex: 1 }} />
        <Input placeholder="CVV" style={{ width: 100 }} />
      </div>
      <Input placeholder="Cardholder name" />
      <Button onPress={() => console.log("Submit", { cardNumber, cardType })}>
        Pay Now
      </Button>
    </form>
  );
}
```

## API Reference

### InputPayment

**InputPaymentProps:**

| Prop                       | Type                                       | Default         | Description                                                                                                   |
| :------------------------- | :----------------------------------------- | :-------------- | :------------------------------------------------------------------------------------------------------------ |
| `testID`                   | `string`                                   | —               | Test ID for testing frameworks. On web this renders as `data-testid`; on React Native it renders as `testID`. |
| value                      | `string`                                   | -               | Card number value.                                                                                            |
| onChange                   | `(e: ChangeEvent) => void`                 | -               | Standard change event handler.                                                                                |
| onChangeText               | `(text: string) => void`                   | -               | Text change handler.                                                                                          |
| size                       | `"xs" \| "sm" \| "md" \| "lg" \| "xl"`     | `"md"`          | Input size variant.                                                                                           |
| placeholder                | `string`                                   | `"Card number"` | Placeholder text.                                                                                             |
| icon                       | `ReactNode`                                | -               | Left icon.                                                                                                    |
| disabled                   | `boolean`                                  | `false`         | Disabled state.                                                                                               |
| error                      | `boolean`                                  | -               | Error state indicator.                                                                                        |
| errorMessage               | `string`                                   | -               | Error message text.                                                                                           |
| possiblePayments           | `PaymentSystemKey[]`                       | See below       | Accepted payment types.                                                                                       |
| maxVisiblePossiblePayments | `number`                                   | `5`             | Max payment icons shown.                                                                                      |
| recognizedPayment          | `PaymentSystemKey`                         | -               | Force recognized payment type.                                                                                |
| onRecognizedPaymentChange  | `(type: PaymentSystemKey \| null) => void` | -               | Detection callback.                                                                                           |
| autoDetect                 | `boolean`                                  | `true`          | Enable auto-detection.                                                                                        |
| aria-label                 | `string`                                   | `"Card number"` | Accessible label.                                                                                             |
| testID                     | `string`                                   | -               | Test identifier.                                                                                              |

**Default possiblePayments:**

```typescript
[
  "mastercard",
  "visa",
  "maestro",
  "diners",
  "amex",
  "discover",
  "jcb",
  "unionpay",
];
```

**PaymentSystemKey:**

```typescript
type PaymentSystemKey =
  | "visa"
  | "mastercard"
  | "amex"
  | "diners"
  | "maestro"
  | "unionpay"
  | "discover"
  | "jcb"
  | "aura"
  | "cartesbancaires"
  | "cirrus"
  | "dankort"
  | "elo"
  | "hipercard"
  | "mir"
  | "naranja"
  | "paypal"
  | "sodexo"
  | "uatp";
```

## Card Detection

The component automatically detects card types based on BIN (Bank Identification Number) ranges:

| Card Type        | BIN Pattern                |
| :--------------- | :------------------------- |
| Visa             | Starts with 4              |
| Mastercard       | 51-55 or 2221-2720         |
| American Express | 34, 37                     |
| Discover         | 6011, 644-649, 65          |
| JCB              | 3528-3589                  |
| Diners Club      | 300-305, 36, 38            |
| UnionPay         | 62 (except Discover range) |
| Maestro          | 50, 56-69                  |
| Mir              | 2200-2204                  |

## Icon Animation

- Payment icons cycle through when multiple are available
- When a card type is detected, other icons slide out
- The recognized card icon remains visible
- Animation is smooth with 300ms transitions

## Accessibility

- Input has `aria-label` for screen readers
- Error messages are linked via `aria-describedby`
- Payment icons have descriptive `aria-label`
- Disabled state is announced with `aria-disabled`
- `inputMode="numeric"` shows numeric keyboard on mobile
