Integrate 2Checkout (now Verifone) into Bolt.new for global payment processing across 200+ countries and 45+ payment methods. Use the InLine Checkout script for card tokenization in the browser and a Next.js API route for server-side order creation via 2Checkout's REST API v6. Handle tax compliance automatically. IPN (Instant Payment Notifications) require a deployed URL on Netlify or Vercel.
Add Global Payments to Bolt.new with 2Checkout (Verifone)
2Checkout, now operated under the Verifone brand following a 2020 acquisition, has built its reputation on one specific strength: handling international sales complexity so you don't have to. When you sell digital goods or software to customers in Germany, Brazil, or Australia, you encounter different VAT rates, local payment methods, currency conversions, and regulatory compliance requirements. 2Checkout handles all of this automatically — calculating and remitting sales tax, VAT, and GST in over 50 jurisdictions as part of its Merchant of Record model. This makes it a common choice for indie developers and SaaS founders selling globally.
The technical integration has two parts that work in conjunction. First, the InLine Checkout — a JavaScript widget that 2Checkout provides — loads on your payment page and renders a payment form that handles card entry, validation, and tokenization directly in the browser. This script can load in Bolt.new's WebContainer preview during development. Second, when the user submits payment, your server-side Next.js API route receives the payment token and calls 2Checkout's REST API v6 to create the order with full product and customer details. The API uses HMAC-SHA256 signature authentication — your Merchant ID and Secret Key are combined with the request data to generate a signature.
For subscription billing, 2Checkout's recurring billing engine handles charge intervals, trial periods, and failed payment retries automatically. This is particularly relevant for SaaS builders using Bolt.new who need a monetization layer without building a custom billing system.
Integration method
2Checkout uses a two-step integration: the InLine Checkout script loads in the browser to collect and tokenize card details (works in Bolt's WebContainer preview), and a Next.js API route handles the server-side order creation using the 2Checkout REST API v6 with your Merchant ID and Secret Key. IPN webhooks for order confirmation and subscription events require a deployed URL since they send incoming POST requests that WebContainers cannot receive.
Prerequisites
- A 2Checkout account at app.2checkout.com — a sandbox account is available for testing at sandbox.2checkout.com
- Your Merchant Code (numeric ID), Secret Key, and Publishable Key from the Merchant Control Panel → Integrations → Webhooks & API
- At least one product set up in your 2Checkout account (Products → Product List) with a product code
- A Bolt.new account with a Next.js project open
- A Netlify account for deployment and IPN webhook configuration
Step-by-step guide
Get 2Checkout credentials and configure your environment
Get 2Checkout credentials and configure your environment
2Checkout uses several credentials that serve different purposes in the integration. Understanding each one prevents common setup mistakes. Log into your 2Checkout Merchant Control Panel at app.2checkout.com (use sandbox.2checkout.com for the test environment). Navigate to Integrations → Webhooks & API → API section. You will find: 1. Merchant Code — A numeric identifier for your account (e.g., `250283345`). This is not a secret — it is included in the frontend InLine Checkout configuration. You will also see it in 2Checkout URLs. Add it as NEXT_PUBLIC_2CHECKOUT_MERCHANT_CODE in your .env since it needs to be accessible client-side. 2. Secret Key (or API Key) — A long alphanumeric string used for HMAC signature generation on server-side API calls. This is sensitive — store it as TWOCHECKOUT_SECRET_KEY (no NEXT_PUBLIC_ prefix) and never expose it in client-side code. 3. Publishable Key — Used to initialize the InLine Checkout JavaScript widget in the browser. This can be NEXT_PUBLIC_ prefixed since it is intended for client-side use. It authenticates the InLine Checkout widget without exposing your secret key. For the sandbox: 2Checkout provides a fully functional test environment at sandbox.2checkout.com with test credit card numbers (4111111111111111 for Visa, with any future expiry and CVV). Sandbox transactions do not process real payments. The sandbox API URL is different: use `https://sandbox.2checkout.com/rest/6.0/` instead of `https://api.2checkout.com/rest/6.0/`. Add all credentials to your .env file. The API base URL should also be configurable so you can switch between sandbox and production without code changes.
Set up 2Checkout credentials in my Next.js project. Create a .env file with: NEXT_PUBLIC_2CHECKOUT_MERCHANT_CODE=your-merchant-code, NEXT_PUBLIC_2CHECKOUT_PUBLISHABLE_KEY=your-publishable-key, TWOCHECKOUT_SECRET_KEY=your-secret-key, and TWOCHECKOUT_API_URL=https://sandbox.2checkout.com/rest/6.0 (sandbox for dev). Create a lib/2checkout.ts file that exports a function generateHmacSignature that accepts the merchant code, secret key, and date string, and returns an HMAC-SHA256 hex digest. Also export a twocheckoutFetch function that builds the Authorization header with 'code={merchantCode} date={date} hash={hmac}' format and calls the 2Checkout API.
Paste this in Bolt.new chat
1// lib/2checkout.ts2import { createHmac } from 'crypto';34export function generateHmacSignature(5 merchantCode: string,6 secretKey: string,7 date: string8): string {9 const stringToHash = `${merchantCode.length}${merchantCode}${date.length}${date}`;10 return createHmac('sha256', secretKey)11 .update(stringToHash)12 .digest('hex');13}1415export async function twocheckoutFetch<T = unknown>(16 endpoint: string,17 init: RequestInit = {}18): Promise<T> {19 const merchantCode = process.env.NEXT_PUBLIC_2CHECKOUT_MERCHANT_CODE;20 const secretKey = process.env.TWOCHECKOUT_SECRET_KEY;21 const apiUrl = process.env.TWOCHECKOUT_API_URL ?? 'https://sandbox.2checkout.com/rest/6.0';2223 if (!merchantCode || !secretKey) {24 throw new Error(25 'NEXT_PUBLIC_2CHECKOUT_MERCHANT_CODE and TWOCHECKOUT_SECRET_KEY must be set in .env'26 );27 }2829 const date = new Date().toISOString().replace('T', ' ').substring(0, 19);30 const hash = generateHmacSignature(merchantCode, secretKey, date);3132 const url = `${apiUrl}${endpoint.startsWith('/') ? endpoint : '/' + endpoint}`;33 const response = await fetch(url, {34 ...init,35 headers: {36 'X-Avangate-Authentication': `code="${merchantCode}" date="${date}" hash="${hash}"`,37 'Content-Type': 'application/json',38 Accept: 'application/json',39 ...init.headers,40 },41 });4243 if (!response.ok) {44 const errorData = await response.text();45 throw new Error(`2Checkout API ${response.status}: ${errorData}`);46 }4748 return response.json() as Promise<T>;49}Pro tip: The 2Checkout HMAC signature must be regenerated on every API request because it includes the current timestamp. Never cache or reuse a signature — each request needs a fresh date and corresponding hash. The date format is 'YYYY-MM-DD HH:mm:ss' in UTC.
Expected result: A configured 2Checkout authentication helper with HMAC signature generation in lib/2checkout.ts, and all credentials stored in .env.
Implement the InLine Checkout for card tokenization
Implement the InLine Checkout for card tokenization
The InLine Checkout is 2Checkout's embeddable payment form — it renders a card entry widget directly on your page without redirecting users to a 2Checkout-hosted page. Users enter their card details into the widget, 2Checkout tokenizes the card data (so your server never handles raw card numbers), and returns a payment token your API route uses to finalize the order. The InLine Checkout is implemented as a third-party JavaScript script (`https://secure.2checkout.com/checkout/api/tco_inline.js`) that you load on your payment page. In React, load it dynamically using a useEffect hook or Next.js's Script component to avoid blocking page load. After loading, call `TCO.loadScript()` and then `TCO.requestToken()` when the user submits the payment form. The `TCO.requestToken()` call requires your merchant code, the PublishableKey, and the card details from your form inputs. It sends these to 2Checkout's servers for tokenization and returns a token in the callback. The token is a short-lived string (expires in a few minutes) that represents the authorized card — your API route uses it to create the actual order. In Bolt.new's WebContainer, the 2Checkout InLine Checkout script loads and the tokenization flow works correctly — it makes outbound calls to 2Checkout's servers, which is fully supported. You can test the complete payment flow in the WebContainer preview using 2Checkout sandbox test card numbers. Build the payment form with standard inputs for card number, expiry month, expiry year, and CVV. Style them to match your app's design. The tokenization happens on submit — the actual card fields are handled by the 2Checkout widget overlay when `TCO.requestToken()` is called, so raw card data never touches your application code.
Create a 2Checkout InLine Checkout payment form. Create a React component at components/TwoCheckoutForm.tsx. Use useEffect to dynamically load the 2Checkout script from https://secure.2checkout.com/checkout/api/tco_inline.js. When the script loads, call window.TCO.loadScript(process.env.NEXT_PUBLIC_2CHECKOUT_MERCHANT_CODE). Create a form with firstName, lastName, email, cardNumber, expMonth, expYear, and cvv inputs. On submit, call window.TCO.requestToken with the card data and publishable key (NEXT_PUBLIC_2CHECKOUT_PUBLISHABLE_KEY). In the callback, if successful send the token to my API route at /api/2checkout/order. Handle loading, success, and error states with appropriate UI feedback.
Paste this in Bolt.new chat
1// components/TwoCheckoutForm.tsx2'use client';3import { useState, useEffect } from 'react';45declare global {6 interface Window {7 TCO: {8 loadScript: (merchantCode: string) => void;9 requestToken: (options: Record<string, unknown>, callback: (token: string, err?: string) => void) => void;10 };11 }12}1314interface TwoCheckoutFormProps {15 productCode: string;16 productName: string;17 price: number;18 currency?: string;19 onSuccess: (orderRef: string) => void;20 onError: (error: string) => void;21}2223export function TwoCheckoutForm({24 productCode,25 productName,26 price,27 currency = 'USD',28 onSuccess,29 onError,30}: TwoCheckoutFormProps) {31 const [isScriptLoaded, setIsScriptLoaded] = useState(false);32 const [isProcessing, setIsProcessing] = useState(false);33 const [formData, setFormData] = useState({34 firstName: '', lastName: '', email: '',35 cardNumber: '', expMonth: '', expYear: '', cvv: '',36 });3738 useEffect(() => {39 const script = document.createElement('script');40 script.src = 'https://secure.2checkout.com/checkout/api/tco_inline.js';41 script.onload = () => {42 window.TCO?.loadScript(process.env.NEXT_PUBLIC_2CHECKOUT_MERCHANT_CODE!);43 setIsScriptLoaded(true);44 };45 document.head.appendChild(script);46 return () => { document.head.removeChild(script); };47 }, []);4849 const handleSubmit = async (e: React.FormEvent) => {50 e.preventDefault();51 if (!isScriptLoaded) return;52 setIsProcessing(true);5354 const tokenOptions = {55 sellerId: process.env.NEXT_PUBLIC_2CHECKOUT_MERCHANT_CODE,56 publishableKey: process.env.NEXT_PUBLIC_2CHECKOUT_PUBLISHABLE_KEY,57 ccNo: formData.cardNumber.replace(/\s/g, ''),58 cvv: formData.cvv,59 expMonth: formData.expMonth,60 expYear: formData.expYear,61 };6263 window.TCO.requestToken(tokenOptions, async (token: string, err?: string) => {64 if (err) {65 setIsProcessing(false);66 onError(err);67 return;68 }6970 try {71 const response = await fetch('/api/2checkout/order', {72 method: 'POST',73 headers: { 'Content-Type': 'application/json' },74 body: JSON.stringify({75 token, productCode, productName, price, currency,76 customer: {77 firstName: formData.firstName,78 lastName: formData.lastName,79 email: formData.email,80 },81 }),82 });83 const result = await response.json();84 if (result.orderRef) {85 onSuccess(result.orderRef);86 } else {87 onError(result.error ?? 'Payment failed');88 }89 } catch {90 onError('Network error — please try again');91 } finally {92 setIsProcessing(false);93 }94 });95 };9697 return (98 <form onSubmit={handleSubmit} className="space-y-4 max-w-md">99 <div className="grid grid-cols-2 gap-4">100 <input required placeholder="First name" value={formData.firstName}101 onChange={e => setFormData(p => ({ ...p, firstName: e.target.value }))}102 className="border rounded px-3 py-2" />103 <input required placeholder="Last name" value={formData.lastName}104 onChange={e => setFormData(p => ({ ...p, lastName: e.target.value }))}105 className="border rounded px-3 py-2" />106 </div>107 <input required type="email" placeholder="Email" value={formData.email}108 onChange={e => setFormData(p => ({ ...p, email: e.target.value }))}109 className="border rounded px-3 py-2 w-full" />110 <input required placeholder="Card number" value={formData.cardNumber}111 onChange={e => setFormData(p => ({ ...p, cardNumber: e.target.value }))}112 className="border rounded px-3 py-2 w-full" maxLength={19} />113 <div className="grid grid-cols-3 gap-4">114 <input required placeholder="MM" value={formData.expMonth}115 onChange={e => setFormData(p => ({ ...p, expMonth: e.target.value }))}116 className="border rounded px-3 py-2" maxLength={2} />117 <input required placeholder="YYYY" value={formData.expYear}118 onChange={e => setFormData(p => ({ ...p, expYear: e.target.value }))}119 className="border rounded px-3 py-2" maxLength={4} />120 <input required placeholder="CVV" value={formData.cvv}121 onChange={e => setFormData(p => ({ ...p, cvv: e.target.value }))}122 className="border rounded px-3 py-2" maxLength={4} />123 </div>124 <button type="submit" disabled={isProcessing || !isScriptLoaded}125 className="w-full bg-blue-600 text-white py-3 rounded font-medium disabled:opacity-50">126 {isProcessing ? 'Processing...' : `Pay ${currency} ${price}`}127 </button>128 </form>129 );130}Pro tip: For sandbox testing, use these 2Checkout test card numbers: Visa 4111111111111111, Mastercard 5500000000000004. Use any future expiry date (e.g., 12/2030) and any 3-digit CVV. These only work with sandbox credentials — switch to production credentials and the production API URL for live processing.
Expected result: A payment form component that loads the 2Checkout InLine Checkout script, collects card details, and returns a payment token to the API route for order creation.
Create orders server-side via the 2Checkout REST API v6
Create orders server-side via the 2Checkout REST API v6
After the InLine Checkout tokenizes the card, your server-side API route creates the actual order using the 2Checkout REST API v6. This is where the product details, customer information, pricing, and currency are specified — the token alone does not contain this information. The 2Checkout orders endpoint is `POST /orders/`. The request body is a JSON object with a specific structure: `BillingDetails` (customer name and address), `PaymentDetails` (the payment method object containing the `PaymentToken`), and `Items` (array of products being purchased). Each item in the `Items` array needs: `Code` (your product code from the 2Checkout product catalog), `Quantity`, `Price`, and `PriceType` (NET for exclusive of tax, GROSS for inclusive). For digital products, set `IsDynamic: true` and provide the price directly in the order — this lets you override the catalog price for dynamic pricing scenarios. Authentication uses the HMAC-SHA256 signature from lib/2checkout.ts. The `X-Avangate-Authentication` header format is: `code="{merchantCode}" date="{formattedDate}" hash="{hmacHash}"`. The date format must be exactly `YYYY-MM-DD HH:mm:ss` in UTC with spaces (not the ISO format with T). The response from a successful order creation includes `RefNo` (the order reference number), `Status` (COMPLETE for immediate payment), and `PaymentDetails` with payment status. Store the `RefNo` in your database as the order identifier for customer support and refund reference. For subscriptions, add `SubscriptionStartDate`, `TrialEndDate`, and `RecurringOptions` to the order body. 2Checkout's recurring billing engine takes over from there.
Create a 2Checkout order creation API route at app/api/2checkout/order/route.ts. Accept POST with token (the payment token from InLine Checkout), productCode, productName, price, currency (default USD), and customer (firstName, lastName, email). Use twocheckoutFetch from lib/2checkout.ts to POST to /orders/ with the full 2Checkout v6 order payload including BillingDetails, PaymentDetails with the token, and Items array. Return { orderRef, status } on success or { error } on failure. Add a comment that tax calculation is handled automatically by 2Checkout for international sales.
Paste this in Bolt.new chat
1// app/api/2checkout/order/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { twocheckoutFetch } from '@/lib/2checkout';45interface OrderResponse {6 RefNo: string;7 Status: string;8 GrossPrice: number;9 Currency: string;10}1112export async function POST(request: NextRequest) {13 const { token, productCode, productName, price, currency = 'USD', customer } =14 await request.json();1516 if (!token || !productCode || !price || !customer?.email) {17 return NextResponse.json(18 { error: 'token, productCode, price, and customer.email are required' },19 { status: 400 }20 );21 }2223 // 2Checkout handles tax calculation automatically for international sales24 // based on customer billing address and product type25 const orderPayload = {26 Currency: currency,27 Language: 'EN',28 Country: 'US',29 CustomerIP: '91.220.163.52', // In production, use the real customer IP30 Source: 'BOLT_APP',31 BillingDetails: {32 FirstName: customer.firstName ?? '',33 LastName: customer.lastName ?? '',34 Email: customer.email,35 Address1: '123 Main St', // In production, collect from form36 City: 'New York',37 State: 'New York',38 CountryCode: 'US',39 Zip: '10001',40 },41 PaymentDetails: {42 Type: 'EES_TOKEN_PAYMENT',43 Currency: currency,44 PaymentMethod: {45 EesToken: token,46 Vendor3DSReturnURL: `${process.env.NEXT_PUBLIC_APP_URL}/payment/return`,47 Vendor3DSCancelURL: `${process.env.NEXT_PUBLIC_APP_URL}/payment/cancel`,48 },49 },50 Items: [{51 Code: productCode,52 Quantity: 1,53 IsDynamic: false,54 Price: {55 Amount: price,56 Type: 'CUSTOM',57 },58 }],59 };6061 const result = await twocheckoutFetch<OrderResponse>('/orders/', {62 method: 'POST',63 body: JSON.stringify(orderPayload),64 });6566 return NextResponse.json({67 orderRef: result.RefNo,68 status: result.Status,69 total: result.GrossPrice,70 currency: result.Currency,71 });72}Pro tip: 2Checkout's order API requires a billing address even for digital products where shipping is not relevant. In production, add billing address fields to your checkout form (at minimum Address, City, Country, and ZIP). Passing placeholder values in development is fine for sandbox testing but will cause issues with real orders.
Expected result: An order creation API route that completes the 2Checkout payment flow, returning an order reference number on successful payment.
Deploy to Netlify and configure IPN webhooks
Deploy to Netlify and configure IPN webhooks
The InLine Checkout script loading and the REST API order creation both work in Bolt.new's WebContainer during development. You can test the complete payment flow in the preview environment using sandbox credentials and test card numbers. The WebContainer limitation applies only to IPN webhooks. 2Checkout's IPN (Instant Payment Notification) system is a webhook mechanism that sends POST requests to your endpoint when payment events occur — order completed, subscription renewed, refund processed, or fraud flagged. Since these are incoming HTTP connections, they require a deployed public URL. To deploy: click Deploy in Bolt.new, connect to Netlify, and after deployment add the following environment variables in Netlify Dashboard → Site Configuration → Environment Variables: `NEXT_PUBLIC_2CHECKOUT_MERCHANT_CODE`, `NEXT_PUBLIC_2CHECKOUT_PUBLISHABLE_KEY`, `TWOCHECKOUT_SECRET_KEY`, `TWOCHECKOUT_API_URL` (change to `https://api.2checkout.com/rest/6.0` for production), and `NEXT_PUBLIC_APP_URL` (your Netlify URL). To configure IPN in 2Checkout: log into your Merchant Control Panel, go to Dashboard → Integrations → Webhooks & API → IPN Settings. Add your deployed URL (e.g., `https://your-app.netlify.app/api/2checkout/ipn`) and select the events to receive. Click Test to verify 2Checkout can reach your endpoint. 2Checkout signs IPN notifications with an HMAC hash using your Secret Key — verify this signature in your handler to ensure the notification genuinely came from 2Checkout. The IPN handler must return HTTP 200 to acknowledge receipt. For production, switch `TWOCHECKOUT_API_URL` to `https://api.2checkout.com/rest/6.0` and ensure your 2Checkout account is fully activated with bank account and identity verification complete — 2Checkout holds payouts until account verification is done.
Create a 2Checkout IPN webhook handler at app/api/2checkout/ipn/route.ts. Accept POST requests. Parse the IPN payload. Verify the IPN signature using your TWOCHECKOUT_SECRET_KEY: concatenate MD5 hash of each IPN parameter value as a string and compare to the HASH parameter in the request. For ORDER_CREATED events, log the order reference and amount. For FRAUD_STATUS_CHANGED events, log the fraud status. Always return 200 with empty body. Add a comment that IPN requires a deployed URL — it cannot be received in Bolt's WebContainer. Create netlify.toml with Next.js configuration.
Paste this in Bolt.new chat
1// app/api/2checkout/ipn/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { createHash } from 'crypto';45// NOTE: 2Checkout IPN webhooks require a publicly deployed URL.6// During Bolt.new WebContainer development, incoming IPN callbacks7// cannot be received. Deploy to Netlify, then configure your IPN URL8// in 2Checkout Merchant Control Panel → Integrations → Webhooks & API → IPN Settings.9export async function POST(request: NextRequest) {10 try {11 const text = await request.text();12 const params = new URLSearchParams(text);13 const ipnData = Object.fromEntries(params.entries());1415 // Verify IPN signature16 const secretKey = process.env.TWOCHECKOUT_SECRET_KEY;17 if (secretKey) {18 const receivedHash = ipnData.HASH;19 // Build hash string: MD5 of concatenated values length+value for each param except HASH20 const sortedKeys = Object.keys(ipnData)21 .filter(k => k !== 'HASH')22 .sort();23 const hashString = sortedKeys24 .map(k => `${ipnData[k].length}${ipnData[k]}`)25 .join('');26 const computedHash = createHash('md5')27 .update(secretKey + hashString)28 .digest('hex');2930 if (computedHash !== receivedHash) {31 console.error('2Checkout IPN: invalid signature');32 // Still return 200 to prevent retries, but log the issue33 }34 }3536 const messageType = ipnData.MESSAGE_TYPE;37 const orderRef = ipnData.REFNO;3839 switch (messageType) {40 case 'ORDER_CREATED':41 console.log(`Order created: ${orderRef}, amount: ${ipnData.IPN_TOTALGENERAL} ${ipnData.CURRENCY}`);42 // TODO: Update order status in your database, send confirmation email43 break;44 case 'FRAUD_STATUS_CHANGED':45 console.warn(`Fraud status for order ${orderRef}: ${ipnData.FRAUD_STATUS}`);46 break;47 case 'SUBSCRIPTION_RENEWED':48 console.log(`Subscription renewed for order ${orderRef}`);49 break;50 default:51 console.log(`2Checkout IPN event: ${messageType} for order ${orderRef}`);52 }53 } catch (error) {54 console.error('IPN processing error:', error);55 }5657 // Always return 200 — 2Checkout retries on non-200 responses58 return new NextResponse('', { status: 200 });59}Pro tip: 2Checkout sends IPN notifications for many event types. Start by handling ORDER_CREATED (payment successful) and FRAUD_STATUS_CHANGED (order flagged for review) — these are the most critical for order fulfillment. Add SUBSCRIPTION_RENEWED and RECURRING_PAYMENT_FAILED as your billing logic matures.
Expected result: A deployed Netlify app with a 2Checkout IPN handler receiving payment and subscription events, with the order reference logged for fulfillment.
Common use cases
One-Time Digital Product Purchase
Sell a digital product (ebook, template pack, software license) using 2Checkout's InLine Checkout. The customer enters card details in an embeddable widget, and your API route creates the order and delivers the digital product after successful payment.
Add a 2Checkout payment flow to my digital product page. Create a React PaymentForm component that loads the 2Checkout InLine Checkout script from process.env.NEXT_PUBLIC_2CHECKOUT_PUBLISHABLE_KEY. On form submit, call TCO.requestToken and send the returned token to my API route. Create an API route at app/api/2checkout/order/route.ts that uses the token to POST to https://api.2checkout.com/rest/6.0/orders/ with HMAC authentication using MERCHANT_CODE and SECRET_KEY from process.env. Return the order reference on success.
Copy this prompt to try it in Bolt.new
SaaS Subscription Checkout
Build a subscription checkout for a SaaS product with monthly and annual billing options. 2Checkout handles the recurring billing automatically after the initial payment, including failed payment retries and dunning emails.
Create a subscription checkout for my SaaS app. Build a pricing page with Monthly ($19/mo) and Annual ($190/yr) plan buttons. Use 2Checkout's InLine Checkout to collect payment details. Create an API route that creates a subscription order at /rest/6.0/orders/ with the SUBSCRIPTION product type and the selected billing cycle. Include the customer's email and name. Return the subscriptionReference and nextRenewalDate. Show a success page with the subscription details after payment.
Copy this prompt to try it in Bolt.new
International E-commerce Checkout
For an e-commerce store selling physical or digital products internationally, use 2Checkout to handle multi-currency pricing and automatic tax calculation. Customers pay in their local currency and 2Checkout remits the taxes to the appropriate jurisdiction.
Add an international checkout to my store using 2Checkout. Create an API route at app/api/2checkout/checkout/route.ts that accepts POST with items (array of productCode, name, price, qty), customerEmail, and currency (default USD). Build the 2Checkout v6 order payload with automatic tax calculation enabled (externalCustomerReference, billingDetails). Sign with HMAC-SHA256 using MERCHANT_CODE and SECRET_KEY. Return the order reference and payment status.
Copy this prompt to try it in Bolt.new
Troubleshooting
HMAC signature authentication error — 'Invalid authentication credentials'
Cause: The most common cause is an incorrectly formatted date string in the HMAC computation. The date must be in 'YYYY-MM-DD HH:mm:ss' format (with a space between date and time, not 'T'), and the time must be in UTC. The string-to-hash format is also very specific: {length}{merchantCode}{length}{date}.
Solution: Verify the date format in lib/2checkout.ts — use new Date().toISOString().replace('T', ' ').substring(0, 19) to get the correct format. Double-check the HMAC string construction: it must be merchantCode.length + merchantCode + date.length + date. Log the hash string and verify it matches the expected pattern before computing the HMAC.
1// Correct date format for 2Checkout HMAC2const date = new Date().toISOString().replace('T', ' ').substring(0, 19); // '2025-04-22 14:30:00'3const stringToHash = `${merchantCode.length}${merchantCode}${date.length}${date}`;InLine Checkout script loads but requestToken returns 'Invalid seller' error
Cause: The NEXT_PUBLIC_2CHECKOUT_MERCHANT_CODE or NEXT_PUBLIC_2CHECKOUT_PUBLISHABLE_KEY values are incorrect, or you are mixing sandbox and production credentials. Sandbox credentials only work with the sandbox script URL, and production credentials only work with the production script URL.
Solution: For sandbox: use credentials from sandbox.2checkout.com and the sandbox InLine script URL if specified. Verify the merchant code is numeric only (no prefixes or spaces). Check the browser console for the specific error message from TCO.requestToken — it typically provides a more descriptive error code.
2Checkout IPN notifications are not arriving during Bolt.new development
Cause: Bolt.new's WebContainer does not have a persistent public URL that 2Checkout's IPN system can reach. IPN sends incoming POST requests to your registered endpoint — this requires a deployed, publicly accessible server.
Solution: Deploy your app to Netlify first, then configure the IPN URL in your 2Checkout Merchant Control Panel → Integrations → Webhooks & API → IPN Settings. Use your Netlify domain for the IPN endpoint URL. This is a fundamental WebContainer limitation — incoming webhook connections always require a deployed environment.
Payment succeeds in sandbox but order status shows as SUSPECT instead of COMPLETE
Cause: 2Checkout's fraud detection system flagged the test order. In sandbox mode, certain test scenarios can trigger fraud review. In production, this can happen for real orders too — particularly for digital goods sold internationally.
Solution: For sandbox, use 2Checkout's documented test card numbers and billing details that don't trigger fraud flags. In production, monitor the Fraud Status dashboard in your Merchant Control Panel and configure your fraud rules. For orders with SUSPECT status, 2Checkout's team reviews them manually — you can also configure automatic approval rules for trusted patterns.
Best practices
- Store TWOCHECKOUT_SECRET_KEY as a server-side environment variable only — never in NEXT_PUBLIC_ variables or client-side code, since it is used to generate authentication signatures
- Always validate the IPN signature using your Secret Key before processing payment notifications — unsigned or incorrectly signed IPNs should be logged but not trusted
- Use 2Checkout's sandbox environment (sandbox.2checkout.com) for all development and testing — sandbox credentials do not process real payments
- Switch TWOCHECKOUT_API_URL to https://api.2checkout.com/rest/6.0 and update credentials when moving to production — never use sandbox credentials in production
- Always return HTTP 200 from your IPN handler even when processing fails internally — 2Checkout retries IPN deliveries on non-200 responses, causing duplicate event processing
- Collect a billing address in your checkout form for production — 2Checkout requires address data for tax calculation and fraud prevention, and placeholder values cause issues with real orders
- For subscription products, store the subscription reference from the order response in your database — you will need it to manage subscription cancellations, upgrades, and renewals via the API
- Test 3DS authentication flows in sandbox before going live — 2Checkout supports 3D Secure for European cards, and your PaymentMethod object must include Vendor3DSReturnURL and Vendor3DSCancelURL
Alternatives
Stripe has superior developer experience and documentation — choose it over 2Checkout for US-focused businesses or when you want the most polished integration experience, but note you will need Stripe Tax as a separate add-on for international tax compliance.
Adyen is an enterprise-grade payment platform used by major e-commerce companies — choose it over 2Checkout if you need enterprise scale, enterprise-level support SLAs, or are processing very high transaction volumes.
Worldpay offers a similar global payment reach with strong presence in European markets — choose it over 2Checkout if your business is based in Europe or the UK and you want a payment provider with strong local bank relationships.
Authorize.net is a US-focused payment gateway with a long track record and wide integration ecosystem — choose it over 2Checkout if you primarily serve US customers and prefer a gateway with extensive plugin support for existing platforms.
Frequently asked questions
Can I test 2Checkout payments in Bolt.new without deploying?
Yes. The InLine Checkout script loads in Bolt.new's WebContainer and the REST API order creation uses outbound HTTP calls — both work in the preview environment during development. Use the sandbox credentials (from sandbox.2checkout.com) and the test card number 4111111111111111 to simulate complete payment flows without real charges. The only feature requiring deployment is IPN webhook notifications.
What makes 2Checkout different from Stripe for international sales?
2Checkout acts as a Merchant of Record in many markets, meaning it handles VAT/GST collection and remittance to tax authorities in 50+ jurisdictions automatically. Stripe requires you to configure Stripe Tax as a separate product and manage tax registrations yourself. For digital goods sold globally to consumers — especially in the EU where VAT compliance is strict — 2Checkout's built-in tax handling reduces compliance burden significantly.
How do I switch from 2Checkout sandbox to production?
Change two things: set TWOCHECKOUT_API_URL to https://api.2checkout.com/rest/6.0 (remove 'sandbox') and replace your sandbox credentials with production credentials from app.2checkout.com. Ensure your 2Checkout production account is fully activated with bank account and identity verification complete — 2Checkout holds payouts until verification is done. Also update the InLine Checkout script URL if you were using a sandbox-specific script URL.
Can 2Checkout handle subscriptions and recurring billing?
Yes. 2Checkout has a comprehensive subscription billing engine. Add recurring options to your order payload when creating the initial subscription. 2Checkout handles subsequent charges on your configured billing cycle, failed payment retries, dunning emails, and sends IPN events for each renewal. This makes it suitable for SaaS billing without building a custom recurring billing system.
What is the 2Checkout IPN and why does it require deployment?
IPN (Instant Payment Notification) is 2Checkout's webhook system — it sends POST requests to your registered endpoint URL when payment events occur (order completed, subscription renewed, fraud flagged). Since these are incoming HTTP connections from 2Checkout's servers to your app, they require a publicly accessible URL. Bolt.new's WebContainer is a browser-based environment without a persistent public URL, so IPN notifications cannot be received during development. Deploy to Netlify first, then register your Netlify URL in the 2Checkout IPN settings.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation