@instockng/api-client 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/enum-types.d.ts +8 -0
- package/dist/enum-types.js +5 -0
- package/dist/fetchers/carts.js +5 -0
- package/dist/hooks/admin/abandoned-carts.js +12 -8
- package/dist/hooks/admin/brands.js +15 -10
- package/dist/hooks/admin/customers.js +3 -2
- package/dist/hooks/admin/delivery-zones.js +24 -16
- package/dist/hooks/admin/discount-codes.js +24 -16
- package/dist/hooks/admin/inventory.js +15 -10
- package/dist/hooks/admin/orders.js +18 -12
- package/dist/hooks/admin/products.js +15 -10
- package/dist/hooks/admin/stats.js +3 -2
- package/dist/hooks/admin/variants.js +18 -12
- package/dist/hooks/admin/warehouses.js +15 -10
- package/dist/hooks/useApiConfig.d.ts +2 -1
- package/dist/hooks/useApiConfig.js +2 -2
- package/dist/provider.d.ts +7 -4
- package/dist/provider.js +5 -3
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/dist/apps/backend/src/generated/zod/index.d.ts +0 -1114
- package/dist/apps/backend/src/generated/zod/index.js +0 -670
- package/dist/apps/backend/src/http-app.d.ts +0 -40
- package/dist/apps/backend/src/http-app.js +0 -106
- package/dist/apps/backend/src/lib/brand-response.d.ts +0 -14
- package/dist/apps/backend/src/lib/brand-response.js +0 -8
- package/dist/apps/backend/src/lib/cart-helpers.d.ts +0 -280
- package/dist/apps/backend/src/lib/cart-helpers.js +0 -93
- package/dist/apps/backend/src/lib/cart-recovery.d.ts +0 -30
- package/dist/apps/backend/src/lib/cart-recovery.js +0 -147
- package/dist/apps/backend/src/lib/cart-response.d.ts +0 -121
- package/dist/apps/backend/src/lib/cart-response.js +0 -150
- package/dist/apps/backend/src/lib/clerk.d.ts +0 -18
- package/dist/apps/backend/src/lib/clerk.js +0 -167
- package/dist/apps/backend/src/lib/delivery-zone-response.d.ts +0 -62
- package/dist/apps/backend/src/lib/delivery-zone-response.js +0 -24
- package/dist/apps/backend/src/lib/discount-code-response.d.ts +0 -42
- package/dist/apps/backend/src/lib/discount-code-response.js +0 -19
- package/dist/apps/backend/src/lib/discount.d.ts +0 -20
- package/dist/apps/backend/src/lib/discount.js +0 -35
- package/dist/apps/backend/src/lib/inventory.d.ts +0 -26
- package/dist/apps/backend/src/lib/inventory.js +0 -160
- package/dist/apps/backend/src/lib/meta-capi.d.ts +0 -48
- package/dist/apps/backend/src/lib/meta-capi.js +0 -120
- package/dist/apps/backend/src/lib/openapi.d.ts +0 -36
- package/dist/apps/backend/src/lib/openapi.js +0 -69
- package/dist/apps/backend/src/lib/order-recovery.d.ts +0 -367
- package/dist/apps/backend/src/lib/order-recovery.js +0 -373
- package/dist/apps/backend/src/lib/order-response.d.ts +0 -136
- package/dist/apps/backend/src/lib/order-response.js +0 -61
- package/dist/apps/backend/src/lib/pricing.d.ts +0 -39
- package/dist/apps/backend/src/lib/pricing.js +0 -62
- package/dist/apps/backend/src/lib/prisma.d.ts +0 -9
- package/dist/apps/backend/src/lib/prisma.js +0 -30
- package/dist/apps/backend/src/lib/product-response.d.ts +0 -82
- package/dist/apps/backend/src/lib/product-response.js +0 -29
- package/dist/apps/backend/src/lib/utils.d.ts +0 -32
- package/dist/apps/backend/src/lib/utils.js +0 -63
- package/dist/apps/backend/src/middleware/clerk-auth.d.ts +0 -8
- package/dist/apps/backend/src/middleware/clerk-auth.js +0 -89
- package/dist/apps/backend/src/middleware/cors.d.ts +0 -8
- package/dist/apps/backend/src/middleware/cors.js +0 -11
- package/dist/apps/backend/src/notifications/producers/meta-capi-producer.d.ts +0 -55
- package/dist/apps/backend/src/notifications/producers/meta-capi-producer.js +0 -125
- package/dist/apps/backend/src/notifications/producers/order-notification.d.ts +0 -9
- package/dist/apps/backend/src/notifications/producers/order-notification.js +0 -18
- package/dist/apps/backend/src/notifications/producers/prospect-recovery-notification.d.ts +0 -10
- package/dist/apps/backend/src/notifications/producers/prospect-recovery-notification.js +0 -11
- package/dist/apps/backend/src/routes/admin/abandoned-carts.d.ts +0 -605
- package/dist/apps/backend/src/routes/admin/abandoned-carts.js +0 -194
- package/dist/apps/backend/src/routes/admin/brands.d.ts +0 -175
- package/dist/apps/backend/src/routes/admin/brands.js +0 -118
- package/dist/apps/backend/src/routes/admin/customers.d.ts +0 -306
- package/dist/apps/backend/src/routes/admin/customers.js +0 -39
- package/dist/apps/backend/src/routes/admin/delivery-zones.d.ts +0 -438
- package/dist/apps/backend/src/routes/admin/delivery-zones.js +0 -300
- package/dist/apps/backend/src/routes/admin/discount-codes.d.ts +0 -478
- package/dist/apps/backend/src/routes/admin/discount-codes.js +0 -418
- package/dist/apps/backend/src/routes/admin/inventory.d.ts +0 -273
- package/dist/apps/backend/src/routes/admin/inventory.js +0 -189
- package/dist/apps/backend/src/routes/admin/orders.d.ts +0 -1478
- package/dist/apps/backend/src/routes/admin/orders.js +0 -503
- package/dist/apps/backend/src/routes/admin/products.d.ts +0 -860
- package/dist/apps/backend/src/routes/admin/products.js +0 -107
- package/dist/apps/backend/src/routes/admin/stats.d.ts +0 -288
- package/dist/apps/backend/src/routes/admin/stats.js +0 -55
- package/dist/apps/backend/src/routes/admin/variants.d.ts +0 -239
- package/dist/apps/backend/src/routes/admin/variants.js +0 -173
- package/dist/apps/backend/src/routes/admin/warehouses.d.ts +0 -373
- package/dist/apps/backend/src/routes/admin/warehouses.js +0 -123
- package/dist/apps/backend/src/routes/public/brands.d.ts +0 -40
- package/dist/apps/backend/src/routes/public/brands.js +0 -38
- package/dist/apps/backend/src/routes/public/carts.d.ts +0 -2655
- package/dist/apps/backend/src/routes/public/carts.js +0 -631
- package/dist/apps/backend/src/routes/public/delivery-zones.d.ts +0 -35
- package/dist/apps/backend/src/routes/public/delivery-zones.js +0 -62
- package/dist/apps/backend/src/routes/public/orders.d.ts +0 -323
- package/dist/apps/backend/src/routes/public/orders.js +0 -160
- package/dist/apps/backend/src/routes/public/products.d.ts +0 -449
- package/dist/apps/backend/src/routes/public/products.js +0 -133
- package/dist/apps/backend/src/types/index.d.ts +0 -42
- package/dist/apps/backend/src/types/index.js +0 -2
- package/dist/apps/backend/src/validators/brand.d.ts +0 -17
- package/dist/apps/backend/src/validators/brand.js +0 -15
- package/dist/apps/backend/src/validators/delivery-zone.d.ts +0 -31
- package/dist/apps/backend/src/validators/delivery-zone.js +0 -51
- package/dist/apps/backend/src/validators/discount-code.d.ts +0 -74
- package/dist/apps/backend/src/validators/discount-code.js +0 -50
- package/dist/apps/backend/src/validators/inventory.d.ts +0 -20
- package/dist/apps/backend/src/validators/inventory.js +0 -15
- package/dist/apps/backend/src/validators/order.d.ts +0 -87
- package/dist/apps/backend/src/validators/order.js +0 -61
- package/dist/apps/backend/src/validators/product.d.ts +0 -18
- package/dist/apps/backend/src/validators/product.js +0 -19
- package/dist/apps/backend/src/validators/variant.d.ts +0 -19
- package/dist/apps/backend/src/validators/variant.js +0 -19
- package/dist/apps/backend/src/validators/warehouse.d.ts +0 -15
- package/dist/apps/backend/src/validators/warehouse.js +0 -15
- package/dist/packages/api-client/src/backend-types.d.ts +0 -10
- package/dist/packages/api-client/src/backend-types.js +0 -10
- package/dist/packages/api-client/src/client.d.ts +0 -20
- package/dist/packages/api-client/src/client.js +0 -40
- package/dist/packages/api-client/src/fetchers/brands.d.ts +0 -25
- package/dist/packages/api-client/src/fetchers/brands.js +0 -26
- package/dist/packages/api-client/src/fetchers/carts.d.ts +0 -2335
- package/dist/packages/api-client/src/fetchers/carts.js +0 -169
- package/dist/packages/api-client/src/fetchers/delivery-zones.d.ts +0 -28
- package/dist/packages/api-client/src/fetchers/delivery-zones.js +0 -26
- package/dist/packages/api-client/src/fetchers/index.d.ts +0 -22
- package/dist/packages/api-client/src/fetchers/index.js +0 -22
- package/dist/packages/api-client/src/fetchers/orders.d.ts +0 -283
- package/dist/packages/api-client/src/fetchers/orders.js +0 -44
- package/dist/packages/api-client/src/fetchers/products.d.ts +0 -386
- package/dist/packages/api-client/src/fetchers/products.js +0 -42
- package/dist/packages/api-client/src/hooks/admin/abandoned-carts.d.ts +0 -535
- package/dist/packages/api-client/src/hooks/admin/abandoned-carts.js +0 -79
- package/dist/packages/api-client/src/hooks/admin/brands.d.ts +0 -79
- package/dist/packages/api-client/src/hooks/admin/brands.js +0 -103
- package/dist/packages/api-client/src/hooks/admin/customers.d.ts +0 -278
- package/dist/packages/api-client/src/hooks/admin/customers.js +0 -25
- package/dist/packages/api-client/src/hooks/admin/delivery-zones.d.ts +0 -270
- package/dist/packages/api-client/src/hooks/admin/delivery-zones.js +0 -168
- package/dist/packages/api-client/src/hooks/admin/discount-codes.d.ts +0 -299
- package/dist/packages/api-client/src/hooks/admin/discount-codes.js +0 -157
- package/dist/packages/api-client/src/hooks/admin/index.d.ts +0 -16
- package/dist/packages/api-client/src/hooks/admin/index.js +0 -16
- package/dist/packages/api-client/src/hooks/admin/inventory.d.ts +0 -224
- package/dist/packages/api-client/src/hooks/admin/inventory.js +0 -102
- package/dist/packages/api-client/src/hooks/admin/orders.d.ts +0 -1380
- package/dist/packages/api-client/src/hooks/admin/orders.js +0 -169
- package/dist/packages/api-client/src/hooks/admin/products.d.ts +0 -374
- package/dist/packages/api-client/src/hooks/admin/products.js +0 -84
- package/dist/packages/api-client/src/hooks/admin/stats.d.ts +0 -277
- package/dist/packages/api-client/src/hooks/admin/stats.js +0 -24
- package/dist/packages/api-client/src/hooks/admin/variants.d.ts +0 -115
- package/dist/packages/api-client/src/hooks/admin/variants.js +0 -121
- package/dist/packages/api-client/src/hooks/admin/warehouses.d.ts +0 -277
- package/dist/packages/api-client/src/hooks/admin/warehouses.js +0 -103
- package/dist/packages/api-client/src/hooks/public/brands.d.ts +0 -33
- package/dist/packages/api-client/src/hooks/public/brands.js +0 -30
- package/dist/packages/api-client/src/hooks/public/carts.d.ts +0 -2405
- package/dist/packages/api-client/src/hooks/public/carts.js +0 -213
- package/dist/packages/api-client/src/hooks/public/delivery-zones.d.ts +0 -34
- package/dist/packages/api-client/src/hooks/public/delivery-zones.js +0 -28
- package/dist/packages/api-client/src/hooks/public/index.d.ts +0 -10
- package/dist/packages/api-client/src/hooks/public/index.js +0 -10
- package/dist/packages/api-client/src/hooks/public/orders.d.ts +0 -302
- package/dist/packages/api-client/src/hooks/public/orders.js +0 -50
- package/dist/packages/api-client/src/hooks/public/products.d.ts +0 -398
- package/dist/packages/api-client/src/hooks/public/products.js +0 -47
- package/dist/packages/api-client/src/hooks/use-query-unwrapped.d.ts +0 -20
- package/dist/packages/api-client/src/hooks/use-query-unwrapped.js +0 -22
- package/dist/packages/api-client/src/hooks/useApiConfig.d.ts +0 -11
- package/dist/packages/api-client/src/hooks/useApiConfig.js +0 -14
- package/dist/packages/api-client/src/index.d.ts +0 -20
- package/dist/packages/api-client/src/index.js +0 -25
- package/dist/packages/api-client/src/provider.d.ts +0 -33
- package/dist/packages/api-client/src/provider.js +0 -52
- package/dist/packages/api-client/src/rpc-client.d.ts +0 -9035
- package/dist/packages/api-client/src/rpc-client.js +0 -78
- package/dist/packages/api-client/src/rpc-types.d.ts +0 -76
- package/dist/packages/api-client/src/rpc-types.js +0 -7
- package/dist/packages/api-client/src/types.d.ts +0 -33
- package/dist/packages/api-client/src/types.js +0 -16
- package/dist/packages/api-client/src/utils/query-keys.d.ts +0 -106
- package/dist/packages/api-client/src/utils/query-keys.js +0 -108
|
@@ -1,631 +0,0 @@
|
|
|
1
|
-
import { Hono } from 'hono';
|
|
2
|
-
import { zValidator } from '@hono/zod-validator';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { getPrismaClient } from '../../lib/prisma';
|
|
5
|
-
import { calculateCartCost } from '../../lib/pricing';
|
|
6
|
-
import { buildCartResponseWithPricing } from '../../lib/cart-response';
|
|
7
|
-
import { toPricingItems } from '../../lib/cart-response';
|
|
8
|
-
import { validateCart, buildCartResponse, etagFrom, paymentFromZone, CART_INCLUDE_FULL, CART_ITEM_WITH_CART_INCLUDE, ORDER_INCLUDE_FULL, } from '../../lib/cart-helpers';
|
|
9
|
-
import { round, toNumber } from '../../lib/utils';
|
|
10
|
-
import { formatOrderResponse } from '../../lib/order-response';
|
|
11
|
-
const app = new Hono()
|
|
12
|
-
// CREATE CART
|
|
13
|
-
.post('/', zValidator('json', z.object({
|
|
14
|
-
brandSlug: z.string(),
|
|
15
|
-
})), async (c) => {
|
|
16
|
-
try {
|
|
17
|
-
const input = c.req.valid('json');
|
|
18
|
-
const prisma = getPrismaClient(c.env.DATABASE_URL);
|
|
19
|
-
// Look up brand by slug
|
|
20
|
-
const brand = await prisma.brand.findFirst({
|
|
21
|
-
where: { slug: input.brandSlug, deletedAt: null },
|
|
22
|
-
});
|
|
23
|
-
if (!brand) {
|
|
24
|
-
return c.json({ error: { code: 'BRAND_NOT_FOUND', message: 'Brand not found' } }, 404);
|
|
25
|
-
}
|
|
26
|
-
// Create cart that expires in 7 days
|
|
27
|
-
const cart = await prisma.cart.create({
|
|
28
|
-
data: {
|
|
29
|
-
brandId: brand.id,
|
|
30
|
-
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
31
|
-
availablePaymentMethods: ['cod', 'online'],
|
|
32
|
-
},
|
|
33
|
-
include: CART_INCLUDE_FULL,
|
|
34
|
-
});
|
|
35
|
-
const responseCart = await buildCartResponseWithPricing(prisma, cart);
|
|
36
|
-
return c.json(responseCart, 201);
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
console.error('Error creating cart:', error);
|
|
40
|
-
return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
|
|
41
|
-
}
|
|
42
|
-
})
|
|
43
|
-
// GET CART
|
|
44
|
-
.get('/:id', zValidator('param', z.object({ id: z.string().uuid() })), async (c) => {
|
|
45
|
-
try {
|
|
46
|
-
const { id: cartId } = c.req.valid('param');
|
|
47
|
-
const prisma = getPrismaClient(c.env.DATABASE_URL);
|
|
48
|
-
// Validate cart exists and is not expired
|
|
49
|
-
const { cart, error } = await validateCart(prisma, cartId);
|
|
50
|
-
if (error) {
|
|
51
|
-
return c.json({ error: { code: error.code, message: error.message } }, error.status);
|
|
52
|
-
}
|
|
53
|
-
const responseCart = await buildCartResponse(prisma, cart);
|
|
54
|
-
c.header('ETag', etagFrom(cart.updatedAt));
|
|
55
|
-
return c.json(responseCart, 200);
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
console.error('Error fetching cart:', error);
|
|
59
|
-
return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
// UPDATE CART
|
|
63
|
-
.patch('/:id', zValidator('param', z.object({ id: z.string().uuid() })), zValidator('json', z.object({
|
|
64
|
-
customerPhone: z.string().optional().nullable(),
|
|
65
|
-
customerEmail: z.string().email().optional().nullable(),
|
|
66
|
-
customerFirstName: z.string().optional().nullable(),
|
|
67
|
-
customerLastName: z.string().optional().nullable(),
|
|
68
|
-
deliveryZoneId: z.string().uuid().optional().nullable(),
|
|
69
|
-
ifUnmodifiedSince: z.string().datetime().optional(),
|
|
70
|
-
})), async (c) => {
|
|
71
|
-
try {
|
|
72
|
-
const { id: cartId } = c.req.valid('param');
|
|
73
|
-
const input = c.req.valid('json');
|
|
74
|
-
const prisma = getPrismaClient(c.env.DATABASE_URL);
|
|
75
|
-
// Validate cart exists and is not expired
|
|
76
|
-
const { cart, error } = await validateCart(prisma, cartId);
|
|
77
|
-
if (error) {
|
|
78
|
-
return c.json({ error: { code: error.code, message: error.message } }, error.status);
|
|
79
|
-
}
|
|
80
|
-
// Handle concurrency check
|
|
81
|
-
const ifMatch = c.req.header('if-match');
|
|
82
|
-
const bodyIfUnmodified = input.ifUnmodifiedSince ? new Date(input.ifUnmodifiedSince) : undefined;
|
|
83
|
-
if (ifMatch && ifMatch !== etagFrom(cart.updatedAt)) {
|
|
84
|
-
return c.json({ error: { code: 'PRECONDITION_FAILED', message: 'Cart has changed' } }, 412);
|
|
85
|
-
}
|
|
86
|
-
if (bodyIfUnmodified && cart.updatedAt.getTime() !== bodyIfUnmodified.getTime()) {
|
|
87
|
-
return c.json({ error: { code: 'PRECONDITION_FAILED', message: 'Cart has changed' } }, 412);
|
|
88
|
-
}
|
|
89
|
-
// Build update data
|
|
90
|
-
const updateData = {
|
|
91
|
-
customerPhone: input.customerPhone,
|
|
92
|
-
customerEmail: input.customerEmail,
|
|
93
|
-
customerFirstName: input.customerFirstName,
|
|
94
|
-
customerLastName: input.customerLastName,
|
|
95
|
-
};
|
|
96
|
-
// Handle delivery zone change
|
|
97
|
-
if (input.deliveryZoneId !== undefined) {
|
|
98
|
-
if (!input.deliveryZoneId) {
|
|
99
|
-
// Unsetting zone
|
|
100
|
-
updateData.deliveryZoneId = null;
|
|
101
|
-
updateData.deliveryZoneLockedAt = null;
|
|
102
|
-
updateData.availablePaymentMethods = ['cod', 'online'];
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
// Setting zone
|
|
106
|
-
const zone = await prisma.deliveryZone.findFirst({
|
|
107
|
-
where: { id: input.deliveryZoneId, isActive: true, deletedAt: null },
|
|
108
|
-
});
|
|
109
|
-
if (!zone) {
|
|
110
|
-
return c.json({ error: { code: 'INVALID_DELIVERY_ZONE', message: 'Zone not available' } }, 400);
|
|
111
|
-
}
|
|
112
|
-
updateData.deliveryZoneId = zone.id;
|
|
113
|
-
updateData.deliveryZoneLockedAt = new Date();
|
|
114
|
-
updateData.availablePaymentMethods = paymentFromZone(zone);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const updatedCart = await prisma.cart.update({
|
|
118
|
-
where: { id: cartId },
|
|
119
|
-
data: updateData,
|
|
120
|
-
include: CART_INCLUDE_FULL,
|
|
121
|
-
});
|
|
122
|
-
const responseCart = await buildCartResponse(prisma, updatedCart);
|
|
123
|
-
c.header('ETag', etagFrom(updatedCart.updatedAt));
|
|
124
|
-
return c.json(responseCart, 200);
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
console.error('Error updating cart:', error);
|
|
128
|
-
return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
// APPLY DISCOUNT CODE
|
|
132
|
-
.post('/:id/apply-discount', zValidator('param', z.object({ id: z.string().uuid() })), zValidator('json', z.object({ code: z.string() })), async (c) => {
|
|
133
|
-
try {
|
|
134
|
-
const { id: cartId } = c.req.valid('param');
|
|
135
|
-
const { code } = c.req.valid('json');
|
|
136
|
-
const prisma = getPrismaClient(c.env.DATABASE_URL);
|
|
137
|
-
// Validate cart exists and is not expired
|
|
138
|
-
const { cart, error } = await validateCart(prisma, cartId);
|
|
139
|
-
if (error) {
|
|
140
|
-
return c.json({ error: { code: error.code, message: error.message } }, error.status);
|
|
141
|
-
}
|
|
142
|
-
const now = new Date();
|
|
143
|
-
const discount = await prisma.discountCode.findFirst({
|
|
144
|
-
where: {
|
|
145
|
-
code,
|
|
146
|
-
deletedAt: null,
|
|
147
|
-
isActive: true,
|
|
148
|
-
validFrom: { lte: now },
|
|
149
|
-
AND: [
|
|
150
|
-
{ OR: [{ validUntil: null }, { validUntil: { gt: now } }] },
|
|
151
|
-
{ OR: [{ brandId: null }, { brandId: cart.brandId }] },
|
|
152
|
-
],
|
|
153
|
-
},
|
|
154
|
-
});
|
|
155
|
-
if (!discount) {
|
|
156
|
-
return c.json({ error: { code: 'INVALID_DISCOUNT_CODE', message: 'Invalid or expired discount code' } }, 400);
|
|
157
|
-
}
|
|
158
|
-
// Check minimum purchase
|
|
159
|
-
const pricing = calculateCartCost(toPricingItems(cart));
|
|
160
|
-
if (discount.minPurchase) {
|
|
161
|
-
const minPurchase = toNumber(discount.minPurchase);
|
|
162
|
-
if (pricing.subtotal < minPurchase) {
|
|
163
|
-
return c.json({ error: { code: 'MIN_PURCHASE_NOT_MET', message: `Minimum purchase of ₦${minPurchase.toLocaleString('en-NG', { minimumFractionDigits: 2 })} required` } }, 400);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
// Attach discount to cart
|
|
167
|
-
const updated = await prisma.cart.update({
|
|
168
|
-
where: { id: cartId },
|
|
169
|
-
data: { appliedDiscountCodeId: discount.id },
|
|
170
|
-
include: CART_INCLUDE_FULL,
|
|
171
|
-
});
|
|
172
|
-
const responseCart = await buildCartResponse(prisma, updated);
|
|
173
|
-
return c.json(responseCart, 200);
|
|
174
|
-
}
|
|
175
|
-
catch (error) {
|
|
176
|
-
console.error('Error applying discount to cart:', error);
|
|
177
|
-
return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
|
|
178
|
-
}
|
|
179
|
-
})
|
|
180
|
-
// REMOVE DISCOUNT CODE
|
|
181
|
-
.post('/:id/remove-discount', zValidator('param', z.object({ id: z.string().uuid() })), async (c) => {
|
|
182
|
-
try {
|
|
183
|
-
const { id: cartId } = c.req.valid('param');
|
|
184
|
-
const prisma = getPrismaClient(c.env.DATABASE_URL);
|
|
185
|
-
// Validate cart exists and is not expired
|
|
186
|
-
const { error } = await validateCart(prisma, cartId);
|
|
187
|
-
if (error) {
|
|
188
|
-
return c.json({ error: { code: error.code, message: error.message } }, error.status);
|
|
189
|
-
}
|
|
190
|
-
const updated = await prisma.cart.update({
|
|
191
|
-
where: { id: cartId },
|
|
192
|
-
data: { appliedDiscountCodeId: null },
|
|
193
|
-
include: CART_INCLUDE_FULL,
|
|
194
|
-
});
|
|
195
|
-
const responseCart = await buildCartResponse(prisma, updated);
|
|
196
|
-
return c.json(responseCart, 200);
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
199
|
-
console.error('Error removing discount from cart:', error);
|
|
200
|
-
return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
|
|
201
|
-
}
|
|
202
|
-
})
|
|
203
|
-
// ADD CART ITEM
|
|
204
|
-
.post('/:id/items', zValidator('param', z.object({ id: z.string().uuid() })), zValidator('json', z.object({
|
|
205
|
-
sku: z.string(),
|
|
206
|
-
quantity: z.number().int().positive(),
|
|
207
|
-
fbc: z.string().optional(), // Facebook Click ID for attribution
|
|
208
|
-
fbp: z.string().optional(), // Facebook Browser ID for attribution
|
|
209
|
-
})), async (c) => {
|
|
210
|
-
try {
|
|
211
|
-
const { id: cartId } = c.req.valid('param');
|
|
212
|
-
const input = c.req.valid('json');
|
|
213
|
-
const prisma = getPrismaClient(c.env.DATABASE_URL);
|
|
214
|
-
// Validate cart exists and is not expired
|
|
215
|
-
const { cart, error } = await validateCart(prisma, cartId);
|
|
216
|
-
if (error) {
|
|
217
|
-
return c.json({ error: { code: error.code, message: error.message } }, error.status);
|
|
218
|
-
}
|
|
219
|
-
// Find variant by SKU for this brand
|
|
220
|
-
const variant = await prisma.productVariant.findFirst({
|
|
221
|
-
where: {
|
|
222
|
-
sku: input.sku,
|
|
223
|
-
product: { brandId: cart.brandId },
|
|
224
|
-
isActive: true,
|
|
225
|
-
deletedAt: null,
|
|
226
|
-
},
|
|
227
|
-
include: {
|
|
228
|
-
product: true,
|
|
229
|
-
},
|
|
230
|
-
});
|
|
231
|
-
if (!variant) {
|
|
232
|
-
return c.json({ error: { code: 'VARIANT_NOT_FOUND', message: 'Product variant not found' } }, 404);
|
|
233
|
-
}
|
|
234
|
-
// Upsert cart item (add new or update quantity)
|
|
235
|
-
const existingItem = await prisma.cartItem.findUnique({
|
|
236
|
-
where: {
|
|
237
|
-
cartId_variantId: {
|
|
238
|
-
cartId,
|
|
239
|
-
variantId: variant.id,
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
});
|
|
243
|
-
let cartItem;
|
|
244
|
-
if (existingItem) {
|
|
245
|
-
// Update quantity
|
|
246
|
-
cartItem = await prisma.cartItem.update({
|
|
247
|
-
where: { id: existingItem.id },
|
|
248
|
-
data: { quantity: existingItem.quantity + input.quantity },
|
|
249
|
-
include: {
|
|
250
|
-
variant: {
|
|
251
|
-
include: {
|
|
252
|
-
product: true,
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
// Create new item
|
|
260
|
-
cartItem = await prisma.cartItem.create({
|
|
261
|
-
data: {
|
|
262
|
-
cartId,
|
|
263
|
-
variantId: variant.id,
|
|
264
|
-
quantity: input.quantity,
|
|
265
|
-
},
|
|
266
|
-
include: {
|
|
267
|
-
variant: {
|
|
268
|
-
include: {
|
|
269
|
-
product: true,
|
|
270
|
-
},
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
// Fetch updated cart for pricing
|
|
276
|
-
const updatedCart = await prisma.cart.findUnique({
|
|
277
|
-
where: { id: cartId },
|
|
278
|
-
include: CART_INCLUDE_FULL,
|
|
279
|
-
});
|
|
280
|
-
// Queue Meta CAPI AddToCart event
|
|
281
|
-
try {
|
|
282
|
-
const { enqueueAddToCartEvent } = await import('../../notifications/producers/meta-capi-producer');
|
|
283
|
-
await enqueueAddToCartEvent(c.env, {
|
|
284
|
-
cartId,
|
|
285
|
-
itemId: cartItem.id,
|
|
286
|
-
pixelId: updatedCart.brand.metaPixelId,
|
|
287
|
-
brandSiteUrl: updatedCart.brand.siteUrl,
|
|
288
|
-
productName: variant.product.name,
|
|
289
|
-
productSlug: variant.product.slug,
|
|
290
|
-
sku: variant.sku,
|
|
291
|
-
price: Number(variant.price),
|
|
292
|
-
quantity: input.quantity,
|
|
293
|
-
customerEmail: updatedCart.customerEmail,
|
|
294
|
-
customerPhone: updatedCart.customerPhone,
|
|
295
|
-
clientIpAddress: c.req.header('cf-connecting-ip'),
|
|
296
|
-
clientUserAgent: c.req.header('user-agent'),
|
|
297
|
-
fbc: input.fbc,
|
|
298
|
-
fbp: input.fbp,
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
catch (error) {
|
|
302
|
-
console.error('Failed to queue Meta CAPI AddToCart event:', error);
|
|
303
|
-
// Don't fail the request if CAPI queuing fails
|
|
304
|
-
}
|
|
305
|
-
const responseCart = await buildCartResponse(prisma, updatedCart);
|
|
306
|
-
return c.json(responseCart, 200);
|
|
307
|
-
}
|
|
308
|
-
catch (error) {
|
|
309
|
-
console.error('Error adding cart item:', error);
|
|
310
|
-
return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
|
|
311
|
-
}
|
|
312
|
-
})
|
|
313
|
-
// UPDATE CART ITEM
|
|
314
|
-
.patch('/:id/items/:itemId', zValidator('param', z.object({
|
|
315
|
-
id: z.string().uuid(),
|
|
316
|
-
itemId: z.string().uuid(),
|
|
317
|
-
})), zValidator('json', z.object({
|
|
318
|
-
quantity: z.number().int().positive(),
|
|
319
|
-
})), async (c) => {
|
|
320
|
-
try {
|
|
321
|
-
const { id: cartId, itemId } = c.req.valid('param');
|
|
322
|
-
const input = c.req.valid('json');
|
|
323
|
-
const prisma = getPrismaClient(c.env.DATABASE_URL);
|
|
324
|
-
const cartItem = await prisma.cartItem.findUnique({
|
|
325
|
-
where: { id: itemId },
|
|
326
|
-
include: CART_ITEM_WITH_CART_INCLUDE,
|
|
327
|
-
});
|
|
328
|
-
if (!cartItem || cartItem.cartId !== cartId) {
|
|
329
|
-
return c.json({ error: { code: 'CART_ITEM_NOT_FOUND', message: 'Cart item not found' } }, 404);
|
|
330
|
-
}
|
|
331
|
-
if (cartItem.cart.expiresAt < new Date()) {
|
|
332
|
-
return c.json({ error: { code: 'CART_EXPIRED', message: 'Cart has expired' } }, 410);
|
|
333
|
-
}
|
|
334
|
-
// Update quantity
|
|
335
|
-
await prisma.cartItem.update({
|
|
336
|
-
where: { id: itemId },
|
|
337
|
-
data: { quantity: input.quantity },
|
|
338
|
-
});
|
|
339
|
-
// Fetch updated cart for pricing
|
|
340
|
-
const updatedCart = await prisma.cart.findUnique({
|
|
341
|
-
where: { id: cartId },
|
|
342
|
-
include: CART_INCLUDE_FULL,
|
|
343
|
-
});
|
|
344
|
-
const responseCart = await buildCartResponse(prisma, updatedCart);
|
|
345
|
-
return c.json(responseCart, 200);
|
|
346
|
-
}
|
|
347
|
-
catch (error) {
|
|
348
|
-
console.error('Error updating cart item:', error);
|
|
349
|
-
return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
|
|
350
|
-
}
|
|
351
|
-
})
|
|
352
|
-
// DELETE CART ITEM
|
|
353
|
-
.delete('/:id/items/:itemId', zValidator('param', z.object({
|
|
354
|
-
id: z.string().uuid(),
|
|
355
|
-
itemId: z.string().uuid(),
|
|
356
|
-
})), async (c) => {
|
|
357
|
-
try {
|
|
358
|
-
const { id: cartId, itemId } = c.req.valid('param');
|
|
359
|
-
const prisma = getPrismaClient(c.env.DATABASE_URL);
|
|
360
|
-
const cartItem = await prisma.cartItem.findUnique({
|
|
361
|
-
where: { id: itemId },
|
|
362
|
-
include: CART_ITEM_WITH_CART_INCLUDE,
|
|
363
|
-
});
|
|
364
|
-
if (!cartItem || cartItem.cartId !== cartId) {
|
|
365
|
-
return c.json({ error: { code: 'CART_ITEM_NOT_FOUND', message: 'Cart item not found' } }, 404);
|
|
366
|
-
}
|
|
367
|
-
if (cartItem.cart.expiresAt < new Date()) {
|
|
368
|
-
return c.json({ error: { code: 'CART_EXPIRED', message: 'Cart has expired' } }, 410);
|
|
369
|
-
}
|
|
370
|
-
// Delete item
|
|
371
|
-
await prisma.cartItem.delete({
|
|
372
|
-
where: { id: itemId },
|
|
373
|
-
});
|
|
374
|
-
// Fetch updated cart for pricing
|
|
375
|
-
const updatedCart = await prisma.cart.findUnique({
|
|
376
|
-
where: { id: cartId },
|
|
377
|
-
include: CART_INCLUDE_FULL,
|
|
378
|
-
});
|
|
379
|
-
if (!updatedCart) {
|
|
380
|
-
return c.json({ error: { code: 'CART_NOT_FOUND', message: 'Cart not found' } }, 404);
|
|
381
|
-
}
|
|
382
|
-
const responseCart = await buildCartResponse(prisma, updatedCart);
|
|
383
|
-
return c.json(responseCart, 200);
|
|
384
|
-
}
|
|
385
|
-
catch (error) {
|
|
386
|
-
console.error('Error removing cart item:', error);
|
|
387
|
-
return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
|
|
388
|
-
}
|
|
389
|
-
})
|
|
390
|
-
// CHECKOUT CART
|
|
391
|
-
.post('/:id/checkout', zValidator('param', z.object({ id: z.uuid() })), zValidator('json', z.object({
|
|
392
|
-
firstName: z.string().min(1),
|
|
393
|
-
lastName: z.string().min(1),
|
|
394
|
-
email: z.email().optional(),
|
|
395
|
-
phone: z.string().optional(),
|
|
396
|
-
address: z.string().min(1),
|
|
397
|
-
city: z.string().min(1),
|
|
398
|
-
deliveryZoneId: z.uuid(),
|
|
399
|
-
paymentMethod: z.enum(['cod', 'online']),
|
|
400
|
-
paystackReference: z.string().optional(),
|
|
401
|
-
ifUnmodifiedSince: z.string().datetime().optional(),
|
|
402
|
-
fbc: z.string().optional(), // Facebook Click ID for attribution
|
|
403
|
-
fbp: z.string().optional(), // Facebook Browser ID for attribution
|
|
404
|
-
})), async (c) => {
|
|
405
|
-
try {
|
|
406
|
-
const { id: cartId } = c.req.valid('param');
|
|
407
|
-
const input = c.req.valid('json');
|
|
408
|
-
const prisma = getPrismaClient(c.env.DATABASE_URL);
|
|
409
|
-
const cart = await prisma.cart.findUnique({
|
|
410
|
-
where: { id: cartId },
|
|
411
|
-
include: CART_INCLUDE_FULL,
|
|
412
|
-
});
|
|
413
|
-
if (!cart) {
|
|
414
|
-
return c.json({ error: { code: 'CART_NOT_FOUND', message: 'Cart not found' } }, 404);
|
|
415
|
-
}
|
|
416
|
-
if (cart.expiresAt < new Date()) {
|
|
417
|
-
return c.json({ error: { code: 'CART_EXPIRED', message: 'Cart has expired' } }, 410);
|
|
418
|
-
}
|
|
419
|
-
if (cart.items.length === 0) {
|
|
420
|
-
return c.json({ error: { code: 'CART_EMPTY', message: 'Cart is empty' } }, 400);
|
|
421
|
-
}
|
|
422
|
-
// Queue Meta CAPI InitiateCheckout event
|
|
423
|
-
try {
|
|
424
|
-
const { enqueueInitiateCheckoutEvent } = await import('../../notifications/producers/meta-capi-producer');
|
|
425
|
-
const pricing = calculateCartCost(toPricingItems(cart));
|
|
426
|
-
const itemCount = cart.items.reduce((sum, item) => sum + item.quantity, 0);
|
|
427
|
-
await enqueueInitiateCheckoutEvent(c.env, {
|
|
428
|
-
cartId,
|
|
429
|
-
pixelId: cart.brand.metaPixelId,
|
|
430
|
-
brandSiteUrl: cart.brand.siteUrl,
|
|
431
|
-
cartTotal: pricing.total,
|
|
432
|
-
itemCount,
|
|
433
|
-
customerEmail: input.email || cart.customerEmail,
|
|
434
|
-
customerPhone: input.phone || cart.customerPhone,
|
|
435
|
-
clientIpAddress: c.req.header('cf-connecting-ip'),
|
|
436
|
-
clientUserAgent: c.req.header('user-agent'),
|
|
437
|
-
fbc: input.fbc,
|
|
438
|
-
fbp: input.fbp,
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
catch (error) {
|
|
442
|
-
console.error('Failed to queue Meta CAPI InitiateCheckout event:', error);
|
|
443
|
-
// Don't fail the checkout if CAPI queuing fails
|
|
444
|
-
}
|
|
445
|
-
// Concurrency check
|
|
446
|
-
const ifMatch = c.req.header('if-match');
|
|
447
|
-
if (ifMatch && ifMatch !== etagFrom(cart.updatedAt)) {
|
|
448
|
-
return c.json({ error: { code: 'PRECONDITION_FAILED', message: 'Cart has changed' } }, 412);
|
|
449
|
-
}
|
|
450
|
-
if (input.ifUnmodifiedSince && new Date(input.ifUnmodifiedSince).getTime() !== cart.updatedAt.getTime()) {
|
|
451
|
-
return c.json({ error: { code: 'PRECONDITION_FAILED', message: 'Cart has changed' } }, 412);
|
|
452
|
-
}
|
|
453
|
-
// Validate and load delivery zone
|
|
454
|
-
if (!input.deliveryZoneId) {
|
|
455
|
-
return c.json({ error: { code: 'MISSING_DELIVERY_ZONE', message: 'Select a delivery zone' } }, 400);
|
|
456
|
-
}
|
|
457
|
-
const zone = await prisma.deliveryZone.findFirst({
|
|
458
|
-
where: { id: input.deliveryZoneId, isActive: true, deletedAt: null },
|
|
459
|
-
include: { state: true },
|
|
460
|
-
});
|
|
461
|
-
if (!zone) {
|
|
462
|
-
return c.json({ error: { code: 'INVALID_DELIVERY_ZONE', message: 'Zone not available' } }, 400);
|
|
463
|
-
}
|
|
464
|
-
// Validate payment method against cart's available methods
|
|
465
|
-
if (!cart.availablePaymentMethods.includes(input.paymentMethod)) {
|
|
466
|
-
return c.json({ error: { code: 'PAYMENT_METHOD_NOT_AVAILABLE', message: 'Payment not available for selection' } }, 400);
|
|
467
|
-
}
|
|
468
|
-
// Calculate pricing with quantity discounts
|
|
469
|
-
const pricing = calculateCartCost(toPricingItems(cart));
|
|
470
|
-
// Compute delivery charge with free shipping threshold
|
|
471
|
-
const threshold = zone.freeShippingThreshold ? Number(zone.freeShippingThreshold) : null;
|
|
472
|
-
const base = Number(zone.deliveryCost);
|
|
473
|
-
const deliveryCharge = threshold !== null && pricing.subtotal >= threshold ? 0 : base;
|
|
474
|
-
const estimatedDays = zone.estimatedDays ?? null;
|
|
475
|
-
// Use the discount code already applied to the cart (if any)
|
|
476
|
-
let discountCodeRecord = null;
|
|
477
|
-
let discountAmount = 0;
|
|
478
|
-
let isRecoveredCart = false;
|
|
479
|
-
if (cart.appliedDiscountCodeId) {
|
|
480
|
-
discountCodeRecord = await prisma.discountCode.findUnique({
|
|
481
|
-
where: { id: cart.appliedDiscountCodeId },
|
|
482
|
-
});
|
|
483
|
-
// Validate discount is still active and valid
|
|
484
|
-
if (discountCodeRecord && discountCodeRecord.isActive) {
|
|
485
|
-
const now = new Date();
|
|
486
|
-
const isValid = (!discountCodeRecord.validFrom || discountCodeRecord.validFrom <= now) &&
|
|
487
|
-
(!discountCodeRecord.validUntil || discountCodeRecord.validUntil > now) &&
|
|
488
|
-
(!discountCodeRecord.brandId || discountCodeRecord.brandId === cart.brandId);
|
|
489
|
-
if (isValid) {
|
|
490
|
-
// Calculate discount amount
|
|
491
|
-
if (discountCodeRecord.type === 'percentage') {
|
|
492
|
-
discountAmount = round(pricing.subtotal * (toNumber(discountCodeRecord.value) / 100));
|
|
493
|
-
// Apply max discount cap if exists
|
|
494
|
-
if (discountCodeRecord.maxDiscount) {
|
|
495
|
-
const maxDiscountNum = toNumber(discountCodeRecord.maxDiscount);
|
|
496
|
-
if (discountAmount > maxDiscountNum) {
|
|
497
|
-
discountAmount = maxDiscountNum;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
else if (discountCodeRecord.type === 'fixed') {
|
|
502
|
-
discountAmount = Math.min(toNumber(discountCodeRecord.value), pricing.subtotal);
|
|
503
|
-
}
|
|
504
|
-
// Check if this is a recovery code
|
|
505
|
-
if (discountCodeRecord.category === 'recovery') {
|
|
506
|
-
isRecoveredCart = true;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
// Discount is no longer valid, ignore it
|
|
511
|
-
discountCodeRecord = null;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
else {
|
|
515
|
-
discountCodeRecord = null;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
// Consider cart recovered if it's at least 1 hour old (when first recovery attempt is sent)
|
|
519
|
-
const oneHourMs = 60 * 60 * 1000;
|
|
520
|
-
if (!isRecoveredCart && Date.now() - new Date(cart.createdAt).getTime() >= oneHourMs) {
|
|
521
|
-
isRecoveredCart = true;
|
|
522
|
-
}
|
|
523
|
-
// Calculate final total
|
|
524
|
-
const finalTotal = round(Math.max(0, pricing.subtotal + deliveryCharge - discountAmount));
|
|
525
|
-
// Build order items
|
|
526
|
-
const orderItems = cart.items.map((item) => {
|
|
527
|
-
const variant = item.variant;
|
|
528
|
-
const product = variant.product;
|
|
529
|
-
let finalPrice = toNumber(variant.price);
|
|
530
|
-
// Apply quantity discount if available
|
|
531
|
-
if (product.quantityDiscounts && typeof product.quantityDiscounts === 'object') {
|
|
532
|
-
const discounts = product.quantityDiscounts;
|
|
533
|
-
const applicableQuantities = Object.keys(discounts)
|
|
534
|
-
.map(Number)
|
|
535
|
-
.filter((qty) => item.quantity >= qty)
|
|
536
|
-
.sort((a, b) => b - a);
|
|
537
|
-
if (applicableQuantities.length > 0) {
|
|
538
|
-
const discountPercent = discounts[applicableQuantities[0].toString()];
|
|
539
|
-
finalPrice = round(toNumber(variant.price) * (1 - discountPercent / 100));
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
return {
|
|
543
|
-
variantId: variant.id,
|
|
544
|
-
quantity: item.quantity,
|
|
545
|
-
priceAtPurchase: finalPrice,
|
|
546
|
-
};
|
|
547
|
-
});
|
|
548
|
-
// Create order in transaction with conversion tracking
|
|
549
|
-
const result = await prisma.$transaction(async (tx) => {
|
|
550
|
-
// Create order
|
|
551
|
-
const order = await tx.order.create({
|
|
552
|
-
data: {
|
|
553
|
-
brandId: cart.brandId,
|
|
554
|
-
firstName: input.firstName,
|
|
555
|
-
lastName: input.lastName,
|
|
556
|
-
phone: (input.phone || cart.customerPhone), // Use checkout phone or fallback to cart phone
|
|
557
|
-
email: input.email || cart.customerEmail, // Use checkout email or fallback to cart email
|
|
558
|
-
address: input.address,
|
|
559
|
-
city: input.city,
|
|
560
|
-
deliveryZoneId: zone.id,
|
|
561
|
-
deliveryCharge,
|
|
562
|
-
estimatedDays,
|
|
563
|
-
totalPrice: finalTotal,
|
|
564
|
-
discountCodeId: discountCodeRecord?.id,
|
|
565
|
-
discountAmount: discountAmount > 0 ? discountAmount : null,
|
|
566
|
-
paymentMethod: input.paymentMethod,
|
|
567
|
-
paystackReference: input.paystackReference,
|
|
568
|
-
status: 'pending',
|
|
569
|
-
items: {
|
|
570
|
-
create: orderItems,
|
|
571
|
-
},
|
|
572
|
-
},
|
|
573
|
-
include: ORDER_INCLUDE_FULL,
|
|
574
|
-
});
|
|
575
|
-
// If discount code was used, create usage log and increment count
|
|
576
|
-
if (discountCodeRecord && discountAmount > 0) {
|
|
577
|
-
await tx.discountCodeUsage.create({
|
|
578
|
-
data: {
|
|
579
|
-
discountCodeId: discountCodeRecord.id,
|
|
580
|
-
orderId: order.id,
|
|
581
|
-
customerPhone: input.phone || cart.customerPhone || '',
|
|
582
|
-
discountAmount,
|
|
583
|
-
originalAmount: pricing.subtotal,
|
|
584
|
-
},
|
|
585
|
-
});
|
|
586
|
-
await tx.discountCode.update({
|
|
587
|
-
where: { id: discountCodeRecord.id },
|
|
588
|
-
data: { usageCount: { increment: 1 } },
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
// Track conversion: Update cart with order ID and recovery status
|
|
592
|
-
await tx.cart.update({
|
|
593
|
-
where: { id: cartId },
|
|
594
|
-
data: {
|
|
595
|
-
convertedToOrderId: order.id,
|
|
596
|
-
wasRecovered: isRecoveredCart,
|
|
597
|
-
},
|
|
598
|
-
});
|
|
599
|
-
return order;
|
|
600
|
-
});
|
|
601
|
-
// Queue order confirmation notification
|
|
602
|
-
try {
|
|
603
|
-
const { enqueueOrderNotification } = await import('../../notifications/producers/order-notification');
|
|
604
|
-
await enqueueOrderNotification(c.env, 'order_confirmation', result.id);
|
|
605
|
-
}
|
|
606
|
-
catch (error) {
|
|
607
|
-
console.error('Failed to queue order confirmation notification:', error);
|
|
608
|
-
// Don't fail the checkout if notification fails
|
|
609
|
-
}
|
|
610
|
-
// Queue Meta CAPI Purchase event
|
|
611
|
-
try {
|
|
612
|
-
const { enqueuePurchaseEvent } = await import('../../notifications/producers/meta-capi-producer');
|
|
613
|
-
await enqueuePurchaseEvent(c.env, result, {
|
|
614
|
-
clientIpAddress: c.req.header('cf-connecting-ip'),
|
|
615
|
-
clientUserAgent: c.req.header('user-agent'),
|
|
616
|
-
fbc: input.fbc,
|
|
617
|
-
fbp: input.fbp,
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
catch (error) {
|
|
621
|
-
console.error('Failed to queue Meta CAPI Purchase event:', error);
|
|
622
|
-
// Don't fail the checkout if CAPI queuing fails
|
|
623
|
-
}
|
|
624
|
-
return c.json(formatOrderResponse(result), 201);
|
|
625
|
-
}
|
|
626
|
-
catch (error) {
|
|
627
|
-
console.error('Error checking out cart:', error);
|
|
628
|
-
return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
|
|
629
|
-
}
|
|
630
|
-
});
|
|
631
|
-
export default app;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { AppContext } from '../../types';
|
|
2
|
-
declare const app: import("hono/hono-base").HonoBase<AppContext, {
|
|
3
|
-
"/": {
|
|
4
|
-
$get: {
|
|
5
|
-
input: {};
|
|
6
|
-
output: {
|
|
7
|
-
id: string;
|
|
8
|
-
name: string;
|
|
9
|
-
zones: {
|
|
10
|
-
id: string;
|
|
11
|
-
name: string;
|
|
12
|
-
deliveryCost: number;
|
|
13
|
-
freeShippingThreshold: number;
|
|
14
|
-
allowCOD: boolean;
|
|
15
|
-
allowOnline: boolean;
|
|
16
|
-
waybillOnly: boolean;
|
|
17
|
-
estimatedDays: number;
|
|
18
|
-
}[];
|
|
19
|
-
}[];
|
|
20
|
-
outputFormat: "json";
|
|
21
|
-
status: 200;
|
|
22
|
-
} | {
|
|
23
|
-
input: {};
|
|
24
|
-
output: {
|
|
25
|
-
error: {
|
|
26
|
-
code: string;
|
|
27
|
-
message: any;
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
outputFormat: "json";
|
|
31
|
-
status: 500;
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
}, "/">;
|
|
35
|
-
export default app;
|