Payments Package

Payments Package (@repo/payments)

This package provides a unified interface for interacting with different payment providers, abstracting away the specific implementation details of each service.

Why You Need This

Payments are essential for monetizing your SaaS. With one wrapper you can swap providers like Stripe or Polar without touching business logic. Read the Monorepo Foundations guide to see how this integrates with other packages.

Purpose

  • Offer a consistent API for common payment operations like managing customers, products, prices, subscriptions, and checkouts.
  • Support multiple payment providers (currently Stripe and Polarsh) through a common interface.
  • Handle configuration and initialization of the selected payment provider.

Structure

  • src/payment-service.ts: Defines the core TypeScript interfaces (PaymentProvider, CheckoutRequest, CustomerRequest, ProductRequest, SubscriptionResult, etc.) that all provider implementations must adhere to.
  • src/providers/: Contains subdirectories for each supported payment provider.
    • stripe/: Implementation of the PaymentProvider interface using the Stripe SDK.
    • polarsh/: Implementation of the PaymentProvider interface using the Polarsh API (details might vary).
  • src/index.server.ts: The server-side entry point. It reads the NEXT_PUBLIC_PAYMENT_PROVIDER environment variable from @repo/config to determine which provider implementation (Stripe or Polarsh) to instantiate and export.

Configuration

The active payment provider is determined by the NEXT_PUBLIC_PAYMENT_PROVIDER environment variable set in your application's environment (e.g., .env).

Set this variable to either stripe or polar.sh.

# .env NEXT_PUBLIC_PAYMENT_PROVIDER=stripe # or polar.sh

The package reads this value via @repo/config and automatically initializes the correct provider implementation.

Usage

In your backend code (API routes, server functions), you import the necessary functions or the provider instance from @repo/payments/server (which resolves to src/index.server.ts).

// Example: In an API route or server function import { getInitializedPaymentProvider, type CheckoutRequest, type CustomerRequest, } from "@repo/payments/server"; const paymentProvider = getInitializedPaymentProvider(); // --- Create a Customer --- async function createBillingCustomer(userId: string, email: string, name: string) { const customerRequest: CustomerRequest = { userId, email, name, }; try { const customer = await paymentProvider.createCustomer(customerRequest); console.log("Created customer:", customer.id); // Link customer.id to your internal user record return customer; } catch (error) { console.error("Error creating customer:", error); throw error; } } // --- Initiate Checkout --- async function startCheckout(customerId: string, priceId: string) { const checkoutRequest: CheckoutRequest = { customerId, lineItems: [{ priceId: priceId, quantity: 1 }], successUrl: `${process.env.NEXT_PUBLIC_APP_URL}/payment/success?session_id={CHECKOUT_SESSION_ID}`, cancelUrl: `${process.env.NEXT_PUBLIC_APP_URL}/payment/canceled`, }; try { const session = await paymentProvider.initiateCheckout(checkoutRequest); console.log("Checkout session URL:", session.url); // Redirect the user to session.url return session.url; } catch (error) { console.error("Error initiating checkout:", error); throw error; } } // --- Get Customer Portal URL --- async function getBillingPortalUrl(customerId: string) { try { const portalUrl = await paymentProvider.getCustomerPortalUrl( customerId, process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000" // Provide a default return URL ); console.log("Customer portal URL:", portalUrl); // Redirect the user to portalUrl return portalUrl; } catch (error) { console.error("Error getting customer portal URL:", error); throw error; } } // --- Handle Webhooks --- // (Implementation depends on your framework - e.g., Hono, Next.js API routes) // You would typically receive the request headers and raw body async function handlePaymentWebhook(headers: Headers, rawBody: string) { try { await paymentProvider.handleWebhook(headers, rawBody); console.log("Webhook processed successfully."); // Return appropriate response to the provider (e.g., 200 OK) } catch (error) { console.error("Error processing webhook:", error); // Return appropriate error response (e.g., 400 or 500) throw error; } }

Core Interfaces (payment-service.ts)

The payment-service.ts file defines the key interfaces used throughout the package:

  • PaymentProvider: The main interface that Stripe and Polarsh implementations adhere to. It defines methods like initiateCheckout, createCustomer, getCustomerPortalUrl, handleWebhook, createProduct, getSubscription, cancelSubscription, etc.
  • CheckoutRequest: Data needed to start a checkout session (customer ID, line items, success/cancel URLs).
  • CheckoutSessionResult: The result of initiating checkout, primarily containing the redirect URL.
  • CustomerRequest: Data needed to create/update a customer (user ID, email, name).
  • CustomerResult: Represents a customer object returned by the provider.
  • ProductRequest: Data needed to create/update a product, including its prices.
  • ProductResult: Represents a product object returned by the provider.
  • PriceInput / PriceResult: Represents price data for creation/retrieval.
  • SubscriptionResult: Represents a subscription object returned by the provider.
  • CouponRequest / CouponResult: Represents coupon data for creation/retrieval.
  • InvoiceResult: Represents a simplified invoice object.
  • TransactionResult: Represents a simplified payment transaction.
  • RefundRequest / RefundResult: Represents refund data for creation/retrieval.

Refer to the packages/payments/src/payment-service.ts file for the exact structure and fields of these interfaces.

Database Interaction

This package primarily interacts with the external payment providers. However, the webhook handlers within the provider implementations are responsible for updating the relevant tables in the @repo/database schema based on events received from the provider. This typically includes:

  • Creating/updating customer records.
  • Creating/updating subscription records (status changes, period updates).
  • Potentially logging paymentTransaction records.
  • Storing raw webhook events in the paymentEvent table for auditing.

Refer to the Database Documentation for details on the payment-related tables (customer, subscription, paymentEvent, paymentTransaction, paymentAccount, paymentSubscription).