Add Mollie checkout flow, billing UI, and payment UX polish #71
@@ -101,3 +101,19 @@ export interface Order {
|
|||||||
export async function getOrders(): Promise<Order[]> {
|
export async function getOrders(): Promise<Order[]> {
|
||||||
return apiRequest<Order[]>('/api/billing/invoices')
|
return apiRequest<Order[]>('/api/billing/invoices')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateEmbeddedCheckoutParams {
|
||||||
|
plan_id: string
|
||||||
|
interval: string
|
||||||
|
limit: number
|
||||||
|
country: string
|
||||||
|
vat_id?: string
|
||||||
|
card_token: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createEmbeddedCheckout(params: CreateEmbeddedCheckoutParams): Promise<{ status: 'success' | 'pending'; redirect_url?: string }> {
|
||||||
|
return apiRequest<{ status: 'success' | 'pending'; redirect_url?: string }>('/api/billing/checkout-embedded', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(params),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
69
lib/mollie.ts
Normal file
69
lib/mollie.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
// Mollie.js types (no official @types package)
|
||||||
|
export interface MollieInstance {
|
||||||
|
createComponent: (type: string, options?: { styles?: Record<string, unknown> }) => MollieComponent
|
||||||
|
createToken: () => Promise<{ token: string | null; error: MollieError | null }>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MollieComponent {
|
||||||
|
mount: (selector: string | HTMLElement) => void
|
||||||
|
unmount: () => void
|
||||||
|
addEventListener: (event: string, callback: (event: unknown) => void) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MollieError {
|
||||||
|
field: string
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
Mollie: (profileId: string, options?: { locale?: string; testmode?: boolean }) => MollieInstance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MOLLIE_PROFILE_ID = process.env.NEXT_PUBLIC_MOLLIE_PROFILE_ID || ''
|
||||||
|
|
||||||
|
// Mollie Components card field styles — matches Pulse dark theme
|
||||||
|
export const MOLLIE_FIELD_STYLES = {
|
||||||
|
base: {
|
||||||
|
color: '#ffffff',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
||||||
|
fontWeight: '400',
|
||||||
|
letterSpacing: '0.025em',
|
||||||
|
'::placeholder': {
|
||||||
|
color: '#737373',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
valid: {
|
||||||
|
color: '#ffffff',
|
||||||
|
},
|
||||||
|
invalid: {
|
||||||
|
color: '#ef4444',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
let mollieInstance: MollieInstance | null = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Mollie.js. Must be called after the Mollie script has loaded.
|
||||||
|
*/
|
||||||
|
export function initMollie(): MollieInstance | null {
|
||||||
|
if (mollieInstance) return mollieInstance
|
||||||
|
if (typeof window === 'undefined' || !window.Mollie || !MOLLIE_PROFILE_ID) return null
|
||||||
|
|
||||||
|
// Mollie auto-detects test/live mode based on the API key configured server-side.
|
||||||
|
mollieInstance = window.Mollie(MOLLIE_PROFILE_ID, {
|
||||||
|
locale: 'en_US',
|
||||||
|
})
|
||||||
|
return mollieInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current Mollie instance (must call initMollie first).
|
||||||
|
*/
|
||||||
|
export function getMollie(): MollieInstance | null {
|
||||||
|
return mollieInstance
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user