feat: add automatic payment/invoice status sync and invoice view page

Core Plugin Enhancements:
- Add afterChange hook to payments collection to auto-update linked invoice status to 'paid' when payment succeeds
- Add afterChange hook to invoices collection for bidirectional payment-invoice relationship management
- Add invoice status sync when manually marked as paid
- Update plugin config types to support collection extension options

Demo Application Features:
- Add professional invoice view page with print-friendly layout (/invoice/[id])
- Add custom message field to payment creation form
- Add invoice API endpoint to fetch complete invoice data with customer info
- Add payment API endpoint to retrieve payment with invoice relationship
- Update payment success page with "View Invoice" button
- Implement beforeChange hook to copy custom message from payment metadata to invoice
- Remove customer collection dependency - use direct customerInfo fields instead

Documentation:
- Update README with automatic status synchronization section
- Add collection extension examples to demo README
- Document new features: bidirectional relationships, status sync, invoice view

Technical Improvements:
- Fix total calculation in invoice API (use 'amount' field instead of 'total')
- Add proper TypeScript types with CollectionSlug casting
- Implement Next.js 15 async params pattern in API routes
- Add customer name/email/company fields to payment creation form

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-08 16:20:01 +01:00
parent f096b5f17f
commit 27da194942
16 changed files with 1092 additions and 139 deletions

View File

@@ -2,13 +2,31 @@
import Link from 'next/link'
import { useSearchParams } from 'next/navigation'
import { Suspense } from 'react'
import { Suspense, useEffect, useState } from 'react'
function PaymentSuccessContent() {
const searchParams = useSearchParams()
const paymentId = searchParams.get('paymentId')
const amount = searchParams.get('amount')
const currency = searchParams.get('currency')
const [invoiceId, setInvoiceId] = useState<string | null>(null)
useEffect(() => {
// Fetch the payment to get the invoice ID
if (paymentId) {
fetch(`/api/demo/payment/${paymentId}`)
.then((res) => res.json())
.then((data) => {
if (data.success && data.payment?.invoice) {
const invId = typeof data.payment.invoice === 'object' ? data.payment.invoice.id : data.payment.invoice
setInvoiceId(invId)
}
})
.catch((err) => {
console.error('Failed to fetch payment invoice:', err)
})
}
}, [paymentId])
return (
<div className="min-h-screen bg-gradient-to-br from-green-600 to-emerald-700 flex items-center justify-center p-4">
@@ -76,6 +94,35 @@ function PaymentSuccessContent() {
<h3 className="font-semibold text-slate-800 text-lg">What's Next?</h3>
<div className="grid gap-3">
{invoiceId && (
<Link
href={`/invoice/${invoiceId}`}
className="flex items-center justify-between p-4 border-2 border-green-500 bg-green-50 rounded-lg hover:bg-green-100 transition-all group cursor-pointer"
>
<div>
<div className="font-semibold text-green-800 group-hover:text-green-900">
📄 View Invoice
</div>
<div className="text-sm text-green-700">
See your invoice with custom message
</div>
</div>
<svg
className="w-5 h-5 text-green-600 group-hover:text-green-800"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</Link>
)}
<Link
href="/"
className="flex items-center justify-between p-4 border-2 border-slate-200 rounded-lg hover:border-green-500 hover:bg-green-50 transition-all group cursor-pointer"