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 thePaymentProvider
interface using the Stripe SDK.polarsh/
: Implementation of thePaymentProvider
interface using the Polarsh API (details might vary).
src/index.server.ts
: The server-side entry point. It reads theNEXT_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 likeinitiateCheckout
,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
).