profit-planet-frontend/src/app/coffee-abonnements/summary/hooks/subscribeAbo.ts
2025-12-13 11:17:48 +01:00

139 lines
5.7 KiB
TypeScript

import { authFetch } from '../../../utils/authFetch'
export type SubscribeAboItem = { coffeeId: string | number; quantity?: number }
export type SubscribeAboInput = {
coffeeId?: string | number // optional when items provided
items?: SubscribeAboItem[] // NEW: whole order in one call
billing_interval?: string
interval_count?: number
is_auto_renew?: boolean
target_user_id?: number
recipient_name?: string
recipient_email?: string
recipient_notes?: string
// NEW: customer fields
firstName?: string
lastName?: string
email?: string
street?: string
postalCode?: string
city?: string
country?: string
frequency?: string
startDate?: string
// NEW: logged-in user id
referred_by?: number
}
type Abonement = any
type HistoryEvent = any
const apiBase = (process.env.NEXT_PUBLIC_API_BASE_URL || '').replace(/\/+$/, '')
const parseJson = async (res: Response) => {
const ct = res.headers.get('content-type') || ''
const isJson = ct.includes('application/json')
const json = isJson ? await res.json().catch(() => ({})) : null
return { json, isJson }
}
export async function subscribeAbo(input: SubscribeAboInput) {
const hasItems = Array.isArray(input.items) && input.items.length > 0
if (!hasItems && !input.coffeeId) throw new Error('coffeeId is required')
const hasRecipientFields = !!(input.recipient_name || input.recipient_email || input.recipient_notes)
if (hasRecipientFields && !input.recipient_name) {
throw new Error('recipient_name is required when gifting to a non-account recipient.')
}
// NEW: validate customer fields (required in UI)
const requiredFields = ['firstName','lastName','email','street','postalCode','city','country','frequency','startDate'] as const
const missing = requiredFields.filter(k => {
const v = (input as any)[k]
return typeof v !== 'string' || v.trim() === ''
})
if (missing.length) {
throw new Error(`Missing required fields: ${missing.join(', ')}`)
}
const body: any = {
billing_interval: input.billing_interval ?? 'month',
interval_count: input.interval_count ?? 1,
is_auto_renew: input.is_auto_renew ?? true,
// NEW: include customer fields
firstName: input.firstName,
lastName: input.lastName,
email: input.email,
street: input.street,
postalCode: input.postalCode,
city: input.city,
country: input.country?.toUpperCase?.() ?? input.country,
frequency: input.frequency,
startDate: input.startDate,
}
if (hasItems) {
body.items = input.items!.map(i => ({
coffeeId: i.coffeeId,
quantity: i.quantity != null ? i.quantity : 1,
}))
// NEW: enforce exactly 12 packs
const sumPacks = body.items.reduce((s: number, it: any) => s + Number(it.quantity || 0), 0)
if (sumPacks !== 12) {
console.warn('[subscribeAbo] Invalid pack total:', sumPacks, 'expected 12')
throw new Error('Order must contain exactly 12 packs (120 capsules).')
}
} else {
body.coffeeId = input.coffeeId
// single-item legacy path — backend expects bundle, prefer items usage
}
// NEW: always include available recipient fields and target_user_id when provided
if (input.target_user_id != null) body.target_user_id = input.target_user_id
if (input.recipient_name) body.recipient_name = input.recipient_name
if (input.recipient_email) body.recipient_email = input.recipient_email
if (input.recipient_notes) body.recipient_notes = input.recipient_notes
// NEW: always include referred_by if provided
if (input.referred_by != null) body.referred_by = input.referred_by
const url = `${apiBase}/api/abonements/subscribe`
console.info('[subscribeAbo] POST', url, { body })
// NEW: explicit JSON preview that matches the actual request body
console.info('[subscribeAbo] Body JSON:', JSON.stringify(body))
const res = await authFetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify(body),
credentials: 'include',
})
const { json } = await parseJson(res)
console.info('[subscribeAbo] Response', res.status, json)
if (!res.ok || !json?.success) throw new Error(json?.message || `Subscribe failed: ${res.status}`)
return json.data as Abonement
}
async function postAction(url: string) {
const res = await authFetch(url, { method: 'POST', headers: { Accept: 'application/json' }, credentials: 'include' })
const { json } = await parseJson(res)
if (!res.ok || !json?.success) throw new Error(json?.message || `Request failed: ${res.status}`)
return json.data as Abonement
}
export const pauseAbo = (id: string | number) => postAction(`${apiBase}/abonements/${id}/pause`)
export const resumeAbo = (id: string | number) => postAction(`${apiBase}/abonements/${id}/resume`)
export const cancelAbo = (id: string | number) => postAction(`${apiBase}/abonements/${id}/cancel`)
export const renewAbo = (id: string | number) => postAction(`${apiBase}/admin/abonements/${id}/renew`)
export async function getMyAbonements(status?: string) {
const qs = status ? `?status=${encodeURIComponent(status)}` : ''
const res = await authFetch(`${apiBase}/abonements/mine${qs}`, { method: 'GET', headers: { Accept: 'application/json' }, credentials: 'include' })
const { json } = await parseJson(res)
if (!res.ok || !json?.success) throw new Error(json?.message || `Fetch failed: ${res.status}`)
return (json.data || []) as Abonement[]
}
export async function getAboHistory(id: string | number) {
const res = await authFetch(`${apiBase}/abonements/${id}/history`, { method: 'GET', headers: { Accept: 'application/json' }, credentials: 'include' })
const { json } = await parseJson(res)
if (!res.ok || !json?.success) throw new Error(json?.message || `History failed: ${res.status}`)
return (json.data || []) as HistoryEvent[]
}