payment-kit 1.29.1 → 1.29.2
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/api/dev.ts +41 -2
- package/api/hono.d.ts +42 -0
- package/api/node-sqlite.d.ts +12 -0
- package/api/src/bootstrap.ts +36 -0
- package/api/src/crons/base.ts +3 -3
- package/api/src/crons/currency.ts +1 -1
- package/api/src/crons/index.ts +27 -24
- package/api/src/crons/metering-subscription-detection.ts +1 -1
- package/api/src/crons/overdue-detection.ts +2 -2
- package/api/src/crons/retry-pending-events.ts +6 -0
- package/api/src/index.ts +22 -161
- package/api/src/integrations/app-store/client.ts +3 -4
- package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
- package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +21 -7
- package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
- package/api/src/integrations/google-play/handlers/voided.ts +2 -2
- package/api/src/integrations/google-play/verify.ts +3 -2
- package/api/src/integrations/iap-reconcile.ts +3 -5
- package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
- package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
- package/api/src/libs/archive/query.ts +19 -0
- package/api/src/libs/audit.ts +61 -4
- package/api/src/libs/auth.ts +99 -38
- package/api/src/libs/context.ts +78 -1
- package/api/src/libs/currency.ts +2 -2
- package/api/src/libs/dayjs.ts +8 -2
- package/api/src/libs/drivers/auth-storage.ts +118 -0
- package/api/src/libs/drivers/cron.ts +264 -0
- package/api/src/libs/drivers/db.ts +170 -0
- package/api/src/libs/drivers/identity.ts +81 -0
- package/api/src/libs/drivers/index.ts +40 -0
- package/api/src/libs/drivers/locks.ts +226 -0
- package/api/src/libs/drivers/migrate-runner.ts +70 -0
- package/api/src/libs/drivers/queue.ts +104 -0
- package/api/src/libs/drivers/secrets.ts +194 -0
- package/api/src/libs/env.ts +170 -54
- package/api/src/libs/exchange-rate/service.ts +7 -6
- package/api/src/libs/http-fetch-adapter.ts +50 -0
- package/api/src/libs/invoice.ts +1 -1
- package/api/src/libs/lock.ts +51 -47
- package/api/src/libs/logger.ts +48 -8
- package/api/src/libs/notification/index.ts +1 -1
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
- package/api/src/libs/overdraft-protection.ts +1 -1
- package/api/src/libs/payout.ts +1 -1
- package/api/src/libs/queue/index.ts +259 -52
- package/api/src/libs/queue/runtime.ts +175 -0
- package/api/src/libs/resource.ts +3 -3
- package/api/src/libs/secrets.ts +38 -0
- package/api/src/libs/session.ts +3 -2
- package/api/src/libs/subscription.ts +5 -5
- package/api/src/libs/tenant.ts +92 -0
- package/api/src/libs/url.ts +3 -3
- package/api/src/libs/util.ts +21 -13
- package/api/src/middlewares/hono/cdn.ts +63 -0
- package/api/src/middlewares/hono/context.ts +73 -0
- package/api/src/middlewares/hono/csrf.ts +72 -0
- package/api/src/middlewares/hono/fallback.ts +194 -0
- package/api/src/middlewares/hono/pipeline.ts +73 -0
- package/api/src/middlewares/hono/resource-mount.ts +42 -0
- package/api/src/middlewares/hono/resource.ts +63 -0
- package/api/src/middlewares/hono/security.ts +214 -0
- package/api/src/middlewares/hono/session.ts +114 -0
- package/api/src/middlewares/hono/xss.ts +61 -0
- package/api/src/queues/auto-recharge.ts +12 -10
- package/api/src/queues/checkout-session.ts +17 -12
- package/api/src/queues/credit-consume.ts +40 -36
- package/api/src/queues/credit-grant.ts +25 -18
- package/api/src/queues/credit-reconciliation.ts +7 -5
- package/api/src/queues/discount-status.ts +9 -6
- package/api/src/queues/event.ts +12 -4
- package/api/src/queues/exchange-rate-health.ts +49 -30
- package/api/src/queues/invoice.ts +18 -15
- package/api/src/queues/notification.ts +14 -7
- package/api/src/queues/payment.ts +41 -28
- package/api/src/queues/payout.ts +9 -5
- package/api/src/queues/refund.ts +18 -12
- package/api/src/queues/subscription.ts +83 -53
- package/api/src/queues/token-transfer.ts +15 -10
- package/api/src/queues/usage-record.ts +8 -5
- package/api/src/queues/vendors/commission.ts +7 -5
- package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
- package/api/src/queues/vendors/fulfillment.ts +4 -2
- package/api/src/queues/vendors/return-processor.ts +5 -3
- package/api/src/queues/vendors/return-scanner.ts +5 -4
- package/api/src/queues/vendors/status-check.ts +10 -7
- package/api/src/queues/webhook.ts +60 -32
- package/api/src/routes/connect/shared.ts +1 -2
- package/api/src/routes/connect/subscribe.ts +3 -3
- package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
- package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
- package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
- package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
- package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
- package/api/src/routes/hono/credit-tokens.ts +43 -0
- package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
- package/api/src/routes/{customers.ts → hono/customers.ts} +193 -223
- package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
- package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
- package/api/src/routes/{events.ts → hono/events.ts} +107 -71
- package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
- package/api/src/routes/hono/exchange-rates.ts +77 -0
- package/api/src/routes/hono/index.ts +115 -0
- package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
- package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
- package/api/src/routes/hono/integrations/stripe.ts +74 -0
- package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
- package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
- package/api/src/routes/hono/meters.ts +288 -0
- package/api/src/routes/hono/passports.ts +73 -0
- package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
- package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
- package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
- package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
- package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
- package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
- package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
- package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
- package/api/src/routes/{products.ts → hono/products.ts} +172 -159
- package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
- package/api/src/routes/hono/redirect.ts +24 -0
- package/api/src/routes/{refunds.ts → hono/refunds.ts} +96 -80
- package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
- package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
- package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
- package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
- package/api/src/routes/hono/tool.ts +69 -0
- package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
- package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
- package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
- package/api/src/routes/hono/webhook-endpoints.ts +126 -0
- package/api/src/service.ts +667 -0
- package/api/src/store/migrations/20230911-seeding.ts +2 -1
- package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
- package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
- package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
- package/api/src/store/models/auto-recharge-config.ts +22 -10
- package/api/src/store/models/checkout-session.ts +15 -14
- package/api/src/store/models/coupon.ts +29 -20
- package/api/src/store/models/credit-grant.ts +38 -29
- package/api/src/store/models/credit-transaction.ts +32 -21
- package/api/src/store/models/customer.ts +19 -17
- package/api/src/store/models/discount.ts +11 -2
- package/api/src/store/models/entitlement-grant.ts +21 -9
- package/api/src/store/models/entitlement-product.ts +21 -9
- package/api/src/store/models/entitlement.ts +19 -10
- package/api/src/store/models/event.ts +18 -9
- package/api/src/store/models/exchange-rate-provider.ts +17 -4
- package/api/src/store/models/invoice-item.ts +18 -9
- package/api/src/store/models/invoice.ts +16 -8
- package/api/src/store/models/meter-event.ts +27 -9
- package/api/src/store/models/meter.ts +31 -22
- package/api/src/store/models/payment-currency.ts +25 -8
- package/api/src/store/models/payment-intent.ts +15 -6
- package/api/src/store/models/payment-link.ts +15 -6
- package/api/src/store/models/payment-method.ts +38 -22
- package/api/src/store/models/payment-stat.ts +18 -9
- package/api/src/store/models/payout.ts +15 -6
- package/api/src/store/models/price-quote.ts +17 -8
- package/api/src/store/models/price.ts +24 -12
- package/api/src/store/models/pricing-table.ts +29 -20
- package/api/src/store/models/product-vendor.ts +20 -10
- package/api/src/store/models/product.ts +15 -6
- package/api/src/store/models/promotion-code.ts +14 -6
- package/api/src/store/models/refund.ts +15 -6
- package/api/src/store/models/revenue-snapshot.ts +21 -9
- package/api/src/store/models/setting.ts +18 -9
- package/api/src/store/models/setup-intent.ts +36 -27
- package/api/src/store/models/subscription-item.ts +21 -9
- package/api/src/store/models/subscription-schedule.ts +21 -9
- package/api/src/store/models/subscription.ts +21 -10
- package/api/src/store/models/tax-rate.ts +29 -21
- package/api/src/store/models/usage-record.ts +11 -2
- package/api/src/store/models/webhook-attempt.ts +18 -9
- package/api/src/store/models/webhook-endpoint.ts +18 -9
- package/api/src/store/scoped-core.ts +55 -0
- package/api/src/store/scoped.ts +247 -0
- package/api/src/store/sequelize.ts +66 -22
- package/api/src/store/sql-migrations.ts +20 -0
- package/api/src/store/tenant-backfill.ts +260 -0
- package/api/src/store/tenant-model.ts +124 -0
- package/api/src/store/tenant-tables.ts +50 -0
- package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
- package/api/tests/fixtures/bare-query-violation.ts +13 -0
- package/api/tests/fixtures/core-env-violation.ts +10 -0
- package/api/tests/fixtures/host-read-violation.ts +19 -0
- package/api/tests/fixtures/tenants.ts +4 -0
- package/api/tests/integrations/iap-tenant.spec.ts +284 -0
- package/api/tests/libs/archive-query.spec.ts +26 -0
- package/api/tests/libs/audit-tenant.spec.ts +153 -0
- package/api/tests/libs/context.spec.ts +204 -0
- package/api/tests/libs/core-config.spec.ts +115 -0
- package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
- package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
- package/api/tests/libs/lock-tenant.spec.ts +66 -0
- package/api/tests/libs/scoped.spec.ts +222 -0
- package/api/tests/libs/secrets-facade.spec.ts +52 -0
- package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
- package/api/tests/libs/tenant-middleware.spec.ts +42 -0
- package/api/tests/libs/tenant-scanner.spec.ts +120 -0
- package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
- package/api/tests/middlewares/hono/context.spec.ts +113 -0
- package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
- package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
- package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
- package/api/tests/middlewares/hono/security.spec.ts +181 -0
- package/api/tests/middlewares/hono/session.spec.ts +42 -0
- package/api/tests/middlewares/hono/xss.spec.ts +81 -0
- package/api/tests/models/tenant-backfill.spec.ts +287 -0
- package/api/tests/models/tenant-columns-model.spec.ts +46 -0
- package/api/tests/models/tenant-columns.spec.ts +161 -0
- package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
- package/api/tests/queues/credit-consume.spec.ts +8 -1
- package/api/tests/queues/event-tenant.spec.ts +236 -0
- package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
- package/api/tests/queues/queue-parity.spec.ts +249 -0
- package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
- package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
- package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
- package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
- package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
- package/api/tests/service/collapse.spec.ts +96 -0
- package/api/tests/store/tenant-crosscut.spec.ts +202 -0
- package/api/tests/store/tenant-model-spike.spec.ts +177 -0
- package/api/tests/store/tenant-model.spec.ts +162 -0
- package/api/tests/store/tenant-residual.spec.ts +196 -0
- package/api/third.d.ts +4 -0
- package/blocklet.yml +1 -1
- package/cloudflare/README.md +26 -6
- package/cloudflare/build.ts +28 -13
- package/cloudflare/did-connect-auth.ts +0 -217
- package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
- package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
- package/cloudflare/migrations/0008_schema_parity.sql +16 -0
- package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
- package/cloudflare/queue-runtime-mode.ts +13 -0
- package/cloudflare/run-build.js +10 -56
- package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
- package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
- package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/util-csrf.ts +13 -0
- package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
- package/cloudflare/shims/cron.ts +38 -158
- package/cloudflare/shims/events.ts +124 -0
- package/cloudflare/shims/fastq.ts +15 -1
- package/cloudflare/shims/nedb-storage.ts +16 -8
- package/cloudflare/shims/xss.ts +8 -0
- package/cloudflare/tenant-middleware.ts +36 -0
- package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
- package/cloudflare/worker.ts +204 -433
- package/cloudflare/wrangler.local-e2e.jsonc +26 -0
- package/jest.config.js +3 -1
- package/package.json +33 -38
- package/scripts/core-env-whitelist.json +1 -0
- package/scripts/e2e-12b-runtime.ts +149 -0
- package/scripts/e2e-core-config.ts +125 -0
- package/scripts/e2e-d1-tenancy.ts +116 -0
- package/scripts/e2e-d2-cron-queue.ts +139 -0
- package/scripts/e2e-d3-embedded-multi.ts +171 -0
- package/scripts/e2e-hono-s2.ts +125 -0
- package/scripts/e2e-hono-s3e.ts +135 -0
- package/scripts/e2e-hono-s4.ts +114 -0
- package/scripts/e2e-migration-contract.ts +100 -0
- package/scripts/e2e-s0.ts +61 -0
- package/scripts/e2e-s1.ts +107 -0
- package/scripts/e2e-s2.ts +178 -0
- package/scripts/e2e-s3.ts +110 -0
- package/scripts/e2e-s4.ts +191 -0
- package/scripts/e2e-s5.ts +139 -0
- package/scripts/e2e-s6.ts +127 -0
- package/scripts/e2e-tenant-model.ts +119 -0
- package/scripts/e2e-tenant-worker.ts +199 -0
- package/scripts/gen-sql-migrations.js +46 -0
- package/scripts/phase8-codemod.js +219 -0
- package/scripts/phase9a-env-getters-codemod.js +82 -0
- package/scripts/scan-core-env.js +109 -0
- package/scripts/scan-tenant-queries.js +235 -0
- package/scripts/schema-drift-guard.ts +210 -0
- package/scripts/tenant-scan-whitelist.json +1 -0
- package/src/env.d.ts +13 -1
- package/tsconfig.json +1 -1
- package/api/src/libs/did-space.ts +0 -235
- package/api/src/libs/middleware.ts +0 -50
- package/api/src/libs/security.ts +0 -192
- package/api/src/queues/space.ts +0 -662
- package/api/src/routes/credit-tokens.ts +0 -38
- package/api/src/routes/exchange-rates.ts +0 -87
- package/api/src/routes/index.ts +0 -142
- package/api/src/routes/integrations/stripe.ts +0 -61
- package/api/src/routes/meters.ts +0 -274
- package/api/src/routes/passports.ts +0 -68
- package/api/src/routes/redirect.ts +0 -20
- package/api/src/routes/tool.ts +0 -65
- package/api/src/routes/webhook-endpoints.ts +0 -126
- package/api/tests/routes/credit-grants.spec.ts +0 -1261
- package/cloudflare/shims/did-space-js.ts +0 -17
- package/cloudflare/shims/did-space.ts +0 -11
- package/cloudflare/shims/express-compat/index.ts +0 -80
- package/cloudflare/shims/express-compat/types.ts +0 -41
- package/cloudflare/shims/lock.ts +0 -115
- package/cloudflare/shims/queue.ts +0 -611
- package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/require-await */
|
|
2
|
-
|
|
2
|
+
// Phase 3 (express→hono) — hono fork of routes/coupons.ts. Sub-app with
|
|
3
|
+
// routes relative to /api/coupons (mounted via mountResourceGroup). The
|
|
4
|
+
// business logic is unchanged; only the express plumbing becomes hono:
|
|
5
|
+
// req.body → c.get('sanitizedBody') ?? {}; res.status(n).json(x) → c.json(x, n).
|
|
6
|
+
import { Hono } from 'hono';
|
|
3
7
|
import Joi from 'joi';
|
|
4
8
|
import pick from 'lodash/pick';
|
|
5
9
|
|
|
6
10
|
import { CustomError, formatError } from '@blocklet/error';
|
|
7
11
|
import { fromTokenToUnit } from '@ocap/util';
|
|
8
|
-
import { authenticate } from '
|
|
9
|
-
import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '
|
|
10
|
-
import { createIdGenerator, formatMetadata } from '
|
|
11
|
-
import { trimDecimals } from '
|
|
12
|
-
import { Coupon, PaymentCurrency, PromotionCode } from '
|
|
13
|
-
import { getRedemptionData } from '
|
|
14
|
-
import logger from '
|
|
15
|
-
|
|
16
|
-
const
|
|
12
|
+
import { authenticate } from '../../middlewares/hono/security';
|
|
13
|
+
import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../../libs/api';
|
|
14
|
+
import { createIdGenerator, formatMetadata } from '../../libs/util';
|
|
15
|
+
import { trimDecimals } from '../../libs/math-utils';
|
|
16
|
+
import { Coupon, PaymentCurrency, PromotionCode } from '../../store/models';
|
|
17
|
+
import { getRedemptionData } from '../../libs/discount/redemption';
|
|
18
|
+
import logger from '../../libs/logger';
|
|
19
|
+
|
|
20
|
+
const app = new Hono();
|
|
17
21
|
const auth = authenticate({ component: true, roles: ['owner', 'admin'] });
|
|
18
22
|
|
|
19
23
|
const PromotionCodeSchema = Joi.object({
|
|
@@ -253,35 +257,39 @@ export async function createCouponAndPromotionCodes(payload: any) {
|
|
|
253
257
|
* POST /api/coupons
|
|
254
258
|
* Create a new coupon with optional promotion codes
|
|
255
259
|
*/
|
|
256
|
-
|
|
260
|
+
app.post('/', auth, async (c) => {
|
|
257
261
|
try {
|
|
258
|
-
|
|
259
|
-
|
|
262
|
+
const body = c.get('sanitizedBody') ?? {};
|
|
263
|
+
logger.info('Creating coupon with body:', body);
|
|
264
|
+
const { error, value } = createCouponSchema.validate(body);
|
|
260
265
|
if (error) {
|
|
261
|
-
return
|
|
262
|
-
|
|
263
|
-
|
|
266
|
+
return c.json(
|
|
267
|
+
{
|
|
268
|
+
error: error.details?.[0]?.message || 'Validation error',
|
|
269
|
+
},
|
|
270
|
+
400
|
|
271
|
+
);
|
|
264
272
|
}
|
|
265
273
|
|
|
266
274
|
const result = await createCouponAndPromotionCodes({
|
|
267
275
|
...value,
|
|
268
|
-
livemode:
|
|
269
|
-
created_via:
|
|
276
|
+
livemode: c.get('livemode'),
|
|
277
|
+
created_via: c.get('user')?.via || 'api',
|
|
270
278
|
});
|
|
271
279
|
|
|
272
280
|
logger.info('Coupon and promotion codes created', {
|
|
273
281
|
couponId: result.coupon.id,
|
|
274
282
|
promotionCodesCount: result.promotion_codes.length,
|
|
275
|
-
requestedBy:
|
|
283
|
+
requestedBy: c.get('user')?.did,
|
|
276
284
|
});
|
|
277
285
|
|
|
278
|
-
return
|
|
286
|
+
return c.json(result);
|
|
279
287
|
} catch (error) {
|
|
280
288
|
logger.error('Error creating coupon', {
|
|
281
289
|
error,
|
|
282
|
-
body:
|
|
290
|
+
body: c.get('sanitizedBody') ?? {},
|
|
283
291
|
});
|
|
284
|
-
return
|
|
292
|
+
return c.json(formatError(error), 500);
|
|
285
293
|
}
|
|
286
294
|
});
|
|
287
295
|
|
|
@@ -298,20 +306,21 @@ const paginationSchema = createListParamSchema<{
|
|
|
298
306
|
* GET /api/coupons
|
|
299
307
|
* List all coupons with pagination and filtering
|
|
300
308
|
*/
|
|
301
|
-
|
|
302
|
-
const
|
|
309
|
+
app.get('/', auth, async (c) => {
|
|
310
|
+
const query = c.req.query();
|
|
311
|
+
const { page, pageSize, valid, name, ...rest } = await paginationSchema.validateAsync(query, {
|
|
303
312
|
stripUnknown: false,
|
|
304
313
|
allowUnknown: true,
|
|
305
314
|
});
|
|
306
315
|
|
|
307
|
-
const where =
|
|
316
|
+
const where = rest.q ? getWhereFromKvQuery(rest.q as any) : {};
|
|
308
317
|
|
|
309
318
|
if (valid !== undefined) {
|
|
310
319
|
where.valid = !!valid;
|
|
311
320
|
}
|
|
312
321
|
|
|
313
|
-
if (typeof
|
|
314
|
-
where.livemode = !!
|
|
322
|
+
if (typeof c.get('livemode') === 'boolean') {
|
|
323
|
+
where.livemode = !!c.get('livemode');
|
|
315
324
|
}
|
|
316
325
|
|
|
317
326
|
if (name) {
|
|
@@ -329,10 +338,10 @@ router.get('/', auth, async (req, res) => {
|
|
|
329
338
|
],
|
|
330
339
|
limit: pageSize,
|
|
331
340
|
offset: (page - 1) * pageSize,
|
|
332
|
-
order: getOrder(
|
|
341
|
+
order: getOrder(query, [['created_at', 'DESC']]),
|
|
333
342
|
});
|
|
334
343
|
|
|
335
|
-
return
|
|
344
|
+
return c.json({
|
|
336
345
|
count,
|
|
337
346
|
list: rows,
|
|
338
347
|
paging: { page, pageSize },
|
|
@@ -343,47 +352,51 @@ router.get('/', auth, async (req, res) => {
|
|
|
343
352
|
* GET /api/coupons/:id
|
|
344
353
|
* Retrieve a specific coupon
|
|
345
354
|
*/
|
|
346
|
-
|
|
347
|
-
const coupon = await getExpandedCoupon(req.
|
|
355
|
+
app.get('/:id', auth, async (c) => {
|
|
356
|
+
const coupon = await getExpandedCoupon(c.req.param('id') as string);
|
|
348
357
|
if (!coupon) {
|
|
349
|
-
return
|
|
358
|
+
return c.json({ error: 'Coupon not found' }, 404);
|
|
350
359
|
}
|
|
351
|
-
return
|
|
360
|
+
return c.json(coupon);
|
|
352
361
|
});
|
|
353
362
|
|
|
354
363
|
/**
|
|
355
364
|
* PUT /api/coupons/:id
|
|
356
365
|
* Update a coupon (limited fields can be updated)
|
|
357
366
|
*/
|
|
358
|
-
|
|
359
|
-
const
|
|
367
|
+
app.put('/:id', auth, async (c) => {
|
|
368
|
+
const body = c.get('sanitizedBody') ?? {};
|
|
369
|
+
const { error, value } = updateCouponSchema.validate(body);
|
|
360
370
|
if (error) {
|
|
361
|
-
return
|
|
362
|
-
|
|
363
|
-
|
|
371
|
+
return c.json(
|
|
372
|
+
{
|
|
373
|
+
error: error.details?.[0]?.message || 'Validation error',
|
|
374
|
+
},
|
|
375
|
+
400
|
|
376
|
+
);
|
|
364
377
|
}
|
|
365
378
|
|
|
366
|
-
if (!req.
|
|
367
|
-
return
|
|
379
|
+
if (!c.req.param('id')) {
|
|
380
|
+
return c.json({ error: 'Coupon ID is required' }, 400);
|
|
368
381
|
}
|
|
369
382
|
|
|
370
|
-
const coupon = await Coupon.findByPk(req.
|
|
383
|
+
const coupon = await Coupon.findByPk(c.req.param('id'));
|
|
371
384
|
|
|
372
385
|
if (!coupon) {
|
|
373
|
-
return
|
|
386
|
+
return c.json({ error: 'Coupon not found' }, 404);
|
|
374
387
|
}
|
|
375
388
|
|
|
376
|
-
if (
|
|
377
|
-
const { error: metadataError } = MetadataSchema.validate(
|
|
389
|
+
if ((body as any).metadata) {
|
|
390
|
+
const { error: metadataError } = MetadataSchema.validate((body as any).metadata);
|
|
378
391
|
if (metadataError) {
|
|
379
|
-
return
|
|
392
|
+
return c.json({ error: `metadata invalid: ${metadataError.message}` }, 400);
|
|
380
393
|
}
|
|
381
394
|
}
|
|
382
395
|
|
|
383
396
|
if (coupon.locked) {
|
|
384
397
|
const allowedUpdates = pick(value, ['name', 'metadata', 'description']);
|
|
385
398
|
if (Object.keys(allowedUpdates).length === 0) {
|
|
386
|
-
return
|
|
399
|
+
return c.json({ error: 'Coupon is locked and cannot be modified' }, 403);
|
|
387
400
|
}
|
|
388
401
|
await coupon.update(Coupon.formatBeforeSave(allowedUpdates));
|
|
389
402
|
}
|
|
@@ -402,9 +415,12 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
402
415
|
await coupon.update({ locked: true });
|
|
403
416
|
const allowedUpdates = pick(value, ['name', 'metadata', 'description']);
|
|
404
417
|
if (Object.keys(allowedUpdates).length === 0) {
|
|
405
|
-
return
|
|
406
|
-
|
|
407
|
-
|
|
418
|
+
return c.json(
|
|
419
|
+
{
|
|
420
|
+
error: 'Coupon is being used. Only name and metadata can be updated.',
|
|
421
|
+
},
|
|
422
|
+
403
|
|
423
|
+
);
|
|
408
424
|
}
|
|
409
425
|
await coupon.update(Coupon.formatBeforeSave(allowedUpdates));
|
|
410
426
|
} else if (!coupon.locked && !isUsed) {
|
|
@@ -413,35 +429,35 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
413
429
|
}
|
|
414
430
|
|
|
415
431
|
logger.info('Coupon updated', {
|
|
416
|
-
couponId: req.
|
|
432
|
+
couponId: c.req.param('id'),
|
|
417
433
|
updatedFields: Object.keys(value),
|
|
418
|
-
requestedBy:
|
|
434
|
+
requestedBy: c.get('user')?.did,
|
|
419
435
|
});
|
|
420
436
|
|
|
421
|
-
const doc = await getExpandedCoupon(req.
|
|
437
|
+
const doc = await getExpandedCoupon(c.req.param('id') as string);
|
|
422
438
|
|
|
423
|
-
return
|
|
439
|
+
return c.json(doc);
|
|
424
440
|
});
|
|
425
441
|
|
|
426
442
|
/**
|
|
427
443
|
* DELETE /api/coupons/:id
|
|
428
444
|
* Delete a coupon (mark as invalid if used, otherwise hard delete)
|
|
429
445
|
*/
|
|
430
|
-
|
|
446
|
+
app.delete('/:id', auth, async (c) => {
|
|
431
447
|
try {
|
|
432
448
|
const coupon = await Coupon.findOne({
|
|
433
449
|
where: {
|
|
434
|
-
id: req.
|
|
435
|
-
livemode:
|
|
450
|
+
id: c.req.param('id'),
|
|
451
|
+
livemode: c.get('livemode'),
|
|
436
452
|
},
|
|
437
453
|
});
|
|
438
454
|
|
|
439
455
|
if (!coupon) {
|
|
440
|
-
return
|
|
456
|
+
return c.json({ error: 'Coupon not found' }, 404);
|
|
441
457
|
}
|
|
442
458
|
|
|
443
459
|
if (coupon.locked) {
|
|
444
|
-
return
|
|
460
|
+
return c.json({ error: 'Coupon is locked and cannot be deleted' }, 403);
|
|
445
461
|
}
|
|
446
462
|
|
|
447
463
|
const isUsed = await coupon.isUsed();
|
|
@@ -450,36 +466,36 @@ router.delete('/:id', auth, async (req, res) => {
|
|
|
450
466
|
if (isUsed) {
|
|
451
467
|
// Mark as invalid instead of deleting
|
|
452
468
|
await coupon.update({ locked: true });
|
|
453
|
-
return
|
|
469
|
+
return c.json({ error: 'Coupon is being used and cannot be deleted' }, 403);
|
|
454
470
|
}
|
|
455
471
|
|
|
456
472
|
// Hard delete if not used
|
|
457
473
|
await coupon.destroy();
|
|
458
474
|
|
|
459
475
|
logger.info('Coupon deleted', {
|
|
460
|
-
couponId: req.
|
|
476
|
+
couponId: c.req.param('id'),
|
|
461
477
|
});
|
|
462
478
|
|
|
463
|
-
return
|
|
479
|
+
return c.json(coupon);
|
|
464
480
|
} catch (error) {
|
|
465
481
|
logger.error('Error deleting coupon', {
|
|
466
|
-
error: error.message,
|
|
467
|
-
couponId: req.
|
|
482
|
+
error: (error as any).message,
|
|
483
|
+
couponId: c.req.param('id'),
|
|
468
484
|
});
|
|
469
|
-
return
|
|
485
|
+
return c.json(formatError(error), 400);
|
|
470
486
|
}
|
|
471
487
|
});
|
|
472
488
|
|
|
473
|
-
|
|
474
|
-
const coupon = await Coupon.findByPk(req.
|
|
489
|
+
app.get('/:id/used', auth, async (c) => {
|
|
490
|
+
const coupon = await Coupon.findByPk(c.req.param('id'));
|
|
475
491
|
if (!coupon) {
|
|
476
|
-
return
|
|
492
|
+
return c.json({ error: 'Coupon not found' }, 404);
|
|
477
493
|
}
|
|
478
494
|
const used = await coupon.isUsed();
|
|
479
495
|
if (used && !coupon.locked) {
|
|
480
496
|
await coupon.update({ locked: true });
|
|
481
497
|
}
|
|
482
|
-
return
|
|
498
|
+
return c.json({ used });
|
|
483
499
|
});
|
|
484
500
|
|
|
485
501
|
// Create redemptions pagination schema
|
|
@@ -490,18 +506,19 @@ const redemptionsSchema = createListParamSchema<{
|
|
|
490
506
|
});
|
|
491
507
|
|
|
492
508
|
// Get active redemptions for a coupon with detailed admin analytics
|
|
493
|
-
|
|
509
|
+
app.get('/:id/redemptions', auth, async (c) => {
|
|
494
510
|
try {
|
|
495
|
-
const couponId = req.
|
|
511
|
+
const couponId = c.req.param('id') as string;
|
|
512
|
+
const query = c.req.query();
|
|
496
513
|
|
|
497
|
-
const { page, pageSize, type } = await redemptionsSchema.validateAsync(
|
|
514
|
+
const { page, pageSize, type } = await redemptionsSchema.validateAsync(query, {
|
|
498
515
|
stripUnknown: false,
|
|
499
516
|
allowUnknown: true,
|
|
500
517
|
});
|
|
501
518
|
|
|
502
519
|
const coupon = await Coupon.findByPk(couponId);
|
|
503
520
|
if (!coupon) {
|
|
504
|
-
return
|
|
521
|
+
return c.json({ error: 'Coupon not found' }, 404);
|
|
505
522
|
}
|
|
506
523
|
|
|
507
524
|
const result = await getRedemptionData(
|
|
@@ -511,15 +528,15 @@ router.get('/:id/redemptions', auth, async (req, res) => {
|
|
|
511
528
|
'coupon'
|
|
512
529
|
);
|
|
513
530
|
|
|
514
|
-
return
|
|
531
|
+
return c.json(result);
|
|
515
532
|
} catch (error: any) {
|
|
516
533
|
logger.error('Error getting coupon redemptions', {
|
|
517
534
|
error: error.message,
|
|
518
535
|
stack: error.stack,
|
|
519
|
-
couponId: req.
|
|
536
|
+
couponId: c.req.param('id'),
|
|
520
537
|
});
|
|
521
|
-
return
|
|
538
|
+
return c.json(formatError(error), 500);
|
|
522
539
|
}
|
|
523
540
|
});
|
|
524
541
|
|
|
525
|
-
export default
|
|
542
|
+
export default app;
|