# Accept stablecoin payments
Start accepting stablecoins by enabling the Crypto payment method.
You can accept *stablecoin* (A cryptocurrency that's pegged to the value of a fiat currency or other asset in order to limit volatility) payments through *Payment Links* (A link to a secure, hosted payment page that you can generate without code. Share it directly with your customers, or point them to it with a button or QR code), *Checkout* (A low-code payment integration that creates a customizable form for collecting payments. You can embed Checkout directly in your website, redirect customers to a Stripe-hosted payment page, or create a customized checkout page with Stripe Elements), *Elements* (A set of UI components for building a web checkout flow. They adapt to your customer's locale, validate input, and use tokenization, keeping sensitive customer data from touching your server) or the *Payment Intents API* (The Payment Intents API tracks the lifecycle of a customer checkout flow and triggers additional authentication steps when required by regulatory mandates, custom Radar fraud rules, or redirect-based payment methods). When paying with stablecoins such as USDC, customers get redirected to **crypto.stripe.com** to connect their crypto wallet and complete the transaction. Funds settle in your Stripe balance in USD.
## Before you begin
> Your customers can use stablecoins as payment globally, but only US businesses can accept stablecoin payments.
To start accepting stablecoin payments:
1. Make sure you’ve [set up your Stripe account](https://dashboard.stripe.com/register).
1. Go to your [Payment methods](https://dashboard.stripe.com/settings/payment_methods) settings in the Dashboard and request the **Stablecoins and Crypto** payment method.
1. Stripe reviews your access request and contacts you for more details if necessary. The payment method appears as **Pending** while we review your request.
1. After we approve your request, the **Stablecoins and Cryptocurrency** payment method becomes active in the Dashboard.
## Use with dynamic payment methods (Recommended)
If you use Stripe’s default [dynamic payment methods](https://docs.stripe.com/payments/payment-methods/dynamic-payment-methods.md) with Payment Links, Hosted Checkout, Embedded Checkout Forms or Elements, then you don’t need to make any further updates. Stripe automatically displays stablecoin payment options to eligible customers.
## Use with a custom integration
If necessary, you can add the crypto payment method to your payment integration manually.
# Checkout Sessions API
To determine which API meets your business needs, see the [comparison guide](https://docs.stripe.com/payments/checkout-sessions-and-payment-intents-comparison.md).
Use the [Payment Element](https://docs.stripe.com/payments/payment-element.md) to embed a custom Stripe payment form in your website or application and offer payment methods to customers. For advanced configurations and customisations, refer to the [Accept a Payment](https://docs.stripe.com/payments/accept-a-payment.md) integration guide.
Embed a custom payment form in your website or application using the [Payment Element](https://docs.stripe.com/payments/payment-element.md). The Payment Element automatically supports crypto and other payment methods. For additional configuration and customisation options, see [Accept a payment](https://docs.stripe.com/payments/accept-a-payment.md?payment-ui=elements&api-integration=checkout).
## Set up the server [Server-side]
Use the official Stripe libraries to access the API from your application.
#### Ruby
```bash
# Available as a gem
sudo gem install stripe
```
```ruby
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
```
## Create a Checkout Session [Server-side]
Add an endpoint on your server that creates a [Checkout Session](https://docs.stripe.com/api/checkout/sessions/create.md) and returns its [client secret](https://docs.stripe.com/api/checkout/sessions/object.md#checkout_session_object-client_secret) to your front end. A Checkout Session represents your customer’s session as they pay for one-off purchases or subscriptions. Checkout Sessions expire 24 hours after creation.
We recommend using [dynamic payment methods](https://docs.stripe.com/payments/payment-methods/dynamic-payment-methods.md) to dynamically display the most relevant eligible payment methods to each customer to maximise conversion. You can also [manually list payment methods](https://docs.stripe.com/payments/payment-methods/integration-options.md#listing-payment-methods-manually), which disables dynamic payment methods.
#### Manage payment methods from the Dashboard
#### TypeScript
```javascript
import express, {Express} from 'express';
const app: Express = express();
app.post('/create-checkout-session', async (req: Express.Request, res: Express.Response) => {
const session = await stripe.checkout.sessions.create({
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'T-shirt',
},
unit_amount: 1099,
},
quantity: 1,
},
],
mode: 'payment',
ui_mode: 'elements',
return_url: 'https://example.com/return?session_id={CHECKOUT_SESSION_ID}'
});
res.json({checkoutSessionClientSecret: session.client_secret});
});
app.listen(3000, () => {
console.log('Running on port 3000');
});
```
#### Manually list payment methods
#### TypeScript
```javascript
import express, {Express} from 'express';
const app: Express = express();
app.post('/create-checkout-session', async (req: Express.Request, res: Express.Response) => {
const session = await stripe.checkout.sessions.create({
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'T-shirt',
},
unit_amount: 1099,
},
quantity: 1,
},
],
mode: 'payment',
ui_mode: 'elements',
payment_method_types: ['crypto'],
return_url: 'https://example.com/return?session_id={CHECKOUT_SESSION_ID}'
});
res.json({checkoutSessionClientSecret: session.client_secret});
});
app.listen(3000, () => {
console.log('Running on port 3000');
});
```
## Set up the front end [Client-side]
#### HTML + JS
Include the Stripe.js script on your checkout page by adding it to the `head` of your HTML file. Always load Stripe.js directly from js.stripe.com to remain PCI compliant. Don’t include the script in a bundle or host a copy of it yourself.
Make sure you’re on the latest Stripe.js version by including the following script tag ``. Learn more about [Stripe.js versioning](https://docs.stripe.com/sdks/stripejs-versioning.md).
```html
Checkout
```
> Stripe provides an npm package that you can use to load Stripe.js as a module. See the [project on GitHub](https://github.com/stripe/stripe-js). Version [7.0.0](https://www.npmjs.com/package/%40stripe/stripe-js/v/7.0.0) or later is required.
Initialise stripe.js.
```js
// Set your publishable key: remember to change this to your live publishable key in production
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = Stripe(
'<>',
);
```
#### React
Install [React Stripe.js](https://www.npmjs.com/package/@stripe/react-stripe-js) and the [Stripe.js loader](https://www.npmjs.com/package/@stripe/stripe-js) from the npm public registry. You need at least version 5.0.0 for React Stripe.js and version 8.0.0 for the Stripe.js loader.
```bash
npm install --save @stripe/react-stripe-js@^5.0.0 @stripe/stripe-js@^8.0.0
```
Initialise a `stripe` instance on your front end with your publishable key.
```javascript
import {loadStripe} from '@stripe/stripe-js';
const stripe = loadStripe("<>");
```
## Initialise Checkout [Client-side]
#### HTML + JS
Call [initCheckoutElementsSdk](https://docs.stripe.com/js/custom_checkout/init), passing in `clientSecret`.
`initCheckoutElementsSdk` returns a [Checkout](https://docs.stripe.com/js/custom_checkout) object that contains data from the Checkout Session and methods to update it.
Read the `total` and `lineItems` from [actions.getSession()](https://docs.stripe.com/js/custom_checkout/session), and display them in your UI. This lets you turn on new features with minimal code changes. For example, adding [manual currency prices](https://docs.stripe.com/payments/custom/localize-prices/manual-currency-prices.md) requires no UI changes if you display the `total`.
```html
```
```javascript
const clientSecret = fetch('/create-checkout-session', {method: 'POST'})
.then((response) => response.json())
.then((json) => json.client_secret);
const checkout = stripe.initCheckoutElementsSdk({clientSecret});
const loadActionsResult = await checkout.loadActions();
if (loadActionsResult.type === 'success') {
const session = loadActionsResult.actions.getSession();
const checkoutContainer = document.getElementById('checkout-container');
checkoutContainer.append(JSON.stringify(session.lineItems, null, 2));
checkoutContainer.append(document.createElement('br'));
checkoutContainer.append(`Total: ${session.total.total.amount}`);
}
```
#### React
Wrap your application with the [CheckoutElementsProvider](https://docs.stripe.com/js/react_stripe_js/checkout/checkout_provider) component, passing in `clientSecret` and the `stripe` instance.
```jsx
import React from 'react';
import {CheckoutElementsProvider} from '@stripe/react-stripe-js/checkout';
import CheckoutForm from './CheckoutForm';
const clientSecret = fetch('/create-checkout-session', {method: 'POST'})
.then((response) => response.json())
.then((json) => json.client_secret);
const App = () => {
return (
);
};
export default App;
```
Access the [Checkout](https://docs.stripe.com/js/custom_checkout) object in your checkout form component by using the `useCheckoutElements()` hook. The `Checkout` object contains data from the Checkout Session and methods to update it.
Read the `total` and `lineItems` from the `Checkout` object, and display them in your UI. This lets you enable features with minimal code changes. For example, adding [manual currency prices](https://docs.stripe.com/payments/custom/localize-prices/manual-currency-prices.md) requires no UI changes if you display the `total`.
```jsx
import React from 'react';
import {useCheckoutElements} from '@stripe/react-stripe-js/checkout';
const CheckoutForm = () => {const checkoutState = useCheckoutElements();
if (checkoutState.type === 'loading') {
return (
Loading...
);
}
if (checkoutState.type === 'error') {
return (
Error: {checkoutState.error.message}
);
}
return (
);
};
```
## Collect customer email address [Client-side]
#### HTML + JS
You must provide a valid customer email when completing a Checkout Session.
Use the [Contact Details Element](https://docs.stripe.com/js/custom_checkout/create_contact_details_element) to collect your customer’s email address. It handles email collection and validation for you, and helps customers sign in to Link.
Alternatively, you can:
- Pass in [customer_email](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-customer_email), [customer_account](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-customer_account) (for customers represented as customer-configured `Account` objects), or [customer](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-customer) (for customers represented as `Customer` objects) when creating the Checkout Session. Stripe validates emails provided this way.
- These prefill the session with an email that customers can’t edit on the checkout page. If you want to prefill with an editable email, use [defaultValues.email](https://docs.stripe.com/js/custom_checkout/init#custom_checkout_init-options-defaultValues-email) when initialising Checkout.
- Pass in an email you already validated on [updateEmail](https://docs.stripe.com/js/custom_checkout/update_email) or [checkout.confirm](https://docs.stripe.com/js/custom_checkout/confirm).
```html
```
```javascript
const contactDetailsElement = checkout.createContactDetailsElement();
contactDetailsElement.mount("#contact-details-element");
```
#### React
You must provide a valid customer email when completing a Checkout Session.
Use the [ContactDetailsElement](https://docs.stripe.com/js/react_stripe_js/checkout/contact_details_element) to collect your customer’s email address. It handles email collection and validation for you, and helps customers sign in to Link.
Alternatively, you can:
- Pass in [customer_email](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-customer_email), [customer_account](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-customer_account) (for customers represented as customer-configured `Account` objects), or [customer](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-customer) (for customers represented as `Customer` objects) when creating the Checkout Session. Stripe validates emails provided this way.
- These prefill the session with an email that customers can’t edit on the checkout page. If you want to prefill with an editable email, use [defaultValues.email](https://docs.stripe.com/js/custom_checkout/init#custom_checkout_init-options-defaultValues-email) when initialising Checkout.
- Pass in an email you already validated on [updateEmail](https://docs.stripe.com/js/react_stripe_js/checkout/update_email) or [confirm](https://docs.stripe.com/js/react_stripe_js/checkout/confirm).
```jsx
import {ContactDetailsElement} from '@stripe/react-stripe-js/checkout';
// Inside your CheckoutForm component:
```
## Collect payment details [Client-side]
Collect payment details on the client with the [Payment Element](https://docs.stripe.com/payments/payment-element.md). The Payment Element is a pre-built UI component that simplifies collecting payment details for a variety of payment methods.
The Payment Element contains an iframe that securely sends payment information to Stripe over an HTTPS connection. Avoid placing the Payment Element within another iframe because some payment methods require redirecting to another page for payment confirmation.
If you choose to use an iframe and want to accept Apple Pay or Google Pay, the iframe must have the [allow](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allowpaymentrequest) attribute set to equal `"payment *"`.
The checkout page address must start with `https://` rather than `http://` for your integration to work. You can test your integration without using HTTPS, but remember to [enable it](https://docs.stripe.com/security/guide.md#tls) when you’re ready to accept live payments.
#### HTML + JS
First, create a container DOM element to mount the [Payment Element](https://docs.stripe.com/payments/payment-element.md). Then create an instance of the `Payment Element` using [checkout.createPaymentElement](https://docs.stripe.com/js/custom_checkout/create_payment_element) and mount it by calling [element.mount](https://docs.stripe.com/js/element/mount), providing either a CSS selector or the container DOM element.
```html
```
```javascript
const paymentElement = checkout.createPaymentElement();
paymentElement.mount('#payment-element');
```
See the [Stripe.js docs](https://docs.stripe.com/js/custom_checkout/create_payment_element#custom_checkout_create_payment_element-options) to view the supported options.
You can [customise the appearance](https://docs.stripe.com/payments/checkout/customization/appearance.md) of all Elements by passing [elementsOptions.appearance](https://docs.stripe.com/js/custom_checkout/init#custom_checkout_init-options-elementsOptions-appearance) when initialising Checkout on the front end.
#### React
Mount the [Payment Element](https://docs.stripe.com/payments/payment-element.md) component within the [CheckoutElementsProvider](https://docs.stripe.com/js/react_stripe_js/checkout/checkout_provider).
```jsx
import React from 'react';import {PaymentElement, useCheckoutElements} from '@stripe/react-stripe-js/checkout';
const CheckoutForm = () => {
const checkoutState = useCheckoutElements();
if (checkoutState.type === 'loading') {
return (
Loading...
);
}
if (checkoutState.type === 'error') {
return (
Error: {checkoutState.error.message}
);
}
return (
);
};
export default CheckoutForm;
```
See the [Stripe.js docs](https://docs.stripe.com/js/custom_checkout/create_payment_element#custom_checkout_create_payment_element-options) to view the supported options.
You can [customise the appearance](https://docs.stripe.com/payments/checkout/customization/appearance.md) of all Elements by passing [elementsOptions.appearance](https://docs.stripe.com/js/react_stripe_js/checkout/checkout_provider#react_checkout_provider-options-elementsOptions-appearance) to the [CheckoutElementsProvider](https://docs.stripe.com/js/react_stripe_js/checkout/checkout_provider).
## Submit the payment [Client-side]
#### HTML + JS
Render a **Pay** button that calls [confirm](https://docs.stripe.com/js/custom_checkout/confirm) from the `Checkout` instance to submit the payment.
```html
```
```js
const checkout = stripe.initCheckoutElementsSdk({clientSecret});
checkout.on('change', (session) => {
document.getElementById('pay-button').disabled = !session.canConfirm;
});
const loadActionsResult = await checkout.loadActions();
if (loadActionsResult.type === 'success') {
const {actions} = loadActionsResult;
const button = document.getElementById('pay-button');
const errors = document.getElementById('confirm-errors');
button.addEventListener('click', () => {
// Clear any validation errors
errors.textContent = '';
actions.confirm().then((result) => {
if (result.type === 'error') {
errors.textContent = result.error.message;
}
});
});
}
```
#### React
Render a **Pay** button that calls [confirm](https://docs.stripe.com/js/custom_checkout/confirm) from [useCheckoutElements](https://docs.stripe.com/js/react_stripe_js/checkout/use_checkout_elements) to submit the payment.
```jsx
import React from 'react';
import {useCheckoutElements} from '@stripe/react-stripe-js/checkout';
const PayButton = () => {
const checkoutState = useCheckoutElements();
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState(null);
if (checkoutState.type !== "success") {
return null;
}
const handleClick = () => {
setLoading(true);checkoutState.checkout.confirm().then((result) => {
if (result.type === 'error') {
setError(result.error)
}
setLoading(false);
})
};
return (
{error &&
{error.message}
}
)
};
export default PayButton;
```
## Test your integration
Test your crypto payment integration by opening the payment redirect page using your test API keys. You can test a successful payment flow at no cost using [testnet assets](https://docs.stripe.com/payments/accept-stablecoin-payments.md#testnet-assets).
1. In a *sandbox* (A sandbox is an isolated test environment that allows you to test Stripe functionality in your account without affecting your live integration. Use sandboxes to safely experiment with new features and changes), create a new transaction using your chosen integration method and open its redirect URL.
1. Connect your preferred wallet and payment network.
1. Complete the payment and validate that you’re redirected to the expected URL.
### Test payments with testnet assets
Most cryptocurrencies offer testnet assets or tokens that have no monetary value, which you can use to test blockchain transactions. Stripe recommends the MetaMask wallet, Polygon Amoy testnet and Circle faucet for testing, but you can use your own preferred services.
#### Install a wallet
1. [Download the MetaMask extension](https://metamask.io/download) for your web browser.
1. [Create a new wallet](https://support.metamask.io/start/creating-a-new-wallet/) or [import an existing one](https://support.metamask.io/start/use-an-existing-wallet/).
#### Enable a testnet
1. In your MetaMask wallet, select **Networks** from the main menu.
1. Click **Add custom network**.
1. Enter the following details:
- **Network name**: `Amoy`
- **Default RPC URL**: `https://rpc-amoy.polygon.technology/`
- **Chain ID**: `80002`
- **Currency symbol**: `POL`
- **Block explorer URL**: `https://amoy.polygonscan.com/`
1. Click **Save**.
#### Import a token
1. In your MetaMask wallet, under **Tokens**, select **Amoy** from the network dropdown.
1. Click the overflow menu (⋯) and select **Import tokens**.
1. Click **Select a network** > **Amoy**.
1. Under **Token contract address**, paste the Polygon Amoy testnet contract address:
```
0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582
```
The **Token symbol** field automatically updates with `USDC` and the **Decimals** field with `6`.
1. Click **Next**.
1. Verify that you’re importing the `USDC` token and then click **Import**.
Your MetaMask wallet now shows **POL** and **USDC** in the tokens list.
When testing refunds, the token sent to your wallet might have a different contract than the one used for payment. We recommend reviewing the [transaction_hash](https://docs.stripe.com/api/refunds/object.md?rds=1#refund_object-destination_details-crypto-reference) of your refund on block explorer, and adding that token’s contract to your wallet. For example, you might see a `0x58277ebcabbe2a6694fbca8daf9e23163dbacf3e` token contract address for Sepolia ETH USDC refunds.
#### Get testnet assets
> To use some testnet faucets, you might need to hold a small amount of mainnet tokens.
1. Open [faucet.circle.com](https://faucet.circle.com/)
1. Click **USDC**.
1. Under **Network**, select **Polygon PoS Amoy**.
1. Under **Send to**, paste your wallet address.
1. Click **Send 20 USDC**.
In addition to USDC for making payments, you need POL to pay transaction costs:
1. Open [faucet.polygon.technology](https://faucet.polygon.technology/).
1. Under **Select Chain & Token**, select **Polygon Amoy** and **POL**.
1. Under **Verify your identity**, click the third-party platform you want to authenticate with and complete the log-in process.
1. Under **Enter Wallet Address**, paste your wallet address.
1. Click **Claim**.
Testnet transactions can take a few minutes to complete. Check your wallet to confirm that the USDC and POL has transferred.
### More testnet faucets
Check these faucet services for more testing token options:
- [Paxos USDP](https://faucet.paxos.com/)
- [Devnet SOL](https://faucet.solana.com/)
- [Sepolia ETH](https://faucets.chain.link/sepolia)
- [Amoy POL](https://faucet.polygon.technology/)