# Billing Plugin Demo Application This is a demo application showcasing the `@xtr-dev/payload-billing` plugin for PayloadCMS 3.x. ## Features - ๐Ÿงช **Test Payment Provider** with customizable scenarios - ๐Ÿ’ณ **Payment Management** with full CRUD operations - ๐Ÿงพ **Invoice Generation** with line items and tax calculation - ๐Ÿ”„ **Automatic Status Sync** - payments and invoices stay in sync automatically - ๐Ÿ”— **Bidirectional Relationships** - payment/invoice links maintained by plugin hooks - ๐ŸŽจ **Custom Payment UI** with modern design - ๐Ÿ“„ **Invoice View Page** - professional printable invoice layout - ๐Ÿ”ง **Collection Extensions** - demonstrates how to extend collections with custom fields and hooks - ๐Ÿ’ฌ **Custom Message Field** - shows hook-based data copying from payment to invoice - ๐Ÿ“Š **No Customer Collection Required** - uses direct customer info fields ## Getting Started ### Installation ```bash # Install dependencies pnpm install ``` ### Running the Demo ```bash # Start the development server pnpm dev # The application will be available at http://localhost:3000 ``` ### Default Credentials - **Email**: `dev@payloadcms.com` - **Password**: `test` ## Demo Routes ### Interactive Demo Page Visit [http://localhost:3000](http://localhost:3000) to access the interactive demo page where you can: - Create test payments - View the custom payment UI - Test different payment scenarios - Navigate to admin collections ### Custom Payment UI The custom test payment UI is available at: ``` http://localhost:3000/test-payment/{payment-id} ``` This page demonstrates: - Modern, responsive payment interface - Payment method selection - Test scenario selection (success, failure, cancellation, etc.) - Real-time payment status updates - Test mode indicators and warnings ### Invoice View Page View and print invoices at: ``` http://localhost:3000/invoice/{invoice-id} ``` This page demonstrates: - Professional printable invoice layout - Customer billing information - Line items table with quantities and amounts - Tax calculations and totals - Custom message field (populated from payment metadata) - Print-friendly styling ### Admin Routes - **Payments**: [http://localhost:3000/admin/collections/payments](http://localhost:3000/admin/collections/payments) - **Invoices**: [http://localhost:3000/admin/collections/invoices](http://localhost:3000/admin/collections/invoices) - **Refunds**: [http://localhost:3000/admin/collections/refunds](http://localhost:3000/admin/collections/refunds) - **Customers**: [http://localhost:3000/admin/collections/customers](http://localhost:3000/admin/collections/customers) ## Sample Data The application includes seed data with: - **2 Customers** - John Doe (Acme Corporation) - Jane Smith (Tech Innovations Inc.) - **2 Invoices** - Paid invoice with web development services - Open invoice with subscription and additional users - **4 Payments** - Successful payment linked to invoice - Pending payment linked to invoice - Standalone successful payment - Failed payment example - **1 Refund** - Partial refund on a successful payment To reset the sample data: ```bash # Delete the database file rm dev/payload.sqlite # Restart the server (will re-seed automatically) pnpm dev ``` ## Configuration The plugin is configured in `dev/payload.config.ts` with: ### Test Provider Setup ```typescript testProvider({ enabled: true, testModeIndicators: { showWarningBanners: true, showTestBadges: true, consoleWarnings: true }, customUiRoute: '/test-payment', }) ``` ### Collection Extension Options This demo showcases how to extend the plugin's collections with custom fields and hooks. The invoices collection is extended to include a `customMessage` field that is automatically populated from payment metadata: ```typescript collections: { payments: 'payments', invoices: { slug: 'invoices', extend: (config) => ({ ...config, fields: [ ...(config.fields || []), { name: 'customMessage', type: 'textarea', admin: { description: 'Custom message from the payment (auto-populated)', }, }, ], hooks: { ...config.hooks, beforeChange: [ ...(config.hooks?.beforeChange || []), async ({ data, req, operation }) => { if (operation === 'create' && data.payment) { const payment = await req.payload.findByID({ collection: 'payments', id: typeof data.payment === 'object' ? data.payment.id : data.payment, }) if ( payment?.metadata && typeof payment.metadata === 'object' && 'customMessage' in payment.metadata && payment.metadata.customMessage ) { data.customMessage = payment.metadata.customMessage as string } } return data }, ], }, }), }, refunds: 'refunds', } ``` ### Customer Relationship ```typescript customerRelationSlug: 'customers', customerInfoExtractor: (customer) => ({ name: customer.name, email: customer.email, phone: customer.phone, company: customer.company, taxId: customer.taxId, billingAddress: customer.address ? { line1: customer.address.line1, line2: customer.address.line2, city: customer.address.city, state: customer.address.state, postalCode: customer.address.postalCode, country: customer.address.country, } : undefined, }) ``` ## Test Payment Scenarios The test provider includes the following scenarios: 1. **Instant Success** - Payment succeeds immediately 2. **Delayed Success** - Payment succeeds after a delay (3s) 3. **Cancelled Payment** - User cancels the payment (1s) 4. **Declined Payment** - Payment is declined by the provider (2s) 5. **Expired Payment** - Payment expires before completion (5s) 6. **Pending Payment** - Payment remains in pending state (1.5s) ## Payment Methods The test provider supports these payment methods: - ๐Ÿฆ iDEAL - ๐Ÿ’ณ Credit Card - ๐Ÿ…ฟ๏ธ PayPal - ๐ŸŽ Apple Pay - ๐Ÿ›๏ธ Bank Transfer ## API Examples ### Creating a Payment (Local API) ```typescript import { getPayload } from 'payload' import configPromise from '@payload-config' const payload = await getPayload({ config: configPromise }) const payment = await payload.create({ collection: 'payments', data: { provider: 'test', amount: 2500, // $25.00 in cents currency: 'USD', description: 'Demo payment', status: 'pending', } }) // The payment will have a providerId that can be used in the custom UI console.log(`Payment URL: /test-payment/${payment.providerId}`) ``` ### Creating an Invoice with Customer ```typescript const invoice = await payload.create({ collection: 'invoices', data: { customer: 'customer-id-here', currency: 'USD', items: [ { description: 'Service', quantity: 1, unitAmount: 5000 // $50.00 } ], taxAmount: 500, // $5.00 status: 'open' } }) ``` ### REST API Example ```bash # Create a payment curl -X POST http://localhost:3000/api/payments \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{ "provider": "test", "amount": 2500, "currency": "USD", "description": "Demo payment", "status": "pending" }' ``` ## Custom Routes The demo includes custom API routes: ### Create Payment ``` POST /api/demo/create-payment ``` Request body: ```json { "amount": 2500, "currency": "USD", "description": "Demo payment", "message": "Custom message to include in the invoice (optional)" } ``` The `message` field will be stored in the payment's metadata and automatically copied to the invoice when it's created, thanks to the collection extension hook. Response: ```json { "success": true, "payment": { "id": "test_pay_1234567890_abc123", "paymentId": "67890", "amount": 2500, "currency": "USD", "description": "Demo payment" } } ``` ### Get Payment ``` GET /api/demo/payment/{payment-provider-id} ``` Fetches payment details including invoice relationship. Used by the payment success page to find the associated invoice. Response: ```json { "success": true, "payment": { "id": "67890", "providerId": "test_pay_1234567890_abc123", "amount": 2500, "currency": "USD", "status": "paid", "description": "Demo payment", "invoice": "invoice-id-here", "metadata": { "customMessage": "Your custom message" } } } ``` ### Get Invoice ``` GET /api/demo/invoice/{invoice-id} ``` Fetches complete invoice data including customer details, line items, and custom message. Used by the invoice view page. Response: ```json { "success": true, "invoice": { "id": "invoice-id", "invoiceNumber": "INV-2024-001", "customer": { "name": "John Doe", "email": "john@example.com", "company": "Acme Corp" }, "currency": "USD", "items": [ { "description": "Service", "quantity": 1, "unitAmount": 2500 } ], "subtotal": 2500, "taxAmount": 250, "total": 2750, "status": "paid", "customMessage": "Your custom message from payment" } } ``` ## Development ### File Structure ``` dev/ โ”œโ”€โ”€ app/ โ”‚ โ”œโ”€โ”€ page.tsx # Interactive demo page (root) โ”‚ โ”œโ”€โ”€ test-payment/ โ”‚ โ”‚ โ””โ”€โ”€ [id]/ โ”‚ โ”‚ โ””โ”€โ”€ page.tsx # Custom payment UI โ”‚ โ”œโ”€โ”€ invoice/ โ”‚ โ”‚ โ””โ”€โ”€ [id]/ โ”‚ โ”‚ โ””โ”€โ”€ page.tsx # Invoice view/print page โ”‚ โ”œโ”€โ”€ payment-success/ โ”‚ โ”‚ โ””โ”€โ”€ page.tsx # Payment success page โ”‚ โ”œโ”€โ”€ payment-failed/ โ”‚ โ”‚ โ””โ”€โ”€ page.tsx # Payment failed page โ”‚ โ”œโ”€โ”€ api/ โ”‚ โ”‚ โ””โ”€โ”€ demo/ โ”‚ โ”‚ โ”œโ”€โ”€ create-payment/ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ route.ts # Payment creation endpoint โ”‚ โ”‚ โ”œโ”€โ”€ invoice/ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [id]/ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ route.ts # Invoice fetch endpoint โ”‚ โ”‚ โ””โ”€โ”€ payment/ โ”‚ โ”‚ โ””โ”€โ”€ [id]/ โ”‚ โ”‚ โ””โ”€โ”€ route.ts # Payment fetch endpoint โ”‚ โ””โ”€โ”€ (payload)/ # PayloadCMS admin routes โ”œโ”€โ”€ helpers/ โ”‚ โ””โ”€โ”€ credentials.ts # Default user credentials โ”œโ”€โ”€ payload.config.ts # PayloadCMS configuration โ”œโ”€โ”€ seed.ts # Sample data seeding โ””โ”€โ”€ README.md # This file ``` ### Modifying the Demo To customize the demo: 1. **Add more test scenarios**: Edit the `testProvider` config in `payload.config.ts` 2. **Customize the payment UI**: Edit `app/test-payment/[id]/page.tsx` 3. **Add more sample data**: Edit `seed.ts` 4. **Add custom collections**: Add to `collections` array in `payload.config.ts` ### Testing Different Providers To test with real payment providers: ```typescript // Install the provider pnpm add stripe // or pnpm add @mollie/api-client // Update payload.config.ts import { stripeProvider, mollieProvider } from '../src/providers' billingPlugin({ providers: [ stripeProvider({ secretKey: process.env.STRIPE_SECRET_KEY!, webhookSecret: process.env.STRIPE_WEBHOOK_SECRET, }), mollieProvider({ apiKey: process.env.MOLLIE_API_KEY!, webhookUrl: process.env.MOLLIE_WEBHOOK_URL, }), // Keep test provider for development testProvider({ enabled: true }), ], // ... rest of config }) ``` ## Troubleshooting ### Database Issues If you encounter database errors: ```bash # Delete the database rm dev/payload.sqlite # Regenerate types pnpm dev:generate-types # Restart the server pnpm dev ``` ### Port Already in Use If port 3000 is already in use: ```bash # Use a different port PORT=3001 pnpm dev ``` ### TypeScript Errors Regenerate Payload types: ```bash pnpm dev:generate-types ``` ## Resources - [Plugin Documentation](../README.md) - [PayloadCMS Documentation](https://payloadcms.com/docs) - [GitHub Repository](https://github.com/xtr-dev/payload-billing) ## License MIT