payment-kit 1.27.2 → 1.29.0
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/__blocklet__.js +37 -0
- package/api/ocap-1.30-subpath-shims.d.ts +35 -0
- package/api/src/crons/index.ts +32 -0
- package/api/src/crons/metering-subscription-detection.ts +12 -14
- package/api/src/crons/overdue-detection.ts +51 -74
- package/api/src/crons/retry-pending-events.ts +58 -0
- package/api/src/integrations/app-store/apple-root-certs.ts +26 -0
- package/api/src/integrations/app-store/client.ts +369 -0
- package/api/src/integrations/app-store/handlers/index.ts +46 -0
- package/api/src/integrations/app-store/handlers/subscription.ts +635 -0
- package/api/src/integrations/app-store/node-apple-receipt-verify.d.ts +17 -0
- package/api/src/integrations/app-store/notification-routing.ts +18 -0
- package/api/src/integrations/app-store/signed-data-verifier.ts +150 -0
- package/api/src/integrations/arcblock/nft.ts +6 -2
- package/api/src/integrations/arcblock/stake.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +4 -4
- package/api/src/integrations/blocklet/notification.ts +1 -1
- package/api/src/integrations/ethereum/tx.ts +29 -0
- package/api/src/integrations/google-play/client.ts +276 -0
- package/api/src/integrations/google-play/handlers/index.ts +69 -0
- package/api/src/integrations/google-play/handlers/subscription.ts +565 -0
- package/api/src/integrations/google-play/handlers/voided.ts +106 -0
- package/api/src/integrations/google-play/setup.ts +43 -0
- package/api/src/integrations/google-play/verify.ts +251 -0
- package/api/src/integrations/iap-reconcile.ts +415 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +70 -53
- package/api/src/integrations/stripe/handlers/payment-intent.ts +8 -1
- package/api/src/integrations/stripe/resource.ts +8 -0
- package/api/src/libs/audit.ts +70 -24
- package/api/src/libs/auth.ts +49 -2
- package/api/src/libs/chain-error.ts +31 -0
- package/api/src/libs/entitlement.ts +399 -0
- package/api/src/libs/env.ts +2 -0
- package/api/src/libs/error.ts +15 -0
- package/api/src/libs/event.ts +42 -1
- package/api/src/libs/invoice.ts +69 -34
- package/api/src/libs/notification/template/customer-auto-recharge-daily-limit-exceeded.ts +1 -3
- package/api/src/libs/notification/template/customer-auto-recharge-failed.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-grant-granted.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +1 -3
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -3
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -3
- package/api/src/libs/notification/template/one-time-payment-refund-succeeded.ts +1 -3
- package/api/src/libs/notification/template/one-time-payment-succeeded.ts +1 -3
- package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -3
- package/api/src/libs/notification/template/subscription-slippage-exceeded.ts +1 -3
- package/api/src/libs/notification/template/subscription-slippage-warning.ts +1 -3
- package/api/src/libs/notification/template/subscription-succeeded.ts +1 -1
- package/api/src/libs/pagination.ts +14 -9
- package/api/src/libs/payment.ts +25 -10
- package/api/src/libs/security.ts +51 -0
- package/api/src/libs/session.ts +1 -1
- package/api/src/libs/subscription.ts +13 -1
- package/api/src/libs/timing.ts +35 -0
- package/api/src/libs/util.ts +29 -15
- package/api/src/libs/wallet-migration.ts +72 -53
- package/api/src/queues/auto-recharge.ts +1 -1
- package/api/src/queues/credit-consume.ts +94 -12
- package/api/src/queues/credit-grant.ts +4 -0
- package/api/src/queues/event.ts +39 -21
- package/api/src/queues/invoice.ts +1 -0
- package/api/src/queues/payment.ts +83 -15
- package/api/src/queues/refund.ts +84 -71
- package/api/src/queues/subscription.ts +1 -0
- package/api/src/queues/webhook.ts +12 -2
- package/api/src/routes/checkout-sessions.ts +82 -43
- package/api/src/routes/connect/change-payment.ts +2 -0
- package/api/src/routes/connect/change-plan.ts +2 -0
- package/api/src/routes/connect/pay.ts +12 -3
- package/api/src/routes/connect/setup.ts +3 -1
- package/api/src/routes/connect/shared.ts +52 -39
- package/api/src/routes/connect/subscribe.ts +4 -1
- package/api/src/routes/credit-grants.ts +25 -17
- package/api/src/routes/donations.ts +2 -2
- package/api/src/routes/entitlements.ts +105 -0
- package/api/src/routes/events.ts +2 -2
- package/api/src/routes/index.ts +12 -2
- package/api/src/routes/integrations/app-store.ts +267 -0
- package/api/src/routes/integrations/google-play.ts +324 -0
- package/api/src/routes/meter-events.ts +16 -6
- package/api/src/routes/payment-links.ts +1 -1
- package/api/src/routes/payment-methods.ts +131 -1
- package/api/src/routes/settings.ts +1 -1
- package/api/src/routes/tax-rates.ts +1 -1
- package/api/src/store/migrations/20260526-iap-foundation.ts +105 -0
- package/api/src/store/models/customer.ts +37 -1
- package/api/src/store/models/entitlement-grant.ts +118 -0
- package/api/src/store/models/entitlement-product.ts +48 -0
- package/api/src/store/models/entitlement.ts +86 -0
- package/api/src/store/models/index.ts +9 -0
- package/api/src/store/models/invoice.ts +20 -0
- package/api/src/store/models/payment-method.ts +66 -1
- package/api/src/store/models/price.ts +23 -14
- package/api/src/store/models/refund.ts +10 -0
- package/api/src/store/models/subscription.ts +14 -0
- package/api/src/store/models/types.ts +32 -0
- package/api/tests/integrations/app-store/client.spec.ts +335 -0
- package/api/tests/integrations/app-store/handlers.spec.ts +480 -0
- package/api/tests/integrations/app-store/notifications.spec.ts +381 -0
- package/api/tests/integrations/app-store/signed-data-verifier.spec.ts +72 -0
- package/api/tests/integrations/app-store/webhook-routing.spec.ts +27 -0
- package/api/tests/integrations/google-play/handlers.spec.ts +341 -0
- package/api/tests/integrations/google-play/verify.spec.ts +215 -0
- package/api/tests/integrations/iap-reconcile.spec.ts +237 -0
- package/api/tests/libs/entitlement.spec.ts +347 -0
- package/api/tests/libs/wallet-migration.spec.ts +4 -4
- package/api/tests/queues/credit-consume-batch.spec.ts +5 -2
- package/api/tests/queues/credit-consume.spec.ts +8 -4
- package/api/tests/routes/credit-grants.spec.ts +1 -0
- package/blocklet.yml +1 -1
- package/cloudflare/MIGRATION-CHALLENGES.md +676 -0
- package/cloudflare/MIGRATION-RUNBOOK.md +777 -0
- package/cloudflare/README.md +499 -0
- package/cloudflare/STAGING-MIGRATION-GUIDE.md +602 -0
- package/cloudflare/build.ts +151 -0
- package/cloudflare/did-connect-auth.ts +527 -0
- package/cloudflare/docs/2026-04-22-sdk-1.30.9-upgrade-retro.md +324 -0
- package/cloudflare/docs/2026-04-24-queue-ops-followup.md +218 -0
- package/cloudflare/docs/cf-queues-ops-alert-analysis.md +663 -0
- package/cloudflare/docs/cf-workers-local-dev-and-fixes.md +284 -0
- package/cloudflare/docs/cleanup-tasks-2026-05.md +62 -0
- package/cloudflare/docs/payment-kit-platform-analysis-2026-04-20.md +354 -0
- package/cloudflare/frontend-shims/buffer-polyfill.ts +9 -0
- package/cloudflare/frontend-shims/js-sdk.ts +43 -0
- package/cloudflare/frontend-shims/mime-types.ts +46 -0
- package/cloudflare/frontend-shims/session.ts +24 -0
- package/cloudflare/frontend-shims/vite-plugin-noop.ts +6 -0
- package/cloudflare/index.html +40 -0
- package/cloudflare/migrate-to-d1.js +252 -0
- package/cloudflare/migrations/0001_initial_schema.sql +82 -0
- package/cloudflare/migrations/0002_indexes.sql +75 -0
- package/cloudflare/migrations/0003_locks_and_constraints.sql +18 -0
- package/cloudflare/migrations/0004_iap_foundation.sql +72 -0
- package/cloudflare/migrations/0005_iap_tenant_backfill.sql +112 -0
- package/cloudflare/run-build.js +391 -0
- package/cloudflare/scripts/test-decrypt.js +102 -0
- package/cloudflare/shims/arcblock-ws.ts +20 -0
- package/cloudflare/shims/axios-http-adapter.ts +4 -0
- package/cloudflare/shims/axios-lite.ts +117 -0
- package/cloudflare/shims/blocklet-sdk/auth-service.ts +33 -0
- package/cloudflare/shims/blocklet-sdk/cdn.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/component-api.ts +35 -0
- package/cloudflare/shims/blocklet-sdk/component.ts +18 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/did.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/env.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/eventbus.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/fallback.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/index.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/logger.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/middlewares.ts +15 -0
- package/cloudflare/shims/blocklet-sdk/notification.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/security.ts +53 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/verify-session.ts +44 -0
- package/cloudflare/shims/blocklet-sdk/verify-sign.ts +38 -0
- package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +6 -0
- package/cloudflare/shims/blocklet-sdk/wallet.ts +103 -0
- package/cloudflare/shims/cookie-parser.ts +3 -0
- package/cloudflare/shims/cors.ts +21 -0
- package/cloudflare/shims/cron.ts +189 -0
- package/cloudflare/shims/crypto-js-warn.ts +7 -0
- package/cloudflare/shims/did-space-js.ts +17 -0
- package/cloudflare/shims/did-space.ts +11 -0
- package/cloudflare/shims/error.ts +18 -0
- package/cloudflare/shims/express-compat/index.ts +80 -0
- package/cloudflare/shims/express-compat/types.ts +41 -0
- package/cloudflare/shims/fastq.ts +105 -0
- package/cloudflare/shims/lock.ts +115 -0
- package/cloudflare/shims/mime-types.ts +56 -0
- package/cloudflare/shims/nedb-storage.ts +9 -0
- package/cloudflare/shims/node-child-process.ts +9 -0
- package/cloudflare/shims/node-fs.ts +20 -0
- package/cloudflare/shims/node-http.ts +13 -0
- package/cloudflare/shims/node-https.ts +4 -0
- package/cloudflare/shims/node-misc.ts +15 -0
- package/cloudflare/shims/node-net.ts +8 -0
- package/cloudflare/shims/node-os.ts +14 -0
- package/cloudflare/shims/node-tty.ts +8 -0
- package/cloudflare/shims/node-zlib.ts +17 -0
- package/cloudflare/shims/noop.ts +26 -0
- package/cloudflare/shims/payment-vendor.ts +14 -0
- package/cloudflare/shims/querystring.ts +12 -0
- package/cloudflare/shims/queue.ts +611 -0
- package/cloudflare/shims/rolldown-runtime.ts +43 -0
- package/cloudflare/shims/sequelize-d1/datatypes.ts +24 -0
- package/cloudflare/shims/sequelize-d1/helpers.ts +46 -0
- package/cloudflare/shims/sequelize-d1/index.ts +34 -0
- package/cloudflare/shims/sequelize-d1/model.ts +1176 -0
- package/cloudflare/shims/sequelize-d1/operators.ts +306 -0
- package/cloudflare/shims/sequelize-d1/retry.ts +85 -0
- package/cloudflare/shims/sequelize-d1/sequelize-class.ts +119 -0
- package/cloudflare/shims/sequelize-d1/timing.ts +81 -0
- package/cloudflare/shims/sequelize-d1/types.ts +35 -0
- package/cloudflare/shims/stripe-cf.ts +29 -0
- package/cloudflare/shims/ws-lite.ts +103 -0
- package/cloudflare/shims/xss.ts +3 -0
- package/cloudflare/tests/shims/cron.spec.ts +210 -0
- package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +87 -0
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +186 -0
- package/cloudflare/vite.config.ts +162 -0
- package/cloudflare/worker.ts +1608 -0
- package/cloudflare/wrangler.json +63 -0
- package/cloudflare/wrangler.jsonc +75 -0
- package/cloudflare/wrangler.staging.json +67 -0
- package/cloudflare/wrangler.toml +28 -0
- package/jest.config.js +4 -12
- package/package.json +30 -22
- package/scripts/seed-google-play.ts +79 -0
- package/src/app.tsx +62 -4
- package/src/components/customer/link.tsx +9 -13
- package/src/components/customer/notification-preference.tsx +3 -2
- package/src/components/filter-toolbar.tsx +4 -0
- package/src/components/invoice/list.tsx +9 -1
- package/src/components/invoice-pdf/utils.ts +2 -1
- package/src/components/layout/admin.tsx +39 -5
- package/src/components/layout/user-cf.tsx +77 -0
- package/src/components/payment-intent/actions.tsx +23 -3
- package/src/components/payment-method/app-store.tsx +103 -0
- package/src/components/payment-method/form.tsx +7 -1
- package/src/components/payment-method/google-play.tsx +85 -0
- package/src/components/safe-did-address.tsx +75 -0
- package/src/components/subscription/list.tsx +20 -0
- package/src/libs/patch-user-card.ts +25 -0
- package/src/libs/util.ts +5 -7
- package/src/locales/en.tsx +63 -0
- package/src/locales/zh.tsx +63 -0
- package/src/pages/admin/billing/meter-events/index.tsx +4 -0
- package/src/pages/admin/billing/subscriptions/detail.tsx +80 -0
- package/src/pages/admin/customers/customers/detail.tsx +8 -2
- package/src/pages/admin/customers/customers/index.tsx +2 -2
- package/src/pages/admin/overview.tsx +3 -1
- package/src/pages/admin/settings/payment-methods/create.tsx +12 -0
- package/src/pages/admin/settings/payment-methods/index.tsx +1 -1
- package/src/pages/customer/subscription/detail.tsx +4 -4
- package/tsconfig.api.json +1 -6
- package/tsconfig.json +3 -4
- package/tsconfig.types.json +2 -1
- package/vite.config.ts +6 -1
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SQLite → D1 data migration script for Payment Kit
|
|
4
|
+
*
|
|
5
|
+
* Reads from the local blocklet-server SQLite database and generates
|
|
6
|
+
* SQL files that can be imported into D1 via `wrangler d1 execute`.
|
|
7
|
+
*
|
|
8
|
+
* Uses sqlite3 CLI (no npm dependencies needed).
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node cloudflare/migrate-to-d1.js [--dry-run] [--table <name>] [--skip-events]
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { execSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
// Parse args
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const flags = {
|
|
21
|
+
dryRun: args.includes('--dry-run'),
|
|
22
|
+
skipEvents: args.includes('--skip-events'),
|
|
23
|
+
batchSize: 50,
|
|
24
|
+
tables: [],
|
|
25
|
+
dbPath: path.join(
|
|
26
|
+
process.env.HOME,
|
|
27
|
+
'blocklet-server-data/.blocklet-server/data',
|
|
28
|
+
'zNKuN3XwXN7xq2NsJQjjfwujyqCxx26DhwgV',
|
|
29
|
+
'z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk',
|
|
30
|
+
'payment-kit.db'
|
|
31
|
+
),
|
|
32
|
+
outputDir: path.resolve(__dirname, 'migration-sql'),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < args.length; i++) {
|
|
36
|
+
if (args[i] === '--batch-size' && args[i + 1]) flags.batchSize = parseInt(args[++i], 10);
|
|
37
|
+
if (args[i] === '--table' && args[i + 1]) flags.tables.push(args[++i]);
|
|
38
|
+
if (args[i] === '--db-path' && args[i + 1]) flags.dbPath = args[++i];
|
|
39
|
+
if (args[i] === '--output-dir' && args[i + 1]) flags.outputDir = args[++i];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Tables to skip
|
|
43
|
+
const SKIP_TABLES = ['customers_backup', 'payment_currencies_tmp', 'SequelizeMeta'];
|
|
44
|
+
|
|
45
|
+
function sqlite3(dbPath, query) {
|
|
46
|
+
try {
|
|
47
|
+
return execSync(`sqlite3 "${dbPath}" "${query}"`, {
|
|
48
|
+
encoding: 'utf-8',
|
|
49
|
+
maxBuffer: 100 * 1024 * 1024, // 100MB buffer
|
|
50
|
+
}).trim();
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error(`sqlite3 error: ${err.message}`);
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sqlite3Json(dbPath, query) {
|
|
58
|
+
try {
|
|
59
|
+
const result = execSync(`sqlite3 -json "${dbPath}" "${query}"`, {
|
|
60
|
+
encoding: 'utf-8',
|
|
61
|
+
maxBuffer: 100 * 1024 * 1024,
|
|
62
|
+
}).trim();
|
|
63
|
+
return result ? JSON.parse(result) : [];
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error(`sqlite3 json error for query: ${query.slice(0, 100)}`);
|
|
66
|
+
console.error(err.message.slice(0, 200));
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function escapeSQL(value) {
|
|
72
|
+
if (value === null || value === undefined) return 'NULL';
|
|
73
|
+
if (typeof value === 'number') return String(value);
|
|
74
|
+
if (typeof value === 'boolean') return value ? '1' : '0';
|
|
75
|
+
const str = String(value).replace(/'/g, "''");
|
|
76
|
+
return `'${str}'`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function main() {
|
|
80
|
+
console.log('=== Payment Kit SQLite → D1 Migration ===');
|
|
81
|
+
console.log(`Source: ${flags.dbPath}`);
|
|
82
|
+
console.log(`Output: ${flags.outputDir}`);
|
|
83
|
+
console.log(`Dry run: ${flags.dryRun}`);
|
|
84
|
+
console.log();
|
|
85
|
+
|
|
86
|
+
if (!fs.existsSync(flags.dbPath)) {
|
|
87
|
+
console.error(`ERROR: Source database not found: ${flags.dbPath}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Get all tables
|
|
92
|
+
const tablesRaw = sqlite3(flags.dbPath, '.tables');
|
|
93
|
+
const allTables = tablesRaw.split(/\s+/).filter(Boolean).sort();
|
|
94
|
+
|
|
95
|
+
const tables = flags.tables.length > 0
|
|
96
|
+
? flags.tables.filter(t => allTables.includes(t))
|
|
97
|
+
: allTables.filter(t => !SKIP_TABLES.includes(t));
|
|
98
|
+
|
|
99
|
+
if (flags.skipEvents) {
|
|
100
|
+
const idx = tables.indexOf('events');
|
|
101
|
+
if (idx > -1) tables.splice(idx, 1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(`Tables to migrate: ${tables.length}`);
|
|
105
|
+
console.log();
|
|
106
|
+
|
|
107
|
+
// Create output directory
|
|
108
|
+
fs.mkdirSync(flags.outputDir, { recursive: true });
|
|
109
|
+
|
|
110
|
+
// Phase 1: Generate schema SQL
|
|
111
|
+
const schemaFile = path.join(flags.outputDir, '000-schema.sql');
|
|
112
|
+
const schemaLines = [];
|
|
113
|
+
|
|
114
|
+
for (const table of tables) {
|
|
115
|
+
const createSQL = sqlite3(flags.dbPath,
|
|
116
|
+
`SELECT sql FROM sqlite_master WHERE type='table' AND name='${table}';`
|
|
117
|
+
);
|
|
118
|
+
if (createSQL) {
|
|
119
|
+
const sql = createSQL.replace(
|
|
120
|
+
/CREATE TABLE\s+/i,
|
|
121
|
+
'CREATE TABLE IF NOT EXISTS '
|
|
122
|
+
);
|
|
123
|
+
schemaLines.push(sql + ';');
|
|
124
|
+
schemaLines.push('');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Indexes
|
|
129
|
+
const indexSQL = sqlite3(flags.dbPath,
|
|
130
|
+
"SELECT sql FROM sqlite_master WHERE type='index' AND sql IS NOT NULL ORDER BY name;"
|
|
131
|
+
);
|
|
132
|
+
if (indexSQL) {
|
|
133
|
+
schemaLines.push('-- Indexes');
|
|
134
|
+
for (const line of indexSQL.split('\n').filter(Boolean)) {
|
|
135
|
+
const sql = line
|
|
136
|
+
.replace(/CREATE INDEX\s+/i, 'CREATE INDEX IF NOT EXISTS ')
|
|
137
|
+
.replace(/CREATE UNIQUE INDEX\s+/i, 'CREATE UNIQUE INDEX IF NOT EXISTS ');
|
|
138
|
+
schemaLines.push(sql + ';');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fs.writeFileSync(schemaFile, schemaLines.join('\n'));
|
|
143
|
+
console.log(`Schema written: ${schemaFile}`);
|
|
144
|
+
|
|
145
|
+
// Phase 2: Generate data SQL files per table
|
|
146
|
+
let totalRows = 0;
|
|
147
|
+
let fileIndex = 1;
|
|
148
|
+
|
|
149
|
+
for (const table of tables) {
|
|
150
|
+
const countStr = sqlite3(flags.dbPath, `SELECT COUNT(*) FROM "${table}";`);
|
|
151
|
+
const count = parseInt(countStr, 10) || 0;
|
|
152
|
+
|
|
153
|
+
if (count === 0) {
|
|
154
|
+
console.log(` ${table}: 0 rows (skip)`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(` ${table}: ${count} rows — exporting...`);
|
|
159
|
+
|
|
160
|
+
// Get column names
|
|
161
|
+
const colInfoRaw = sqlite3(flags.dbPath, `PRAGMA table_info("${table}");`);
|
|
162
|
+
const colNames = colInfoRaw.split('\n').filter(Boolean).map(line => {
|
|
163
|
+
const parts = line.split('|');
|
|
164
|
+
return parts[1]; // column name is the second field
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Export in pages to avoid memory issues
|
|
168
|
+
const pageSize = 500;
|
|
169
|
+
let offset = 0;
|
|
170
|
+
let partIndex = 0;
|
|
171
|
+
const paddedIdx = String(fileIndex).padStart(3, '0');
|
|
172
|
+
|
|
173
|
+
while (offset < count) {
|
|
174
|
+
const rows = sqlite3Json(flags.dbPath,
|
|
175
|
+
`SELECT * FROM "${table}" LIMIT ${pageSize} OFFSET ${offset};`
|
|
176
|
+
);
|
|
177
|
+
if (!rows || rows.length === 0) break;
|
|
178
|
+
|
|
179
|
+
const sqlLines = [];
|
|
180
|
+
sqlLines.push(`-- Table: ${table} (rows ${offset + 1}-${offset + rows.length} of ${count})`);
|
|
181
|
+
|
|
182
|
+
// Generate INSERT statements in batches
|
|
183
|
+
for (let i = 0; i < rows.length; i += flags.batchSize) {
|
|
184
|
+
const batch = rows.slice(i, i + flags.batchSize);
|
|
185
|
+
const values = batch.map(row => {
|
|
186
|
+
const vals = colNames.map(col => escapeSQL(row[col]));
|
|
187
|
+
return `(${vals.join(', ')})`;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
sqlLines.push(
|
|
191
|
+
`INSERT OR IGNORE INTO "${table}" (${colNames.map(c => `"${c}"`).join(', ')}) VALUES`
|
|
192
|
+
);
|
|
193
|
+
sqlLines.push(values.join(',\n') + ';');
|
|
194
|
+
sqlLines.push('');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const partSuffix = partIndex > 0 ? `-part${partIndex}` : '';
|
|
198
|
+
const dataFile = path.join(flags.outputDir, `${paddedIdx}-${table}${partSuffix}.sql`);
|
|
199
|
+
fs.writeFileSync(dataFile, sqlLines.join('\n'));
|
|
200
|
+
|
|
201
|
+
partIndex++;
|
|
202
|
+
offset += rows.length;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
totalRows += count;
|
|
206
|
+
fileIndex++;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log();
|
|
210
|
+
console.log(`Total: ${totalRows} rows across ${tables.length} tables`);
|
|
211
|
+
console.log(`SQL files generated in: ${flags.outputDir}`);
|
|
212
|
+
|
|
213
|
+
if (flags.dryRun) {
|
|
214
|
+
console.log('\nDry run — skipping D1 import.');
|
|
215
|
+
console.log('To import, run:');
|
|
216
|
+
console.log(` for f in ${flags.outputDir}/*.sql; do`);
|
|
217
|
+
console.log(' echo "Importing $f..."');
|
|
218
|
+
console.log(` npx wrangler d1 execute payment-kit-dev --local --file="$f" --config=cloudflare/wrangler.jsonc`);
|
|
219
|
+
console.log(' done');
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Phase 3: Execute SQL files against D1
|
|
224
|
+
console.log('\n=== Importing into D1 ===');
|
|
225
|
+
const sqlFiles = fs.readdirSync(flags.outputDir)
|
|
226
|
+
.filter(f => f.endsWith('.sql'))
|
|
227
|
+
.sort();
|
|
228
|
+
|
|
229
|
+
const cwd = path.resolve(__dirname, '..');
|
|
230
|
+
for (const file of sqlFiles) {
|
|
231
|
+
const filePath = path.join(flags.outputDir, file);
|
|
232
|
+
const stat = fs.statSync(filePath);
|
|
233
|
+
const sizeMB = (stat.size / 1024 / 1024).toFixed(2);
|
|
234
|
+
|
|
235
|
+
process.stdout.write(` ${file} (${sizeMB}MB)... `);
|
|
236
|
+
try {
|
|
237
|
+
execSync(
|
|
238
|
+
`npx wrangler d1 execute payment-kit-dev --local --file="${filePath}" --config=cloudflare/wrangler.jsonc`,
|
|
239
|
+
{ cwd, stdio: 'pipe', timeout: 120000 }
|
|
240
|
+
);
|
|
241
|
+
console.log('OK');
|
|
242
|
+
} catch (err) {
|
|
243
|
+
const errMsg = err.stderr?.toString()?.slice(0, 300) || err.message;
|
|
244
|
+
console.log('FAILED');
|
|
245
|
+
console.error(` ${errMsg}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log('\nMigration complete!');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
main();
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
-- Payment Kit: Complete D1 schema
|
|
2
|
+
-- All statements use IF NOT EXISTS for idempotency.
|
|
3
|
+
|
|
4
|
+
CREATE TABLE IF NOT EXISTS `archive_locks` (`id` VARCHAR(40) NOT NULL PRIMARY KEY, `locked_by` VARCHAR(64), `locked_at` INTEGER, `expires_at` INTEGER, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
5
|
+
|
|
6
|
+
CREATE TABLE IF NOT EXISTS `archive_metadata` (`id` VARCHAR(40) NOT NULL PRIMARY KEY, `archive_file` VARCHAR(128) NOT NULL, `date_range_start` INTEGER NOT NULL, `date_range_end` INTEGER NOT NULL, `tables` JSON NOT NULL DEFAULT '{}', `total_records` INTEGER NOT NULL DEFAULT 0, `checksum` VARCHAR(128), `file_size` INTEGER, `duration_ms` INTEGER, `triggered_by` TEXT NOT NULL, `triggered_by_user_id` VARCHAR(64), `status` TEXT NOT NULL, `error` TEXT, `query_count` INTEGER NOT NULL DEFAULT 0, `query_actor_ids` JSON DEFAULT '[]', `last_queried_at` INTEGER, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
7
|
+
|
|
8
|
+
CREATE TABLE IF NOT EXISTS `auto_recharge_configs` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `customer_id` VARCHAR(18) NOT NULL REFERENCES `customers` (`id`), `livemode` TINYINT(1) NOT NULL, `enabled` TINYINT(1) NOT NULL DEFAULT 0, `threshold` VARCHAR(32) NOT NULL, `payment_method_id` VARCHAR(15), `currency_id` VARCHAR(15) NOT NULL, `recharge_currency_id` VARCHAR(15), `price_id` VARCHAR(32) NOT NULL, `quantity` INTEGER NOT NULL DEFAULT 1, `payment_settings` JSON DEFAULT '{"payment_method_types":[],"payment_method_options":{}}', `payment_details` JSON, `daily_limits` JSON DEFAULT '{"max_attempts":0,"max_amount":"0"}', `last_recharge_date` VARCHAR(10), `daily_stats` JSON NOT NULL DEFAULT '{"attempt_count":0,"total_amount":"0"}', `metadata` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `slippage_config` JSON);
|
|
9
|
+
|
|
10
|
+
CREATE TABLE IF NOT EXISTS `checkout_sessions` (`id` VARCHAR(64) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `client_reference_id` VARCHAR(128), `currency_id` VARCHAR(15) NOT NULL, `invoice_id` VARCHAR(30), `customer_id` VARCHAR(30), `customer_did` VARCHAR(40), `payment_link_id` VARCHAR(30), `subscription_id` VARCHAR(30), `recovered_from` VARCHAR(30), `payment_intent_id` VARCHAR(30), `mode` TEXT NOT NULL, `status` TEXT NOT NULL, `payment_status` TEXT NOT NULL, `line_items` JSON NOT NULL, `amount_subtotal` VARCHAR(32) NOT NULL, `amount_total` VARCHAR(32) NOT NULL, `total_details` JSON NOT NULL, `url` VARCHAR(512), `cancel_url` VARCHAR(512), `success_url` VARCHAR(512), `currency_conversion` JSON, `after_expiration` JSON, `allow_promotion_codes` TINYINT(1) DEFAULT 0, `consent_collection` JSON, `consent` JSON, `custom_fields` JSON DEFAULT '[]', `custom_text` JSON, `customer_creation` TEXT NOT NULL, `customer_update` JSON DEFAULT '{"address":"never","name":"never","shipping":"never"}', `expires_at` INTEGER, `invoice_creation` JSON, `payment_method_types` JSON DEFAULT '[]', `phone_number_collection` JSON, `billing_address_collection` TEXT NOT NULL DEFAULT 'auto', `submit_type` TEXT NOT NULL, `subscription_data` JSON, `payment_details` JSON, `metadata` JSON, `created_at` DATETIME NOT NULL, `created_via` TEXT, `updated_at` DATETIME NOT NULL, `nft_mint_settings` JSON, `payment_intent_data` JSON, `nft_mint_details` JSON, `nft_mint_status` TEXT NOT NULL DEFAULT 'disabled', `cross_sell_behavior` TEXT DEFAULT 'auto', `setup_intent_id` VARCHAR(30), `enable_subscription_grouping` TINYINT(1) DEFAULT 0, `subscription_groups` JSON, `success_subscription_count` INTEGER DEFAULT 0, "discounts" JSON, `fulfillment_status` VARCHAR(255), `vendor_info` JSON, `slippage_percent` DECIMAL(5,2) NOT NULL DEFAULT 0.5);
|
|
11
|
+
|
|
12
|
+
CREATE TABLE IF NOT EXISTS `coupons` (`id` VARCHAR(15) NOT NULL UNIQUE PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `amount_off` VARCHAR(32) DEFAULT '0', `percent_off` INTEGER DEFAULT '0', `currency_id` VARCHAR(15) NOT NULL, `duration` TEXT, `duration_in_months` INTEGER NOT NULL, `name` VARCHAR(64) NOT NULL, `applies_to` JSON, `currency_options` JSON DEFAULT '{}', `max_redemptions` INTEGER, `times_redeemed` INTEGER DEFAULT '0', `valid` TINYINT(1) DEFAULT 0, `metadata` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `created_via` TEXT, `locked` TINYINT(1) NOT NULL DEFAULT 0, `redeem_by` INTEGER, `redeem_by_new` INTEGER, `description` TEXT);
|
|
13
|
+
|
|
14
|
+
CREATE TABLE IF NOT EXISTS `credit_grants` (`id` VARCHAR(18) NOT NULL PRIMARY KEY, `object` VARCHAR(32) NOT NULL DEFAULT 'credit_grant', `amount` VARCHAR(32) NOT NULL, `currency_id` VARCHAR(15) NOT NULL, `applicability_config` JSON, `category` TEXT NOT NULL, `customer_id` VARCHAR(18) NOT NULL, `effective_at` INTEGER, `expires_at` INTEGER, `livemode` TINYINT(1) NOT NULL, `metadata` JSON, `name` VARCHAR(255), `priority` INTEGER NOT NULL DEFAULT 50, `test_clock` VARCHAR(32), `voided_at` INTEGER, `status` TEXT NOT NULL DEFAULT 'granted', `remaining_amount` VARCHAR(32) NOT NULL, `created_by` VARCHAR(40), `updated_by` VARCHAR(40), `created_via` TEXT NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `chain_status` TEXT, `chain_detail` JSON);
|
|
15
|
+
|
|
16
|
+
CREATE TABLE IF NOT EXISTS `credit_transactions` (`id` VARCHAR(18) NOT NULL PRIMARY KEY, `quantity` VARCHAR(32) NOT NULL, `credit_amount` VARCHAR(32) NOT NULL, `remaining_balance` VARCHAR(32) NOT NULL, `customer_id` VARCHAR(18) NOT NULL, `credit_grant_id` VARCHAR(18) NOT NULL, `meter_id` VARCHAR(18), `subscription_id` VARCHAR(18), `source` VARCHAR(255), `meter_event_name` VARCHAR(128) NOT NULL, `meter_unit` VARCHAR(32) NOT NULL, `description` TEXT, `metadata` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `transfer_status` TEXT, `transfer_hash` VARCHAR(255));
|
|
17
|
+
|
|
18
|
+
CREATE TABLE IF NOT EXISTS `customers` (`id` VARCHAR(18) NOT NULL UNIQUE PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `did` VARCHAR(40) NOT NULL, `address` JSON, `description` VARCHAR(512), `name` VARCHAR(64), `email` VARCHAR(128), `phone` VARCHAR(32), `shipping` JSON, `balance` VARCHAR(32) DEFAULT '0', `currency_id` VARCHAR(15), `delinquent` TINYINT(1) NOT NULL, `discount_id` VARCHAR(32), `metadata` JSON, `invoice_credit_balance` JSON, `invoice_prefix` VARCHAR(30), `invoice_settings` JSON, `next_invoice_sequence` NUMBER DEFAULT '1', `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `token_balance` JSON DEFAULT '"{}"', `last_sync_at` INTEGER, `preference` JSON DEFAULT '"{\"notification\":{\"frequency\":\"monthly\",\"schedule\":{\"time\":\"10:00\",\"date\":1}}}"');
|
|
19
|
+
|
|
20
|
+
CREATE TABLE IF NOT EXISTS `discounts` (`id` VARCHAR(15) NOT NULL UNIQUE PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `coupon_id` VARCHAR(30) NOT NULL, `customer_id` VARCHAR(30) NOT NULL, `promotion_code_id` VARCHAR(30), `subscription_id` VARCHAR(30), `checkout_session_id` VARCHAR(30), `invoice_id` VARCHAR(30), `invoice_item_id` VARCHAR(30), `metadata` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `verification_method` VARCHAR(50), `verification_data` JSON, `start` INTEGER NOT NULL, `end` INTEGER, `confirmed` TINYINT(1) NOT NULL DEFAULT 1);
|
|
21
|
+
|
|
22
|
+
CREATE TABLE IF NOT EXISTS `events` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `type` VARCHAR(128) NOT NULL, `object_type` VARCHAR(64) NOT NULL, `object_id` VARCHAR(64) NOT NULL, `data` JSON NOT NULL, `request` JSON, `pending_webhooks` INTEGER NOT NULL DEFAULT 0, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
23
|
+
|
|
24
|
+
CREATE TABLE IF NOT EXISTS `exchange_rate_providers` (`id` VARCHAR(32) NOT NULL PRIMARY KEY, `name` VARCHAR(64) NOT NULL UNIQUE, `enabled` TINYINT(1) NOT NULL DEFAULT 1, `priority` INTEGER NOT NULL DEFAULT 1, `status` TEXT NOT NULL DEFAULT 'active', `paused_reason` VARCHAR(512), `config` JSON, `last_success_at` DATETIME, `last_failure_at` DATETIME, `failure_count` INTEGER NOT NULL DEFAULT 0, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `type` VARCHAR(32) NOT NULL DEFAULT 'token-data');
|
|
25
|
+
|
|
26
|
+
CREATE TABLE IF NOT EXISTS `invoice_items` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `amount` VARCHAR(32) NOT NULL, `quantity` INTEGER NOT NULL, `description` VARCHAR(256) NOT NULL, `period` JSON, `customer_id` VARCHAR(18) NOT NULL, `currency_id` VARCHAR(15) NOT NULL, `price_id` VARCHAR(30) NOT NULL, `invoice_id` VARCHAR(30) NOT NULL, `subscription_id` VARCHAR(30), `subscription_item_id` VARCHAR(30), `discountable` TINYINT(1) NOT NULL, `discount_amounts` JSON DEFAULT '[]', `discounts` JSON DEFAULT '[]', `proration` TINYINT(1) DEFAULT 0, `proration_details` JSON, `metadata` JSON DEFAULT '{}', `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `tax_rate_id` VARCHAR(30));
|
|
27
|
+
|
|
28
|
+
CREATE TABLE IF NOT EXISTS `invoices` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `auto_advance` TINYINT(1) NOT NULL, `account_country` VARCHAR(64), `account_name` VARCHAR(64), `charge_id` VARCHAR(30), `collection_method` TEXT NOT NULL, `currency_id` VARCHAR(15) NOT NULL, `customer_id` VARCHAR(18) NOT NULL, `description` VARCHAR(512), `hosted_invoice_url` VARCHAR(512), `payment_intent_id` VARCHAR(30), `period_end` INTEGER DEFAULT 0, `period_start` INTEGER DEFAULT 0, `status` TEXT NOT NULL, `checkout_session_id` VARCHAR(64), `subscription_id` VARCHAR(30), `subscription_details` JSON DEFAULT '{}', `subscription_proration_date` INTEGER, `total` VARCHAR(32) NOT NULL, `subtotal` VARCHAR(32) NOT NULL, `subtotal_excluding_tax` VARCHAR(32) NOT NULL, `tax` VARCHAR(32) DEFAULT '0', `amount_due` VARCHAR(32) NOT NULL, `amount_paid` VARCHAR(32) DEFAULT '0', `amount_remaining` VARCHAR(32) NOT NULL, `amount_shipping` VARCHAR(32) DEFAULT '0', `attempt_count` INTEGER DEFAULT 0, `attempted` TINYINT(1) DEFAULT 0, `next_payment_attempt` INTEGER, `billing_reason` TEXT, `custom_fields` JSON DEFAULT '[]', `customer_address` JSON, `customer_name` VARCHAR(64), `customer_email` VARCHAR(128), `customer_phone` VARCHAR(32), `customer_shipping` JSON, `default_payment_method_id` VARCHAR(30), `discounts` JSON DEFAULT '[]', `due_date` INTEGER, `effective_at` INTEGER, `ending_balance` VARCHAR(32) DEFAULT '0', `footer` VARCHAR(512), `from_invoice_id` VARCHAR(30), `invoice_pdf` VARCHAR(512), `last_finalization_error` JSON, `latest_revision` VARCHAR(30), `number` VARCHAR(64), `paid` TINYINT(1) DEFAULT 0, `paid_out_of_band` TINYINT(1) DEFAULT 0, `payment_settings` JSON, `post_payment_credit_notes_amount` VARCHAR(32) DEFAULT '0', `pre_payment_credit_notes_amount` VARCHAR(32) DEFAULT '0', `quote_id` VARCHAR(30), `receipt_number` VARCHAR(64), `days_until_due` NUMBER, `rendering_options` JSON, `starting_balance` VARCHAR(32) DEFAULT '0', `statement_descriptor` VARCHAR(64), `status_transitions` JSON DEFAULT '{}', `test_clock_id` VARCHAR(30), `threshold_reason` JSON, `total_discount_amounts` JSON DEFAULT '[]', `webhooks_delivered_at` INTEGER, `metadata` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `starting_token_balance` JSON DEFAULT '"{}"', `ending_token_balance` JSON DEFAULT '"{}"');
|
|
29
|
+
|
|
30
|
+
CREATE TABLE IF NOT EXISTS `jobs` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `queue` VARCHAR(32) NOT NULL, `job` JSON NOT NULL, `retry_count` INTEGER DEFAULT 0, `delay` INTEGER DEFAULT -1, `will_run_at` INTEGER DEFAULT -1, `cancelled` TINYINT(1) DEFAULT 0, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS `locks` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `lock_at` INTEGER DEFAULT 0, `release_at` INTEGER DEFAULT -1, `reason` VARCHAR(255), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
33
|
+
|
|
34
|
+
CREATE TABLE IF NOT EXISTS `meter_events` (`id` VARCHAR(18) NOT NULL PRIMARY KEY, `event_name` VARCHAR(128) NOT NULL, `timestamp` INTEGER NOT NULL, `payload` JSON NOT NULL, `identifier` VARCHAR(255) NOT NULL UNIQUE, `livemode` TINYINT(1) NOT NULL, `status` TEXT NOT NULL DEFAULT 'pending', `attempt_count` INTEGER NOT NULL DEFAULT 0, `next_attempt` INTEGER, `processed_at` INTEGER, `credit_consumed` VARCHAR(40) NOT NULL DEFAULT '0', `credit_pending` VARCHAR(40) NOT NULL DEFAULT '0', `metadata` JSON, `created_by` VARCHAR(40), `created_via` TEXT NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `source_data` JSON);
|
|
35
|
+
|
|
36
|
+
CREATE TABLE IF NOT EXISTS `meters` (`id` VARCHAR(18) NOT NULL PRIMARY KEY, `object` VARCHAR(32) NOT NULL DEFAULT 'meter', `name` VARCHAR(255) NOT NULL, `event_name` VARCHAR(128) NOT NULL UNIQUE, `aggregation_method` TEXT NOT NULL DEFAULT 'sum', `status` TEXT NOT NULL DEFAULT 'active', `unit` VARCHAR(32) NOT NULL, `description` TEXT, `component_did` VARCHAR(40), `currency_id` VARCHAR(40), `livemode` TINYINT(1) NOT NULL, `metadata` JSON, `created_by` VARCHAR(40), `updated_by` VARCHAR(40), `created_via` TEXT NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
37
|
+
|
|
38
|
+
CREATE TABLE IF NOT EXISTS `payment_currencies` (`id` VARCHAR(15) NOT NULL PRIMARY KEY, `active` TINYINT(1) NOT NULL, `livemode` TINYINT(1) NOT NULL, `locked` TINYINT(1) DEFAULT 0, `is_base_currency` TINYINT(1) DEFAULT 0, `payment_method_id` VARCHAR(30) NOT NULL, `name` VARCHAR(64) NOT NULL, `description` VARCHAR(512), `logo` VARCHAR(512) NOT NULL, `symbol` VARCHAR(16) NOT NULL, `decimal` NUMBER DEFAULT 2, `maximum_precision` NUMBER DEFAULT 6, `minimum_payment_amount` VARCHAR(32) DEFAULT '0', `maximum_payment_amount` VARCHAR(32) DEFAULT '0', `contract` VARCHAR(256), `metadata` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `vault_config` JSON, `type` TEXT NOT NULL DEFAULT 'standard', `recharge_config` JSON, `token_config` JSON);
|
|
39
|
+
|
|
40
|
+
CREATE TABLE IF NOT EXISTS `payment_intents` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `amount` VARCHAR(32) NOT NULL, `amount_received` VARCHAR(32), `amount_capturable` VARCHAR(32), `amount_details` JSON, `currency_id` VARCHAR(16) NOT NULL, `customer_id` VARCHAR(30), `description` VARCHAR(512), `last_payment_error` JSON, `last_charge` VARCHAR(30), `metadata` JSON, `invoice_id` VARCHAR(30), `payment_method_id` VARCHAR(30) NOT NULL, `receipt_email` VARCHAR(255), `statement_descriptor` VARCHAR(32), `statement_descriptor_suffix` VARCHAR(32), `status` TEXT NOT NULL, `canceled_at` DATETIME, `cancellation_reason` TEXT, `capture_method` TEXT NOT NULL, `confirmation_method` TEXT NOT NULL, `payment_method_types` JSON DEFAULT '[]', `review` VARCHAR(30), `payment_details` JSON, `setup_future_usage` TEXT, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `beneficiaries` JSON, `quote_locked_at` DATETIME);
|
|
41
|
+
|
|
42
|
+
CREATE TABLE IF NOT EXISTS `payment_links` (`id` VARCHAR(40) NOT NULL UNIQUE PRIMARY KEY, `active` TINYINT(1) NOT NULL, `livemode` TINYINT(1) NOT NULL, `line_items` JSON DEFAULT '[]', `name` VARCHAR(255), `currency_id` VARCHAR(16), `after_completion` JSON DEFAULT '{"type":"hosted_confirmation"}', `allow_promotion_codes` TINYINT(1) DEFAULT 0, `consent_collection` JSON, `custom_fields` JSON DEFAULT '[]', `custom_text` JSON, `customer_creation` TEXT NOT NULL, `invoice_creation` JSON, `payment_method_types` JSON DEFAULT '[]', `phone_number_collection` JSON, `billing_address_collection` TEXT NOT NULL DEFAULT 'auto', `submit_type` TEXT NOT NULL, `subscription_data` JSON, `metadata` JSON, `created_at` DATETIME NOT NULL, `created_via` TEXT, `updated_at` DATETIME NOT NULL, `nft_mint_settings` JSON, `cross_sell_behavior` TEXT DEFAULT 'auto', `donation_settings` JSON, `beneficiaries` JSON, `enable_subscription_grouping` TINYINT(1) DEFAULT 0, `lookup_key` VARCHAR(128));
|
|
43
|
+
|
|
44
|
+
CREATE TABLE IF NOT EXISTS `payment_methods` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `active` TINYINT(1) NOT NULL, `livemode` TINYINT(1) NOT NULL, `locked` TINYINT(1) DEFAULT 0, `type` TEXT, `name` VARCHAR(64), `description` VARCHAR(512), `logo` VARCHAR(512), `default_currency_id` VARCHAR(15), `confirmation` JSON NOT NULL, `settings` JSON NOT NULL, `features` JSON NOT NULL, `metadata` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
45
|
+
|
|
46
|
+
CREATE TABLE IF NOT EXISTS `payment_stats` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `timestamp` INTEGER DEFAULT 0, `currency_id` VARCHAR(16) NOT NULL, `amount_paid` VARCHAR(64) DEFAULT '0', `amount_payout` VARCHAR(64) DEFAULT '0', `amount_refund` VARCHAR(64) DEFAULT '0', `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS `payouts` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `automatic` TINYINT(1) NOT NULL, `description` VARCHAR(512), `amount` VARCHAR(32) NOT NULL, `destination` VARCHAR(512) NOT NULL, `payment_details` JSON, `currency_id` VARCHAR(16) NOT NULL, `customer_id` VARCHAR(30), `payment_intent_id` VARCHAR(16) NOT NULL, `payment_method_id` VARCHAR(30), `metadata` JSON, `status` TEXT NOT NULL, `failure_message` VARCHAR(256), `failure_code` TEXT, `attempt_count` INTEGER DEFAULT 0, `attempted` TINYINT(1) DEFAULT 0, `next_attempt` INTEGER, `last_attempt_error` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `vendor_info` JSON);
|
|
49
|
+
|
|
50
|
+
CREATE TABLE IF NOT EXISTS `price_quotes` (`id` VARCHAR(32) NOT NULL PRIMARY KEY, `price_id` VARCHAR(32) NOT NULL, `session_id` VARCHAR(32), `invoice_id` VARCHAR(32), `idempotency_key` VARCHAR(128) NOT NULL UNIQUE, `base_currency` VARCHAR(8) NOT NULL DEFAULT 'USD', `base_amount` VARCHAR(32) NOT NULL, `target_currency_id` VARCHAR(16) NOT NULL, `rate_currency_symbol` VARCHAR(16) NOT NULL, `exchange_rate` VARCHAR(32) NOT NULL, `quoted_amount` VARCHAR(64) NOT NULL, `rate_provider_id` VARCHAR(32) NOT NULL, `rate_provider_name` VARCHAR(64) NOT NULL, `rate_timestamp_ms` BIGINT NOT NULL, `expires_at` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'active', `metadata` JSON, `created_at` DATETIME NOT NULL, `slippage_percent` DECIMAL(5,2), `max_payable_token` VARCHAR(64), `min_acceptable_rate` VARCHAR(32), `slippage_derived_at_ms` BIGINT);
|
|
51
|
+
|
|
52
|
+
CREATE TABLE IF NOT EXISTS `prices` (`id` VARCHAR(32) NOT NULL PRIMARY KEY, `product_id` VARCHAR(32) NOT NULL, `nickname` VARCHAR(512), `active` TINYINT(1) NOT NULL, `livemode` TINYINT(1) NOT NULL, `locked` TINYINT(1) DEFAULT 0, `type` TEXT NOT NULL, `billing_scheme` TEXT NOT NULL, `unit_amount` VARCHAR(32) NOT NULL, `recurring` JSON, `tiers_mode` TEXT, `tiers` JSON, `custom_unit_amount` JSON, `lookup_key` VARCHAR(128), `metadata` JSON, `transform_quantity` JSON, `currency_id` VARCHAR(16), `currency_options` JSON DEFAULT '[]', `created_at` DATETIME NOT NULL, `created_via` TEXT, `updated_at` DATETIME NOT NULL, `upsell` JSON, `quantity_available` INTEGER NOT NULL DEFAULT 0, `quantity_sold` INTEGER NOT NULL DEFAULT 0, `quantity_limit_per_checkout` INTEGER NOT NULL DEFAULT 0, `tax_behavior` TEXT NOT NULL DEFAULT 'inclusive', `pricing_type` TEXT NOT NULL DEFAULT 'fixed', `base_currency` VARCHAR(8), `base_amount` VARCHAR(32), `dynamic_pricing_config` JSON);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE IF NOT EXISTS `pricing_tables` (`id` VARCHAR(42) NOT NULL PRIMARY KEY, `active` TINYINT(1) NOT NULL, `livemode` TINYINT(1) NOT NULL, `locked` TINYINT(1) NOT NULL, `name` VARCHAR(255), `items` JSON DEFAULT '[]', `branding_settings` JSON NOT NULL, `metadata` JSON, `created_at` DATETIME NOT NULL, `created_via` TEXT, `updated_at` DATETIME NOT NULL);
|
|
55
|
+
|
|
56
|
+
CREATE TABLE IF NOT EXISTS `product_vendors` (`id` STRING NOT NULL UNIQUE PRIMARY KEY, `vendor_key` STRING NOT NULL, `name` STRING NOT NULL, `description` TEXT, `app_url` STRING NOT NULL, `app_pid` STRING NOT NULL, `app_logo` STRING, `status` STRING NOT NULL DEFAULT 'active', `metadata` JSON DEFAULT '{}', `created_by` STRING, `created_at` DATE NOT NULL DEFAULT 'CURRENT_TIMESTAMP', `updated_at` DATE NOT NULL DEFAULT 'CURRENT_TIMESTAMP', `vendor_type` STRING NOT NULL DEFAULT 'launcher', `vendor_did` STRING, `extends` JSON);
|
|
57
|
+
|
|
58
|
+
CREATE TABLE IF NOT EXISTS `products` (`id` VARCHAR(18) NOT NULL PRIMARY KEY, `active` TINYINT(1) NOT NULL, `livemode` TINYINT(1) NOT NULL, `locked` TINYINT(1) DEFAULT 0, `type` TEXT, `name` VARCHAR(512), `description` VARCHAR(2048), `images` JSON DEFAULT '[]', `features` JSON DEFAULT '[]', `unit_label` VARCHAR(32), `default_price_id` VARCHAR(32), `metadata` JSON, `statement_descriptor` VARCHAR(32), `nft_factory` VARCHAR(40), `created_at` DATETIME NOT NULL, `created_via` TEXT, `updated_at` DATETIME NOT NULL, `cross_sell` JSON, `vendor_config` JSON, `tax_code` VARCHAR(20));
|
|
59
|
+
|
|
60
|
+
CREATE TABLE IF NOT EXISTS `promotion_codes` (`id` VARCHAR(30) NOT NULL UNIQUE PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `active` TINYINT(1) DEFAULT 1, `code` VARCHAR(16) NOT NULL, `coupon_id` VARCHAR(30), `max_redemptions` INTEGER, `restrictions` JSON DEFAULT '{}', `customer_id` VARCHAR(30), `times_redeemed` INTEGER DEFAULT '0', `metadata` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `verification_type` TEXT NOT NULL DEFAULT 'code', `nft_config` JSON, `vc_config` JSON, `customer_dids` JSON, `created_via` TEXT, `locked` TINYINT(1) NOT NULL DEFAULT 0, `expires_at` INTEGER, `expires_at_new` INTEGER, `description` TEXT);
|
|
61
|
+
|
|
62
|
+
CREATE TABLE IF NOT EXISTS `refunds` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `description` VARCHAR(512), `livemode` TINYINT(1) NOT NULL, `amount` VARCHAR(32) NOT NULL, `payment_details` JSON, `currency_id` VARCHAR(16) NOT NULL, `customer_id` VARCHAR(30), `payment_intent_id` VARCHAR(16) NOT NULL, `invoice_id` VARCHAR(30), `subscription_id` VARCHAR(30), `metadata` JSON, `status` TEXT NOT NULL, `reason` TEXT, `failure_reason` TEXT, `attempt_count` INTEGER DEFAULT 0, `attempted` TINYINT(1) DEFAULT 0, `next_attempt` INTEGER, `last_attempt_error` JSON, `starting_balance` VARCHAR(32) DEFAULT '0', `ending_balance` VARCHAR(32) DEFAULT '0', `starting_token_balance` JSON DEFAULT '{}', `ending_token_balance` JSON DEFAULT '{}', `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `payment_method_id` VARCHAR(30), `type` TEXT DEFAULT 'refund');
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS `revenue_snapshots` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `currency_id` VARCHAR(16) NOT NULL, `timestamp` INTEGER NOT NULL, `period_type` TEXT NOT NULL DEFAULT 'monthly', `total_revenue` VARCHAR(64) DEFAULT '0', `refund_amount` VARCHAR(64) DEFAULT '0', `promotion_cost` VARCHAR(64) DEFAULT '0', `credit_grant_cost` VARCHAR(64) DEFAULT '0', `vendor_cost` VARCHAR(64) DEFAULT '0', `taxed_revenue` VARCHAR(64) DEFAULT '0', `net_revenue` VARCHAR(64) DEFAULT '0', `archive_metadata_id` VARCHAR(40), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
65
|
+
|
|
66
|
+
CREATE TABLE IF NOT EXISTS `settings` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `active` TINYINT(1) NOT NULL DEFAULT 1, `type` VARCHAR(64) NOT NULL, `mount_location` VARCHAR(255) NOT NULL, `component_did` VARCHAR(255), `description` VARCHAR(255) NOT NULL, `settings` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
67
|
+
|
|
68
|
+
CREATE TABLE IF NOT EXISTS `setup_intents` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `currency_id` VARCHAR(16) NOT NULL, `customer_id` VARCHAR(30), `description` VARCHAR(512), `last_setup_error` JSON, `last_attempt` VARCHAR(30), `metadata` JSON, `status` TEXT NOT NULL, `usage` TEXT NOT NULL, `canceled_at` INTEGER, `cancellation_reason` TEXT, `flow_directions` JSON DEFAULT '["inbound","outbound"]', `payment_method_types` JSON DEFAULT '[]', `payment_method_options` JSON, `payment_method_id` VARCHAR(30) NOT NULL, `setup_details` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
69
|
+
|
|
70
|
+
CREATE TABLE IF NOT EXISTS `subscription_items` (`id` VARCHAR(18) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `subscription_id` VARCHAR(30) NOT NULL, `price_id` VARCHAR(30) NOT NULL, `quantity` INTEGER, `metadata` JSON, `billing_threshold` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
71
|
+
|
|
72
|
+
CREATE TABLE IF NOT EXISTS `subscription_schedules` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `current_phase` JSON NOT NULL, `customer_id` VARCHAR(30) NOT NULL, `subscription_id` VARCHAR(30) NOT NULL, `released_subscription_id` VARCHAR(30), `status` TEXT NOT NULL, `default_settings` JSON, `end_behavior` TEXT NOT NULL, `test_clock_id` VARCHAR(30), `metadata` JSON, `canceled_at` INTEGER, `completed_at` INTEGER, `released_at` INTEGER, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
73
|
+
|
|
74
|
+
CREATE TABLE IF NOT EXISTS `subscriptions` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `currency_id` VARCHAR(15) NOT NULL, `customer_id` VARCHAR(18) NOT NULL, `cancel_at_period_end` TINYINT(1) NOT NULL, `current_period_end` INTEGER NOT NULL, `current_period_start` INTEGER NOT NULL, `default_payment_method_id` VARCHAR(30), `description` VARCHAR(512), `latest_invoice_id` VARCHAR(30), `pending_setup_intent` VARCHAR(30), `pending_update` JSON, `status` TEXT NOT NULL, `cancel_at` INTEGER, `canceled_at` INTEGER, `cancelation_details` JSON, `billing_cycle_anchor` INTEGER NOT NULL, `billing_thresholds` JSON, `collection_method` TEXT NOT NULL, `days_until_due` NUMBER, `discount_id` VARCHAR(30), `next_pending_invoice_item_invoice_id` VARCHAR(30), `pause_collection` JSON, `payment_settings` JSON, `pending_invoice_item_interval` JSON NOT NULL, `schedule_id` VARCHAR(30), `end_at` INTEGER, `start_date` INTEGER NOT NULL, `payment_details` JSON, `metadata` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `proration_behavior` TEXT DEFAULT 'none', `payment_behavior` TEXT DEFAULT 'default_incomplete', `days_until_cancel` NUMBER, `service_actions` JSON DEFAULT '"[]"', `trial_start` INTEGER, `trial_end` INTEGER, `trial_settings` JSON, `recovered_from` VARCHAR(40), `overdraft_protection` JSON DEFAULT '"{\"enabled\":false,\"payment_method_id\":null,\"payment_details\":null}"', `credit_schedule_state` JSON DEFAULT NULL, `slippage_config` JSON);
|
|
75
|
+
|
|
76
|
+
CREATE TABLE IF NOT EXISTS `tax_rates` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `active` TINYINT(1) DEFAULT 1, `country` VARCHAR(2) NOT NULL, `state` VARCHAR(50), `postal_code` VARCHAR(20), `tax_code` VARCHAR(20), `percentage` DECIMAL(5,4) NOT NULL, `display_name` VARCHAR(100) NOT NULL, `description` VARCHAR(500), `metadata` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
77
|
+
|
|
78
|
+
CREATE TABLE IF NOT EXISTS `usage_records` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `timestamp` INTEGER NOT NULL, `quantity` BIGINT NOT NULL, `subscription_item_id` VARCHAR(30) NOT NULL, `metadata` JSON, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `billed` TINYINT(1) DEFAULT 0);
|
|
79
|
+
|
|
80
|
+
CREATE TABLE IF NOT EXISTS `webhook_attempts` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `event_id` VARCHAR(30) NOT NULL, `webhook_endpoint_id` VARCHAR(30) NOT NULL, `status` TEXT NOT NULL, `response_status` INTEGER NOT NULL, `response_body` JSON NOT NULL, `retry_count` INTEGER DEFAULT 0, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL);
|
|
81
|
+
|
|
82
|
+
CREATE TABLE IF NOT EXISTS `webhook_endpoints` (`id` VARCHAR(30) NOT NULL PRIMARY KEY, `livemode` TINYINT(1) NOT NULL, `api_version` VARCHAR(16) NOT NULL, `url` VARCHAR(512) NOT NULL, `description` VARCHAR(512), `enabled_events` JSON NOT NULL, `metadata` JSON, `status` TEXT NOT NULL, `created_at` DATETIME NOT NULL, `created_via` TEXT, `updated_at` DATETIME NOT NULL);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
-- Payment Kit: All indexes
|
|
2
|
+
-- Matches the indexes from the original Sequelize migrations.
|
|
3
|
+
|
|
4
|
+
CREATE INDEX IF NOT EXISTS `idx_auto_recharge_configs_currency_id` ON `auto_recharge_configs` (`currency_id`);
|
|
5
|
+
CREATE INDEX IF NOT EXISTS `idx_auto_recharge_configs_customer_id` ON `auto_recharge_configs` (`customer_id`);
|
|
6
|
+
CREATE INDEX IF NOT EXISTS `idx_checkout_sessions_fulfillment_status` ON `checkout_sessions` (`fulfillment_status`);
|
|
7
|
+
CREATE INDEX IF NOT EXISTS `idx_checkout_sessions_payment_intent` ON `checkout_sessions` (`payment_intent_id`);
|
|
8
|
+
CREATE INDEX IF NOT EXISTS `idx_checkout_sessions_payment_link_status_livemode` ON `checkout_sessions` (`payment_link_id`, `status`, `livemode`);
|
|
9
|
+
CREATE INDEX IF NOT EXISTS `idx_checkout_sessions_status` ON `checkout_sessions` (`status`);
|
|
10
|
+
CREATE INDEX IF NOT EXISTS `idx_checkout_sessions_subscription` ON `checkout_sessions` (`subscription_id`);
|
|
11
|
+
CREATE INDEX IF NOT EXISTS idx_credit_grant_stats_by_grantor ON credit_grants(json_extract(metadata, '$.granted_by'), currency_id, created_at) WHERE json_extract(metadata, '$.granted_by') IS NOT NULL;
|
|
12
|
+
CREATE INDEX IF NOT EXISTS `idx_credit_grants_chain_status` ON `credit_grants` (`chain_status`);
|
|
13
|
+
CREATE INDEX IF NOT EXISTS `idx_credit_grants_currency` ON `credit_grants` (`currency_id`);
|
|
14
|
+
CREATE INDEX IF NOT EXISTS `idx_credit_grants_customer` ON `credit_grants` (`customer_id`);
|
|
15
|
+
CREATE INDEX IF NOT EXISTS `idx_credit_grants_customer_currency_status` ON `credit_grants` (`customer_id`, `currency_id`, `status`);
|
|
16
|
+
CREATE INDEX IF NOT EXISTS `idx_credit_grants_status` ON `credit_grants` (`status`);
|
|
17
|
+
CREATE INDEX IF NOT EXISTS `idx_credit_transactions_created` ON `credit_transactions` (`created_at`);
|
|
18
|
+
CREATE INDEX IF NOT EXISTS `idx_credit_transactions_customer_created` ON `credit_transactions` (`customer_id`, `created_at`);
|
|
19
|
+
CREATE INDEX IF NOT EXISTS `idx_credit_transactions_event_created` ON `credit_transactions` (`meter_event_name`, `created_at`);
|
|
20
|
+
CREATE INDEX IF NOT EXISTS `idx_credit_transactions_meter_created` ON `credit_transactions` (`meter_id`, `created_at`);
|
|
21
|
+
CREATE INDEX IF NOT EXISTS `idx_credit_transactions_subscription_created` ON `credit_transactions` (`subscription_id`, `created_at`);
|
|
22
|
+
CREATE INDEX IF NOT EXISTS `idx_credit_transactions_transfer_status` ON `credit_transactions` (`transfer_status`);
|
|
23
|
+
CREATE INDEX IF NOT EXISTS `idx_customers_did` ON `customers` (`did`);
|
|
24
|
+
CREATE INDEX IF NOT EXISTS `idx_erp_enabled_priority` ON `exchange_rate_providers` (`enabled`, `priority`);
|
|
25
|
+
CREATE UNIQUE INDEX IF NOT EXISTS `idx_erp_name` ON `exchange_rate_providers` (`name`);
|
|
26
|
+
CREATE INDEX IF NOT EXISTS `idx_events_object_id_created` ON `events` (`object_id`, `created_at`);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS `idx_events_object_type_id_created` ON `events` (`object_type`, `object_id`, `created_at`);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS `idx_events_type_created` ON `events` (`type`, `created_at`);
|
|
29
|
+
CREATE INDEX IF NOT EXISTS `idx_invoice_currency_id` ON `invoices` (`currency_id`);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS `idx_invoice_customer_id` ON `invoices` (`customer_id`);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS `idx_invoice_customer_total` ON `invoices` (`customer_id`, `total`);
|
|
32
|
+
CREATE INDEX IF NOT EXISTS `idx_invoice_items_invoice_id` ON `invoice_items` (`invoice_id`);
|
|
33
|
+
CREATE INDEX IF NOT EXISTS `idx_invoice_items_tax_rate_id_invoice_id` ON `invoice_items` (`tax_rate_id`, `invoice_id`);
|
|
34
|
+
CREATE INDEX IF NOT EXISTS `idx_invoice_status_collection` ON `invoices` (`status`, `collection_method`);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS `idx_invoice_status_total` ON `invoices` (`status`, `total`);
|
|
36
|
+
CREATE INDEX IF NOT EXISTS `idx_invoice_subscription_id` ON `invoices` (`subscription_id`);
|
|
37
|
+
CREATE INDEX IF NOT EXISTS `idx_invoice_subtotal` ON `invoices` (`subtotal`);
|
|
38
|
+
CREATE INDEX IF NOT EXISTS `idx_invoice_total` ON `invoices` (`total`);
|
|
39
|
+
CREATE INDEX IF NOT EXISTS `idx_invoices_created_at` ON `invoices` (`created_at`);
|
|
40
|
+
CREATE INDEX IF NOT EXISTS `idx_invoices_status` ON `invoices` (`status`);
|
|
41
|
+
CREATE INDEX IF NOT EXISTS `idx_jobs_cancelled_run_at` ON `jobs` (`cancelled`, `will_run_at`);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS `idx_jobs_queue_cancelled_run_at_delay` ON `jobs` (`queue`, `cancelled`, `will_run_at`, `delay`);
|
|
43
|
+
CREATE INDEX IF NOT EXISTS `idx_jobs_queue_id` ON `jobs` (`queue`, `id`);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS `idx_jobs_queue_run_at` ON `jobs` (`queue`, `will_run_at`);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_meter_events_customer_status ON meter_events(json_extract(payload, '$.customer_id'), status, livemode, created_at);
|
|
46
|
+
CREATE INDEX IF NOT EXISTS `idx_meter_events_event_livemode_created` ON `meter_events` (`event_name`, `livemode`, `created_at`);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS `idx_meter_events_livemode_event_timestamp` ON `meter_events` (`livemode`, `event_name`, `timestamp`);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS `idx_meter_events_status_created` ON `meter_events` (`status`, `created_at`);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_meter_events_subscription_status ON meter_events(json_extract(payload, '$.subscription_id'), status, livemode, created_at);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS `idx_meters_livemode_status` ON `meters` (`livemode`, `status`);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS `idx_payment_currencies_base_livemode` ON `payment_currencies` (`is_base_currency`, `livemode`);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS `idx_payment_intent_currency_id` ON `payment_intents` (`currency_id`);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS `idx_payment_intent_customer_id` ON `payment_intents` (`customer_id`);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS `idx_payment_intent_invoice_id` ON `payment_intents` (`invoice_id`);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS `idx_payment_intent_status_updated_at` ON `payment_intents` (`status`, `updated_at`);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS `idx_payment_stats_timestamp_currency_id` ON `payment_stats` (`timestamp`, `currency_id`);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS `idx_payouts_updated_at_status` ON `payouts` (`updated_at`, `status`);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS `idx_pq_created` ON `price_quotes` (`created_at`);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS `idx_pq_currency_created` ON `price_quotes` (`rate_currency_symbol`, `created_at`);
|
|
60
|
+
CREATE UNIQUE INDEX IF NOT EXISTS `idx_pq_idempotency` ON `price_quotes` (`idempotency_key`);
|
|
61
|
+
CREATE INDEX IF NOT EXISTS `idx_pq_invoice_status` ON `price_quotes` (`invoice_id`, `status`);
|
|
62
|
+
CREATE INDEX IF NOT EXISTS `idx_pq_session_status_expires` ON `price_quotes` (`session_id`, `status`, `expires_at`);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS `idx_refunds_payment_intent_id_type` ON `refunds` (`payment_intent_id`, `type`);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS `idx_refunds_status_type_updated_at` ON `refunds` (`status`, `type`, `updated_at`);
|
|
65
|
+
CREATE INDEX IF NOT EXISTS `idx_refunds_subscription_id_type` ON `refunds` (`subscription_id`, `type`);
|
|
66
|
+
CREATE UNIQUE INDEX IF NOT EXISTS `idx_revenue_snapshots_unique` ON `revenue_snapshots` (`timestamp`, `currency_id`, `livemode`, `period_type`);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS `idx_subscription_item_subscription_id_price_id` ON `subscription_items` (`subscription_id`, `price_id`);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS `idx_subscription_period` ON `subscriptions` (`current_period_start`, `current_period_end`);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS `idx_subscription_status` ON `subscriptions` (`status`);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS `idx_subscriptions_customer_id` ON `subscriptions` (`customer_id`);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS `idx_usage_records_subscription_item_id_timestamp` ON `usage_records` (`subscription_item_id`, `timestamp`);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS `idx_webhook_attempts_event_id` ON `webhook_attempts` (`event_id`);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS `idx_webhook_attempts_webhook_endpoint_id` ON `webhook_attempts` (`webhook_endpoint_id`);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS `idx_webhook_endpoint_status_livemode` ON `webhook_endpoints` (`status`, `livemode`);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS `tax_rates_query_index` ON `tax_rates` (`country`, `state`, `postal_code`, `tax_code`);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-- Distributed locks table for D1-based locking across CF Worker isolates
|
|
2
|
+
CREATE TABLE IF NOT EXISTS _locks (
|
|
3
|
+
name TEXT PRIMARY KEY,
|
|
4
|
+
owner TEXT NOT NULL,
|
|
5
|
+
expires_at INTEGER NOT NULL
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
-- Unique index on Stripe invoice ID to prevent duplicate mirroring
|
|
9
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_invoices_stripe_id
|
|
10
|
+
ON invoices(json_extract(metadata, '$.stripe_id'))
|
|
11
|
+
WHERE json_extract(metadata, '$.stripe_id') IS NOT NULL;
|
|
12
|
+
|
|
13
|
+
-- DID Connect token storage (D1 for strong consistency instead of KV)
|
|
14
|
+
CREATE TABLE IF NOT EXISTS _did_connect_tokens (
|
|
15
|
+
token TEXT PRIMARY KEY,
|
|
16
|
+
data TEXT NOT NULL,
|
|
17
|
+
expires_at INTEGER NOT NULL
|
|
18
|
+
);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
-- Payment Kit: IAP foundation
|
|
2
|
+
-- Adds schema needed for App Store + Google Play subscription channels.
|
|
3
|
+
-- Mirrors blocklets/core/api/src/store/migrations/20260526-iap-foundation.ts.
|
|
4
|
+
--
|
|
5
|
+
-- NOTE: SQLite does not support `ALTER TABLE ADD COLUMN IF NOT EXISTS`.
|
|
6
|
+
-- Wrangler tracks applied migrations in the `d1_migrations` table and skips
|
|
7
|
+
-- re-application by filename, so re-running this file via `wrangler d1
|
|
8
|
+
-- migrations apply` is safe. Do not invoke via `wrangler d1 execute` more
|
|
9
|
+
-- than once on the same database.
|
|
10
|
+
|
|
11
|
+
-- 1. Customer: per-channel UUID for IAP appAccountToken / obfuscatedAccountId mapping (D-004)
|
|
12
|
+
ALTER TABLE customers ADD COLUMN app_store_uuid VARCHAR(36);
|
|
13
|
+
ALTER TABLE customers ADD COLUMN google_play_uuid VARCHAR(36);
|
|
14
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_customers_app_store_uuid
|
|
15
|
+
ON customers(app_store_uuid)
|
|
16
|
+
WHERE app_store_uuid IS NOT NULL;
|
|
17
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_customers_google_play_uuid
|
|
18
|
+
ON customers(google_play_uuid)
|
|
19
|
+
WHERE google_play_uuid IS NOT NULL;
|
|
20
|
+
|
|
21
|
+
-- 2. Subscription: channel + environment (D-005)
|
|
22
|
+
ALTER TABLE subscriptions ADD COLUMN channel VARCHAR(20);
|
|
23
|
+
ALTER TABLE subscriptions ADD COLUMN environment VARCHAR(20) DEFAULT 'production';
|
|
24
|
+
|
|
25
|
+
-- 3. Invoice: three-segment amounts (D-001 A)
|
|
26
|
+
ALTER TABLE invoices ADD COLUMN gross_amount VARCHAR(32);
|
|
27
|
+
ALTER TABLE invoices ADD COLUMN platform_fee VARCHAR(32) DEFAULT '0';
|
|
28
|
+
ALTER TABLE invoices ADD COLUMN net_amount VARCHAR(32);
|
|
29
|
+
UPDATE invoices SET gross_amount = total, net_amount = total WHERE gross_amount IS NULL;
|
|
30
|
+
|
|
31
|
+
-- 4. Refund: origin source (merchant_initiated | platform_initiated)
|
|
32
|
+
ALTER TABLE refunds ADD COLUMN source VARCHAR(30) DEFAULT 'merchant_initiated';
|
|
33
|
+
|
|
34
|
+
-- 5. Entitlement tables (D-003 B)
|
|
35
|
+
CREATE TABLE IF NOT EXISTS `entitlements` (
|
|
36
|
+
`id` VARCHAR(30) NOT NULL PRIMARY KEY,
|
|
37
|
+
`livemode` TINYINT(1) NOT NULL,
|
|
38
|
+
`key` VARCHAR(64) NOT NULL UNIQUE,
|
|
39
|
+
`name` VARCHAR(255),
|
|
40
|
+
`description` TEXT,
|
|
41
|
+
`metadata` JSON,
|
|
42
|
+
`created_at` DATETIME NOT NULL,
|
|
43
|
+
`updated_at` DATETIME NOT NULL
|
|
44
|
+
);
|
|
45
|
+
CREATE UNIQUE INDEX IF NOT EXISTS `idx_entitlements_key` ON `entitlements` (`key`);
|
|
46
|
+
|
|
47
|
+
CREATE TABLE IF NOT EXISTS `entitlement_products` (
|
|
48
|
+
`entitlement_id` VARCHAR(30) NOT NULL,
|
|
49
|
+
`product_id` VARCHAR(30) NOT NULL,
|
|
50
|
+
`created_at` DATETIME NOT NULL,
|
|
51
|
+
`updated_at` DATETIME NOT NULL,
|
|
52
|
+
PRIMARY KEY (`entitlement_id`, `product_id`)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE TABLE IF NOT EXISTS `entitlement_grants` (
|
|
56
|
+
`id` VARCHAR(30) NOT NULL PRIMARY KEY,
|
|
57
|
+
`livemode` TINYINT(1) NOT NULL,
|
|
58
|
+
`entitlement_id` VARCHAR(30) NOT NULL,
|
|
59
|
+
`customer_id` VARCHAR(18) NOT NULL,
|
|
60
|
+
`source_subscription_id` VARCHAR(30),
|
|
61
|
+
`source_channel` VARCHAR(20) NOT NULL,
|
|
62
|
+
`active_from` INTEGER NOT NULL,
|
|
63
|
+
`active_until` INTEGER NOT NULL,
|
|
64
|
+
`status` VARCHAR(20) NOT NULL DEFAULT 'active',
|
|
65
|
+
`metadata` JSON,
|
|
66
|
+
`created_at` DATETIME NOT NULL,
|
|
67
|
+
`updated_at` DATETIME NOT NULL
|
|
68
|
+
);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS `idx_entitlement_grants_lookup`
|
|
70
|
+
ON `entitlement_grants` (`customer_id`, `entitlement_id`, `status`);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS `idx_entitlement_grants_source_sub`
|
|
72
|
+
ON `entitlement_grants` (`source_subscription_id`);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
-- Payment Kit: IAP multi-tenant backfill
|
|
2
|
+
-- Backfills metadata.bundle_id / metadata.package_name on existing Prices and
|
|
3
|
+
-- payment_details.app_store.bundle_id / payment_details.google_play.package_name
|
|
4
|
+
-- on existing Subscriptions, so Payment Kit can be wired into multiple iOS /
|
|
5
|
+
-- Android apps without same-SKU collisions across App Store / Play Console
|
|
6
|
+
-- namespaces (each store's SKU space is per-app, not global).
|
|
7
|
+
--
|
|
8
|
+
-- Backend code already filters Price.findOne by (sku, bundle_id) / (sku,
|
|
9
|
+
-- package_name); without this backfill, every pre-existing Price would stop
|
|
10
|
+
-- resolving the moment the new lookup ships.
|
|
11
|
+
--
|
|
12
|
+
-- Safety: tenant value is DERIVED from the configured PaymentMethods (which
|
|
13
|
+
-- store bundle_id / package_name as plain text under settings JSON — only
|
|
14
|
+
-- private keys are encrypted). The migration deliberately refuses to guess in
|
|
15
|
+
-- ambiguous setups:
|
|
16
|
+
--
|
|
17
|
+
-- * For Subscriptions we always use the sub's own
|
|
18
|
+
-- `default_payment_method_id` to resolve the tenant, which is a 1:1 map —
|
|
19
|
+
-- never ambiguous as long as the row points at a real PaymentMethod.
|
|
20
|
+
-- * For Prices we update only when EXACTLY ONE active PaymentMethod of the
|
|
21
|
+
-- matching type + livemode exists with a non-null tenant. Multi-tenant
|
|
22
|
+
-- installations (two iOS apps sharing one Payment Kit, etc.) skip the
|
|
23
|
+
-- Price backfill — admin must set bundle_id / package_name explicitly
|
|
24
|
+
-- because the migration can't safely guess which app a Price belongs to.
|
|
25
|
+
--
|
|
26
|
+
-- Idempotent. The IS NULL guards make re-runs a no-op for already-backfilled
|
|
27
|
+
-- rows, and the subquery filters skip rows that can't be resolved safely.
|
|
28
|
+
|
|
29
|
+
-- 1. Prices with App Store SKU → set bundle_id (only when one active
|
|
30
|
+
-- app_store PaymentMethod for the same livemode unambiguously identifies
|
|
31
|
+
-- the tenant).
|
|
32
|
+
UPDATE prices
|
|
33
|
+
SET metadata = json_set(
|
|
34
|
+
metadata,
|
|
35
|
+
'$.bundle_id',
|
|
36
|
+
(SELECT json_extract(pm.settings, '$.app_store.bundle_id')
|
|
37
|
+
FROM payment_methods pm
|
|
38
|
+
WHERE pm.type = 'app_store'
|
|
39
|
+
AND pm.livemode = prices.livemode
|
|
40
|
+
AND pm.active = 1
|
|
41
|
+
AND json_extract(pm.settings, '$.app_store.bundle_id') IS NOT NULL
|
|
42
|
+
LIMIT 1)
|
|
43
|
+
)
|
|
44
|
+
WHERE json_extract(metadata, '$.app_store_product_id') IS NOT NULL
|
|
45
|
+
AND json_extract(metadata, '$.bundle_id') IS NULL
|
|
46
|
+
AND (
|
|
47
|
+
SELECT COUNT(*) FROM payment_methods pm
|
|
48
|
+
WHERE pm.type = 'app_store'
|
|
49
|
+
AND pm.livemode = prices.livemode
|
|
50
|
+
AND pm.active = 1
|
|
51
|
+
AND json_extract(pm.settings, '$.app_store.bundle_id') IS NOT NULL
|
|
52
|
+
) = 1;
|
|
53
|
+
|
|
54
|
+
-- 2. Prices with Google Play SKU → set package_name (same single-tenant guard).
|
|
55
|
+
UPDATE prices
|
|
56
|
+
SET metadata = json_set(
|
|
57
|
+
metadata,
|
|
58
|
+
'$.package_name',
|
|
59
|
+
(SELECT json_extract(pm.settings, '$.google_play.package_name')
|
|
60
|
+
FROM payment_methods pm
|
|
61
|
+
WHERE pm.type = 'google_play'
|
|
62
|
+
AND pm.livemode = prices.livemode
|
|
63
|
+
AND pm.active = 1
|
|
64
|
+
AND json_extract(pm.settings, '$.google_play.package_name') IS NOT NULL
|
|
65
|
+
LIMIT 1)
|
|
66
|
+
)
|
|
67
|
+
WHERE json_extract(metadata, '$.google_play_product_id') IS NOT NULL
|
|
68
|
+
AND json_extract(metadata, '$.package_name') IS NULL
|
|
69
|
+
AND (
|
|
70
|
+
SELECT COUNT(*) FROM payment_methods pm
|
|
71
|
+
WHERE pm.type = 'google_play'
|
|
72
|
+
AND pm.livemode = prices.livemode
|
|
73
|
+
AND pm.active = 1
|
|
74
|
+
AND json_extract(pm.settings, '$.google_play.package_name') IS NOT NULL
|
|
75
|
+
) = 1;
|
|
76
|
+
|
|
77
|
+
-- 3. App Store Subscriptions → set payment_details.app_store.bundle_id from
|
|
78
|
+
-- the sub's own default_payment_method (1:1 — always safe).
|
|
79
|
+
UPDATE subscriptions
|
|
80
|
+
SET payment_details = json_set(
|
|
81
|
+
payment_details,
|
|
82
|
+
'$.app_store.bundle_id',
|
|
83
|
+
(SELECT json_extract(pm.settings, '$.app_store.bundle_id')
|
|
84
|
+
FROM payment_methods pm
|
|
85
|
+
WHERE pm.id = subscriptions.default_payment_method_id)
|
|
86
|
+
)
|
|
87
|
+
WHERE channel = 'app_store'
|
|
88
|
+
AND json_extract(payment_details, '$.app_store.bundle_id') IS NULL
|
|
89
|
+
AND default_payment_method_id IS NOT NULL
|
|
90
|
+
AND (
|
|
91
|
+
SELECT json_extract(pm.settings, '$.app_store.bundle_id')
|
|
92
|
+
FROM payment_methods pm
|
|
93
|
+
WHERE pm.id = subscriptions.default_payment_method_id
|
|
94
|
+
) IS NOT NULL;
|
|
95
|
+
|
|
96
|
+
-- 4. Google Play Subscriptions → set payment_details.google_play.package_name.
|
|
97
|
+
UPDATE subscriptions
|
|
98
|
+
SET payment_details = json_set(
|
|
99
|
+
payment_details,
|
|
100
|
+
'$.google_play.package_name',
|
|
101
|
+
(SELECT json_extract(pm.settings, '$.google_play.package_name')
|
|
102
|
+
FROM payment_methods pm
|
|
103
|
+
WHERE pm.id = subscriptions.default_payment_method_id)
|
|
104
|
+
)
|
|
105
|
+
WHERE channel = 'google_play'
|
|
106
|
+
AND json_extract(payment_details, '$.google_play.package_name') IS NULL
|
|
107
|
+
AND default_payment_method_id IS NOT NULL
|
|
108
|
+
AND (
|
|
109
|
+
SELECT json_extract(pm.settings, '$.google_play.package_name')
|
|
110
|
+
FROM payment_methods pm
|
|
111
|
+
WHERE pm.id = subscriptions.default_payment_method_id
|
|
112
|
+
) IS NOT NULL;
|