# Embedded Components onramp quickstart

Build a fiat-to-crypto onramp flow with the Embedded Components SDK.

## Set up the Embedded Components onramp 

This quickstart runs minimal code for the full flow. Use this when you want to get started quickly, or to explore the integration before you start building.

For React Native, the production SDK is [@stripe/stripe-react-native](https://github.com/stripe/stripe-react-native). For web, use [@stripe/crypto](https://www.npmjs.com/package/@stripe/crypto).

See our sample apps: [React Native (Expo)](https://github.com/stripe-samples/crypto-embedded-components-onramp) | [iOS](https://github.com/stripe/stripe-ios/tree/master/Example/CryptoOnramp%20Example) | [Android](https://github.com/stripe/stripe-android/tree/master/crypto-onramp-example). Follow the [integration guide](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md) for step-by-step instructions to build your integration.

### Install the Stripe Node library

Install the package and import it in your code. Alternatively, if you’re starting from scratch and need a package.json file, download the project files using the Download link in the code editor.

#### npm

Install the library:

```bash
npm install --save stripe
```

#### GitHub

Or download the stripe-node library source code directly [from GitHub](https://github.com/stripe/stripe-node).

### Install the Stripe Ruby library

Install the Stripe ruby gem and require it in your code. Alternatively, if you’re starting from scratch and need a Gemfile, download the project files using the link in the code editor.

#### Terminal

Install the gem:

```bash
gem install stripe
```

#### Bundler

Add this line to your Gemfile:

```bash
gem 'stripe'
```

#### GitHub

Or download the stripe-ruby gem source code directly [from GitHub](https://github.com/stripe/stripe-ruby).

### Install the Stripe Java library

Add the dependency to your build and import the library. Alternatively, if you’re starting from scratch and need a sample pom.xml file (for Maven), download the project files using the link in the code editor.

#### Maven

Add the following dependency to your POM and replace {VERSION} with the version number you want to use.

```bash
<dependency>\n<groupId>com.stripe</groupId>\n<artifactId>stripe-java</artifactId>\n<version>{VERSION}</version>\n</dependency>
```

#### Gradle

Add the dependency to your build.gradle file and replace {VERSION} with the version number you want to use.

```bash
implementation "com.stripe:stripe-java:{VERSION}"
```

#### GitHub

Download the JAR directly [from GitHub](https://github.com/stripe/stripe-java/releases/latest).

### Install the Stripe Python package

Install the Stripe package and import it in your code. Alternatively, if you’re starting from scratch and need a requirements.txt file, download the project files using the link in the code editor.

#### pip

Install the package through pip:

```bash
pip3 install stripe
```

#### GitHub

Download the stripe-python library source code directly [from GitHub](https://github.com/stripe/stripe-python).

### Install the Stripe PHP library

Install the library with composer and initialize with your secret API key. Alternatively, if you’re starting from scratch and need a composer.json file, download the files using the link in the code editor.

#### Composer

Install the library:

```bash
composer require stripe/stripe-php
```

#### GitHub

Or download the stripe-php library source code directly [from GitHub](https://github.com/stripe/stripe-php).

### Set up your server

Add the dependency to your build and import the library. Alternatively, if you’re starting from scratch and need a go.mod file, download the project files using the link in the code editor.

#### Go

Make sure to initialize with Go Modules:

```bash
go get -u github.com/stripe/stripe-go/v86
```

#### GitHub

Or download the stripe-go module source code directly [from GitHub](https://github.com/stripe/stripe-go).

### Install the Stripe.net library

Install the package with .NET or NuGet. Alternatively, if you’re starting from scratch, download the files which contains a configured .csproj file.

#### dotnet

Install the library:

```bash
dotnet add package Stripe.net
```

#### NuGet

Install the library:

```bash
Install-Package Stripe.net
```

#### GitHub

Or download the Stripe.net library source code directly [from GitHub](https://github.com/stripe/stripe-dotnet).

### Install the Stripe libraries

Install the packages and import them in your code. Alternatively, if you’re starting from scratch and need a `package.json` file, download the project files using the link in the code editor.

Install the libraries:

```bash
npm install --save stripe @stripe/stripe-js next
```

### Set the Stripe-Version header

All backend requests to the Stripe API must include the `Stripe-Version` header with the `crypto_onramp_beta=v2` flag:

```
Stripe-Version: 2026-03-25.dahlia;crypto_onramp_beta=v2
```

### Create a LinkAuthIntent

Add an endpoint that creates a [LinkAuthIntent](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#create-a-linkauthintent) with the onramp OAuth scopes described in the integration guide. The client uses the returned `authIntentId` when calling `authenticate()` from the SDK.

### Exchange for access tokens

After the user consents using `authenticate()` on the client, you can exchange the LinkAuthIntent for an OAuth access token using [Retrieve Access Tokens](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#retrieve-access-tokens) API. Store the access token and use it in the `Stripe-OAuth-Token` header for all subsequent crypto API calls from your backend. Don’t expose the access token to the client.

### Refresh an expired access token

When the access token expires, call `POST https://login.link.com/auth/token` with `grant_type=refresh_token` and the refresh token from the original token exchange response to obtain a new access token. Pass your `LINK_CLIENT_ID` and `LINK_CLIENT_SECRET` along with the same OAuth scopes used in the original request.

### Retrieve a CryptoCustomer

Add an endpoint that calls the [Retrieve a CryptoCustomer](https://docs.stripe.com/api/crypto/customers/retrieve.md) API with the `customerId` from the authorize flow. Use the response to check the customer’s KYC and verification status so the client can prompt them complete setup (KYC, identity verification, wallet registration, payment method) before creating an onramp session.

### List ConsumerWallets

Add an endpoint that calls the [List ConsumerWallets](https://docs.stripe.com/api/crypto/consumer_wallets/list.md) API. Use it to see whether the user already has wallet addresses on file. If the list is empty, the client calls `registerWalletAddress()` before creating an onramp session.

### List PaymentTokens

Add an endpoint that calls the [List PaymentTokens](https://docs.stripe.com/api/crypto/payment_tokens/list.md) API. Use it to see which payment methods the user already has. If the list is empty, have the client call `collectPaymentMethod()` before creating an onramp session.

### Create a CryptoOnrampSession

Add an endpoint that creates a [CryptoOnrampSession](https://docs.stripe.com/api/crypto/onramp_sessions/create.md) with `ui_mode: headless`. Pass the `crypto_customer_id` from the authorize flow and `payment_token` from `collectPaymentMethod()` or an existing `payment_token` from [List PaymentTokens](https://docs.stripe.com/api/crypto/payment_tokens/list.md). Include the amount, currencies, network, and wallet address.

### Refresh an executable quote

Add an endpoint that calls the [Refresh a Quote](https://docs.stripe.com/api/crypto/onramp_sessions/quote.md) API to refresh a quote for the onramp session. Refresh the quote before it expires. Checkout will return a HTTP 400 response if you checkout with an expired quote.

### Perform checkout

Add an endpoint that calls the [Checkout](https://docs.stripe.com/api/crypto/onramp_sessions/checkout.md) API to confirm and fulfill the onramp session. Your back end returns the `client_secret` so the client SDK can validate and complete any required steps (for example, 3DS) using `performCheckout()`.

### Install the SDK

Install the `@stripe/crypto` package.

```bash
npm install @stripe/crypto
```

### Authentication flow

Call your server’s `/create-link-auth-intent` endpoint with the user’s email. A `404` response means the user doesn’t have a Link account — in that case, call `onramp.registerLinkUser(email, phoneNumber, country)` to create one. A successful response returns an `authIntentId` that you use in the next step.

Your server creates a LinkAuthIntent and returns the `authIntentId`. Call `authenticate(authIntentId, callback)` to start the authentication flow. The SDK returns an element to mount in your page that displays the authentication UI. We recommend presenting the authentication UI in a modal. When the user completes authentication, the callback fires with `crypto_customer_id`. Store it in your backend and use for the rest of the flow.

### Identity flow

Check the customer’s verification status by calling your server. If KYC is missing, call `submitKycInfo()` with the customer’s personal details. If identity verification is required, call `verifyDocuments()`. After each step completes, re-check the customer status to determine the next required action.

### Register a wallet address

Call `registerWalletAddress(walletAddress, network)` to register the user’s wallet address for the selected network.

### Payment flow

You can use [List PaymentTokens](https://docs.stripe.com/api/crypto/payment_tokens/list.md) to render a list of customer payment methods. If the customer has no payment method, call `collectPaymentMethod()` to display the payment UI and collect a `cryptoPaymentToken`.

### Checkout

Create a CryptoOnrampSession on your server. Then call `performCheckout(sessionId, fetchClientSecret)`, passing a callback that calls your backend’s `/checkout/:sessionId` and return the `client_secret`. If the quote expires, call your server’s `/quote/:sessionId` endpoint to refresh the quote.

### Install the Stripe Node library

Install the package and import it in your code. Alternatively, if you’re starting from scratch and need a package.json file, download the project files using the Download link in the code editor.

#### npm

Install the library:

```bash
npm install --save stripe
```

#### GitHub

Or download the stripe-node library source code directly [from GitHub](https://github.com/stripe/stripe-node).

### Install the Stripe Ruby library

Install the Stripe ruby gem and require it in your code. Alternatively, if you’re starting from scratch and need a Gemfile, download the project files using the link in the code editor.

#### Terminal

Install the gem:

```bash
gem install stripe
```

#### Bundler

Add this line to your Gemfile:

```bash
gem 'stripe'
```

#### GitHub

Or download the stripe-ruby gem source code directly [from GitHub](https://github.com/stripe/stripe-ruby).

### Install the Stripe Java library

Add the dependency to your build and import the library. Alternatively, if you’re starting from scratch and need a sample pom.xml file (for Maven), download the project files using the link in the code editor.

#### Maven

Add the following dependency to your POM and replace {VERSION} with the version number you want to use.

```bash
<dependency>\n<groupId>com.stripe</groupId>\n<artifactId>stripe-java</artifactId>\n<version>{VERSION}</version>\n</dependency>
```

#### Gradle

Add the dependency to your build.gradle file and replace {VERSION} with the version number you want to use.

```bash
implementation "com.stripe:stripe-java:{VERSION}"
```

#### GitHub

Download the JAR directly [from GitHub](https://github.com/stripe/stripe-java/releases/latest).

### Install the Stripe Python package

Install the Stripe package and import it in your code. Alternatively, if you’re starting from scratch and need a requirements.txt file, download the project files using the link in the code editor.

#### pip

Install the package through pip:

```bash
pip3 install stripe
```

#### GitHub

Download the stripe-python library source code directly [from GitHub](https://github.com/stripe/stripe-python).

### Install the Stripe PHP library

Install the library with composer and initialize with your secret API key. Alternatively, if you’re starting from scratch and need a composer.json file, download the files using the link in the code editor.

#### Composer

Install the library:

```bash
composer require stripe/stripe-php
```

#### GitHub

Or download the stripe-php library source code directly [from GitHub](https://github.com/stripe/stripe-php).

### Set up your server

Add the dependency to your build and import the library. Alternatively, if you’re starting from scratch and need a go.mod file, download the project files using the link in the code editor.

#### Go

Make sure to initialize with Go Modules:

```bash
go get -u github.com/stripe/stripe-go/v86
```

#### GitHub

Or download the stripe-go module source code directly [from GitHub](https://github.com/stripe/stripe-go).

### Install the Stripe.net library

Install the package with .NET or NuGet. Alternatively, if you’re starting from scratch, download the files which contains a configured .csproj file.

#### dotnet

Install the library:

```bash
dotnet add package Stripe.net
```

#### NuGet

Install the library:

```bash
Install-Package Stripe.net
```

#### GitHub

Or download the Stripe.net library source code directly [from GitHub](https://github.com/stripe/stripe-dotnet).

### Install the Stripe libraries

Install the packages and import them in your code. Alternatively, if you’re starting from scratch and need a `package.json` file, download the project files using the link in the code editor.

Install the libraries:

```bash
npm install --save stripe @stripe/stripe-js next
```

### Set the Stripe-Version header

All backend requests to the Stripe API must include the `Stripe-Version` header with the `crypto_onramp_beta=v2` flag:

```
Stripe-Version: 2026-03-25.dahlia;crypto_onramp_beta=v2
```

### Create a LinkAuthIntent

Add an endpoint that creates a [LinkAuthIntent](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#create-a-linkauthintent) with the onramp OAuth scopes described in the integration guide. The client uses the returned `authIntentId` when calling `authorize()` from the SDK.

### Exchange for access tokens

After the user consents using `authorize()` on the client, you can exchange the LinkAuthIntent for an OAuth access token using [Retrieve Access Tokens](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#retrieve-access-tokens) API. Store the access token and use it in the `Stripe-OAuth-Token` header for all subsequent crypto API calls from your backend. Don’t expose the access token to the client.

### Refresh an expired access token

When the access token expires, call `POST https://login.link.com/auth/token` with `grant_type=refresh_token` and the refresh token from the original token exchange response to obtain a new access token. Pass your `LINK_CLIENT_ID` and `LINK_CLIENT_SECRET` along with the same OAuth scopes used in the original request.

### Retrieve a CryptoCustomer

Add an endpoint that calls the [Retrieve a CryptoCustomer](https://docs.stripe.com/api/crypto/customers/retrieve.md) API with the `customerId` from the authorize flow. Use the response to check the customer’s KYC and verification status so the client can prompt them complete setup (KYC, identity verification, wallet registration, payment method) before creating an onramp session.

### List ConsumerWallets

Add an endpoint that calls the [List ConsumerWallets](https://docs.stripe.com/api/crypto/consumer_wallets/list.md) API. Use it to see whether the user already has wallet addresses on file. If the list is empty, the client calls `registerWalletAddress()` before creating an onramp session.

### List PaymentTokens

Add an endpoint that calls the [List PaymentTokens](https://docs.stripe.com/api/crypto/payment_tokens/list.md) API. Use it to see which payment methods the user already has. If the list is empty, have the client call `collectPaymentMethod()` before creating an onramp session.

### Create a CryptoOnrampSession

Add an endpoint that creates a [CryptoOnrampSession](https://docs.stripe.com/api/crypto/onramp_sessions/create.md) with `ui_mode: headless`. Pass the `crypto_customer_id` from the authorize flow and `payment_token` from `collectPaymentMethod()` or an existing `payment_token` from [List PaymentTokens](https://docs.stripe.com/api/crypto/payment_tokens/list.md). Include the amount, currencies, network, and wallet address.

### Refresh an executable quote

Add an endpoint that calls the [Refresh a Quote](https://docs.stripe.com/api/crypto/onramp_sessions/quote.md) API to refresh a quote for the onramp session. Refresh the quote before it expires. Checkout will return a HTTP 400 response if you checkout with an expired quote.

### Perform checkout

Add an endpoint that calls the [Checkout](https://docs.stripe.com/api/crypto/onramp_sessions/checkout.md) API to confirm and fulfill the onramp session. Your back end returns the `client_secret` so the client SDK can validate and complete any required steps (for example, 3DS) using `performCheckout()`.

### Basic setup

Wrap your application with `StripeProvider` at a high level so Stripe functionality is available throughout your component tree. The key properties are:

- `publishableKey` – Your Stripe publishable key
- `merchantIdentifier` – Your Apple Merchant ID (required for Apple Pay)
- `urlScheme` – Required for return URLs in authentication flows

### Initial configuration

Before you can call any Onramp APIs, configure the SDK using the `configure` method from the `useOnramp()` hook. `configure` takes an `Onramp.Configuration` instance to customize your business display name and appearance (colors, theme) for Stripe-provided interfaces such as wallets, one-time passcodes, and identity verification UIs. Call `configure()` when your component mounts.

### Authentication flow

Call your server’s `/create-link-auth-intent` endpoint with the user’s email. A `404` response means the user doesn’t have a Link account — in that case, call `registerLinkUser(email, phoneNumber, country)` to create one. Either way, proceed to authentication.

Call `authorize(authIntentId)` to prompt the user to login into their Link account. Store the returned `customerId` in your backend and use for the rest of the flow.

### Identity flow

Check verification status (your back end calls [Retrieve CryptoCustomer](https://docs.stripe.com/api/crypto/customers/retrieve.md)). If KYC is missing, call `attachKycInfo()`. If identity verification is required, call `verifyIdentity()`. Advance to the next step after each completes.

### Register a wallet address

Call `registerWalletAddress(walletAddress, network)` to register the user’s wallet address for the selected network.

### Payment flow

You can use [List PaymentTokens](https://docs.stripe.com/api/crypto/payment_tokens/list.md) to render a list of customer payment methods. If the customer has no payment method, call `collectPaymentMethod()` to display the payment UI, then call `createCryptoPaymentToken()` to tokenize the collected payment method.

### Checkout

Create a CryptoOnrampSession on your server. Then call `performCheckout(sessionId, fetchClientSecret)`, passing a callback that calls your backend’s `/checkout/:sessionId` and return the `client_secret`. If the quote is expired, call your server’s `/quote/:sessionId` endpoint to refresh the quote.

// This is a public sample test API key.
// Don't submit any personally identifiable information in requests made with this key.
// Sign in to see your own test API key embedded in code samples.
// Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
const secretKey = '<<YOUR_SECRET_KEY>>';
const oauthScopes = process.env.LINK_OAUTH_SCOPES || 'crypto:ramp,kyc.status:read';
// Install the latest private-preview Stripe SDK listed at https://docs.stripe.com/sdks/server-side#stripeclient to use these bindings.
const Stripe = require('stripe');
const stripe = Stripe(secretKey, {
  apiVersion: `${Stripe.API_VERSION};crypto_onramp_beta=v2`,
});
const requestOptions = oauthToken => ({
  headers: {'Stripe-OAuth-Token': oauthToken},
});
app.post('/create-link-auth-intent', async (req, res) => {
  const { email } = req.body;
  const linkRes = await fetch('https://login.link.com/v1/link_auth_intent', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${secretKey}`,
    },
    body: JSON.stringify({
      email,
      oauth_scopes: oauthScopes,
      oauth_client_id: process.env.LINK_CLIENT_ID || 'your_oauth_client_id',
    }),
  });
  const data = await linkRes.json();
  if (data.id) {
    res.json({ authIntentId: data.id });
  } else {
    res.status(linkRes.status).json(data);
  }
});
async function exchangeTokens(authIntentId) {
  const res = await fetch(`https://login.link.com/v1/link_auth_intent/${authIntentId}/tokens`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${secretKey}` },
  });
  const data = await res.json();
  if (!data.access_token) throw new Error('Token exchange failed');
  return data.access_token;
}
async function refreshExchangeTokens(refreshToken) {
  const params = new URLSearchParams({
    client_id: process.env.LINK_CLIENT_ID || 'lwlpk_xxxxx',
    client_secret: process.env.LINK_CLIENT_SECRET || 'lwlsk_xxxxx',
    grant_type: 'refresh_token',
    refresh_token: refreshToken,
    scope: oauthScopes,
  });
  const res = await fetch('https://login.link.com/auth/token', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${secretKey}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: params,
  });
  const data = await res.json();
  if (!data.access_token) throw new Error('Token refresh failed');
  return data.access_token;
}
app.get('/crypto/customer/:id', async (req, res) => {
  const oauthToken = await exchangeTokens(req.query.authIntentId);
  const customer = await stripe.crypto.customers.retrieve(
    req.params.id,
    {},
    requestOptions(oauthToken)
  );
  res.json(customer);
});
app.get('/crypto/customer/:id/wallets', async (req, res) => {
  const oauthToken = await exchangeTokens(req.query.authIntentId);
  const wallets = await stripe.crypto.customers.listConsumerWallets(
    req.params.id,
    {},
    requestOptions(oauthToken)
  );
  res.json(wallets);
});
app.get('/crypto/customer/:id/payment-tokens', async (req, res) => {
  const oauthToken = await exchangeTokens(req.query.authIntentId);
  const paymentTokens = await stripe.crypto.customers.listPaymentTokens(
    req.params.id,
    {},
    requestOptions(oauthToken)
  );
  res.json(paymentTokens);
});
app.post('/create-onramp-session', async (req, res) => {
  const {
    authIntentId,
    crypto_customer_id,
    payment_token,
    source_amount,
    source_currency,
    destination_currency,
    destination_network,
    wallet_address,
  } = req.body;

  const oauthToken = await exchangeTokens(authIntentId);
  const session = await stripe.crypto.onrampSessions.create(
    {
      ui_mode: 'headless',
      crypto_customer_id,
      payment_token,
      source_amount: String(source_amount),
      source_currency,
      destination_currency,
      destination_currencies: [destination_currency],
      destination_network,
      destination_networks: [destination_network],
      wallet_address,
      customer_ip_address: req.ip || req.socket.remoteAddress,
    },
    requestOptions(oauthToken)
  );
  res.json({
    id: session.id,
    quote_expires_at: session.quote?.expires_at ?? null,
  });
});
app.post('/quote/:sessionId', async (req, res) => {
  const oauthToken = await exchangeTokens(req.body.authIntentId);
  const session = await stripe.crypto.onrampSessions.quote(
    req.params.sessionId,
    {},
    requestOptions(oauthToken)
  );
  res.json(session);
});
app.post('/checkout/:sessionId', async (req, res) => {
  const oauthToken = await exchangeTokens(req.body.authIntentId);
  const session = await stripe.crypto.onrampSessions.checkout(
    req.params.sessionId,
    {
      mandate_data: {
        customer_acceptance: {
          type: 'online',
          accepted_at: Math.floor(Date.now() / 1000),
          online: {
            ip_address: req.ip || req.socket.remoteAddress || '',
            user_agent: req.headers['user-agent'] || '',
          },
        },
      },
    },
    requestOptions(oauthToken)
  );
  res.json(session);
});
\# This is a public sample test API key.
# Don't submit any personally identifiable information in requests made with this key.
# Sign in to see your own test API key embedded in code samples.
\# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
SECRET_KEY = '<<YOUR_SECRET_KEY>>'
OAUTH_SCOPES = ENV['LINK_OAUTH_SCOPES'] || 'crypto:ramp,kyc.status:read'
\# Install the latest private-preview Stripe SDK listed at https://docs.stripe.com/sdks/server-side#stripeclient to use these bindings.
Stripe.add_beta_version("crypto_onramp_beta", "v2")
client = Stripe::StripeClient.new(SECRET_KEY)
post '/create-link-auth-intent' do
  body = JSON.parse(request.body.read)
  uri = URI('https://login.link.com/v1/link_auth_intent')
  req = Net::HTTP::Post.new(uri)
  req['Authorization'] = "Bearer #{SECRET_KEY}"
  req['Content-Type'] = 'application/x-www-form-urlencoded'
  req.body = URI.encode_www_form(
    email: body['email'],
    oauth_scopes: OAUTH_SCOPES,
    oauth_client_id: ENV['LINK_CLIENT_ID'] || 'your_oauth_client_id'
  )
  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  data = JSON.parse(res.body)
  data['id'] ? { authIntentId: data['id'] }.to_json : [res.code.to_i, data.to_json]
end
def exchange_tokens(auth_intent_id)
  uri = URI("https://login.link.com/v1/link_auth_intent/#{auth_intent_id}/tokens")
  req = Net::HTTP::Post.new(uri)
  req['Authorization'] = "Bearer #{SECRET_KEY}"
  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  JSON.parse(res.body)['access_token']
end
def refresh_exchange_tokens(refresh_token)
  uri = URI('https://login.link.com/auth/token')
  req = Net::HTTP::Post.new(uri)
  req['Authorization'] = "Bearer #{SECRET_KEY}"
  req['Content-Type'] = 'application/x-www-form-urlencoded'
  req.body = URI.encode_www_form(
    client_id: ENV['LINK_CLIENT_ID'] || 'lwlpk_xxxxx',
    client_secret: ENV['LINK_CLIENT_SECRET'] || 'lwlsk_xxxxx',
    grant_type: 'refresh_token',
    refresh_token: refresh_token,
    scope: OAUTH_SCOPES
  )
  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  JSON.parse(res.body)['access_token']
end
get '/crypto/customer/:id' do
  oauth_token = exchange_tokens(params['authIntentId'])
  customer = client.v1.crypto.customers.retrieve(
    params['id'],
    {},
    { headers: { 'Stripe-OAuth-Token' => oauth_token } }
  )
  customer.to_json
end
get '/crypto/customer/:id/wallets' do
  oauth_token = exchange_tokens(params['authIntentId'])
  wallets = client.v1.crypto.customers.consumer_wallets.list(
    params['id'],
    {},
    { headers: { 'Stripe-OAuth-Token' => oauth_token } }
  )
  wallets.to_json
end
get '/crypto/customer/:id/payment-tokens' do
  oauth_token = exchange_tokens(params['authIntentId'])
  payment_tokens = client.v1.crypto.customers.payment_tokens.list(
    params['id'],
    {},
    { headers: { 'Stripe-OAuth-Token' => oauth_token } }
  )
  payment_tokens.to_json
end
post '/create-onramp-session' do
  body = JSON.parse(request.body.read)
  oauth_token = exchange_tokens(body['authIntentId'])
  session = client.v1.crypto.onramp_sessions.create(
    {
      ui_mode: 'headless',
      crypto_customer_id: body['crypto_customer_id'],
      payment_token: body['payment_token'],
      source_amount: body['source_amount'].to_s,
      source_currency: body['source_currency'],
      destination_currency: body['destination_currency'],
      destination_currencies: [body['destination_currency']],
      destination_network: body['destination_network'],
      destination_networks: [body['destination_network']],
      wallet_address: body['wallet_address'],
      customer_ip_address: request.ip
    },
    { headers: { 'Stripe-OAuth-Token' => oauth_token } }
  )
  { id: session.id, quote_expires_at: session['quote']&.expires_at }.to_json
end
post '/quote/:sessionId' do
  body = JSON.parse(request.body.read)
  oauth_token = exchange_tokens(body['authIntentId'])
  session = client.v1.crypto.onramp_sessions.quote(
    params['sessionId'],
    {},
    { headers: { 'Stripe-OAuth-Token' => oauth_token } }
  )
  session.to_json
end
post '/checkout/:sessionId' do
  body = JSON.parse(request.body.read)
  oauth_token = exchange_tokens(body['authIntentId'])
  session = client.v1.crypto.onramp_sessions.checkout(
    params['sessionId'],
    {
      mandate_data: {
        customer_acceptance: {
          type: 'online',
          accepted_at: Time.now.to_i,
          online: {
            ip_address: request.ip,
            user_agent: request.env['HTTP_USER_AGENT'] || ''
          }
        }
      }
    },
    { headers: { 'Stripe-OAuth-Token' => oauth_token } }
  )
  session.to_json
end
import stripe

\# This is a public sample test API key.
# Don't submit any personally identifiable information in requests made with this key.
# Sign in to see your own test API key embedded in code samples.
\# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
SECRET_KEY = '<<YOUR_SECRET_KEY>>'
OAUTH_SCOPES = os.environ.get('LINK_OAUTH_SCOPES', 'crypto:ramp,kyc.status:read')
\# Install the latest private-preview Stripe SDK listed at https://docs.stripe.com/sdks/server-side#stripeclient to use these bindings.
stripe.add_beta_version("crypto_onramp_beta", "v2")
client = stripe.StripeClient(SECRET_KEY)
@app.route('/create-link-auth-intent', methods=['POST'])
def create_link_auth_intent():
    body = request.get_json()
    data = urllib.parse.urlencode({
        'email': body['email'],
        'oauth_scopes': OAUTH_SCOPES,
        'oauth_client_id': os.environ.get('LINK_CLIENT_ID', 'your_oauth_client_id'),
    }).encode()
    req = urllib.request.Request(
        'https://login.link.com/v1/link_auth_intent',
        data=data,
        headers={
            'Authorization': f'Bearer {SECRET_KEY}',
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        method='POST'
    )
    try:
        with urllib.request.urlopen(req) as res:
            link_data = json.loads(res.read().decode())
    except urllib.error.HTTPError as e:
        return jsonify(json.loads(e.read().decode())), e.code
    if 'id' in link_data:
        return jsonify(authIntentId=link_data['id'])
    return jsonify(link_data), 400
def exchange_tokens(auth_intent_id):
    req = urllib.request.Request(
        f'https://login.link.com/v1/link_auth_intent/{auth_intent_id}/tokens',
        headers={'Authorization': f'Bearer {SECRET_KEY}'},
        method='POST'
    )
    with urllib.request.urlopen(req) as res:
        return json.loads(res.read().decode()).get('access_token')
def refresh_exchange_tokens(refresh_token):
    data = urllib.parse.urlencode({
        'client_id': os.environ.get('LINK_CLIENT_ID', 'lwlpk_xxxxx'),
        'client_secret': os.environ.get('LINK_CLIENT_SECRET', 'lwlsk_xxxxx'),
        'grant_type': 'refresh_token',
        'refresh_token': refresh_token,
        'scope': OAUTH_SCOPES,
    }).encode()
    req = urllib.request.Request(
        'https://login.link.com/auth/token',
        data=data,
        headers={
            'Authorization': f'Bearer {SECRET_KEY}',
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        method='POST'
    )
    with urllib.request.urlopen(req) as res:
        return json.loads(res.read().decode()).get('access_token')
@app.route('/crypto/customer/<customer_id>')
def get_crypto_customer(customer_id):
    oauth_token = exchange_tokens(request.args.get('authIntentId'))
    customer = client.v1.crypto.customers.retrieve(
        customer_id,
        options=request_options(oauth_token),
    )
    return jsonify(customer)
@app.route('/crypto/customer/<customer_id>/wallets')
def get_wallets(customer_id):
    oauth_token = exchange_tokens(request.args.get('authIntentId'))
    wallets = client.v1.crypto.customers.consumer_wallets.list(
        customer_id,
        options=request_options(oauth_token),
    )
    return jsonify(wallets)
@app.route('/crypto/customer/<customer_id>/payment-tokens')
def get_payment_tokens(customer_id):
    oauth_token = exchange_tokens(request.args.get('authIntentId'))
    payment_tokens = client.v1.crypto.customers.payment_tokens.list(
        customer_id,
        options=request_options(oauth_token),
    )
    return jsonify(payment_tokens)
@app.route('/create-onramp-session', methods=['POST'])
def create_onramp_session():
    body = request.get_json()
    oauth_token = exchange_tokens(body['authIntentId'])
    session = client.v1.crypto.onramp_sessions.create(
        {
            'ui_mode': 'headless',
            'crypto_customer_id': body['crypto_customer_id'],
            'payment_token': body['payment_token'],
            'source_amount': str(body['source_amount']),
            'source_currency': body['source_currency'],
            'destination_currency': body['destination_currency'],
            'destination_currencies': [body['destination_currency']],
            'destination_network': body['destination_network'],
            'destination_networks': [body['destination_network']],
            'wallet_address': body['wallet_address'],
            'customer_ip_address': request.remote_addr,
        },
        options=request_options(oauth_token),
    )
    quote = session.get('quote') or {}
    return jsonify(id=session.id, quote_expires_at=quote.get('expires_at'))
@app.route('/quote/<session_id>', methods=['POST'])
def refresh_quote(session_id):
    oauth_token = exchange_tokens(request.get_json()['authIntentId'])
    session = client.v1.crypto.onramp_sessions.quote(
        session_id,
        options=request_options(oauth_token),
    )
    return jsonify(session)
@app.route('/checkout/<session_id>', methods=['POST'])
def checkout(session_id):
    oauth_token = exchange_tokens(request.get_json()['authIntentId'])
    session = client.v1.crypto.onramp_sessions.checkout(
        session_id,
        {
            'mandate_data': {
                'customer_acceptance': {
                    'type': 'online',
                    'accepted_at': int(time.time()),
                    'online': {
                        'ip_address': request.remote_addr,
                        'user_agent': request.headers.get('User-Agent', ''),
                    },
                },
            },
        },
        options=request_options(oauth_token),
    )
    return jsonify(session)
$secretKey = $stripeSecretKey;
$oauthScopes = getenv('LINK_OAUTH_SCOPES') ?: 'crypto:ramp,kyc.status:read';
// Install the latest private-preview Stripe SDK listed at https://docs.stripe.com/sdks/server-side#stripeclient to use these bindings.
\Stripe\Stripe::addBetaVersion('crypto_onramp_beta', 'v2');
$stripe = new \Stripe\StripeClient($secretKey);
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $path === '/create-link-auth-intent') {
    $ch = curl_init('https://login.link.com/v1/link_auth_intent');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => http_build_query([
            'email' => $input['email'] ?? '',
            'oauth_scopes' => $oauthScopes,
            'oauth_client_id' => getenv('LINK_CLIENT_ID') ?: 'your_oauth_client_id',
        ]),
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . $secretKey,
            'Content-Type: application/x-www-form-urlencoded',
        ],
        CURLOPT_RETURNTRANSFER => true,
    ]);
    $res = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    $data = json_decode($res, true);
    if ($data['id']) {
        echo json_encode(['authIntentId' => $data['id']]);
    } else {
        http_response_code($httpCode);
        echo $res;
    }
    exit;
}
function exchangeTokens($authIntentId, $secretKey) {
    $ch = curl_init("https://login.link.com/v1/link_auth_intent/{$authIntentId}/tokens");
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $secretKey],
        CURLOPT_RETURNTRANSFER => true,
    ]);
    $data = json_decode(curl_exec($ch), true);
    return $data['access_token'] ?? null;
}
function refreshExchangeTokens($refreshToken, $secretKey, $oauthScopes) {
    $ch = curl_init('https://login.link.com/auth/token');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => http_build_query([
            'client_id' => getenv('LINK_CLIENT_ID') ?: 'lwlpk_xxxxx',
            'client_secret' => getenv('LINK_CLIENT_SECRET') ?: 'lwlsk_xxxxx',
            'grant_type' => 'refresh_token',
            'refresh_token' => $refreshToken,
            'scope' => $oauthScopes,
        ]),
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . $secretKey,
            'Content-Type: application/x-www-form-urlencoded',
        ],
        CURLOPT_RETURNTRANSFER => true,
    ]);
    $data = json_decode(curl_exec($ch), true);
    return $data['access_token'] ?? null;
}
if ($_SERVER['REQUEST_METHOD'] === 'GET' && preg_match('#^/crypto/customer/([^/]+)$#', $path, $m)) {
    $customerId = $m[1];
    $oauthToken = exchangeTokens($_GET['authIntentId'] ?? '', $secretKey);
    $customer = $stripe->crypto->customers->retrieve(
        $customerId,
        [],
        ['headers' => ['Stripe-OAuth-Token' => $oauthToken]]
    );
    echo json_encode($customer->toArray());
    exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'GET' && preg_match('#^/crypto/customer/([^/]+)/wallets$#', $path, $m)) {
    $customerId = $m[1];
    $oauthToken = exchangeTokens($_GET['authIntentId'] ?? '', $secretKey);
    $wallets = $stripe->crypto->customers->allCryptoConsumerWallets(
        $customerId,
        [],
        ['headers' => ['Stripe-OAuth-Token' => $oauthToken]]
    );
    echo json_encode($wallets->toArray());
    exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'GET' && preg_match('#^/crypto/customer/([^/]+)/payment-tokens$#', $path, $m)) {
    $customerId = $m[1];
    $oauthToken = exchangeTokens($_GET['authIntentId'] ?? '', $secretKey);
    $paymentTokens = $stripe->crypto->customers->allPaymentTokens(
        $customerId,
        [],
        ['headers' => ['Stripe-OAuth-Token' => $oauthToken]]
    );
    echo json_encode($paymentTokens->toArray());
    exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $path === '/create-onramp-session') {
    $oauthToken = exchangeTokens($input['authIntentId'] ?? '', $secretKey);
    $session = $stripe->crypto->onrampSessions->create(
        [
            'ui_mode' => 'headless',
            'crypto_customer_id' => $input['crypto_customer_id'],
            'payment_token' => $input['payment_token'],
            'source_amount' => (string) $input['source_amount'],
            'source_currency' => $input['source_currency'],
            'destination_currency' => $input['destination_currency'],
            'destination_currencies' => [$input['destination_currency']],
            'destination_network' => $input['destination_network'],
            'destination_networks' => [$input['destination_network']],
            'wallet_address' => $input['wallet_address'],
            'customer_ip_address' => $_SERVER['REMOTE_ADDR'],
        ],
        ['headers' => ['Stripe-OAuth-Token' => $oauthToken]]
    );
    echo json_encode([
        'id' => $session->id,
        'quote_expires_at' => $session->quote->expires_at ?? null,
    ]);
    exit;
}
if (preg_match('#^/quote/([^/]+)$#', $path, $m) && $_SERVER['REQUEST_METHOD'] === 'POST') {
    $sessionId = $m[1];
    $oauthToken = exchangeTokens($input['authIntentId'] ?? '', $secretKey);
    $session = $stripe->crypto->onrampSessions->quote(
        $sessionId,
        [],
        ['headers' => ['Stripe-OAuth-Token' => $oauthToken]]
    );
    echo json_encode($session->toArray());
    exit;
}
if (preg_match('#^/checkout/([^/]+)$#', $path, $m) && $_SERVER['REQUEST_METHOD'] === 'POST') {
    $sessionId = $m[1];
    $oauthToken = exchangeTokens($input['authIntentId'] ?? '', $secretKey);
    $session = $stripe->crypto->onrampSessions->checkout(
        $sessionId,
        [
            'mandate_data' => [
                'customer_acceptance' => [
                    'type' => 'online',
                    'accepted_at' => time(),
                    'online' => [
                        'ip_address' => $_SERVER['REMOTE_ADDR'],
                        'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
                    ],
                ],
            ],
        ],
        ['headers' => ['Stripe-OAuth-Token' => $oauthToken]]
    );
    echo json_encode($session->toArray());
    exit;
}
// Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
$stripeSecretKey = '<<YOUR_SECRET_KEY>>';
// This is a public sample test API key.
// Don't submit any personally identifiable information in requests made with this key.
// Sign in to see your own test API key embedded in code samples.
// Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
var secretKey = "<<YOUR_SECRET_KEY>>"
var oauthScopes = func() string {
	if v := os.Getenv("LINK_OAUTH_SCOPES"); v != "" {
		return v
	}
	return "crypto:ramp,kyc.status:read"
}()
// Install the latest private-preview Stripe SDK listed at https://docs.stripe.com/sdks/server-side#stripeclient to use these bindings.
var sc = func() *stripe.Client {
	stripe.AddBetaVersion("crypto_onramp_beta", "v2")
	return stripe.NewClient(secretKey)
}()

func addRequestHeaders(params *stripe.Params, oauthToken string) {
	params.Headers = http.Header{
		"Stripe-OAuth-Token": {oauthToken},
	}
}

func advanceList[T any](list *stripe.V1List[T]) error {
	var iterErr error
	list.All(context.Background())(func(_ T, err error) bool {
		iterErr = err
		return false
	})
	return iterErr
}
func handleCreateAuthIntent(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", 405)
		return
	}
	var body struct {
		Email string `json:"email"`
	}
	json.NewDecoder(r.Body).Decode(&body)
	form := url.Values{}
	form.Set("email", body.Email)
	form.Set("oauth_scopes", oauthScopes)
	form.Set("oauth_client_id", os.Getenv("LINK_CLIENT_ID"))
	req, _ := http.NewRequest("POST", "https://login.link.com/v1/link_auth_intent", strings.NewReader(form.Encode()))
	req.Header.Set("Authorization", "Bearer "+secretKey)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	w.WriteHeader(res.StatusCode)
	io.Copy(w, res.Body)
}
func exchangeTokens(authIntentID string) (string, error) {
	req, _ := http.NewRequest("POST", "https://login.link.com/v1/link_auth_intent/"+authIntentID+"/tokens", nil)
	req.Header.Set("Authorization", "Bearer "+secretKey)
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	var data map[string]interface{}
	json.NewDecoder(res.Body).Decode(&data)
	accessToken, ok := data["access_token"].(string)
	if !ok {
		return "", fmt.Errorf("token exchange failed")
	}
	return accessToken, nil
}
func refreshExchangeTokens(refreshToken string) (string, error) {
	form := url.Values{}
	form.Set("client_id", os.Getenv("LINK_CLIENT_ID"))
	form.Set("client_secret", os.Getenv("LINK_CLIENT_SECRET"))
	form.Set("grant_type", "refresh_token")
	form.Set("refresh_token", refreshToken)
	form.Set("scope", oauthScopes)
	req, _ := http.NewRequest("POST", "https://login.link.com/auth/token", strings.NewReader(form.Encode()))
	req.Header.Set("Authorization", "Bearer "+secretKey)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	var data map[string]interface{}
	json.NewDecoder(res.Body).Decode(&data)
	accessToken, ok := data["access_token"].(string)
	if !ok {
		return "", fmt.Errorf("token refresh failed")
	}
	return accessToken, nil
}
func handleCryptoCustomer(w http.ResponseWriter, r *http.Request) {
	path := strings.TrimPrefix(r.URL.Path, "/crypto/customer/")
	parts := strings.SplitN(path, "/", 2)
	customerID := parts[0]
	oauthToken, err := exchangeTokens(r.URL.Query().Get("authIntentId"))
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	w.Header().Set("Content-Type", "application/json")

	if len(parts) > 1 && parts[1] == "wallets" {
		params := &stripe.CryptoCustomerConsumerWalletListParams{ID: stripe.String(customerID)}
		addRequestHeaders(&params.Params, oauthToken)
		wallets := sc.V1CryptoCustomerConsumerWallets.List(context.Background(), params)
		if err := advanceList(wallets); err != nil {
			http.Error(w, err.Error(), 500)
			return
		}
		w.Write(wallets.LastResponse().RawJSON)
		return
	}
	if len(parts) > 1 && parts[1] == "payment-tokens" {
		params := &stripe.CryptoCustomerPaymentTokenListParams{ID: stripe.String(customerID)}
		addRequestHeaders(&params.Params, oauthToken)
		paymentTokens := sc.V1CryptoCustomerPaymentTokens.List(context.Background(), params)
		if err := advanceList(paymentTokens); err != nil {
			http.Error(w, err.Error(), 500)
			return
		}
		w.Write(paymentTokens.LastResponse().RawJSON)
		return
	}

	params := &stripe.CryptoCustomerRetrieveParams{}
	addRequestHeaders(&params.Params, oauthToken)
	customer, err := sc.V1CryptoCustomers.Retrieve(context.Background(), customerID, params)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	json.NewEncoder(w).Encode(customer)
}
func handleCreateSession(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", 405)
		return
	}
	var body struct {
		AuthIntentID        string  `json:"authIntentId"`
		CryptoCustomerID    string  `json:"crypto_customer_id"`
		PaymentToken        string  `json:"payment_token"`
		SourceAmount        float64 `json:"source_amount"`
		SourceCurrency      string  `json:"source_currency"`
		DestinationCurrency string  `json:"destination_currency"`
		DestinationNetwork  string  `json:"destination_network"`
		WalletAddress       string  `json:"wallet_address"`
	}
	json.NewDecoder(r.Body).Decode(&body)
	oauthToken, err := exchangeTokens(body.AuthIntentID)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	clientIP := r.RemoteAddr
	if i := strings.LastIndex(clientIP, ":"); i >= 0 {
		clientIP = clientIP[:i]
	}
	params := &stripe.CryptoOnrampSessionCreateParams{
		CustomerIPAddress:     stripe.String(clientIP),
		SourceAmount:          stripe.String(fmt.Sprintf("%.0f", body.SourceAmount)),
		SourceCurrency:        stripe.String(body.SourceCurrency),
		DestinationCurrency:   stripe.String(body.DestinationCurrency),
		DestinationCurrencies: []*string{stripe.String(body.DestinationCurrency)},
		DestinationNetwork:    stripe.String(body.DestinationNetwork),
		DestinationNetworks:   []*string{stripe.String(body.DestinationNetwork)},
	}
	addRequestHeaders(&params.Params, oauthToken)
	params.AddExtra("ui_mode", "headless")
	params.AddExtra("crypto_customer_id", body.CryptoCustomerID)
	params.AddExtra("payment_token", body.PaymentToken)
	params.AddExtra("wallet_address", body.WalletAddress)
	session, err := sc.V1CryptoOnrampSessions.Create(context.Background(), params)
	if err != nil {
		http.Error(w, err.Error(), 400)
		return
	}
	var response map[string]interface{}
	json.Unmarshal(session.LastResponse.RawJSON, &response)
	quote, _ := response["quote"].(map[string]interface{})
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"id": session.ID,
		"quote_expires_at": quote["expires_at"],
	})
}
func handleRefreshQuote(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", 405)
		return
	}
	parts := strings.Split(r.URL.Path, "/")
	sessionID := parts[len(parts)-1]
	var body struct {
		AuthIntentID string `json:"authIntentId"`
	}
	json.NewDecoder(r.Body).Decode(&body)
	oauthToken, err := exchangeTokens(body.AuthIntentID)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	params := &stripe.CryptoOnrampSessionQuoteParams{}
	addRequestHeaders(&params.Params, oauthToken)
	session, err := sc.V1CryptoOnrampSessions.Quote(context.Background(), sessionID, params)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(session)
}
func handleCheckout(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", 405)
		return
	}
	parts := strings.Split(r.URL.Path, "/")
	sessionID := parts[len(parts)-1]
	var body struct {
		AuthIntentID string `json:"authIntentId"`
	}
	json.NewDecoder(r.Body).Decode(&body)
	oauthToken, err := exchangeTokens(body.AuthIntentID)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	clientIP := r.RemoteAddr
	if i := strings.LastIndex(clientIP, ":"); i >= 0 {
		clientIP = clientIP[:i]
	}
	params := &stripe.CryptoOnrampSessionCheckoutParams{
		MandateData: &stripe.CryptoOnrampSessionCheckoutMandateDataParams{
			CustomerAcceptance: &stripe.CryptoOnrampSessionCheckoutMandateDataCustomerAcceptanceParams{
				Type: stripe.String("online"),
				AcceptedAt: stripe.Int64(time.Now().Unix()),
				Online: &stripe.CryptoOnrampSessionCheckoutMandateDataCustomerAcceptanceOnlineParams{
					IPAddress: stripe.String(clientIP),
					UserAgent: stripe.String(r.Header.Get("User-Agent")),
				},
			},
		},
	}
	addRequestHeaders(&params.Params, oauthToken)
	session, err := sc.V1CryptoOnrampSessions.Checkout(context.Background(), sessionID, params)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(session)
}
// This is a public sample test API key.
// Don't submit any personally identifiable information in requests made with this key.
// Sign in to see your own test API key embedded in code samples.
// Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
var secretKey = "<<YOUR_SECRET_KEY>>";
var oauthScopes = Environment.GetEnvironmentVariable("LINK_OAUTH_SCOPES") ?? "crypto:ramp,kyc.status:read";

// Install the latest private-preview Stripe SDK listed at https://docs.stripe.com/sdks/server-side#stripeclient to use these bindings.
StripeConfiguration.AddBetaVersion("crypto_onramp_beta", "v2");
StripeClient StripeClient(string oauthToken)
{
    var handler = new OnrampHeaderHandler(oauthToken);
    var httpClient = new SystemNetHttpClient(new System.Net.Http.HttpClient(handler));
    return new StripeClient(secretKey, httpClient: httpClient);
}
app.MapPost("/create-link-auth-intent", async (HttpRequest req) => {
    var body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body);
    var content = new FormUrlEncodedContent(new[] {
        new KeyValuePair<string, string>("email", body.GetProperty("email").GetString()!),
        new KeyValuePair<string, string>("oauth_scopes", oauthScopes),
        new KeyValuePair<string, string>("oauth_client_id", Environment.GetEnvironmentVariable("LINK_CLIENT_ID") ?? "your_oauth_client_id"),
    });
    var msg = new HttpRequestMessage(HttpMethod.Post, "https://login.link.com/v1/link_auth_intent") {
        Content = content,
        Headers = { { "Authorization", $"Bearer {secretKey}" } }
    };
    var res = await http.SendAsync(msg);
    return Results.Json(await res.Content.ReadFromJsonAsync<JsonElement>(), statusCode: (int)res.StatusCode);
});
async Task<string?> exchangeTokens(string authIntentId) {
    var msg = new HttpRequestMessage(HttpMethod.Post, $"https://login.link.com/v1/link_auth_intent/{authIntentId}/tokens") {
        Headers = { { "Authorization", $"Bearer {secretKey}" } }
    };
    var res = await http.SendAsync(msg);
    var data = await res.Content.ReadFromJsonAsync<JsonElement>();
    return data.TryGetProperty("access_token", out var tok) ? tok.GetString() : null;
}
async Task<string?> refreshExchangeTokens(string refreshToken) {
    var content = new FormUrlEncodedContent(new[] {
        new KeyValuePair<string, string>("client_id", Environment.GetEnvironmentVariable("LINK_CLIENT_ID") ?? "lwlpk_xxxxx"),
        new KeyValuePair<string, string>("client_secret", Environment.GetEnvironmentVariable("LINK_CLIENT_SECRET") ?? "lwlsk_xxxxx"),
        new KeyValuePair<string, string>("grant_type", "refresh_token"),
        new KeyValuePair<string, string>("refresh_token", refreshToken),
        new KeyValuePair<string, string>("scope", oauthScopes),
    });
    var msg = new HttpRequestMessage(HttpMethod.Post, "https://login.link.com/auth/token") {
        Content = content,
        Headers = { { "Authorization", $"Bearer {secretKey}" } }
    };
    var res = await http.SendAsync(msg);
    var data = await res.Content.ReadFromJsonAsync<JsonElement>();
    return data.TryGetProperty("access_token", out var tok) ? tok.GetString() : null;
}
app.MapPost("/create-onramp-session", async (HttpRequest req) => {
    var body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body);
    var oauthToken = await exchangeTokens(body.GetProperty("authIntentId").GetString()!);
    var options = new OnrampSessionCreateOptions {
        SourceAmount = body.GetProperty("source_amount").GetRawText(),
        SourceCurrency = body.GetProperty("source_currency").GetString(),
        DestinationCurrency = body.GetProperty("destination_currency").GetString(),
        DestinationCurrencies = new List<string> { body.GetProperty("destination_currency").GetString()! },
        DestinationNetwork = body.GetProperty("destination_network").GetString(),
        DestinationNetworks = new List<string> { body.GetProperty("destination_network").GetString()! },
        CustomerIpAddress = req.HttpContext.Connection.RemoteIpAddress?.ToString(),
    };
    options.AddExtraParam("ui_mode", "headless");
    options.AddExtraParam("crypto_customer_id", body.GetProperty("crypto_customer_id").GetString()!);
    options.AddExtraParam("payment_token", body.GetProperty("payment_token").GetString()!);
    options.AddExtraParam("wallet_address", body.GetProperty("wallet_address").GetString()!);
    var service = new OnrampSessionService(StripeClient(oauthToken ?? ""));
    var session = await service.CreateAsync(options);
    var quoteExpiresAt = session.RawJObject["quote"]?["expires_at"]?.ToObject<long>();
    return Results.Json(new {
        id = session.Id,
        quote_expires_at = quoteExpiresAt,
    });
});
app.MapPost("/quote/{sessionId}", async (string sessionId, HttpRequest req) => {
    var body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body);
    var oauthToken = await exchangeTokens(body.GetProperty("authIntentId").GetString()!);
    var service = new OnrampSessionService(StripeClient(oauthToken ?? ""));
    var session = await service.QuoteAsync(sessionId);
    return Results.Json(session);
});
app.MapPost("/checkout/{sessionId}", async (string sessionId, HttpRequest req) => {
    var body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body);
    var oauthToken = await exchangeTokens(body.GetProperty("authIntentId").GetString()!);
    var options = new OnrampSessionCheckoutOptions {
        MandateData = new OnrampSessionMandateDataOptions {
            CustomerAcceptance = new OnrampSessionMandateDataCustomerAcceptanceOptions {
                Type = "online",
                AcceptedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
                Online = new OnrampSessionMandateDataCustomerAcceptanceOnlineOptions {
                    IpAddress = req.HttpContext.Connection.RemoteIpAddress?.ToString(),
                    UserAgent = req.Headers["User-Agent"].FirstOrDefault() ?? "",
                },
            },
        },
    };
    var service = new OnrampSessionService(StripeClient(oauthToken ?? ""));
    var session = await service.CheckoutAsync(sessionId, options);
    return Results.Json(session);
});
app.MapGet("/crypto/customer/{*path}", async (string path, HttpRequest req) => {
    var oauthToken = await exchangeTokens(req.Query["authIntentId"].FirstOrDefault()!);
    var parts = (path ?? "").Split('/', 2, StringSplitOptions.RemoveEmptyEntries);
    var stripeClient = StripeClient(oauthToken ?? "");

    if (parts.Length == 2 && parts[1] == "wallets")
    {
        var walletService = new CustomerConsumerWalletService(stripeClient);
        return Results.Json(await walletService.ListAsync(parts[0]));
    }
    if (parts.Length == 2 && parts[1] == "payment-tokens")
    {
        var tokenService = new CustomerPaymentTokenService(stripeClient);
        return Results.Json(await tokenService.ListAsync(parts[0]));
    }

    var customerService = new CustomerService(stripeClient);
    return Results.Json(await customerService.GetAsync(parts[0]));
});
  // This is a public sample test API key.
  // Don't submit any personally identifiable information in requests made with this key.
  // Sign in to see your own test API key embedded in code samples.
  // Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
  private static final String SECRET_KEY = "<<YOUR_SECRET_KEY>>";
  private static final String OAUTH_SCOPES = System.getenv("LINK_OAUTH_SCOPES") != null
      ? System.getenv("LINK_OAUTH_SCOPES") : "crypto:ramp,kyc.status:read";

  // Install the latest private-preview Stripe SDK listed at https://docs.stripe.com/sdks/server-side#stripeclient to use these bindings.
  static {
    Stripe.addBetaVersion("crypto_onramp_beta", "v2");
  }

  private static StripeClient stripeClient(String oauthToken) {
    HttpURLConnectionClient httpClient = new HttpURLConnectionClient() {
      @Override
      public StripeResponse request(StripeRequest request) throws ApiConnectionException {
        return super.request(request.withAdditionalHeader("Stripe-OAuth-Token", oauthToken));
      }
    };
    return StripeClient.builder().setApiKey(SECRET_KEY).setHttpClient(httpClient).build();
  }
  private static String exchangeTokens(String authIntentId) throws Exception {
    HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create("https://login.link.com/v1/link_auth_intent/" + authIntentId + "/tokens"))
        .header("Authorization", "Bearer " + SECRET_KEY)
        .POST(HttpRequest.BodyPublishers.noBody())
        .build();
    HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString());
    JsonObject data = gson.fromJson(resp.body(), JsonObject.class);
    if (!data.has("access_token")) throw new RuntimeException("Token exchange failed");
    return data.get("access_token").getAsString();
  }
  private static String refreshExchangeTokens(String refreshToken) throws Exception {
    String form = "client_id=" + (System.getenv("LINK_CLIENT_ID") != null ? System.getenv("LINK_CLIENT_ID") : "lwlpk_xxxxx")
        + "&client_secret=" + (System.getenv("LINK_CLIENT_SECRET") != null ? System.getenv("LINK_CLIENT_SECRET") : "lwlsk_xxxxx")
        + "&grant_type=refresh_token"
        + "&refresh_token=" + refreshToken
        + "&scope=" + OAUTH_SCOPES;
    HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create("https://login.link.com/auth/token"))
        .header("Authorization", "Bearer " + SECRET_KEY)
        .header("Content-Type", "application/x-www-form-urlencoded")
        .POST(HttpRequest.BodyPublishers.ofString(form))
        .build();
    HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString());
    JsonObject data = gson.fromJson(resp.body(), JsonObject.class);
    if (!data.has("access_token")) throw new RuntimeException("Token refresh failed");
    return data.get("access_token").getAsString();
  }
    post("/create-link-auth-intent", (req, res) -> {
      JsonObject body = gson.fromJson(req.body(), JsonObject.class);
      String clientId = System.getenv("LINK_CLIENT_ID") != null ? System.getenv("LINK_CLIENT_ID") : "your_oauth_client_id";
      String form = "email=" + body.get("email").getAsString()
          + "&oauth_scopes=" + OAUTH_SCOPES
          + "&oauth_client_id=" + clientId;
      HttpRequest req2 = HttpRequest.newBuilder()
          .uri(URI.create("https://login.link.com/v1/link_auth_intent"))
          .header("Authorization", "Bearer " + SECRET_KEY)
          .header("Content-Type", "application/x-www-form-urlencoded")
          .POST(HttpRequest.BodyPublishers.ofString(form))
          .build();
      HttpResponse<String> resp = http.send(req2, HttpResponse.BodyHandlers.ofString());
      JsonObject data = gson.fromJson(resp.body(), JsonObject.class);
      if (data.has("id")) return "{\"authIntentId\":\"" + data.get("id").getAsString() + "\"}";
      res.status(resp.statusCode());
      return resp.body();
    });
    get("/crypto/customer/:id", (req, res) -> {
      String customerId = req.params(":id");
      String oauthToken = exchangeTokens(req.queryParams("authIntentId"));
      Customer customer = stripeClient(oauthToken).v1().crypto().customers().retrieve(customerId);
      return customer.toJson();
    });
    get("/crypto/customer/:id/wallets", (req, res) -> {
      String customerId = req.params(":id");
      String oauthToken = exchangeTokens(req.queryParams("authIntentId"));
      StripeCollection<CustomerConsumerWallet> wallets = stripeClient(oauthToken)
          .v1().crypto().customers().consumerWallets().list(customerId);
      return wallets.toJson();
    });
    get("/crypto/customer/:id/payment-tokens", (req, res) -> {
      String customerId = req.params(":id");
      String oauthToken = exchangeTokens(req.queryParams("authIntentId"));
      StripeCollection<CustomerPaymentToken> paymentTokens = stripeClient(oauthToken)
          .v1().crypto().customers().paymentTokens().list(customerId);
      return paymentTokens.toJson();
    });
    post("/create-onramp-session", (req, res) -> {
      JsonObject body = gson.fromJson(req.body(), JsonObject.class);
      String oauthToken = exchangeTokens(body.get("authIntentId").getAsString());
      OnrampSessionCreateParams params = OnrampSessionCreateParams.builder()
          .setCustomerIpAddress(req.ip())
          .setSourceAmount(body.get("source_amount").getAsString())
          .setSourceCurrency(OnrampSessionCreateParams.SourceCurrency.valueOf(
              body.get("source_currency").getAsString().toUpperCase()))
          .setDestinationCurrency(OnrampSessionCreateParams.DestinationCurrency.valueOf(
              body.get("destination_currency").getAsString().toUpperCase()))
          .addDestinationCurrency(OnrampSessionCreateParams.DestinationCurrency.valueOf(
              body.get("destination_currency").getAsString().toUpperCase()))
          .setDestinationNetwork(OnrampSessionCreateParams.DestinationNetwork.valueOf(
              body.get("destination_network").getAsString().toUpperCase()))
          .addDestinationNetwork(OnrampSessionCreateParams.DestinationNetwork.valueOf(
              body.get("destination_network").getAsString().toUpperCase()))
          .putExtraParam("ui_mode", "headless")
          .putExtraParam("crypto_customer_id", body.get("crypto_customer_id").getAsString())
          .putExtraParam("payment_token", body.get("payment_token").getAsString())
          .putExtraParam("wallet_address", body.get("wallet_address").getAsString())
          .build();
      OnrampSession session = stripeClient(oauthToken).v1().crypto().onrampSessions().create(params);
      JsonObject raw = session.getRawJsonObject();
      JsonObject result = new JsonObject();
      result.addProperty("id", session.getId());
      if (raw.has("quote") && !raw.get("quote").isJsonNull()) {
        JsonElement expiresAt = raw.getAsJsonObject("quote").get("expires_at");
        if (expiresAt != null && !expiresAt.isJsonNull()) {
          result.add("quote_expires_at", expiresAt);
        }
      }
      return result.toString();
    });
    post("/quote/:sessionId", (req, res) -> {
      JsonObject body = gson.fromJson(req.body(), JsonObject.class);
      String oauthToken = exchangeTokens(body.get("authIntentId").getAsString());
      OnrampSession session = stripeClient(oauthToken).v1().crypto().onrampSessions().quote(
          req.params(":sessionId"));
      return session.toJson();
    });
    post("/checkout/:sessionId", (req, res) -> {
      JsonObject body = gson.fromJson(req.body(), JsonObject.class);
      String oauthToken = exchangeTokens(body.get("authIntentId").getAsString());
      OnrampSessionCheckoutParams params = OnrampSessionCheckoutParams.builder()
          .setMandateData(
              OnrampSessionCheckoutParams.MandateData.builder()
                  .setCustomerAcceptance(
                      OnrampSessionCheckoutParams.MandateData.CustomerAcceptance.builder()
                          .setType(OnrampSessionCheckoutParams.MandateData.CustomerAcceptance.Type.ONLINE)
                          .setAcceptedAt(System.currentTimeMillis() / 1000)
                          .setOnline(
                              OnrampSessionCheckoutParams.MandateData.CustomerAcceptance.Online.builder()
                                  .setIpAddress(req.ip())
                                  .setUserAgent(req.headers("User-Agent") != null ? req.headers("User-Agent") : "")
                                  .build())
                          .build())
                  .build())
          .build();
      OnrampSession session = stripeClient(oauthToken).v1().crypto().onrampSessions().checkout(
          req.params(":sessionId"), params);
      return session.toJson();
    });
import React, { useState, useEffect, useRef } from 'react';
import { loadCryptoOnrampAndInitialize } from '@stripe/crypto';
export default function App() {
  const [onramp, setOnramp] = useState(null);
  useEffect(() => {
    loadCryptoOnrampAndInitialize('<<YOUR_PUBLISHABLE_KEY>>').then(setOnramp);
  }, []);
  async function handleLogin() {
    let res = await fetch(`${SERVER}/create-link-auth-intent`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email }),
    });

    if (res.status === 404) {
      await onramp.registerLinkUser(email, '+18004444444', 'US');
      res = await fetch(`${SERVER}/create-link-auth-intent`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email }),
      });
    }
    const { authIntentId } = await res.json();

    setLinkAuthIntentId(authIntentId);
    const element = await onramp.authenticate(authIntentId, async ({ crypto_customer_id }) => {
      setAuthElement(null);
      setCryptoCustomerId(crypto_customer_id);
      await routeNext(crypto_customer_id, authIntentId);
    });
    setAuthElement(element);
  }
  async function handleSubmitKyc() {
    await onramp.submitKycInfo({
      given_name: 'John',
      surname: 'Verified',
      date_of_birth: { day: 1, month: 1, year: 1990 },
      address: {
        line1: 'address_full_match',
        city: 'San Francisco',
        state: 'CA', // 2-letter uppercase code; see https://en.wikipedia.org/wiki/ISO_3166-2:US
        postal_code: '94111',
        country: 'US', // 2-letter uppercase ISO code; see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
      },
      id_number: { type: 'us_ssn', value: '000000000' },
    });
    await routeNext(cryptoCustomerId, linkAuthIntentId);
  }

  async function handleVerify() {
    await onramp.verifyDocuments();
    await routeNext(cryptoCustomerId, linkAuthIntentId);
  }
  async function handleRegisterWallet() {
    await onramp.registerWalletAddress(walletAddress, walletNetwork);
    await routeNext(cryptoCustomerId, linkAuthIntentId);
  }
  async function handleCollectPayment() {
    const element = await onramp.collectPaymentMethod(
      { payment_method_types: ['card'], wallets: { applePay: 'auto', googlePay: 'auto' } },
      ({ cryptoPaymentToken: token }) => {
        setCryptoPaymentToken(token);
        setStep('checkout');
      },
    );
    paymentRef.current?.replaceChildren(element);
  }
  async function handleCheckout() {
    const session = await fetch(`${SERVER}/create-onramp-session`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        authIntentId: linkAuthIntentId,
        crypto_customer_id: cryptoCustomerId,
        payment_token: cryptoPaymentToken,
        source_amount: parseFloat(amount),
        source_currency: 'usd',
        destination_currency: 'usdc',
        destination_network: walletNetwork || 'base',
        wallet_address: walletAddress,
      }),
    }).then(r => r.json());

    const now = Math.floor(Date.now() / 1000);
    if (!session.quote_expires_at || now >= session.quote_expires_at) {
      await fetch(`${SERVER}/quote/${session.id}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ authIntentId: linkAuthIntentId }),
      });
    }

    await onramp.performCheckout(session.id, async () => {
      const { client_secret } = await fetch(`${SERVER}/checkout/${session.id}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ authIntentId: linkAuthIntentId }),
      }).then(r => r.json());
      return client_secret;
    });
    setStep('complete');
  }
    <div id="step-auth">
      <input type="email" id="email" placeholder="Email" />
      <button id="btn-login">Log in with Link</button>
      <div id="auth-container"></div>
    </div>
    <div id="step-kyc" hidden>
      <p>Submit your identity information to complete KYC verification.</p>
      <button id="btn-kyc">Submit KYC info</button>
    </div>
    <div id="step-verify" hidden>
      <p>Additional identity verification is required.</p>
      <button id="btn-verify">Verify identity</button>
    </div>
    <div id="step-payment" hidden>
      <button id="btn-payment">Add payment method</button>
      <div id="payment-container"></div>
    </div>
    <div id="step-checkout" hidden>
      <input id="amount" type="number" value="100" placeholder="Amount in USD" />
      <button id="btn-checkout">Buy crypto</button>
    </div>
    <script type="module" src="app.js"></script>
import { loadCryptoOnrampAndInitialize } from '@stripe/crypto';
const onramp = await loadCryptoOnrampAndInitialize('<<YOUR_PUBLISHABLE_KEY>>');
document.getElementById('btn-login').onclick = async () => {
  const email = document.getElementById('email').value;
  let res = await fetch(`${SERVER}/create-link-auth-intent`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email }),
  });

  if (res.status === 404) {
    await onramp.registerLinkUser(email, '+18004444444', 'US');
    res = await fetch(`${SERVER}/create-link-auth-intent`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email }),
    });
  }

  const { authIntentId } = await res.json();

  const element = await onramp.authenticate(authIntentId, async ({ crypto_customer_id }) => {
    document.getElementById('auth-container').replaceChildren();
    cryptoCustomerId = crypto_customer_id;
    linkAuthIntentId = authIntentId;
    await routeNext(crypto_customer_id, authIntentId);
  });
  document.getElementById('auth-container').replaceChildren(element);
};
document.getElementById('btn-kyc').onclick = async () => {
  await onramp.submitKycInfo({
    given_name: 'John',
    surname: 'Verified',
    date_of_birth: { day: 1, month: 1, year: 1990 },
    address: {
      line1: 'address_full_match',
      city: 'San Francisco',
      state: 'CA', // 2-letter uppercase code; see https://en.wikipedia.org/wiki/ISO_3166-2:US
      postal_code: '94111',
      country: 'US', // 2-letter uppercase ISO code; see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
    },
    id_number: { type: 'us_ssn', value: '000000000' },
  });
  await routeNext(cryptoCustomerId, linkAuthIntentId);
};

