feat: add customer cart with expiry, shipping addresses with USPS validation, and multi-item PayPal checkout
SonarQube Analysis / sonarqube (pull_request) Failing after 59s
Tests / backend-unit (pull_request) Successful in 34s
Tests / backend-integration (pull_request) Failing after 1m33s
Tests / frontend-e2e (pull_request) Failing after 1m5s

This commit is contained in:
2026-08-14 14:02:25 -05:00
parent 433d7e1db5
commit 9ab689e624
18 changed files with 1242 additions and 189 deletions
+97
View File
@@ -0,0 +1,97 @@
export interface CartItem {
item_id: number;
name: string;
price_cents: number;
status: string;
added_at: string;
expires_at: string;
images: { id: number; image_path: string }[];
}
export interface ShippingAddress {
id: number;
full_name: string;
address_line1: string;
address_line2: string | null;
city: string;
state: string;
postal_code: string;
country: string;
is_default: boolean;
usps_validated: boolean;
}
async function handle<T>(res: Response): Promise<T> {
if (!res.ok) {
const data = await res.json().catch(() => ({}));
throw new Error(data.error || 'Request failed');
}
return res.json();
}
export function fetchCart(): Promise<{ items: CartItem[] }> {
return fetch('/api/cart').then(res => handle(res));
}
export function addToCart(itemId: number): Promise<{ itemId: number; expiresAt: string }> {
return fetch(`/api/cart/items/${itemId}`, { method: 'POST' }).then(res => handle(res));
}
export function removeFromCart(itemId: number): Promise<void> {
return fetch(`/api/cart/items/${itemId}`, { method: 'DELETE' }).then(() => undefined);
}
export function fetchAddresses(): Promise<ShippingAddress[]> {
return fetch('/api/customers/me/addresses').then(res => handle(res));
}
export interface AddressInput {
fullName: string;
addressLine1: string;
addressLine2?: string;
city: string;
state: string;
postalCode: string;
country?: string;
isDefault?: boolean;
}
export function createAddress(input: AddressInput): Promise<{ address: ShippingAddress; uspsCheck: { validated: boolean; deliverable: boolean | null; reason?: string }; uspsConfigured: boolean }> {
return fetch('/api/customers/me/addresses', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input)
}).then(res => handle(res));
}
export function deleteAddress(id: number): Promise<void> {
return fetch(`/api/customers/me/addresses/${id}`, { method: 'DELETE' }).then(() => undefined);
}
export function setDefaultAddress(id: number): Promise<ShippingAddress> {
return fetch(`/api/customers/me/addresses/${id}/set-default`, { method: 'POST' }).then(res => handle(res));
}
export function createCartPaypalOrder(shippingAddressId: number): Promise<{ orderID: string }> {
return fetch('/api/checkout/cart/paypal/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ shippingAddressId })
}).then(res => handle(res));
}
export function captureCartPaypalOrder(orderID: string): Promise<void> {
return fetch('/api/checkout/cart/paypal/capture', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderID })
}).then(res => handle(res));
}
export function demoCartPurchase(shippingAddressId: number): Promise<void> {
return fetch('/api/checkout/cart/demo/purchase', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ shippingAddressId })
}).then(res => handle(res));
}