Payment processors

Accept Bitcoin, USDT & USDC Next to Stripe

Keep Stripe for cards and add BTC, USDT & USDC next to it, with card plans and prepaid passes on one design.

Tested in production since June 2026

A subscription software business sells card plans through Stripe and prepaid passes in BTC, USDT & USDC through OpenReceive on this design, side by side. The agent directions below build the same design into your app.

Get agent directions

Add to my Stripe app

This app already takes card payments through Stripe.

Start a new app with both

No payments yet. Build card and BTC, USDT & USDC checkout together.

On WordPress with WooCommerce? Install the OpenReceive plugin and it appears next to the Stripe gateway at checkout. Automatic subscription renewals stay card-only. OpenReceive for WooCommerce.

Java, Go or .NET? Not supported yet, so get in touch. Selling through Payment Links or a site builder with no server of its own? Start from a platform instead. See platforms.

Keep Stripe for cards and add BTC, USDT & USDC, paid straight into a wallet you control. BTC arrives over Lightning, and a swap provider converts USDT and USDC to BTC on the way in. This page is the design we run in production next to Stripe, and agent directions that build it into your app.

Tested in production

This is not a sketch. Since June 2026 this design has run a subscription software business, with Stripe and OpenReceive side by side on one pricing page:

Everything on this page comes from running it, including the mistakes we fixed. The agent directions below build the same design into your app.

What changes, and what doesn’t

Stripe is a payment processor; OpenReceive is not. Stripe is a company: it takes each card payment, approves your account and pays you out. OpenReceive is software that runs inside your app and makes each invoice in your own wallet. It never receives the money, so there is no account and no payout.

Stripe stays as it is. Checkout, subscriptions, the customer portal, webhooks, receipts and tax settings keep working exactly as they do today. Your customers who pay by card see no difference.

OpenReceive adds a second way to pay. Next to “Pay with card” your pricing page gets “Pay with BTC, USDT & USDC”. The invoice comes from your own Lightning wallet through a receive-only connection code, so there is no account to open, no payout to wait for and no one holding your money. A configured swap provider turns USDT and USDC payments into BTC on the way in.

Both end in the same place. A card payment and an OpenReceive payment run the same piece of your code: the one that grants what was bought. Everything after that (access, credits, features) doesn’t care how the customer paid.

Why BTC, USDT & USDC plans are prepaid passes

A card subscription works because Stripe keeps the card on file and charges it every month. Nobody can pull BTC, USDT or USDC out of a customer’s wallet. Each payment is one the customer sends, so there is nothing to bill next month.

So the two payment methods sell time differently:

  Card (Stripe) BTC, USDT & USDC (OpenReceive)
Plans Monthly or yearly, renews automatically A prepaid pass of 60, 180 or 365 days, never renews
Paid Every period Once, up front
Credits Each month The whole pass’s worth, at purchase
Credit packs Same price Same price
Chargebacks Possible None

Pricing a prepaid pass

Start from the card price. A pass costs the monthly price times the months it covers, less a discount for paying up front: about 10% for 60 days, 20% for 180 days and 26% for 365 days. Count 365 days as 365/30 months, and round to whole dollars.

Plan Card, per month 60-day pass 180-day pass 365-day pass
Basic $19 $34 $91 $171
Plus $39 $70 $187 $351
Business $99 $178 $475 $891

The discount is fair, not a giveaway. You get the money up front, with no card fees, no failed renewals and no chargebacks.

Credit packs

If you sell usage, credit packs cost the same in dollars by card or in BTC, USDT & USDC, and they never expire. When a customer has both, spend the plan’s or pass’s credits first, since those expire, and pack credits last.

When a customer has both

These rules come straight from production, where getting them wrong cost us a customer’s paid time.

The design

This is what the agent directions build. It fits any framework OpenReceive supports.

Stripe webhook ─────────┐
                        ├──▶ settle(order) ──▶ grant plan time, credits or the product
OpenReceive on_paid ────┘
  1. One order table for both. Every purchase is an order row. It records which payment method it used, what kind of purchase it is (a card plan period, a prepaid pass, a credit pack or a product), and the price in dollars. Revenue reports read dollars for both, never satoshis.
  2. One settle function. The Stripe webhook and OpenReceive’s on_paid hook both call it. It locks the order, does nothing if the order is already paid, and grants what the order bought. Every grant has a unique key, so a replayed event can never grant twice.
  3. Separate access records. A card plan’s access lives on its Stripe subscription record, which only the Stripe sync updates. A prepaid pass lives on its own record, with a paid-until date. One helper reads both and decides what the customer can do.
  4. The OpenReceive order comes first. When the customer clicks “Pay with BTC, USDT & USDC”, the app creates the order with its dollar price and an opaque, never-reused reference. OpenReceive’s amount_for returns that saved price, and authorize checks that the order belongs to the signed-in customer. OpenReceive converts dollars to BTC at checkout and refuses when the exchange rate is stale.
  5. Stripe is store-then-process. Verify the signature, save the event, and answer 200. Then process it, re-reading the subscription from Stripe, because events arrive out of order. Nothing is granted on the success redirect.
  6. OpenReceive needs no webhook. The checkout polls, every OpenReceive request checks for payments first, and an optional worker makes it instant when no browser is open. on_paid runs inside OpenReceive’s database transaction, so emails and live updates go out after it commits.
  7. The checkout link is the refund link. If a USDT or USDC deposit arrives short or late, the swap provider refunds it, and the customer claims it on the same checkout page. Keep that page reachable from the order.

Get the agent directions

Pick your situation and your framework at the top of this page, and copy the directions into Claude Code, Codex, Cursor or another coding agent. There are two sets:

Either way, the agent first asks for two codes from your wallet and swap provider, one at a time. Then it asks one question about your pass prices, where the defaults above are a fine answer.

A coding agent reading this page can fetch the directions itself: https://openreceive.org/agent-directions/stripe/add/<stack>/full.md to add to an app that already uses Stripe, or …/stripe/new/<stack>/full.md for a new app. <stack> is node (Express), fastify, next, rails, django, fastapi, laravel or php.

What the agent won’t touch

Questions

Can I refund a BTC, USDT or USDC payment? Not automatically. The connection code can receive but never send, which is what keeps your wallet safe. Refund from your wallet by hand if you need to. USDT and USDC deposits that arrive short or late are refunded to the payer by the swap provider. The customer claims the refund on the checkout page.

Receipts? Stripe emails card receipts. For OpenReceive orders, show the order on the customer’s account page or send your own email after on_paid commits.

Where does the money go? To the Lightning wallet you connected. USDT and USDC payments arrive there as BTC, converted by the swap provider. Which networks are available depends on the provider.

Which frameworks? Express, Fastify, Next.js, Rails, Django, FastAPI, Laravel and plain PHP. On WordPress with WooCommerce, install the OpenReceive plugin, and it appears next to the Stripe gateway at checkout. Java, Go and .NET servers aren’t supported yet. If you sell through Stripe Payment Links or a site builder with no server of your own, start from a platform instead.