document.getElementById('btn-verify').onclick = async () => {
  await onramp.verifyDocuments();
  await routeNext(cryptoCustomerId, linkAuthIntentId);
};
document.getElementById('btn-register-wallet').onclick = async () => {
  walletAddress = document.getElementById('wallet-address').value;
  walletNetwork = document.getElementById('wallet-network').value;
  await onramp.registerWalletAddress(walletAddress, walletNetwork);
  await routeNext(cryptoCustomerId, linkAuthIntentId);
};
document.getElementById('btn-payment').onclick = async () => {
  const element = await onramp.collectPaymentMethod(
    { payment_method_types: ['card'], wallets: { applePay: 'auto', googlePay: 'auto' } },
    ({ cryptoPaymentToken: token }) => {
      cryptoPaymentToken = token;
      show('step-checkout');
    },
  );
  document.getElementById('payment-container').replaceChildren(element);
};
document.getElementById('btn-checkout').onclick = async () => {
  const amount = document.getElementById('amount').value;
  const session = await fetch(`${SERVER}/create-onramp-session`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      authIntentId: linkAuthIntentId,
      crypto_customer_id: cryptoCustomerId,
      payment_token: cryptoPaymentToken,
      source_amount: parseFloat(amount),
      source_currency: 'usd',
      destination_currency: 'usdc',
      destination_network: walletNetwork || 'base',
      wallet_address: walletAddress,
    }),
  }).then(r => r.json());

  const now = Math.floor(Date.now() / 1000);
  if (!session.quote_expires_at || now >= session.quote_expires_at) {
    await fetch(`${SERVER}/quote/${session.id}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ authIntentId: linkAuthIntentId }),
    });
  }

  await onramp.performCheckout(session.id, async () => {
    const { client_secret } = await fetch(`${SERVER}/checkout/${session.id}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ authIntentId: linkAuthIntentId }),
    }).then(r => r.json());
    return client_secret;
  });
  show('step-complete');
};
import React from 'react';
import { StripeProvider } from '@stripe/stripe-react-native';
import { EmbeddedComponentsOnramp } from './EmbeddedComponentsOnramp';

