payment-kit 1.29.1 → 1.29.3
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 +47 -0
- package/api/src/crons/base.ts +3 -3
- package/api/src/crons/currency.ts +1 -1
- package/api/src/crons/index.ts +41 -37
- 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/crons/tenant-fanout.ts +82 -0
- package/api/src/host-node/did-connect-runtime-node.ts +33 -0
- package/api/src/host-node/serve-static-arc.ts +68 -0
- package/api/src/host-node/serve-static.ts +41 -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 +247 -47
- package/api/src/libs/context.ts +89 -1
- package/api/src/libs/currency.ts +2 -2
- package/api/src/libs/dayjs.ts +8 -2
- package/api/src/libs/did-connect/runtime-did-connect-js.ts +88 -0
- package/api/src/libs/did-connect/tenant-identity.ts +221 -0
- 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 +142 -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 +60 -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 +271 -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 +80 -0
- package/api/src/middlewares/hono/csrf.ts +83 -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 +209 -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 +38 -21
- 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 +41 -11
- 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 +64 -37
- package/api/src/queues/payout.ts +37 -21
- package/api/src/queues/refund.ts +36 -18
- 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} +199 -224
- 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} +98 -83
- 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 +814 -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 +82 -23
- 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/bootstrap/bootstrap.spec.ts +162 -0
- package/api/tests/crons/tenant-fanout.spec.ts +158 -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/did-connect-runtime-js.spec.ts +98 -0
- package/api/tests/libs/did-connect-tenant-identity.spec.ts +159 -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/service-host.spec.ts +37 -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 +292 -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/service/didconnect-storage-slot.spec.ts +60 -0
- package/api/tests/service/fail-closed-http.spec.ts +79 -0
- package/api/tests/service/static-arc-handler.spec.ts +101 -0
- package/api/tests/service/static-externalized.spec.ts +48 -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/MIGRATION-RUNBOOK.md +3 -8
- package/cloudflare/README.md +34 -27
- package/cloudflare/STAGING-MIGRATION-GUIDE.md +3 -15
- package/cloudflare/build.ts +33 -13
- package/cloudflare/cf-adapter.ts +419 -0
- package/cloudflare/did-connect-runtime.ts +96 -0
- package/cloudflare/did-connect-token-storage.ts +151 -0
- package/cloudflare/esbuild-cf-config.cjs +407 -0
- 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 +33 -403
- package/cloudflare/scripts/cf-package-import-probe.mjs +90 -0
- package/cloudflare/scripts/didconnect-mock-smoke.mjs +140 -0
- 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/wallet-authenticator.ts +16 -1
- package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +18 -3
- 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/cf-adapter.spec.ts +244 -0
- package/cloudflare/tests/did-connect-token-storage.spec.ts +105 -0
- package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +69 -0
- package/cloudflare/vite.config.ts +53 -45
- package/cloudflare/worker.ts +261 -448
- package/cloudflare/wrangler.json +0 -6
- package/cloudflare/wrangler.jsonc +0 -6
- package/cloudflare/wrangler.local-e2e.jsonc +25 -0
- package/cloudflare/wrangler.staging.json +0 -6
- package/jest.config.js +3 -1
- package/package.json +33 -38
- package/scripts/bootstrap-inject.ts +166 -0
- 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/app.tsx +2 -1
- package/src/env.d.ts +13 -1
- package/src/libs/service-host.ts +13 -0
- package/tsconfig.json +1 -1
- package/vite.arc.config.ts +159 -0
- 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/did-connect-auth.ts +0 -527
- 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,23 +1,34 @@
|
|
|
1
|
+
// Phase 3 (express→hono) — hono fork of routes/products.ts. Sub-app with
|
|
2
|
+
// routes relative to /api/products (mounted via mountResourceGroup). The
|
|
3
|
+
// business logic is unchanged; only the express plumbing becomes hono:
|
|
4
|
+
// req.body → c.get('sanitizedBody'); res.status(n).json(x) → c.json(x, n).
|
|
1
5
|
import { BN, fromTokenToUnit } from '@ocap/util';
|
|
2
|
-
import {
|
|
6
|
+
import { Hono } from 'hono';
|
|
3
7
|
import Joi from 'joi';
|
|
4
8
|
import cloneDeep from 'lodash/cloneDeep';
|
|
5
9
|
import pick from 'lodash/pick';
|
|
6
10
|
import { Op } from 'sequelize';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
import { allowChangeLockedPrice } from '../../libs/env';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
createListParamSchema,
|
|
15
|
+
getOrder,
|
|
16
|
+
getWhereFromKvQuery,
|
|
17
|
+
getWhereFromQuery,
|
|
18
|
+
MetadataSchema,
|
|
19
|
+
} from '../../libs/api';
|
|
20
|
+
import logger from '../../libs/logger';
|
|
21
|
+
import { authenticate } from '../../middlewares/hono/security';
|
|
22
|
+
import { formatMetadata } from '../../libs/util';
|
|
23
|
+
import { PaymentCurrency } from '../../store/models/payment-currency';
|
|
24
|
+
import { Price } from '../../store/models/price';
|
|
25
|
+
import { Product } from '../../store/models/product';
|
|
26
|
+
import { ProductVendor } from '../../store/models/product-vendor';
|
|
27
|
+
|
|
28
|
+
import type { CustomUnitAmount } from '../../store/models/types';
|
|
29
|
+
import { checkCurrencySupportRecurring } from '../../libs/product';
|
|
30
|
+
|
|
31
|
+
const app = new Hono();
|
|
21
32
|
|
|
22
33
|
const auth = authenticate<Product>({ component: true, roles: ['owner', 'admin'] });
|
|
23
34
|
|
|
@@ -40,7 +51,7 @@ const ProductAndPriceSchema = Joi.object({
|
|
|
40
51
|
tax_code: Joi.string().max(30).allow(null).empty('').optional(),
|
|
41
52
|
statement_descriptor: Joi.string()
|
|
42
53
|
.max(22)
|
|
43
|
-
.pattern(/^(?=.*[A-Za-z])[^\u4e00-\u9fa5<>"’\\]*$/)
|
|
54
|
+
.pattern(/^(?=.*[A-Za-z])[^\u4e00-\u9fa5<>"'’\\]*$/)
|
|
44
55
|
.messages({
|
|
45
56
|
'string.pattern.base':
|
|
46
57
|
'statement_descriptor should be at least one letter and cannot include Chinese characters and special characters such as <, >、"、’ or \\',
|
|
@@ -283,32 +294,106 @@ export async function createProductAndPrices(payload: any) {
|
|
|
283
294
|
|
|
284
295
|
// FIXME: @wangshijun use schema validation
|
|
285
296
|
// create product and price
|
|
286
|
-
//
|
|
287
|
-
|
|
297
|
+
// static POST route registered before /:id patterns to avoid shadowing
|
|
298
|
+
app.post('/batch-price-update', auth, async (c) => {
|
|
299
|
+
if (!allowChangeLockedPrice()) {
|
|
300
|
+
return c.json({ error: 'forbidden' }, 403);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const body = c.get('sanitizedBody') ?? {};
|
|
304
|
+
const dryRun = typeof body.dryRun === 'undefined' || body.dryRun !== '0';
|
|
305
|
+
const factor = Number(body.factor);
|
|
306
|
+
if (Number.isNaN(factor)) {
|
|
307
|
+
return c.json({ error: 'factor is required' }, 400);
|
|
308
|
+
}
|
|
309
|
+
if (factor <= 0) {
|
|
310
|
+
return c.json({ error: 'factor must be positive' }, 400);
|
|
311
|
+
}
|
|
312
|
+
if (!body.description) {
|
|
313
|
+
return c.json({ error: 'description is required' }, 400);
|
|
314
|
+
}
|
|
315
|
+
if (!body.before) {
|
|
316
|
+
return c.json({ error: 'before is required' }, 400);
|
|
317
|
+
}
|
|
318
|
+
|
|
288
319
|
try {
|
|
289
|
-
const
|
|
320
|
+
const updated: any[] = [];
|
|
321
|
+
const products = await Product.findAll({
|
|
322
|
+
where: { active: false, livemode: !!c.get('livemode'), created_at: { [Op.lt]: body.before } },
|
|
323
|
+
});
|
|
324
|
+
await Promise.all(
|
|
325
|
+
products.map(async (product) => {
|
|
326
|
+
if (!product.metadata?.domain) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const prices = await Price.findAll({ where: { product_id: product.id } });
|
|
331
|
+
for (const price of prices) {
|
|
332
|
+
if (price.currency_id === c.get('baseCurrency').id) {
|
|
333
|
+
const unit = new BN(price.unit_amount).div(new BN(factor)).toString();
|
|
334
|
+
const options = cloneDeep(price.currency_options);
|
|
335
|
+
const option = options.find((x) => x.currency_id === price.currency_id);
|
|
336
|
+
if (option) {
|
|
337
|
+
option.unit_amount = unit;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const metadata = price.metadata || {};
|
|
341
|
+
metadata.update_history = [metadata.update_history, body.description].filter(Boolean).join(';');
|
|
342
|
+
|
|
343
|
+
if (dryRun) {
|
|
344
|
+
updated.push({ price: price.toJSON(), metadata, unit_amount: unit, currency_options: options });
|
|
345
|
+
} else {
|
|
346
|
+
// eslint-disable-next-line
|
|
347
|
+
await Price.update(
|
|
348
|
+
{ metadata, unit_amount: unit, currency_options: options },
|
|
349
|
+
{ where: { id: price.id } }
|
|
350
|
+
);
|
|
351
|
+
updated.push(product.id);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
logger.info('Batch price update completed', {
|
|
359
|
+
updatedCount: updated.length,
|
|
360
|
+
dryRun,
|
|
361
|
+
factor,
|
|
362
|
+
requestedBy: c.get('user')?.did,
|
|
363
|
+
});
|
|
364
|
+
return c.json(updated);
|
|
365
|
+
} catch (err) {
|
|
366
|
+
logger.error('batch price update error', err);
|
|
367
|
+
return c.json({ error: (err as any).message }, 400);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
app.post('/', auth, async (c) => {
|
|
372
|
+
try {
|
|
373
|
+
const body = c.get('sanitizedBody') ?? {};
|
|
374
|
+
const { error } = ProductAndPriceSchema.validate(body);
|
|
290
375
|
if (error) {
|
|
291
|
-
return
|
|
376
|
+
return c.json({ error: `Product create request invalid: ${error.message}` }, 400);
|
|
292
377
|
}
|
|
293
378
|
const result = await createProductAndPrices({
|
|
294
|
-
...
|
|
379
|
+
...body,
|
|
295
380
|
active: true,
|
|
296
|
-
type:
|
|
297
|
-
livemode: !!
|
|
298
|
-
created_via:
|
|
299
|
-
currency_id:
|
|
300
|
-
metadata: formatMetadata(
|
|
381
|
+
type: body.type || 'service',
|
|
382
|
+
livemode: !!c.get('livemode'),
|
|
383
|
+
created_via: c.get('user')?.via,
|
|
384
|
+
currency_id: c.get('baseCurrency').id,
|
|
385
|
+
metadata: formatMetadata(body.metadata),
|
|
301
386
|
});
|
|
302
387
|
logger.info('Product and prices created', {
|
|
303
388
|
productId: result.id,
|
|
304
389
|
name: result.name,
|
|
305
390
|
priceCount: result.prices.length,
|
|
306
|
-
requestedBy:
|
|
391
|
+
requestedBy: c.get('user')?.did,
|
|
307
392
|
});
|
|
308
|
-
|
|
393
|
+
return c.json(result);
|
|
309
394
|
} catch (err) {
|
|
310
395
|
logger.error('create product error', err);
|
|
311
|
-
return
|
|
396
|
+
return c.json({ error: (err as any).message }, 400);
|
|
312
397
|
}
|
|
313
398
|
});
|
|
314
399
|
|
|
@@ -330,16 +415,17 @@ const paginationSchema = createListParamSchema<{
|
|
|
330
415
|
meter_id: Joi.string().empty(''),
|
|
331
416
|
type: Joi.string().empty(''),
|
|
332
417
|
});
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
418
|
+
app.get('/', auth, async (c) => {
|
|
419
|
+
const query = c.req.query();
|
|
420
|
+
const { page, pageSize, active, livemode, name, description, ...rest } = await paginationSchema.validateAsync(query, {
|
|
421
|
+
stripUnknown: false,
|
|
422
|
+
allowUnknown: true,
|
|
423
|
+
});
|
|
424
|
+
const where = getWhereFromKvQuery(rest.q);
|
|
425
|
+
|
|
426
|
+
if (rest.status) {
|
|
341
427
|
// 兼容处理,支持 status
|
|
342
|
-
where.active =
|
|
428
|
+
where.active = rest.status === 'active';
|
|
343
429
|
}
|
|
344
430
|
|
|
345
431
|
if (typeof active === 'boolean') {
|
|
@@ -354,36 +440,36 @@ router.get('/', auth, async (req, res) => {
|
|
|
354
440
|
if (description) {
|
|
355
441
|
where.description = description;
|
|
356
442
|
}
|
|
357
|
-
if (
|
|
443
|
+
if (rest.donation === 'hide') {
|
|
358
444
|
where.created_via = { [Op.not]: 'donation' };
|
|
359
445
|
}
|
|
360
|
-
Object.keys(
|
|
446
|
+
Object.keys(rest)
|
|
361
447
|
.filter((x) => x.startsWith('metadata.'))
|
|
362
448
|
.forEach((key: string) => {
|
|
363
449
|
// @ts-ignore
|
|
364
|
-
where[key] =
|
|
450
|
+
where[key] = rest[key];
|
|
365
451
|
});
|
|
366
452
|
|
|
367
|
-
if (
|
|
453
|
+
if (rest.type === 'credit') {
|
|
368
454
|
where.type = 'credit';
|
|
369
455
|
}
|
|
370
456
|
const findOptions: any = {
|
|
371
457
|
where,
|
|
372
|
-
order: getOrder(
|
|
458
|
+
order: getOrder(query, [['created_at', rest.o === 'asc' ? 'ASC' : 'DESC']]),
|
|
373
459
|
offset: (page - 1) * pageSize,
|
|
374
460
|
limit: pageSize,
|
|
375
461
|
include: [{ model: Price, as: 'prices' }],
|
|
376
462
|
distinct: true,
|
|
377
463
|
};
|
|
378
464
|
|
|
379
|
-
if (
|
|
465
|
+
if (rest.type !== 'credit' && rest.meter_id) {
|
|
380
466
|
findOptions.include = [
|
|
381
467
|
{
|
|
382
468
|
model: Price,
|
|
383
469
|
as: 'prices',
|
|
384
470
|
where: {
|
|
385
471
|
recurring: {
|
|
386
|
-
meter_id:
|
|
472
|
+
meter_id: rest.meter_id,
|
|
387
473
|
},
|
|
388
474
|
},
|
|
389
475
|
required: true,
|
|
@@ -391,14 +477,14 @@ router.get('/', auth, async (req, res) => {
|
|
|
391
477
|
];
|
|
392
478
|
}
|
|
393
479
|
|
|
394
|
-
if (
|
|
480
|
+
if (rest.type === 'credit' && rest.meter_id) {
|
|
395
481
|
findOptions.include = [
|
|
396
482
|
{
|
|
397
483
|
model: Price,
|
|
398
484
|
as: 'prices',
|
|
399
485
|
where: {
|
|
400
486
|
metadata: {
|
|
401
|
-
meter_id:
|
|
487
|
+
meter_id: rest.meter_id,
|
|
402
488
|
},
|
|
403
489
|
},
|
|
404
490
|
required: true,
|
|
@@ -408,17 +494,18 @@ router.get('/', auth, async (req, res) => {
|
|
|
408
494
|
|
|
409
495
|
const { rows: list, count } = await Product.findAndCountAll(findOptions);
|
|
410
496
|
|
|
411
|
-
|
|
497
|
+
return c.json({ count, list, paging: { page, pageSize } });
|
|
412
498
|
});
|
|
413
499
|
|
|
414
500
|
// search products
|
|
501
|
+
// registered before /:id to prevent shadowing
|
|
415
502
|
const searchSchema = createListParamSchema<{
|
|
416
503
|
query: string;
|
|
417
504
|
}>({
|
|
418
505
|
query: Joi.string(),
|
|
419
506
|
});
|
|
420
|
-
|
|
421
|
-
const { page, pageSize, query, livemode, q } = await searchSchema.validateAsync(req.query, {
|
|
507
|
+
app.get('/search', auth, async (c) => {
|
|
508
|
+
const { page, pageSize, query, livemode, q } = await searchSchema.validateAsync(c.req.query(), {
|
|
422
509
|
stripUnknown: false,
|
|
423
510
|
allowUnknown: true,
|
|
424
511
|
});
|
|
@@ -430,37 +517,37 @@ router.get('/search', auth, async (req, res) => {
|
|
|
430
517
|
|
|
431
518
|
const { rows: list, count } = await Product.findAndCountAll({
|
|
432
519
|
where,
|
|
433
|
-
order: getOrder(req.query, [['created_at', 'DESC']]),
|
|
520
|
+
order: getOrder(c.req.query(), [['created_at', 'DESC']]),
|
|
434
521
|
offset: (page - 1) * pageSize,
|
|
435
522
|
limit: pageSize,
|
|
436
523
|
include: [{ model: Price, as: 'prices', separate: true }],
|
|
437
524
|
});
|
|
438
525
|
|
|
439
|
-
|
|
526
|
+
return c.json({ count, list });
|
|
440
527
|
});
|
|
441
528
|
|
|
442
529
|
// get product detail
|
|
443
|
-
|
|
444
|
-
const doc = await Product.expand(req.
|
|
530
|
+
app.get('/:id', async (c) => {
|
|
531
|
+
const doc = await Product.expand(c.req.param('id') as string);
|
|
445
532
|
if (doc) {
|
|
446
|
-
|
|
447
|
-
} else {
|
|
448
|
-
res.status(404).json(null);
|
|
533
|
+
return c.json(doc);
|
|
449
534
|
}
|
|
535
|
+
return c.json(null, 404);
|
|
450
536
|
});
|
|
451
537
|
|
|
452
538
|
// update product
|
|
453
|
-
|
|
454
|
-
const product = await Product.findByPk(req.
|
|
539
|
+
app.put('/:id', auth, async (c) => {
|
|
540
|
+
const product = await Product.findByPk(c.req.param('id'));
|
|
455
541
|
|
|
456
542
|
if (!product) {
|
|
457
|
-
return
|
|
543
|
+
return c.json({ error: 'product not found' }, 404);
|
|
458
544
|
}
|
|
459
545
|
if (product.locked) {
|
|
460
|
-
return
|
|
546
|
+
return c.json({ error: 'product locked' }, 403);
|
|
461
547
|
}
|
|
462
548
|
|
|
463
|
-
const
|
|
549
|
+
const body = c.get('sanitizedBody') ?? {};
|
|
550
|
+
const updates: Partial<Product> = pick(body, [
|
|
464
551
|
'status',
|
|
465
552
|
'name',
|
|
466
553
|
'description',
|
|
@@ -476,19 +563,19 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
476
563
|
'tax_code',
|
|
477
564
|
]);
|
|
478
565
|
|
|
479
|
-
if (Array.isArray(
|
|
480
|
-
const { error: vendorConfigError } = VendorConfigSchema.validate(
|
|
566
|
+
if (Array.isArray(body.vendor_config)) {
|
|
567
|
+
const { error: vendorConfigError } = VendorConfigSchema.validate(body.vendor_config);
|
|
481
568
|
if (vendorConfigError) {
|
|
482
|
-
return
|
|
569
|
+
return c.json({ error: `vendor_config validation failed: ${vendorConfigError.message}` }, 400);
|
|
483
570
|
}
|
|
484
571
|
|
|
485
572
|
const vendorConfigs = await ProductVendor.findAll({
|
|
486
573
|
where: {
|
|
487
|
-
id:
|
|
574
|
+
id: body.vendor_config.map((x: any) => x.vendor_id),
|
|
488
575
|
},
|
|
489
576
|
});
|
|
490
577
|
|
|
491
|
-
updates.vendor_config =
|
|
578
|
+
updates.vendor_config = body.vendor_config.map((config: any) => {
|
|
492
579
|
const vendorConfig = vendorConfigs.find((x) => x.id === config.vendor_id);
|
|
493
580
|
if (!vendorConfig) {
|
|
494
581
|
throw new Error(`vendor ${config.vendor_id} not found`);
|
|
@@ -507,7 +594,7 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
507
594
|
}
|
|
508
595
|
const { error } = ProductAndPriceSchema.validate(updates);
|
|
509
596
|
if (error) {
|
|
510
|
-
return
|
|
597
|
+
return c.json({ error: `Product update request invalid: ${error.message}` }, 400);
|
|
511
598
|
}
|
|
512
599
|
if (updates.metadata) {
|
|
513
600
|
updates.metadata = formatMetadata(updates.metadata);
|
|
@@ -516,48 +603,48 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
516
603
|
logger.info('Product updated', {
|
|
517
604
|
productId: product.id,
|
|
518
605
|
updatedFields: Object.keys(updates),
|
|
519
|
-
requestedBy:
|
|
606
|
+
requestedBy: c.get('user')?.did,
|
|
520
607
|
});
|
|
521
|
-
return
|
|
608
|
+
return c.json(await Product.expand(c.req.param('id') as string));
|
|
522
609
|
});
|
|
523
610
|
|
|
524
611
|
// archive
|
|
525
|
-
|
|
526
|
-
const product = await Product.findByPk(req.
|
|
612
|
+
app.put('/:id/archive', auth, async (c) => {
|
|
613
|
+
const product = await Product.findByPk(c.req.param('id'));
|
|
527
614
|
|
|
528
615
|
if (!product) {
|
|
529
|
-
return
|
|
616
|
+
return c.json({ error: 'product not found' }, 404);
|
|
530
617
|
}
|
|
531
618
|
if (product.locked) {
|
|
532
|
-
return
|
|
619
|
+
return c.json({ error: 'product locked' }, 403);
|
|
533
620
|
}
|
|
534
621
|
|
|
535
622
|
await product.update({ active: !product.active });
|
|
536
623
|
|
|
537
624
|
// FIXME: deactivate payment-links, pricing-tables
|
|
538
|
-
return
|
|
625
|
+
return c.json(await Product.expand(c.req.param('id') as string));
|
|
539
626
|
});
|
|
540
627
|
|
|
541
628
|
// delete product
|
|
542
|
-
|
|
629
|
+
app.delete('/:id', auth, async (c) => {
|
|
543
630
|
try {
|
|
544
|
-
const product = await Product.findByPk(req.
|
|
631
|
+
const product = await Product.findByPk(c.req.param('id'));
|
|
545
632
|
|
|
546
633
|
if (!product) {
|
|
547
|
-
return
|
|
634
|
+
return c.json({ error: 'product not found' }, 404);
|
|
548
635
|
}
|
|
549
636
|
if (product.locked) {
|
|
550
|
-
return
|
|
637
|
+
return c.json({ error: 'product locked' }, 403);
|
|
551
638
|
}
|
|
552
639
|
|
|
553
640
|
const prices = await Price.findAll({ where: { product_id: product.id } });
|
|
554
641
|
for (const price of prices) {
|
|
555
642
|
if (price.locked) {
|
|
556
|
-
return
|
|
643
|
+
return c.json({ error: 'product have prices that is locked' }, 403);
|
|
557
644
|
}
|
|
558
645
|
// eslint-disable-next-line no-await-in-loop
|
|
559
646
|
if (await price.isUsed(false)) {
|
|
560
|
-
return
|
|
647
|
+
return c.json({ error: 'product have prices that is used by other resources' }, 403);
|
|
561
648
|
}
|
|
562
649
|
}
|
|
563
650
|
|
|
@@ -565,87 +652,13 @@ router.delete('/:id', auth, async (req, res) => {
|
|
|
565
652
|
await Price.destroy({ where: { product_id: product.id } });
|
|
566
653
|
logger.info('Product and associated prices deleted', {
|
|
567
654
|
productId: product.id,
|
|
568
|
-
requestedBy:
|
|
655
|
+
requestedBy: c.get('user')?.did,
|
|
569
656
|
});
|
|
570
|
-
return
|
|
657
|
+
return c.json(product);
|
|
571
658
|
} catch (err) {
|
|
572
659
|
logger.error('delete product error', err);
|
|
573
|
-
return
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
// This is a dangerous operation, only allow to run with a special env variable
|
|
578
|
-
// Only meant to be used in DID Domain
|
|
579
|
-
router.post('/batch-price-update', auth, async (req, res) => {
|
|
580
|
-
if (process.env.PAYMENT_CHANGE_LOCKED_PRICE !== '1') {
|
|
581
|
-
return res.status(403).json({ error: 'forbidden' });
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const dryRun = typeof req.body.dryRun === 'undefined' || req.body.dryRun !== '0';
|
|
585
|
-
const factor = Number(req.body.factor);
|
|
586
|
-
if (Number.isNaN(factor)) {
|
|
587
|
-
return res.status(400).json({ error: 'factor is required' });
|
|
588
|
-
}
|
|
589
|
-
if (factor <= 0) {
|
|
590
|
-
return res.status(400).json({ error: 'factor must be positive' });
|
|
591
|
-
}
|
|
592
|
-
if (!req.body.description) {
|
|
593
|
-
return res.status(400).json({ error: 'description is required' });
|
|
594
|
-
}
|
|
595
|
-
if (!req.body.before) {
|
|
596
|
-
return res.status(400).json({ error: 'before is required' });
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
try {
|
|
600
|
-
const updated: any[] = [];
|
|
601
|
-
const products = await Product.findAll({
|
|
602
|
-
where: { active: false, livemode: !!req.livemode, created_at: { [Op.lt]: req.body.before } },
|
|
603
|
-
});
|
|
604
|
-
await Promise.all(
|
|
605
|
-
products.map(async (product) => {
|
|
606
|
-
if (!product.metadata?.domain) {
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
const prices = await Price.findAll({ where: { product_id: product.id } });
|
|
611
|
-
for (const price of prices) {
|
|
612
|
-
if (price.currency_id === req.baseCurrency.id) {
|
|
613
|
-
const unit = new BN(price.unit_amount).div(new BN(factor)).toString();
|
|
614
|
-
const options = cloneDeep(price.currency_options);
|
|
615
|
-
const option = options.find((x) => x.currency_id === price.currency_id);
|
|
616
|
-
if (option) {
|
|
617
|
-
option.unit_amount = unit;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
const metadata = price.metadata || {};
|
|
621
|
-
metadata.update_history = [metadata.update_history, req.body.description].filter(Boolean).join(';');
|
|
622
|
-
|
|
623
|
-
if (dryRun) {
|
|
624
|
-
updated.push({ price: price.toJSON(), metadata, unit_amount: unit, currency_options: options });
|
|
625
|
-
} else {
|
|
626
|
-
// eslint-disable-next-line
|
|
627
|
-
await Price.update(
|
|
628
|
-
{ metadata, unit_amount: unit, currency_options: options },
|
|
629
|
-
{ where: { id: price.id } }
|
|
630
|
-
);
|
|
631
|
-
updated.push(product.id);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
})
|
|
636
|
-
);
|
|
637
|
-
|
|
638
|
-
logger.info('Batch price update completed', {
|
|
639
|
-
updatedCount: updated.length,
|
|
640
|
-
dryRun,
|
|
641
|
-
factor,
|
|
642
|
-
requestedBy: req.user?.did,
|
|
643
|
-
});
|
|
644
|
-
return res.json(updated);
|
|
645
|
-
} catch (err) {
|
|
646
|
-
logger.error('batch price update error', err);
|
|
647
|
-
return res.status(400).json({ error: err.message });
|
|
660
|
+
return c.json({ error: (err as any).message }, 400);
|
|
648
661
|
}
|
|
649
662
|
});
|
|
650
663
|
|
|
651
|
-
export default
|
|
664
|
+
export default app;
|