139 lines
5.7 KiB
TypeScript
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[]
|
|
}
|