export default function App() {
  return (
    <StripeProvider
      publishableKey="pk_test_..."
      merchantIdentifier="merchant.identifier"
      urlScheme="your-url-scheme"
    >
      <EmbeddedComponentsOnramp />
    </StripeProvider>
  );
}
import { useOnramp } from '@stripe/stripe-react-native';
export function EmbeddedComponentsOnramp() {
  const {
    configure,
    registerLinkUser,
    authorize,
    attachKycInfo,
    verifyIdentity,
    registerWalletAddress,
    collectPaymentMethod,
    createCryptoPaymentToken,
    performCheckout,
  } = useOnramp();
  useEffect(() => {
    configure({ merchantDisplayName: 'My Crypto App' });
  }, [configure]);
  async function handleLogInWithLink() {
    let res = await fetch(SERVER_BASE_URL + '/create-link-auth-intent', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email }),
    });
    if (res.status === 404) {
      await registerLinkUser(email, '+18004444444', 'US');
      res = await fetch(SERVER_BASE_URL + '/create-link-auth-intent', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email }),
      });
    }
    const { authIntentId } = await res.json();
    await loginWithLink(authIntentId);
  }

  async function loginWithLink(authIntentId: string) {
    setLinkAuthIntentId(authIntentId);
    const auth = await authorize(authIntentId);
    if (auth?.status !== 'Consented' || !auth.customerId) return;

    setCryptoCustomerId(auth.customerId);
    await routeToNextStep(auth.customerId, authIntentId);
  }
  async function handleAttachKycInfo() {
    await attachKycInfo({
      firstName: 'John',
      lastName: 'Verified',
      idNumber: '000000000',
      dateOfBirth: { day: 1, month: 1, year: 1990 },
      address: {
        line1: 'address_full_match',
        city: 'San Francisco',
        state: 'CA', // 2-letter uppercase code; see https://en.wikipedia.org/wiki/ISO_3166-2:US
        postalCode: '94111',
        country: 'US', // 2-letter uppercase ISO code; see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
      },
    });
    if (cryptoCustomerId && linkAuthIntentId) await routeToNextStep(cryptoCustomerId, linkAuthIntentId);
  }

  async function handleVerifyIdentity() {
    await verifyIdentity();
    if (cryptoCustomerId && linkAuthIntentId) await routeToNextStep(cryptoCustomerId, linkAuthIntentId);
  }
  async function handleRegisterWallet() {
    await registerWalletAddress(walletAddress, destinationNetwork);
    if (cryptoCustomerId && linkAuthIntentId) await routeToNextStep(cryptoCustomerId, linkAuthIntentId);
  }
  async function handleCollectPaymentMethod() {
    const result = await collectPaymentMethod('Card');
    if (result?.displayData) setStep('payment_token');
  }

  async function handleCreatePaymentToken() {
    const result = await createCryptoPaymentToken();
    if (result?.cryptoPaymentToken) {
      setCryptoPaymentToken(result.cryptoPaymentToken);
      setStep('checkout');
    }
  }
  async function handleCheckout() {
    if (!cryptoCustomerId || !cryptoPaymentToken || !linkAuthIntentId) return;

    const session = await fetch(`${SERVER_BASE_URL}/create-onramp-session`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        authIntentId: linkAuthIntentId,
        crypto_customer_id: cryptoCustomerId,
        payment_token: cryptoPaymentToken,
        source_amount: parseFloat(amount) || 100,
        source_currency: 'usd',
        destination_currency: 'usdc',
        destination_network: 'base',
        wallet_address: walletAddress,
      }),
    }).then(r => r.json());

    const now = Math.floor(Date.now() / 1000);
    if (!session.quote_expires_at || now >= session.quote_expires_at) {
      await fetch(`${SERVER_BASE_URL}/quote/${session.id}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ authIntentId: linkAuthIntentId }),
      });
    }

    const result = await performCheckout(session.id, async () => {
      const { client_secret } = await fetch(`${SERVER_BASE_URL}/checkout/${session.id}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ authIntentId: linkAuthIntentId }),
      }).then(r => r.json());
      return client_secret;
    });
    setStep('complete');
  }
{
  "name": "stripe-sample",
  "version": "1.0.0",
  "description": "A sample Stripe implementation",
  "_comment": "Install the latest private-preview Stripe SDK listed at https://docs.stripe.com/sdks/server-side#stripeclient",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "author": "stripe-samples",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "stripe": "private-preview"
  }
}
{
  "name": "stripe-sample",
  "version": "0.1.0",
  "_comment": "Install the latest private-preview Stripe SDK listed at https://docs.stripe.com/sdks/server-side#stripeclient",
  "dependencies": {
    "@stripe/react-stripe-js": "^3.7.0",
    "@stripe/stripe-js": "^7.3.0",
    "express": "^4.17.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "^3.4.0",
    "stripe": "private-preview"
  },
  "devDependencies": {
    "concurrently": "4.1.2"
  },
  "homepage": "http://localhost:3000/checkout",
  "proxy": "http://localhost:4242",
  "scripts": {
    "start-client": "react-scripts start",
    "start-server": "node server.js",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "start": "concurrently \"npm run start-client\" \"npm run start-server\""
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
## Next steps

#### [Embedded Components onramp integration guide](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md)

Detailed step-by-step instructions for building the integration. Use this to build your own flow.

#### [Embedded Components onramp overview](https://docs.stripe.com/crypto/onramp/embedded-components.md)

Learn about the customer flow and integration phases for the Embedded Components onramp.
