How to Use Payments
Learn how to use payment APIs with both Stripe and Creem in NEXTDEVKIT
🔧 Payment Provider Architecture
NEXTDEVKIT uses a provider pattern to support multiple payment systems. Both Stripe and Creem implement the same interface, making it easy to switch between providers.
Provider Interface
export interface PaymentProvider {
createCheckoutLink(params: CreateCheckoutLinkParams): Promise<string>;
createCustomerPortalLink(params: CreatePortalLinkParams): Promise<string>;
handleWebhook(payload: string, signature: string): Promise<void>;
}
Stripe Provider Implementation
import Stripe from 'stripe';
import { PaymentProvider, CreateCheckoutLinkParams } from '@/payment/types';
export class StripeProvider implements PaymentProvider {
private stripe: Stripe;
constructor() {
this.stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
});
}
async createCheckoutLink(params: CreateCheckoutLinkParams): Promise<string> {
//...rest of the code
return session.url!;
}
async createCustomerPortalLink(params: CreatePortalLinkParams): Promise<string> {
//...rest of the code
return session.url!;
}
async handleWebhook(payload: string, signature: string): Promise<void> {
const event = this.stripe.webhooks.constructEvent(
payload,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
switch (event.type) {
case 'checkout.session.completed':
await this.handleCheckoutCompleted(event.data.object);
break;
case 'customer.subscription.created':
await this.handleSubscriptionCreated(event.data.object);
break;
case 'customer.subscription.updated':
await this.handleSubscriptionUpdated(event.data.object);
break;
case 'customer.subscription.deleted':
await this.handleSubscriptionDeleted(event.data.object);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
}
}
Creem Provider Implementation
import { createHmac } from "node:crypto";
import { PaymentProvider, CreateCheckoutLinkParams } from '@/payment/types';
export class CreemProvider implements PaymentProvider {
async createCheckoutLink(params: CreateCheckoutLinkParams): Promise<string> {
//...rest of the code
return checkout_url;
}
async createCustomerPortalLink(params: CreatePortalLinkParams): Promise<string> {
//...rest of the code
return customer_portal_link;
}
async handleWebhook(payload: string, signature: string): Promise<void> {
// Verify webhook signature
//...rest of the code
const event = JSON.parse(payload);
switch (event.eventType) {
case "checkout.completed":
await this.handleOneTimePayment(event);
break;
case "subscription.active":
await this.handleSubscriptionActive(event);
break;
case "subscription.trialing":
await this.handleSubscriptionTrialing(event);
break;
case "subscription.canceled":
case "subscription.expired":
await this.handleSubscriptionCanceled(event);
break;
default:
console.log(`Unhandled Creem event: ${event.eventType}`);
}
}
}
🔄 Webhooks
Stripe Webhook Handler
import { getPaymentProvider } from '@/payment/providers';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const payload = await request.text();
const signature = request.headers.get('stripe-signature')!;
try {
const paymentProvider = getPaymentProvider();
await paymentProvider.handleWebhook(payload, signature);
return NextResponse.json({ received: true });
} catch (error) {
console.error('Stripe webhook error:', error);
return NextResponse.json(
{ error: 'Webhook handler failed' },
{ status: 400 }
);
}
}
Creem Webhook Handler
import { getPaymentProvider } from '@/payment/providers';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const payload = await request.text();
const signature = request.headers.get('creem-signature')!;
try {
const paymentProvider = getPaymentProvider();
await paymentProvider.handleWebhook(payload, signature);
return NextResponse.json({ success: true });
} catch (error) {
console.error('Creem webhook error:', error);
return NextResponse.json(
{ error: 'Webhook handler failed' },
{ status: 400 }
);
}
}
💳 Frontend Usage
Creating Checkout Links
import { createCheckoutLink } from '@/payment/actions';
import { PaymentType } from '@/payment/types';
// In your React component
const handleSubscribe = async (priceId: string) => {
const result = await createCheckoutLink({
type: PaymentType.SUBSCRIPTION,
priceId: priceId,
redirectUrl: window.location.origin + "/app/dashboard",
});
if (result.data?.checkoutUrl) {
window.location.href = result.data.checkoutUrl;
}
};
// Usage with different providers
// The same code works for both Stripe and Creem!
<button onClick={() => handleSubscribe("price_monthly")}>
Subscribe Monthly
</button>
Customer Portal Access
import { createCustomerPortal } from '@/payment/actions';
const handleManageBilling = async () => {
const result = await createCustomerPortal({
customerId: user.customerId,
redirectUrl: window.location.href,
});
if (result.data?.portalUrl) {
window.location.href = result.data.portalUrl;
}
};
<button onClick={handleManageBilling}>
Manage Billing
</button>
🎯 Next Steps
Now that you understand how to use the payment system: