> ## Documentation Index
> Fetch the complete documentation index at: https://docs.momentco.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart: Integrate Specific Payment Methods

> Control which payment methods appear on the hosted checkout

export const PaymentMethodsByCountry = ({data}) => {
  const ALL_CATEGORIES = ["Cards", "Bank transfers", "Bank redirects", "Real-time payments", "Consumer wallets", "External payments", "Mobile money"];
  const CATEGORY_ENUMS = {
    "Cards": "cards",
    "Bank transfers": "bank_transfers",
    "Bank redirects": "bank_redirects",
    "Real-time payments": "real_time_payments",
    "Consumer wallets": "consumer_wallets",
    "External payments": "external_payments",
    "Mobile money": "mobile_money"
  };
  const DISABLED_CATEGORIES = [];
  const countryEntries = Object.entries(data);
  const [selected, setSelected] = useState(countryEntries[0]?.[0] ?? "");
  const [selectedCategory, setSelectedCategory] = useState("All");
  const [categoryOpen, setCategoryOpen] = useState(false);
  const selectedInfo = data[selected] ?? ({});
  const allMethods = selectedInfo.methods ?? [];
  const availableCategories = new Set(allMethods.map(m => m.category));
  const visibleMethods = allMethods.filter(m => !DISABLED_CATEGORIES.includes(m.category));
  const methods = selectedCategory === "All" ? visibleMethods : visibleMethods.filter(m => m.category === selectedCategory);
  const CATEGORY_CONFIG = {
    "Cards": {
      color: "text-blue-600 dark:text-blue-400",
      iconPath: "M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5z"
    },
    "Bank transfers": {
      color: "text-sky-600 dark:text-sky-400",
      iconPath: "M12 21v-8.25M15.75 21v-8.25M8.25 21v-8.25M3 9l9-6 9 6m-1.5 12V10.332A48.36 48.36 0 0012 9.75c-2.551 0-5.056.2-7.5.582V21M3 21h18M12 6.75h.008v.008H12V6.75z"
    },
    "Bank redirects": {
      color: "text-cyan-600 dark:text-cyan-400",
      iconPath: "M19.5 12c0-1.232-.046-2.453-.138-3.662a4.006 4.006 0 00-3.7-3.7 48.678 48.678 0 00-7.324 0 4.006 4.006 0 00-3.7 3.7c-.017.22-.032.441-.046.662M19.5 12l3-3m-3 3l-3-3m-12 3c0 1.232.046 2.453.138 3.662a4.006 4.006 0 003.7 3.7 48.656 48.656 0 007.324 0 4.006 4.006 0 003.7-3.7c.017-.22.032-.441.046-.662M4.5 12l3 3m-3-3l-3 3"
    },
    "Real-time payments": {
      color: "text-teal-600 dark:text-teal-400",
      iconPath: "M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z"
    },
    "Consumer wallets": {
      color: "text-orange-600 dark:text-orange-400",
      iconPath: "M21 12a2.25 2.25 0 00-2.25-2.25H15a3 3 0 11-6 0H5.25A2.25 2.25 0 003 12m18 0v6a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 18v-6m18 0V9M3 12V9m18 0a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 9m18 0V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v3"
    },
    "External payments": {
      color: "text-zinc-500 dark:text-zinc-400",
      iconPath: "M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"
    },
    "Mobile money": {
      color: "text-green-600 dark:text-green-400",
      iconPath: "M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 8.25h3"
    }
  };
  const handleCountryChange = e => {
    setSelected(e.target.value);
    setSelectedCategory("All");
  };
  return <div className="not-prose p-4 border border-zinc-200 dark:border-zinc-800 rounded-xl">
      <div className="mb-6 flex flex-wrap gap-6 items-end">
        <div>
          <label className="block text-xs font-semibold uppercase tracking-widest text-zinc-400 mb-2">
            Business location
          </label>
          <div className="relative inline-block">
            <select value={selected} onChange={handleCountryChange} className="appearance-none pl-3 pr-10 py-2.5 border border-zinc-200 dark:border-zinc-700 rounded-lg bg-white dark:bg-zinc-900 text-sm font-medium text-zinc-900 dark:text-white cursor-pointer min-w-[240px] outline-none">
              {countryEntries.map(([code, info]) => <option key={code} value={code}>
                  {info.flag}{"  "}{info.label}
                </option>)}
            </select>
            <svg className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-zinc-400" viewBox="0 0 20 20" fill="currentColor" width="16" height="16">
              <path fillRule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clipRule="evenodd" />
            </svg>
          </div>
        </div>

        <div>
          <label className="block text-xs font-semibold uppercase tracking-widest text-zinc-400 mb-2">
            Category
          </label>
          <div className="relative inline-block">
            {categoryOpen && <div className="fixed inset-0 z-10" onClick={() => setCategoryOpen(false)} />}
            <button onClick={() => setCategoryOpen(o => !o)} className="appearance-none pl-3 pr-10 py-2.5 border border-zinc-200 dark:border-zinc-700 rounded-lg bg-white dark:bg-zinc-900 text-sm font-medium text-zinc-900 dark:text-white cursor-pointer min-w-[320px] outline-none text-left">
              {selectedCategory === "All" ? "All categories" : selectedCategory}
            </button>
            <svg className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none text-zinc-400" viewBox="0 0 20 20" fill="currentColor" width="16" height="16">
              <path fillRule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clipRule="evenodd" />
            </svg>
            {categoryOpen && <div className="absolute top-full mt-1 left-0 z-20 min-w-full bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded-lg shadow-lg py-1">
                <div onClick={() => {
    setSelectedCategory("All");
    setCategoryOpen(false);
  }} className="px-3 py-2 text-sm font-medium text-zinc-900 dark:text-white cursor-pointer hover:bg-zinc-100 dark:hover:bg-zinc-800">
                  All categories
                </div>
                {ALL_CATEGORIES.map(cat => {
    const disabled = !availableCategories.has(cat) || DISABLED_CATEGORIES.includes(cat);
    return <div key={cat} className="relative group" onClick={() => {
      if (!disabled) {
        setSelectedCategory(cat);
        setCategoryOpen(false);
      }
    }}>
                      <div className={["px-3 py-2 text-sm font-medium flex items-center justify-between gap-4 whitespace-nowrap", disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-zinc-100 dark:hover:bg-zinc-800"].join(" ")}>
                        <span style={{
      color: disabled ? "#d1d5db" : undefined
    }}>{cat}</span>
                        {CATEGORY_ENUMS[cat] && <code className="font-mono text-xs" style={{
      color: disabled ? "#d1d5db" : undefined
    }}>{CATEGORY_ENUMS[cat]}</code>}
                      </div>
                      {disabled && <div className="absolute right-3 top-1/2 -translate-y-1/2 hidden group-hover:flex items-center z-30">
                          <span className="bg-zinc-800 dark:bg-zinc-700 text-white text-xs px-2 py-0.5 rounded whitespace-nowrap">
                            Unavailable
                          </span>
                        </div>}
                    </div>;
  })}
              </div>}
          </div>
        </div>
      </div>

      <p className="mb-4 text-xs text-zinc-400 pl-1">
        {methods.length} payment method{methods.length !== 1 ? "s" : ""}
        {selectedCategory !== "All" ? ` in ${selectedCategory}` : " available"}
      </p>


      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2.5">
        {methods.map(method => {
    const cfg = CATEGORY_CONFIG[method.category] ?? ({
      color: "text-zinc-500 dark:text-zinc-400",
      iconPath: ""
    });
    return <div key={method.id} className="flex flex-col border border-zinc-200 dark:border-zinc-700 rounded-xl bg-white dark:bg-zinc-900 overflow-hidden">
              <div className="flex items-center gap-3 p-4">
                <div className="w-12 h-12 rounded-xl flex-shrink-0 flex items-center justify-center text-white font-bold tracking-wide" style={{
      backgroundColor: method.color || "#9ca3af",
      fontSize: "10px"
    }}>
                  {method.abbr}
                </div>
                <div className="flex flex-col gap-1 min-w-0">
                  <div className="flex items-center gap-2 flex-wrap">
                    <span className="font-semibold text-zinc-900 dark:text-white leading-tight" style={{
      fontSize: "14px"
    }}>
                      {method.name}
                    </span>
                    {method.comingSoon && <span className="inline-flex items-center rounded-full px-2 py-0.5 font-medium whitespace-nowrap" style={{
      fontSize: "10px",
      backgroundColor: "#fef9c3",
      color: "#854d0e"
    }}>
                        Coming soon
                      </span>}
                  </div>
                  <div className="flex items-center gap-1">
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-3.5 h-3.5 flex-shrink-0 text-zinc-400 dark:text-zinc-500">
                      <path strokeLinecap="round" strokeLinejoin="round" d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3zM6 6h.008v.008H6V6z" />
                    </svg>
                    <code className="font-mono text-zinc-500 dark:text-zinc-400" style={{
      fontSize: "11px"
    }}>
                      {method.id}
                    </code>
                  </div>
                </div>
              </div>
              <div className="border-t border-zinc-100 dark:border-zinc-800 px-4 pt-2.5 pb-1.5 flex items-center gap-2">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className={`w-3.5 h-3.5 flex-shrink-0 ${cfg.color}`}>
                  <path strokeLinecap="round" strokeLinejoin="round" d={cfg.iconPath} />
                </svg>
                <span className={`text-xs font-medium ${cfg.color}`}>
                  {method.category}
                </span>
              </div>
              <div className="px-4 pb-2.5">
                <code className="font-mono text-zinc-400 dark:text-zinc-500" style={{
      fontSize: "10px"
    }}>
                  {CATEGORY_ENUMS[method.category] ?? method.category}.{method.id}
                </code>
              </div>
            </div>;
  })}
      </div>
    </div>;
};

export const PAYMENT_METHODS_DATA = {
  ZA: {
    label: 'South Africa',
    flag: '🇿🇦',
    methods: [{
      id: 'card',
      name: 'Card',
      category: 'Cards',
      color: '#1d4ed8',
      abbr: 'CARD'
    }, {
      id: 'card_wallet',
      name: 'Card Wallet',
      category: 'Cards',
      color: '#de12bf',
      abbr: 'CARDW',
      comingSoon: true
    }, {
      id: 'pay_by_bank',
      name: 'Pay By Bank',
      category: 'Bank redirects',
      color: '#0d9488',
      abbr: 'BR',
      comingSoon: true
    }, {
      id: 'capitec_pay',
      name: 'Capitec Pay',
      category: 'Real-time payments',
      color: '#1e3a8a',
      abbr: 'CAP',
      comingSoon: true
    }, {
      id: 'payshap',
      name: 'PayShap',
      category: 'Real-time payments',
      color: '#0891b2',
      abbr: 'PYSP',
      comingSoon: true
    }, {
      id: 'snapscan',
      name: 'SnapScan',
      category: 'Consumer wallets',
      color: '#ea580c',
      abbr: 'SNAP',
      comingSoon: true
    }, {
      id: 'zapper',
      name: 'Zapper',
      category: 'Consumer wallets',
      color: '#7c3aed',
      abbr: 'ZAP',
      comingSoon: true
    }, {
      id: 'scantopay',
      name: 'ScanToPay',
      category: 'Consumer wallets',
      color: '#16a34a',
      abbr: 'SCAN',
      comingSoon: true
    }]
  },
  NG: {
    label: 'Nigeria',
    flag: '🇳🇬',
    methods: [{
      id: 'card',
      name: 'Card',
      category: 'Cards',
      color: '#1d4ed8',
      abbr: 'CARD'
    }, {
      id: 'quickteller',
      name: 'Quickteller',
      category: 'Bank transfers',
      color: '#2563eb',
      abbr: 'QTLR',
      comingSoon: true
    }, {
      id: 'nibss',
      name: 'Pay by Transfer',
      category: 'Real-time payments',
      color: '#4f46e5',
      abbr: 'NIBS',
      comingSoon: true
    }, {
      id: 'opay',
      name: 'OPay',
      category: 'Mobile money',
      color: '#15803d',
      abbr: 'OPAY',
      comingSoon: true
    }, {
      id: 'palmpay',
      name: 'PalmPay',
      category: 'Mobile money',
      color: '#166534',
      abbr: 'PALM',
      comingSoon: true
    }, {
      id: 'pocket',
      name: 'Pocket',
      category: 'Consumer wallets',
      color: '#1e3a8a',
      abbr: 'PCKT',
      comingSoon: true
    }]
  },
  KE: {
    label: 'Kenya',
    flag: '🇰🇪',
    methods: [{
      id: 'card',
      name: 'Card',
      category: 'Cards',
      color: '#1d4ed8',
      abbr: 'CARD'
    }, {
      id: 'mpesa',
      name: 'M-Pesa',
      category: 'Mobile money',
      color: '#16a34a',
      abbr: 'MPESA',
      comingSoon: true
    }]
  },
  TZ: {
    label: 'Tanzania',
    flag: '🇹🇿',
    methods: [{
      id: 'card',
      name: 'Card',
      category: 'Cards',
      color: '#1d4ed8',
      abbr: 'CARD'
    }, {
      id: 'mpesa',
      name: 'M-Pesa',
      category: 'Mobile money',
      color: '#16a34a',
      abbr: 'MPESA',
      comingSoon: true
    }, {
      id: 'tigopesa',
      name: 'Tigo Pesa',
      category: 'Mobile money',
      color: '#2563eb',
      abbr: 'TIGO',
      comingSoon: true
    }, {
      id: 'airtel_money',
      name: 'Airtel Money',
      category: 'Mobile money',
      color: '#dc2626',
      abbr: 'AIRT',
      comingSoon: true
    }, {
      id: 'at_money',
      name: 'AT Money',
      category: 'Mobile money',
      color: '#7c3aed',
      abbr: 'AT',
      comingSoon: true
    }]
  },
  GH: {
    label: 'Ghana',
    flag: '🇬🇭',
    methods: [{
      id: 'card',
      name: 'Card',
      category: 'Cards',
      color: '#1d4ed8',
      abbr: 'CARD'
    }, {
      id: 'mtn_momo',
      name: 'MTN MoMo',
      category: 'Mobile money',
      color: '#a16207',
      abbr: 'MTN',
      comingSoon: true
    }]
  },
  ZW: {
    label: 'Zimbabwe',
    flag: '🇿🇼',
    methods: [{
      id: 'card',
      name: 'Card',
      category: 'Cards',
      color: '#1d4ed8',
      abbr: 'CARD'
    }, {
      id: 'ecocash',
      name: 'EcoCash',
      category: 'Mobile money',
      color: '#15803d',
      abbr: 'ECO',
      comingSoon: true
    }, {
      id: 'telecel_cash',
      name: 'Telecel Cash',
      category: 'Mobile money',
      color: '#ea580c',
      abbr: 'TLC',
      comingSoon: true
    }]
  },
  ZM: {
    label: 'Zambia',
    flag: '🇿🇲',
    methods: [{
      id: 'card',
      name: 'Card',
      category: 'Cards',
      color: '#1d4ed8',
      abbr: 'CARD'
    }, {
      id: 'zamtel',
      name: 'Zamtel Kwacha',
      category: 'Mobile money',
      color: '#16a34a',
      abbr: 'ZMTL',
      comingSoon: true
    }, {
      id: 'airtel_money',
      name: 'Airtel Money',
      category: 'Mobile money',
      color: '#dc2626',
      abbr: 'AIRT',
      comingSoon: true
    }]
  }
};

Merchants can control which payment methods appear on the hosted checkout using the Payment Sessions API.\
\
By default, Moment Checkout displays every payment method enabled for the merchant and available for the session's country and currency. This guide shows how to limit the checkout to a specific method or a subset of methods, and how to pre-select one method so the customer is taken directly into that payment method's flow.\
\
There are two typical integration patterns:

* **Moment-hosted payment method selection:** Moment Checkout displays the payment methods specified in `visible_payment_methods` and allows the customer to select how they want to pay. Moment Checkout manages both payment method selection and payment collection. This is the standard integration pattern favored by Moment as it gives better conversion.
* **Merchant-controlled payment method selection:** The merchant displays the payment methods and allows the customer to select how they want to pay within their own checkout. The merchant then creates a Payment Session for the selected payment method and redirects the customer to Moment Checkout to complete the payment.

\
This guide builds on the [One-Time Payments quickstart](/api-reference/collect/payment_sessions/quickstart-one-time). If you have not created a one-time payment session before, start there.

## Prerequisites

* A test API key. See [Authentication](/api-reference/getting-started/authentication).

## How it works

When you create a payment session, the response includes a `session_url` that renders the Moment hosted checkout. Without any additional configuration, the checkout lists all payment methods supported for the merchant and that transaction.\
\
To control this list, pass the `visible_payment_methods` array inside `options.checkout_options` when you create the session. To pre-select one of those methods, add `selected_payment_method`.

<Note>
  If you pass only one payment method in the `visible_payment_methods` array, then by default that payment method will be pre-selected and the user will be taken straight into that payment method's journey.

  For example, if `cards` is the only payment method passed in `visible_payment_methods` field then the user will be taken straight to the card information page to enter their card details.
</Note>

<Tip>
  See [Supported Payment Methods](/api-reference/collect/payment_sessions/quickstart-control-payment-methods#supported-payment-methods) for more details on setting the `visible_payment_methods` and `selected_payment_method` attributes.
</Tip>

## **Moment-hosted payment method selection**

Use this pattern when you want Moment Checkout to manage both payment method selection and payment collection.\
\
Create a Payment Session with the payment methods you want to make available in `visible_payment_methods`, then redirect the customer to the returned `session_url`.\
\
Moment Checkout displays the available payment methods and allows the customer to select how they want to pay. The customer remains within the hosted checkout while completing the payment and, where additional attempts are available, can retry or select another payment method without returning to your checkout.\
\
This is the standard integration pattern and provides the simplest integration because Moment Checkout manages the complete payment experience.

```mermaid theme={"system"}
sequenceDiagram
    participant Customer
    participant MerchantCheckout as Merchant Checkout
    participant MerchantServer as Merchant Server
    participant MomentAPI as Moment Payment Sessions API
    participant MomentCheckout as Moment Checkout

    Customer->>MerchantCheckout: Clicks pay
    MerchantCheckout->>MerchantServer: Request payment session
    MerchantServer->>MomentAPI: Create Payment Session with visible_payment_methods
    MomentAPI-->>MerchantServer: Return session_url
    MerchantServer-->>MerchantCheckout: Return session_url
    MerchantCheckout->>MomentCheckout: Redirect customer
    MomentCheckout->>Customer: Display available payment methods
    Customer->>MomentCheckout: Select payment method and complete payment
    MomentCheckout-->>Customer: Show payment result
    MomentCheckout-->>MerchantCheckout: Redirect to return_url
```

### Step 1: Restrict the visible payment methods

Add `visible_payment_methods` to `options.checkout_options`. Pass an array of the payment method types you want to display. Only these methods will appear on the checkout.

For example, to support Capitec Pay (`capitec_pay`) and Card (`card`):

```shellscript theme={"system"}
curl -X POST https://api.momentpay.net/collect/payment_sessions \
  -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10000,
    "currency": "ZAR",
    "type": "one_time",
    "options": {
      "checkout_options": {
        "visible_payment_methods": ["capitec_pay", "card"]
      }
    }
  }'
```

The response is the same as a standard one-time session. The `id` can be used to match the webhook event.

```json theme={"system"}
{
  "id": "ps_kfAWZgfQIoeG0q",
  "status": "active",
  "payment_status": "unpaid",
  "payment_outcome": "not_started",
  "amount": 10000,
  "currency": "ZAR",
  "session_url": "https://checkout.momentpay.net/ckt802zEb2P2uS4Ql",
  "created_at": "2025-03-18T14:06:51.149Z",
  "updated_at": "2025-03-18T14:06:51.149Z"
}
```

Redirect the customer to the `session_url` returned when the Payment Session is created.

<Info>
  An alternative to redirecting is to use our SDK, to implement a modal/pop-up styled hosted checkout. See [Libraries and SDKs](/libraries-and-sdks/web/javascript).
</Info>

The checkout will be restricted as follows:

<Frame>
  <img src="https://mintcdn.com/momentholdingslimited/Vv_xQQxXQYcJZW2A/images/Screenshot-2026-07-09-at-8.03.13-PM.png?fit=max&auto=format&n=Vv_xQQxXQYcJZW2A&q=85&s=891c8b5ff5404ee23077e845adb1b5e8" alt="Screenshot 2026 07 09 At 8 03 13 PM" width="3016" height="1890" data-path="images/Screenshot-2026-07-09-at-8.03.13-PM.png" />
</Frame>

### Step 2 (optional): Pre-select a payment method

When `visible_payment_methods` contains more than one method, you can pre-select one of them with `selected_payment_method`. The pre-selected method is applied by default when the checkout loads, and the customer is taken directly into that payment method's journey without having to choose it on the payment method selection screen.

```json theme={"system"}
"options": {
   "checkout_options": {
     "visible_payment_methods": ["capitec_pay", "card"],
     "selected_payment_method": "capitec_pay"
   }
}
```

The checkout will directly present the Capitec Pay (`capitec_pay`) payment method, but the Card (`card`)  payment method remains available if the customer chooses that instead:

<Frame>
  <img src="https://mintcdn.com/momentholdingslimited/Vv_xQQxXQYcJZW2A/images/Screenshot-2026-07-09-at-8.02.41-PM.png?fit=max&auto=format&n=Vv_xQQxXQYcJZW2A&q=85&s=28d90b55d7903a291f5398bc45606467" alt="Screenshot 2026 07 09 At 8 02 41 PM" width="3012" height="1892" data-path="images/Screenshot-2026-07-09-at-8.02.41-PM.png" />
</Frame>

## **Merchant-controlled payment method selection**

Use this pattern when you want to manage payment method selection within your own checkout while using Moment Checkout to complete the payment.\
\
Your checkout displays the available payment methods and allows the customer to select how they want to pay. When the customer proceeds with a selected payment method, create a Payment Session with that method as the only value in `visible_payment_methods`.\
\
Because only one payment method is visible, Moment Checkout opens directly into the selected payment method’s flow without displaying the payment method selection screen.\
\
Set `max_attempts` to `1` so control returns to your checkout after the selected payment attempt succeeds or fails. Your checkout can then display the payment outcome or allow the customer to select another payment method.

```mermaid theme={"system"}
sequenceDiagram
    participant Customer
    participant MerchantCheckout as Merchant Checkout
    participant MerchantServer as Merchant Server
    participant MomentAPI as Moment Payment Sessions API
    participant MomentCheckout as Moment Checkout

    MerchantCheckout->>Customer: Display available payment methods
    Customer->>MerchantCheckout: Select payment method
    MerchantCheckout->>MerchantServer: Request session for selected method
    MerchantServer->>MomentAPI: Create Payment Session<br/>visible_payment_methods: [selected_method]<br/>max_attempts: 1
    MomentAPI-->>MerchantServer: Return session_url
    MerchantServer-->>MerchantCheckout: Return session_url
    MerchantCheckout->>MomentCheckout: Redirect customer
    MomentCheckout->>Customer: Open selected payment method flow
    Customer->>MomentCheckout: Complete payment attempt

    alt Payment succeeds
        MomentCheckout-->>MerchantCheckout: Redirect to return_url
        MerchantCheckout->>Customer: Display payment confirmation
    else Payment fails
        MomentCheckout-->>MerchantCheckout: Redirect to return_url
        MerchantCheckout->>Customer: Display payment methods
        Customer->>MerchantCheckout: Select another payment method
        MerchantCheckout->>MerchantServer: Request a new Payment Session
        MerchantServer->>MomentAPI: Create new Payment Session<br/>for newly selected method
        MomentAPI-->>MerchantServer: Return new session_url
        MerchantServer-->>MerchantCheckout: Return new session_url
        MerchantCheckout->>MomentCheckout: Redirect customer
    end
```

<Note>
  This pattern allows you to own the payment method selection experience while using Moment Checkout to complete the selected payment method flow.
</Note>

### **Step 1: Display payment methods in your checkout**

Display the payment methods you want to make available using your own checkout interface.

Your integration is responsible for determining which payment methods to display and mapping each option to its corresponding Moment payment method type.

### **Step 2: Create a Payment Session for the selected payment method**

When the customer selects a payment method and proceeds to pay, create a Payment Session from your server.

Pass the selected payment method as the only value in `visible_payment_methods` and set `max_attempts` to `1`.

For example, if the customer selects Capitec Pay:

```shellscript theme={"system"}
curl -X POST https://api.momentpay.net/collect/payment_sessions \
  -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10000,
    "currency": "ZAR",
    "type": "one_time",
    "options": {
      "checkout_options": {
        "visible_payment_methods": ["capitec_pay"],
        "max_attempts": 1,
        "return_url": "https://merchant.example/checkout/order_123/payment-result"
      }
    },
    "external_reference": "order_123"
  }'
```

The response is the same as a standard one-time session. The `id` can be used to match the webhook event.

```json theme={"system"}
{
  "id": "ps_kfAWZgfQIoeG0q",
  "status": "active",
  "payment_status": "unpaid",
  "payment_outcome": "not_started",
  "amount": 10000,
  "currency": "ZAR",
  "session_url": "https://checkout.momentpay.net/ckt802zEb2P2uS4Ql",
  "created_at": "2025-03-18T14:06:51.149Z",
  "updated_at": "2025-03-18T14:06:51.149Z"
}
```

Redirect the customer to the `session_url` returned when the Payment Session is created.

<Info>
  An alternative to redirecting is to use our SDK, to implement a modal/pop-up styled hosted checkout. See [Libraries and SDKs](/libraries-and-sdks/web/javascript).
</Info>

The checkout will directly present the Capitec Pay (`capitec_pay`) payment method as the only option:

<Frame>
  <img src="https://mintcdn.com/momentholdingslimited/Vv_xQQxXQYcJZW2A/images/Screenshot-2026-07-09-at-8.02.41-PM.png?fit=max&auto=format&n=Vv_xQQxXQYcJZW2A&q=85&s=28d90b55d7903a291f5398bc45606467" alt="Screenshot 2026 07 09 At 8 02 41 PM" width="3012" height="1892" data-path="images/Screenshot-2026-07-09-at-8.02.41-PM.png" />
</Frame>

### **Step 3: Handle the customer’s return**

After the payment attempt succeeds or fails, Moment Checkout redirects the customer to the configured `return_url`.

When the customer returns, retrieve the Payment Session to display the latest payment status. Use webhooks as the authoritative source for asynchronous payment status updates.

If the payment was unsuccessful, you can allow the customer to select the same or a different payment method from your checkout. When the customer proceeds again, create a new Payment Session using the newly selected payment method (as per the earlier step above).

## Supported Payment Methods

The following payment methods can be configured by providing `visible_payment_methods` and `selected_payment_method`:

<PaymentMethodsByCountry data={PAYMENT_METHODS_DATA} />

<Tip>**Note:** Use the payment method `category` and payment method `type` above to control the visible and selected payment methods.</Tip>

### How to pass `visible_payment_methods` :

When you pass the `visible_payment_methods` in the PS API request, it needs to follow the following format. You need to pass the payment method's `category.type` or you can pass only the `category` or only the `type`. See below the examples for all 3 variations.

**Example 1 - Passing** `category.type `**:**

Passing a `category` and `type` in the request tells Moment to show only that `type` within the `category`.

**Request:**

```shellscript highlight={10} theme={"system"}
curl -X POST https://api.momentpay.net/collect/payment_sessions \
  -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10000,
    "currency": "ZAR",
    "type": "one_time",
    "options": {
      "checkout_options": {
        "visible_payment_methods": ["real_time_payments.capitec_pay", "cards.card"]
      }
    }
  }'
```

**Response:**

```shellscript theme={"system"}
{
  "id": "ps_kfAWZgfQIoeG0q",
  "status": "active",
  "payment_status": "unpaid",
  "payment_outcome": "not_started",
  "amount": 10000,
  "currency": "ZAR",
  "session_url": "https://checkout.momentpay.net/ckt802zEb2P2uS4Ql",
  "created_at": "2025-03-18T14:06:51.149Z",
  "updated_at": "2025-03-18T14:06:51.149Z"
}
```

**Example 2 - Passing only** `category `**:**

Passing a `category` in the request tells Moment to show all the types within the `category`.

**Request:**

```shellscript highlight={10} theme={"system"}
curl -X POST https://api.momentpay.net/collect/payment_sessions \
  -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10000,
    "currency": "ZAR",
    "type": "one_time",
    "options": {
      "checkout_options": {
        "visible_payment_methods": ["real_time_payments", "cards"]
      }
    }
  }'
```

**Response:**

```shellscript theme={"system"}
{
  "id": "ps_kfAWZgfQIoeG0q",
  "status": "active",
  "payment_status": "unpaid",
  "payment_outcome": "not_started",
  "amount": 10000,
  "currency": "ZAR",
  "session_url": "https://checkout.momentpay.net/ckt802zEb2P2uS4Ql",
  "created_at": "2025-03-18T14:06:51.149Z",
  "updated_at": "2025-03-18T14:06:51.149Z"
}
```

**Example 3 - Passing only** `type` **:**

Passing only `type` in the request tells Moment to show only that `type`.

**Request:**

```shellscript highlight={10} theme={"system"}
curl -X POST https://api.momentpay.net/collect/payment_sessions \
  -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10000,
    "currency": "ZAR",
    "type": "one_time",
    "options": {
      "checkout_options": {
        "visible_payment_methods": ["capitec_pay", "card"]
      }
    }
  }'
```

**Response:**

```shellscript theme={"system"}
{
  "id": "ps_kfAWZgfQIoeG0q",
  "status": "active",
  "payment_status": "unpaid",
  "payment_outcome": "not_started",
  "amount": 10000,
  "currency": "ZAR",
  "session_url": "https://checkout.momentpay.net/ckt802zEb2P2uS4Ql",
  "created_at": "2025-03-18T14:06:51.149Z",
  "updated_at": "2025-03-18T14:06:51.149Z"
}
```

### How to pass `selected_payment_method` :

Pass a category and type in `selected_payment_method` to pre-select that method when the checkout loads. For example, `selected_payment_method: "cards.card"` opens the customer directly into the card flow, skipping the method selection screen. The value must resolve to one of the methods present in `visible_payment_methods`, and it applies only when more than one method is visible.

How `selected_payment_method` can be specified depends on how `visible_payment_methods` was set:

* If `visible_payment_methods` specifies only a category, `selected_payment_method` can be either `category.type` or just `type`.
* If `visible_payment_methods` specifies a type, `selected_payment_method` cannot be a category on its own.

Please see below example of when a wrong value is passed in the `selected_payment_method` parameter:

**Request:**

```shellscript highlight={10-11} theme={"system"}
curl -X POST https://api.momentpay.net/collect/payment_sessions \
  -H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10000,
    "currency": "ZAR",
    "type": "one_time",
    "options": {
      "checkout_options": {
        "visible_payment_methods": ["capitec_pay", "card"],
        "selected_payment_method": "real_time_payments"
      }
    }
  }'
```

**Response:**

```shellscript theme={"system"}
{
    "title": "Invalid payment methods in visible_payment_methods",
    "status": 400,
    "code": "invalid_visible_payment_methods",
    "detail": "The payment methods specified in visible_payment_methods are invalid. Please re-check.",
    "instance": "/collect/payment_sessions"
}
```

<Note>
  **<u>Rules</u>** for `visible_payment_methods:`

  * It is an array of strings.
  * The visible payment methods must be a strict subset of the enabled payment methods for the merchant account.
  * When not specified all payment methods enabled for the merchant account and available for the session's country and currency are visible.
  * If you pass only one payment method, then by default that payment method will be pre-selected and the user will be taken straight into that payment method's journey.
</Note>

<Note>
  **<u>Rules</u>** for `selected_payment_method:`

  * It is a string and accepts one value only.
  * The value must also be present in `visible_payment_methods`.
  * It applies only when `visible_payment_methods` contains more than one entry.
</Note>

## Match payment methods to the currency

The methods you request must be valid for the session's country and currency. For example, a session created in `ZAR` must list methods supported in South Africa.

If any method in `visible_payment_methods` is not supported for the session's currency, the request fails with `400 Bad Request`.

## Launch the checkout, handle the webhook, and test

Launching the checkout, handling the `payment_session.completed` webhook, verifying the signature, and testing are identical to the standard flow. See [Quickstart: One-Time Payments](/api-reference/collect/payment_sessions/quickstart-one-time) for these steps.
