MoveFast supports three payment providers. Your variant comes with one of them pre-configured:
- Stripe - The most popular payment processor
- Dodo Payments - Merchant of record (handles taxes for you)
- Polar - Built for developers and open source
All three support both one-time payments and recurring subscriptions through the same unified interface.
Payment Architecture
MoveFast uses a provider-agnostic design. All payment providers implement the same interface:
interface IPaymentProvider {
createOneTimePayment(params) → CheckoutSession
createSubscription(params) → CheckoutSession
cancelSubscription(subscriptionId) → void
getSubscription(subscriptionId) → SubscriptionDetails
handleWebhookEvent(payload, signature) → WebhookHandleResult
createBillingPortalSession(customerId) → { url }
}This means the rest of your app never talks to Stripe/Dodo/Polar directly. It always goes through the service layer.
Payment Models
Configure your payment model in src/config/pricing.config.ts:
export const PAYMENT_MODEL: PaymentModel = PaymentModel.ONE_TIME;
// or
export const PAYMENT_MODEL: PaymentModel = PaymentModel.SUBSCRIPTION;ONE_TIME: Users pay once and get lifetime access. The purchased planId is stored on the user record.
SUBSCRIPTION: Users pay monthly or yearly. A subscription record is created and managed through webhooks.
Configuring Pricing Plans
Edit the PRICING_PLANS array in src/config/pricing.config.ts:
export const PRICING_PLANS: PricingPlan[] = [
{
name: "Free",
description: "Perfect for trying out",
productIds: {
monthly: "free", // Special value, no checkout needed
yearly: "free",
},
price: { monthly: 0, yearly: 0 },
oneTimePrice: 0,
features: [
{ name: "Feature one", included: true },
{ name: "Feature two", included: false },
],
cta: { text: "Get Started", variant: "outline" },
},
{
name: "Pro",
productIds: {
monthly: "your-monthly-product-id",
yearly: "your-yearly-product-id",
oneTime: "your-one-time-product-id",
},
price: { monthly: 29, yearly: 23 },
oneTimePrice: 149,
highlighted: true,
badge: "Most Popular",
features: [ ... ],
cta: { text: "Start Free Trial", variant: "default" },
},
];Key points:
productIdsmust match the product/price IDs in your payment provider dashboard- For
ONE_TIMEmodel, use theoneTimefield inproductIds - For
SUBSCRIPTIONmodel, use themonthlyandyearlyfields price.yearlyis the per-month price when billed annually (for display)- Set
highlighted: trueto visually emphasize a plan - Set
badgeto show a label like "Most Popular"
Currency
export const DEFAULT_CURRENCY = "USD"; // Sent to payment provider (ISO 4217)
export const CURRENCY_SYMBOL = "$"; // Shown in the UIStripe Setup
1. Create an Account
Sign up at stripe.com and complete onboarding.
2. Get API Keys
Go to dashboard.stripe.com/apikeys and copy your secret key.
3. Environment Variables
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...4. Create Products
Go to dashboard.stripe.com/products and create products for each plan. You do not need to create prices in Stripe since MoveFast creates them dynamically using price_data during checkout.
Use the product names as reference, then put any identifier in productIds in your pricing config (MoveFast uses these as metadata, not as Stripe price IDs).
5. Set Up Webhooks
In Stripe Dashboard > Developers > Webhooks, add an endpoint:
https://yourdomain.com/api/payment/webhook/stripeSelect these events:
payment_intent.succeededinvoice.payment_succeededcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deleted
Copy the webhook signing secret into STRIPE_WEBHOOK_SECRET.
6. Test Mode
Use sk_test_ keys during development. Switch to sk_live_ for production.
Dodo Payments Setup
1. Create an Account
Sign up at dodopayments.com.
2. Environment Variables
DODO_API_KEY=your-dodo-api-key
DODO_WEBHOOK_SECRET=your-dodo-webhook-secret
DODO_ENVIRONMENT=test_mode3. Create Products
Create products in the Dodo dashboard. Copy each product ID into productIds in your pricing config.
4. Set Up Webhooks
Add this webhook endpoint in your Dodo dashboard:
https://yourdomain.com/api/payment/webhook/dodo5. Switch to Production
When ready for real payments, change:
DODO_ENVIRONMENT=live_modePolar Setup
1. Create an Account
Sign up at polar.sh.
2. Create an Access Token
In your Polar dashboard, create an organization access token.
3. Environment Variables
POLAR_ACCESS_TOKEN=polar_at_...
POLAR_WEBHOOK_SECRET=your-webhook-secret
POLAR_ENVIRONMENT=sandbox4. Create Products
Create products in the Polar dashboard. Copy each product ID into productIds in your pricing config.
5. Set Up Webhooks
Add this webhook endpoint in Polar:
https://yourdomain.com/api/payment/webhook/polarSelect these events:
order.paidsubscription.createdsubscription.updatedsubscription.activesubscription.canceledsubscription.revoked
6. Switch to Production
POLAR_ENVIRONMENT=productionHow Checkout Works
- User clicks a pricing plan button on the frontend
- Frontend calls
POST /api/payment/checkoutwith theproductId - Server validates the request and checks if the user already has an active subscription (for subscription model)
- Server calls the payment provider to create a checkout session
- Server returns the checkout URL
- Frontend redirects the user to the hosted checkout page
- After payment, user is redirected back to
/payments - The payment provider sends a webhook to confirm the payment
- Webhook handler creates a payment/subscription record in your database
How Webhooks Work
All three providers send webhook events to your app when something happens (payment completed, subscription cancelled, etc.). MoveFast normalizes these into 5 standard event types:
| Event | What it means |
|---|---|
PAYMENT_COMPLETED | One-time payment succeeded |
SUBSCRIPTION_CREATED | New subscription started |
SUBSCRIPTION_UPDATED | Subscription renewed or plan changed |
SUBSCRIPTION_CANCELLED | User cancelled (may still be active until period end) |
SUBSCRIPTION_DELETED | Subscription fully terminated |
The WebhookCoordinator routes each event to either PaymentService or SubscriptionService for processing.
Billing Portal
For subscription users, MoveFast provides a "Manage Subscription" button that opens the payment provider's billing portal. Users can:
- View their subscription details
- Update payment method
- Cancel or resume subscription
- View invoice history
The billing portal URL is fetched via GET /api/subscription/billing-portal.
Testing Webhooks Locally
To test webhooks during development, you need to expose your local server to the internet.
For Stripe, use the Stripe CLI:
stripe listen --forward-to localhost:3000/api/payment/webhook/stripeFor Dodo and Polar, use a tool like ngrok:
ngrok http 3000Then add the ngrok URL as your webhook endpoint in the provider dashboard temporarily.
