Docs
Features

Payments

Dual payment provider integration with Stripe and PayPal — one-time payments, subscriptions, webhook auto-sync, guest checkout, and automated email notifications.

Stripe

Setup

  1. Create an account at stripe.com
  2. Copy your API keys from the Stripe Dashboard
  3. Create a webhook endpoint pointing to https://yourdomain.com/api/stripe/webhook
  4. Enable the events listed in the webhook section below

Configuration

# .env
STRIPE_SECRET_KEY="sk_test_xxx"
STRIPE_PUBLISHABLE_KEY="pk_test_xxx"
STRIPE_WEBHOOK_SECRET="whsec_xxx"

Use test keys during development. Switch to live keys before going to production.

One-time Payment

import { createStripeCheckoutSession } from '@/lib/payment/stripe'

const session = await createStripeCheckoutSession({
  productId: product.id,
  userId: user?.id,          // optional — guest checkout supported
  successUrl: 'https://yourdomain.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancelUrl:  'https://yourdomain.com/cancel',
})

// Redirect the browser
window.location.href = session.url

Subscription

import { createStripeSubscription } from '@/lib/payment/stripe'

const subscription = await createStripeSubscription({
  customerId: stripeCustomerId,
  priceId: 'price_xxx',
  metadata: { userId: user.id },
})

Webhook Events

TikShip handles these Stripe events automatically:

EventAction
checkout.session.completedMark order completed, trigger confirmation email
payment_intent.payment_failedMark order failed, trigger failure email
invoice.payment_succeededRenew subscription order
customer.subscription.deletedCancel subscription
// src/app/api/stripe/webhook/route.ts  (auto-handled)
export async function POST(req: NextRequest) {
  const event = await verifyStripeWebhook(req)

  switch (event.type) {
    case 'checkout.session.completed':
      await handleStripeCheckoutComplete(event.data.object)
      break
    case 'payment_intent.payment_failed':
      await handleStripePaymentFailed(event.data.object)
      break
  }

  return new Response('OK')
}

Test Cards

ScenarioCard number
Success4242 4242 4242 4242
Decline4000 0000 0000 0002
Insufficient funds4000 0000 0000 9995
3D Secure4000 0025 0000 3155

Use any future expiry date, any 3-digit CVC, and any 5-digit ZIP.

PayPal

Setup

  1. Create an account at developer.paypal.com
  2. Create a new app to get Client ID and Secret
  3. Create a webhook and point it to https://yourdomain.com/api/paypal/webhook

Configuration

# .env
PAYPAL_MODE="sandbox"          # sandbox | live
PAYPAL_CLIENT_ID="your-client-id"
PAYPAL_CLIENT_SECRET="your-client-secret"
PAYPAL_WEBHOOK_ID="your-webhook-id"

One-time Payment

import { createPayPalOrder } from '@/lib/payment/paypal'

const order = await createPayPalOrder({
  productId: product.id,
  userId: user?.id,
  returnUrl: 'https://yourdomain.com/success',
  cancelUrl:  'https://yourdomain.com/cancel',
})

window.location.href = order.approvalUrl

Subscription Plans

import { createPayPalSubscription } from '@/lib/payment/paypal'

const subscription = await createPayPalSubscription({
  planId: 'P-xxx',           // created in PayPal dashboard
  returnUrl: 'https://yourdomain.com/success',
  cancelUrl:  'https://yourdomain.com/cancel',
})

Webhook Events

EventAction
PAYMENT.CAPTURE.COMPLETEDMark order completed, trigger confirmation email
PAYMENT.CAPTURE.DENIEDMark order failed, trigger failure email
BILLING.SUBSCRIPTION.CREATEDActivate subscription order
BILLING.SUBSCRIPTION.CANCELLEDCancel subscription

Sandbox Accounts

Use auto-generated sandbox accounts from the PayPal Developer Dashboard:

  • Buyer: personal sandbox account
  • Seller: business sandbox account

Flexible Product Pricing

Each product can enable any combination of payment methods:

Admin → Products → Edit → Payment Methods
  ✅ Stripe (one-time)
  ✅ Stripe (subscription — monthly)
  ✅ PayPal (one-time)
  ✅ PayPal (subscription — monthly)

At checkout the customer sees only the payment options configured for that product.

Order Lifecycle

All orders share a single status model:

pending → completed
        → failed
        → refunded
        → expired
StatusMeaning
pendingPayment initiated, awaiting webhook confirmation
completedPayment confirmed by provider webhook
failedPayment declined or errored
refundedRefund issued via provider dashboard or API
expiredPending order not completed within the expiry window

Query Orders

import { prisma } from '@/lib/db/prisma'

// User's order history
const orders = await prisma.order.findMany({
  where: { userId: user.id },
  select: {
    id: true, status: true, amount: true, currency: true,
    paymentProvider: true, createdAt: true,
    product: { select: { name: true } },
  },
  orderBy: { createdAt: 'desc' },
})

Guest Checkout

Orders can be placed without a registered account. When userId is omitted the order is stored with the guest's email address only.

const session = await createStripeCheckoutSession({
  productId: product.id,
  guestEmail: 'guest@example.com',
  successUrl: '...',
  cancelUrl: '...',
})

Email Notifications

Payment events automatically trigger transactional emails (no configuration required):

EventEmail sent
Order completedPayment confirmation with order details
Order failedPayment failure notice with retry link
Order expiredExpiry notice with checkout link

See Email to customize the templates.

Best Practices

  1. Never expose secret keys — keep them in .env, never commit them
  2. Always verify webhook signaturesverifyStripeWebhook and verifyPayPalWebhook do this for you
  3. Validate amounts server-side — never trust the amount coming from the client
  4. Use idempotency keys — for retry-safe Stripe API calls
  5. Test all scenarios — success, decline, 3DS, subscription renewal, cancellation

Next Steps

  • Email — customize payment notification templates
  • Admin Panel — manage orders and providers from the UI
  • Database — Order and Product schema reference

Detailed Integration Guides

  • Stripe & PayPal Setup Guide — step-by-step walkthrough: API keys, webhook configuration, Stripe CLI local testing, PayPal sandbox, subscription plans, and production checklist
Payments | Tikship