@rovela-ai/sdk 0.5.17 → 0.6.1
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/admin/api/customer-groups.d.ts +61 -0
- package/dist/admin/api/customer-groups.d.ts.map +1 -0
- package/dist/admin/api/customer-groups.js +147 -0
- package/dist/admin/api/customer-groups.js.map +1 -0
- package/dist/admin/api/customers.d.ts.map +1 -1
- package/dist/admin/api/customers.js +28 -9
- package/dist/admin/api/customers.js.map +1 -1
- package/dist/admin/api/index.d.ts +2 -0
- package/dist/admin/api/index.d.ts.map +1 -1
- package/dist/admin/api/index.js +4 -0
- package/dist/admin/api/index.js.map +1 -1
- package/dist/admin/api/payment-methods.d.ts +56 -0
- package/dist/admin/api/payment-methods.d.ts.map +1 -0
- package/dist/admin/api/payment-methods.js +150 -0
- package/dist/admin/api/payment-methods.js.map +1 -0
- package/dist/admin/components/CustomerDetails.d.ts.map +1 -1
- package/dist/admin/components/CustomerDetails.js +9 -3
- package/dist/admin/components/CustomerDetails.js.map +1 -1
- package/dist/admin/components/CustomerGroupsManager.d.ts +7 -0
- package/dist/admin/components/CustomerGroupsManager.d.ts.map +1 -0
- package/dist/admin/components/CustomerGroupsManager.js +86 -0
- package/dist/admin/components/CustomerGroupsManager.js.map +1 -0
- package/dist/admin/components/CustomerTable.d.ts.map +1 -1
- package/dist/admin/components/CustomerTable.js +21 -8
- package/dist/admin/components/CustomerTable.js.map +1 -1
- package/dist/admin/components/OrderDetails.d.ts.map +1 -1
- package/dist/admin/components/OrderDetails.js +7 -2
- package/dist/admin/components/OrderDetails.js.map +1 -1
- package/dist/admin/components/PaymentMethodsManager.d.ts +5 -0
- package/dist/admin/components/PaymentMethodsManager.d.ts.map +1 -0
- package/dist/admin/components/PaymentMethodsManager.js +92 -0
- package/dist/admin/components/PaymentMethodsManager.js.map +1 -0
- package/dist/admin/components/PaymentSettings.d.ts.map +1 -1
- package/dist/admin/components/PaymentSettings.js +2 -1
- package/dist/admin/components/PaymentSettings.js.map +1 -1
- package/dist/admin/components/index.d.ts +4 -0
- package/dist/admin/components/index.d.ts.map +1 -1
- package/dist/admin/components/index.js +2 -0
- package/dist/admin/components/index.js.map +1 -1
- package/dist/admin/hooks/index.d.ts +4 -0
- package/dist/admin/hooks/index.d.ts.map +1 -1
- package/dist/admin/hooks/index.js +2 -0
- package/dist/admin/hooks/index.js.map +1 -1
- package/dist/admin/hooks/useAdminCustomerGroups.d.ts +12 -0
- package/dist/admin/hooks/useAdminCustomerGroups.d.ts.map +1 -0
- package/dist/admin/hooks/useAdminCustomerGroups.js +79 -0
- package/dist/admin/hooks/useAdminCustomerGroups.js.map +1 -0
- package/dist/admin/hooks/useAdminCustomers.d.ts.map +1 -1
- package/dist/admin/hooks/useAdminCustomers.js +5 -1
- package/dist/admin/hooks/useAdminCustomers.js.map +1 -1
- package/dist/admin/hooks/useAdminPaymentMethods.d.ts +12 -0
- package/dist/admin/hooks/useAdminPaymentMethods.d.ts.map +1 -0
- package/dist/admin/hooks/useAdminPaymentMethods.js +78 -0
- package/dist/admin/hooks/useAdminPaymentMethods.js.map +1 -0
- package/dist/admin/index.d.ts +3 -3
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +3 -3
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/types.d.ts +53 -0
- package/dist/admin/types.d.ts.map +1 -1
- package/dist/auth/server/customer-service.d.ts.map +1 -1
- package/dist/auth/server/customer-service.js +7 -1
- package/dist/auth/server/customer-service.js.map +1 -1
- package/dist/checkout/api/index.d.ts +2 -0
- package/dist/checkout/api/index.d.ts.map +1 -1
- package/dist/checkout/api/index.js +2 -0
- package/dist/checkout/api/index.js.map +1 -1
- package/dist/checkout/api/manual.d.ts +36 -0
- package/dist/checkout/api/manual.d.ts.map +1 -0
- package/dist/checkout/api/manual.js +289 -0
- package/dist/checkout/api/manual.js.map +1 -0
- package/dist/checkout/api/payment-methods.d.ts +25 -0
- package/dist/checkout/api/payment-methods.d.ts.map +1 -0
- package/dist/checkout/api/payment-methods.js +36 -0
- package/dist/checkout/api/payment-methods.js.map +1 -0
- package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
- package/dist/checkout/components/CheckoutFlow.js +33 -4
- package/dist/checkout/components/CheckoutFlow.js.map +1 -1
- package/dist/checkout/hooks/useCheckout.d.ts.map +1 -1
- package/dist/checkout/hooks/useCheckout.js +76 -0
- package/dist/checkout/hooks/useCheckout.js.map +1 -1
- package/dist/checkout/server/order-service.d.ts.map +1 -1
- package/dist/checkout/server/order-service.js +17 -7
- package/dist/checkout/server/order-service.js.map +1 -1
- package/dist/checkout/types.d.ts +24 -2
- package/dist/checkout/types.d.ts.map +1 -1
- package/dist/core/db/index.d.ts +3 -3
- package/dist/core/db/index.d.ts.map +1 -1
- package/dist/core/db/index.js +4 -0
- package/dist/core/db/index.js.map +1 -1
- package/dist/core/db/queries.d.ts +194 -10
- package/dist/core/db/queries.d.ts.map +1 -1
- package/dist/core/db/queries.js +300 -2
- package/dist/core/db/queries.js.map +1 -1
- package/dist/core/db/schema.d.ts +376 -0
- package/dist/core/db/schema.d.ts.map +1 -1
- package/dist/core/db/schema.js +70 -1
- package/dist/core/db/schema.js.map +1 -1
- package/dist/core/server/index.d.ts +2 -2
- package/dist/core/server/index.d.ts.map +1 -1
- package/dist/core/server/index.js +4 -0
- package/dist/core/server/index.js.map +1 -1
- package/dist/emails/send/orders.d.ts +2 -0
- package/dist/emails/send/orders.d.ts.map +1 -1
- package/dist/emails/send/orders.js +1 -0
- package/dist/emails/send/orders.js.map +1 -1
- package/dist/emails/templates/order-confirmation.d.ts.map +1 -1
- package/dist/emails/templates/order-confirmation.js +9 -0
- package/dist/emails/templates/order-confirmation.js.map +1 -1
- package/dist/emails/types.d.ts +2 -0
- package/dist/emails/types.d.ts.map +1 -1
- package/package.json +9 -1
package/dist/core/db/queries.js
CHANGED
|
@@ -387,6 +387,8 @@ export async function findCategoryBySlug(slug) {
|
|
|
387
387
|
* Find orders with filtering and pagination
|
|
388
388
|
*/
|
|
389
389
|
export async function findOrders(options = {}) {
|
|
390
|
+
// orders SELECT enumerates the payment columns — self-heal older stores first.
|
|
391
|
+
await ensurePaymentMethodsSchemaSafe();
|
|
390
392
|
const db = getDb();
|
|
391
393
|
const conditions = [];
|
|
392
394
|
if (options.status) {
|
|
@@ -412,6 +414,8 @@ export async function findOrders(options = {}) {
|
|
|
412
414
|
* Find order by ID with items (includes product images)
|
|
413
415
|
*/
|
|
414
416
|
export async function findOrderById(id) {
|
|
417
|
+
// orders SELECT enumerates the payment columns — self-heal older stores first.
|
|
418
|
+
await ensurePaymentMethodsSchemaSafe();
|
|
415
419
|
const db = getDb();
|
|
416
420
|
const order = await db
|
|
417
421
|
.select()
|
|
@@ -520,6 +524,8 @@ export async function findOrCreateGuestCustomer(email, name) {
|
|
|
520
524
|
* Find customer by email
|
|
521
525
|
*/
|
|
522
526
|
export async function findCustomerByEmail(email) {
|
|
527
|
+
// SELECT enumerates customers.group_id — self-heal older stores first.
|
|
528
|
+
await ensureCustomerGroupsSchemaSafe();
|
|
523
529
|
const db = getDb();
|
|
524
530
|
const result = await db
|
|
525
531
|
.select()
|
|
@@ -532,6 +538,8 @@ export async function findCustomerByEmail(email) {
|
|
|
532
538
|
* Find customer by ID
|
|
533
539
|
*/
|
|
534
540
|
export async function findCustomerById(id) {
|
|
541
|
+
// SELECT enumerates customers.group_id — self-heal older stores first.
|
|
542
|
+
await ensureCustomerGroupsSchemaSafe();
|
|
535
543
|
const db = getDb();
|
|
536
544
|
const result = await db
|
|
537
545
|
.select()
|
|
@@ -544,10 +552,15 @@ export async function findCustomerById(id) {
|
|
|
544
552
|
* Find all customers with pagination
|
|
545
553
|
*/
|
|
546
554
|
export async function findCustomers(options = {}) {
|
|
555
|
+
// SELECT enumerates customers.group_id — self-heal older stores first.
|
|
556
|
+
await ensureCustomerGroupsSchemaSafe();
|
|
547
557
|
const db = getDb();
|
|
548
558
|
const conditions = [];
|
|
549
559
|
if (options.search) {
|
|
550
|
-
conditions.push(ilike(schema.customers.email, `%${options.search}%`));
|
|
560
|
+
conditions.push(or(ilike(schema.customers.email, `%${options.search}%`), ilike(schema.customers.name, `%${options.search}%`)));
|
|
561
|
+
}
|
|
562
|
+
if (options.groupId) {
|
|
563
|
+
conditions.push(eq(schema.customers.groupId, options.groupId));
|
|
551
564
|
}
|
|
552
565
|
const query = db
|
|
553
566
|
.select()
|
|
@@ -857,6 +870,8 @@ export async function getRecentOrders(limit = 5, periodDays) {
|
|
|
857
870
|
* Create a new order
|
|
858
871
|
*/
|
|
859
872
|
export async function createOrder(data) {
|
|
873
|
+
// orders SELECT enumerates the payment columns — self-heal older stores first.
|
|
874
|
+
await ensurePaymentMethodsSchemaSafe();
|
|
860
875
|
const db = getDb();
|
|
861
876
|
const [order] = await db
|
|
862
877
|
.insert(schema.orders)
|
|
@@ -906,6 +921,8 @@ export async function updateOrderShipping(orderId, shippingData) {
|
|
|
906
921
|
* Used for idempotency in webhook handling
|
|
907
922
|
*/
|
|
908
923
|
export async function findOrderByPaymentIntent(paymentIntentId) {
|
|
924
|
+
// orders SELECT enumerates the payment columns — self-heal older stores first.
|
|
925
|
+
await ensurePaymentMethodsSchemaSafe();
|
|
909
926
|
const db = getDb();
|
|
910
927
|
const [order] = await db
|
|
911
928
|
.select()
|
|
@@ -1153,10 +1170,14 @@ export async function countCategories() {
|
|
|
1153
1170
|
* Count customers with optional search
|
|
1154
1171
|
*/
|
|
1155
1172
|
export async function countCustomers(options = {}) {
|
|
1173
|
+
await ensureCustomerGroupsSchemaSafe();
|
|
1156
1174
|
const db = getDb();
|
|
1157
1175
|
const conditions = [];
|
|
1158
1176
|
if (options.search) {
|
|
1159
|
-
conditions.push(ilike(schema.customers.email, `%${options.search}%`));
|
|
1177
|
+
conditions.push(or(ilike(schema.customers.email, `%${options.search}%`), ilike(schema.customers.name, `%${options.search}%`)));
|
|
1178
|
+
}
|
|
1179
|
+
if (options.groupId) {
|
|
1180
|
+
conditions.push(eq(schema.customers.groupId, options.groupId));
|
|
1160
1181
|
}
|
|
1161
1182
|
const result = await db
|
|
1162
1183
|
.select({ count: sql `count(*)` })
|
|
@@ -1168,6 +1189,8 @@ export async function countCustomers(options = {}) {
|
|
|
1168
1189
|
* Find orders for a specific customer
|
|
1169
1190
|
*/
|
|
1170
1191
|
export async function findCustomerOrders(customerId) {
|
|
1192
|
+
// orders SELECT enumerates the payment columns — self-heal older stores first.
|
|
1193
|
+
await ensurePaymentMethodsSchemaSafe();
|
|
1171
1194
|
const db = getDb();
|
|
1172
1195
|
return db
|
|
1173
1196
|
.select()
|
|
@@ -1445,6 +1468,281 @@ export async function setDefaultCustomerAddress(id, customerId, kind) {
|
|
|
1445
1468
|
return true;
|
|
1446
1469
|
}
|
|
1447
1470
|
// =============================================================================
|
|
1471
|
+
// Customer Groups (merchant-defined customer types)
|
|
1472
|
+
// =============================================================================
|
|
1473
|
+
let customerGroupsSchemaEnsured = false;
|
|
1474
|
+
/**
|
|
1475
|
+
* Self-heal the customer-groups schema on stores provisioned before this
|
|
1476
|
+
* feature. New stores get it from schema.ts at branch creation. Idempotent +
|
|
1477
|
+
* module-cached (mirrors ensureCustomerAddressSchema). ALSO guards the
|
|
1478
|
+
* `customers.group_id` column that every `customers` SELECT now enumerates —
|
|
1479
|
+
* called (best-effort) from the customer read chokepoints so an SDK upgrade
|
|
1480
|
+
* self-migrates instead of breaking reads.
|
|
1481
|
+
*/
|
|
1482
|
+
export async function ensureCustomerGroupsSchema() {
|
|
1483
|
+
if (customerGroupsSchemaEnsured)
|
|
1484
|
+
return;
|
|
1485
|
+
const db = getDb();
|
|
1486
|
+
try {
|
|
1487
|
+
await db.execute(sql `ALTER TABLE customers ADD COLUMN IF NOT EXISTS group_id uuid`);
|
|
1488
|
+
await db.execute(sql `CREATE INDEX IF NOT EXISTS customers_group_id_idx ON customers (group_id)`);
|
|
1489
|
+
await db.execute(sql `
|
|
1490
|
+
CREATE TABLE IF NOT EXISTS customer_groups (
|
|
1491
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1492
|
+
name varchar(100) NOT NULL,
|
|
1493
|
+
description varchar(255),
|
|
1494
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
1495
|
+
sort_order integer NOT NULL DEFAULT 0,
|
|
1496
|
+
created_at timestamp NOT NULL DEFAULT now(),
|
|
1497
|
+
updated_at timestamp NOT NULL DEFAULT now()
|
|
1498
|
+
)
|
|
1499
|
+
`);
|
|
1500
|
+
await db.execute(sql `CREATE INDEX IF NOT EXISTS customer_groups_sort_order_idx ON customer_groups (sort_order)`);
|
|
1501
|
+
customerGroupsSchemaEnsured = true;
|
|
1502
|
+
}
|
|
1503
|
+
catch (err) {
|
|
1504
|
+
console.error('[ensureCustomerGroupsSchema] migration failed:', err);
|
|
1505
|
+
throw err;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
/** Best-effort variant for hot read paths (login, listings): a transient
|
|
1509
|
+
* ensure failure must not block the read — if the schema truly is missing,
|
|
1510
|
+
* the subsequent SELECT fails with a clear error anyway. Exported for the
|
|
1511
|
+
* customer-service read paths that select customers.* directly. */
|
|
1512
|
+
export async function ensureCustomerGroupsSchemaSafe() {
|
|
1513
|
+
try {
|
|
1514
|
+
await ensureCustomerGroupsSchema();
|
|
1515
|
+
}
|
|
1516
|
+
catch {
|
|
1517
|
+
// logged inside ensureCustomerGroupsSchema
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
/** List all groups (sorted), each with its live member count. */
|
|
1521
|
+
export async function findCustomerGroups() {
|
|
1522
|
+
await ensureCustomerGroupsSchema();
|
|
1523
|
+
const db = getDb();
|
|
1524
|
+
return db
|
|
1525
|
+
.select({
|
|
1526
|
+
id: schema.customerGroups.id,
|
|
1527
|
+
name: schema.customerGroups.name,
|
|
1528
|
+
description: schema.customerGroups.description,
|
|
1529
|
+
isActive: schema.customerGroups.isActive,
|
|
1530
|
+
sortOrder: schema.customerGroups.sortOrder,
|
|
1531
|
+
createdAt: schema.customerGroups.createdAt,
|
|
1532
|
+
updatedAt: schema.customerGroups.updatedAt,
|
|
1533
|
+
// Plain identifier (not a drizzle column interpolation): inside a raw
|
|
1534
|
+
// correlated subquery the interpolated form mis-renders and the count
|
|
1535
|
+
// silently returns 0. Outer table is unaliased `customer_groups`.
|
|
1536
|
+
customerCount: sql `(
|
|
1537
|
+
SELECT count(*)::int FROM customers c WHERE c.group_id = customer_groups.id
|
|
1538
|
+
)`,
|
|
1539
|
+
})
|
|
1540
|
+
.from(schema.customerGroups)
|
|
1541
|
+
.orderBy(asc(schema.customerGroups.sortOrder), asc(schema.customerGroups.createdAt));
|
|
1542
|
+
}
|
|
1543
|
+
export async function findCustomerGroupById(id) {
|
|
1544
|
+
await ensureCustomerGroupsSchema();
|
|
1545
|
+
const db = getDb();
|
|
1546
|
+
const rows = await db
|
|
1547
|
+
.select()
|
|
1548
|
+
.from(schema.customerGroups)
|
|
1549
|
+
.where(eq(schema.customerGroups.id, id))
|
|
1550
|
+
.limit(1);
|
|
1551
|
+
return rows[0] ?? null;
|
|
1552
|
+
}
|
|
1553
|
+
export async function createCustomerGroup(data) {
|
|
1554
|
+
await ensureCustomerGroupsSchema();
|
|
1555
|
+
const db = getDb();
|
|
1556
|
+
const [group] = await db
|
|
1557
|
+
.insert(schema.customerGroups)
|
|
1558
|
+
.values({
|
|
1559
|
+
name: data.name,
|
|
1560
|
+
description: data.description ?? null,
|
|
1561
|
+
isActive: data.isActive ?? true,
|
|
1562
|
+
sortOrder: data.sortOrder ?? 0,
|
|
1563
|
+
})
|
|
1564
|
+
.returning();
|
|
1565
|
+
return group;
|
|
1566
|
+
}
|
|
1567
|
+
export async function updateCustomerGroup(id, data) {
|
|
1568
|
+
await ensureCustomerGroupsSchema();
|
|
1569
|
+
const db = getDb();
|
|
1570
|
+
const set = { updatedAt: new Date() };
|
|
1571
|
+
if (data.name !== undefined)
|
|
1572
|
+
set.name = data.name;
|
|
1573
|
+
if (data.description !== undefined)
|
|
1574
|
+
set.description = data.description ?? null;
|
|
1575
|
+
if (data.isActive !== undefined)
|
|
1576
|
+
set.isActive = data.isActive;
|
|
1577
|
+
if (data.sortOrder !== undefined)
|
|
1578
|
+
set.sortOrder = data.sortOrder;
|
|
1579
|
+
const [group] = await db
|
|
1580
|
+
.update(schema.customerGroups)
|
|
1581
|
+
.set(set)
|
|
1582
|
+
.where(eq(schema.customerGroups.id, id))
|
|
1583
|
+
.returning();
|
|
1584
|
+
return group ?? null;
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Delete a group. Members are UNGROUPED (group_id set to null) first — there
|
|
1588
|
+
* is no hard FK, so the unset is explicit here to avoid dangling references.
|
|
1589
|
+
*/
|
|
1590
|
+
export async function deleteCustomerGroup(id) {
|
|
1591
|
+
await ensureCustomerGroupsSchema();
|
|
1592
|
+
const db = getDb();
|
|
1593
|
+
await db
|
|
1594
|
+
.update(schema.customers)
|
|
1595
|
+
.set({ groupId: null })
|
|
1596
|
+
.where(eq(schema.customers.groupId, id));
|
|
1597
|
+
const deleted = await db
|
|
1598
|
+
.delete(schema.customerGroups)
|
|
1599
|
+
.where(eq(schema.customerGroups.id, id))
|
|
1600
|
+
.returning({ id: schema.customerGroups.id });
|
|
1601
|
+
return deleted.length > 0;
|
|
1602
|
+
}
|
|
1603
|
+
/** Assign (or unassign with null) a customer's group. Caller validates the group id. */
|
|
1604
|
+
export async function setCustomerGroup(customerId, groupId) {
|
|
1605
|
+
await ensureCustomerGroupsSchema();
|
|
1606
|
+
const db = getDb();
|
|
1607
|
+
const [customer] = await db
|
|
1608
|
+
.update(schema.customers)
|
|
1609
|
+
.set({ groupId, updatedAt: new Date() })
|
|
1610
|
+
.where(eq(schema.customers.id, customerId))
|
|
1611
|
+
.returning();
|
|
1612
|
+
return customer ?? null;
|
|
1613
|
+
}
|
|
1614
|
+
// =============================================================================
|
|
1615
|
+
// Payment Methods (merchant-managed payment options beyond native Stripe)
|
|
1616
|
+
// =============================================================================
|
|
1617
|
+
let paymentMethodsSchemaEnsured = false;
|
|
1618
|
+
/**
|
|
1619
|
+
* Self-heal the payment-methods schema on stores provisioned before this
|
|
1620
|
+
* feature: the payment_methods table + the orders payment columns that every
|
|
1621
|
+
* `orders` SELECT now enumerates. `payment_provider` matches the PayPal
|
|
1622
|
+
* feature card's lazy column EXACTLY so both converge idempotently.
|
|
1623
|
+
* Idempotent + module-cached.
|
|
1624
|
+
*/
|
|
1625
|
+
export async function ensurePaymentMethodsSchema() {
|
|
1626
|
+
if (paymentMethodsSchemaEnsured)
|
|
1627
|
+
return;
|
|
1628
|
+
const db = getDb();
|
|
1629
|
+
try {
|
|
1630
|
+
await db.execute(sql `
|
|
1631
|
+
CREATE TABLE IF NOT EXISTS payment_methods (
|
|
1632
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1633
|
+
kind varchar(20) NOT NULL DEFAULT 'manual',
|
|
1634
|
+
name varchar(100) NOT NULL,
|
|
1635
|
+
instructions text,
|
|
1636
|
+
enabled boolean NOT NULL DEFAULT true,
|
|
1637
|
+
sort_order integer NOT NULL DEFAULT 0,
|
|
1638
|
+
config jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
1639
|
+
created_at timestamp NOT NULL DEFAULT now(),
|
|
1640
|
+
updated_at timestamp NOT NULL DEFAULT now()
|
|
1641
|
+
)
|
|
1642
|
+
`);
|
|
1643
|
+
await db.execute(sql `CREATE INDEX IF NOT EXISTS payment_methods_enabled_idx ON payment_methods (enabled)`);
|
|
1644
|
+
await db.execute(sql `CREATE INDEX IF NOT EXISTS payment_methods_sort_order_idx ON payment_methods (sort_order)`);
|
|
1645
|
+
await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS payment_provider varchar(20) NOT NULL DEFAULT 'stripe'`);
|
|
1646
|
+
await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS payment_method_id uuid`);
|
|
1647
|
+
await db.execute(sql `ALTER TABLE orders ADD COLUMN IF NOT EXISTS payment_method_label varchar(100)`);
|
|
1648
|
+
paymentMethodsSchemaEnsured = true;
|
|
1649
|
+
}
|
|
1650
|
+
catch (err) {
|
|
1651
|
+
console.error('[ensurePaymentMethodsSchema] migration failed:', err);
|
|
1652
|
+
throw err;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
/** Best-effort variant for hot order read/write paths (see groups sibling). */
|
|
1656
|
+
async function ensurePaymentMethodsSchemaSafe() {
|
|
1657
|
+
try {
|
|
1658
|
+
await ensurePaymentMethodsSchema();
|
|
1659
|
+
}
|
|
1660
|
+
catch {
|
|
1661
|
+
// logged inside ensurePaymentMethodsSchema
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
/** All methods, admin view (sorted). */
|
|
1665
|
+
export async function findPaymentMethods() {
|
|
1666
|
+
await ensurePaymentMethodsSchema();
|
|
1667
|
+
const db = getDb();
|
|
1668
|
+
return db
|
|
1669
|
+
.select()
|
|
1670
|
+
.from(schema.paymentMethods)
|
|
1671
|
+
.orderBy(asc(schema.paymentMethods.sortOrder), asc(schema.paymentMethods.createdAt));
|
|
1672
|
+
}
|
|
1673
|
+
/** Enabled methods only — what checkout offers alongside native Stripe. */
|
|
1674
|
+
export async function findEnabledPaymentMethods() {
|
|
1675
|
+
await ensurePaymentMethodsSchema();
|
|
1676
|
+
const db = getDb();
|
|
1677
|
+
return db
|
|
1678
|
+
.select()
|
|
1679
|
+
.from(schema.paymentMethods)
|
|
1680
|
+
.where(eq(schema.paymentMethods.enabled, true))
|
|
1681
|
+
.orderBy(asc(schema.paymentMethods.sortOrder), asc(schema.paymentMethods.createdAt));
|
|
1682
|
+
}
|
|
1683
|
+
export async function findPaymentMethodById(id) {
|
|
1684
|
+
await ensurePaymentMethodsSchema();
|
|
1685
|
+
const db = getDb();
|
|
1686
|
+
const rows = await db
|
|
1687
|
+
.select()
|
|
1688
|
+
.from(schema.paymentMethods)
|
|
1689
|
+
.where(eq(schema.paymentMethods.id, id))
|
|
1690
|
+
.limit(1);
|
|
1691
|
+
return rows[0] ?? null;
|
|
1692
|
+
}
|
|
1693
|
+
export async function createPaymentMethod(data) {
|
|
1694
|
+
await ensurePaymentMethodsSchema();
|
|
1695
|
+
const db = getDb();
|
|
1696
|
+
const [method] = await db
|
|
1697
|
+
.insert(schema.paymentMethods)
|
|
1698
|
+
.values({
|
|
1699
|
+
kind: data.kind ?? 'manual',
|
|
1700
|
+
name: data.name,
|
|
1701
|
+
instructions: data.instructions ?? null,
|
|
1702
|
+
enabled: data.enabled ?? true,
|
|
1703
|
+
sortOrder: data.sortOrder ?? 0,
|
|
1704
|
+
config: data.config ?? {},
|
|
1705
|
+
})
|
|
1706
|
+
.returning();
|
|
1707
|
+
return method;
|
|
1708
|
+
}
|
|
1709
|
+
export async function updatePaymentMethod(id, data) {
|
|
1710
|
+
await ensurePaymentMethodsSchema();
|
|
1711
|
+
const db = getDb();
|
|
1712
|
+
const set = { updatedAt: new Date() };
|
|
1713
|
+
if (data.kind !== undefined)
|
|
1714
|
+
set.kind = data.kind;
|
|
1715
|
+
if (data.name !== undefined)
|
|
1716
|
+
set.name = data.name;
|
|
1717
|
+
if (data.instructions !== undefined)
|
|
1718
|
+
set.instructions = data.instructions ?? null;
|
|
1719
|
+
if (data.enabled !== undefined)
|
|
1720
|
+
set.enabled = data.enabled;
|
|
1721
|
+
if (data.sortOrder !== undefined)
|
|
1722
|
+
set.sortOrder = data.sortOrder;
|
|
1723
|
+
if (data.config !== undefined)
|
|
1724
|
+
set.config = data.config;
|
|
1725
|
+
const [method] = await db
|
|
1726
|
+
.update(schema.paymentMethods)
|
|
1727
|
+
.set(set)
|
|
1728
|
+
.where(eq(schema.paymentMethods.id, id))
|
|
1729
|
+
.returning();
|
|
1730
|
+
return method ?? null;
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Hard delete is safe: orders snapshot the method's id + LABEL at order time,
|
|
1734
|
+
* so history stays readable after the method row is gone.
|
|
1735
|
+
*/
|
|
1736
|
+
export async function deletePaymentMethod(id) {
|
|
1737
|
+
await ensurePaymentMethodsSchema();
|
|
1738
|
+
const db = getDb();
|
|
1739
|
+
const deleted = await db
|
|
1740
|
+
.delete(schema.paymentMethods)
|
|
1741
|
+
.where(eq(schema.paymentMethods.id, id))
|
|
1742
|
+
.returning({ id: schema.paymentMethods.id });
|
|
1743
|
+
return deleted.length > 0;
|
|
1744
|
+
}
|
|
1745
|
+
// =============================================================================
|
|
1448
1746
|
// Order Queries (for admin module)
|
|
1449
1747
|
// =============================================================================
|
|
1450
1748
|
/**
|