payment-kit 1.27.2 → 1.28.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.
Files changed (184) hide show
  1. package/__blocklet__.js +37 -0
  2. package/api/ocap-1.30-subpath-shims.d.ts +35 -0
  3. package/api/src/crons/index.ts +10 -0
  4. package/api/src/crons/metering-subscription-detection.ts +12 -14
  5. package/api/src/crons/overdue-detection.ts +51 -74
  6. package/api/src/integrations/arcblock/nft.ts +6 -2
  7. package/api/src/integrations/arcblock/stake.ts +3 -2
  8. package/api/src/integrations/arcblock/token.ts +4 -4
  9. package/api/src/integrations/blocklet/notification.ts +1 -1
  10. package/api/src/integrations/ethereum/tx.ts +29 -0
  11. package/api/src/integrations/stripe/handlers/invoice.ts +70 -53
  12. package/api/src/integrations/stripe/handlers/payment-intent.ts +8 -1
  13. package/api/src/integrations/stripe/resource.ts +8 -0
  14. package/api/src/libs/audit.ts +32 -16
  15. package/api/src/libs/auth.ts +49 -2
  16. package/api/src/libs/chain-error.ts +31 -0
  17. package/api/src/libs/error.ts +15 -0
  18. package/api/src/libs/event.ts +42 -1
  19. package/api/src/libs/invoice.ts +69 -34
  20. package/api/src/libs/notification/template/customer-auto-recharge-daily-limit-exceeded.ts +1 -3
  21. package/api/src/libs/notification/template/customer-auto-recharge-failed.ts +1 -3
  22. package/api/src/libs/notification/template/customer-credit-grant-granted.ts +1 -3
  23. package/api/src/libs/notification/template/customer-credit-insufficient.ts +1 -3
  24. package/api/src/libs/notification/template/customer-credit-low-balance.ts +1 -3
  25. package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -3
  26. package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -3
  27. package/api/src/libs/notification/template/one-time-payment-refund-succeeded.ts +1 -3
  28. package/api/src/libs/notification/template/one-time-payment-succeeded.ts +1 -3
  29. package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -3
  30. package/api/src/libs/notification/template/subscription-slippage-exceeded.ts +1 -3
  31. package/api/src/libs/notification/template/subscription-slippage-warning.ts +1 -3
  32. package/api/src/libs/notification/template/subscription-succeeded.ts +1 -1
  33. package/api/src/libs/pagination.ts +14 -9
  34. package/api/src/libs/payment.ts +25 -10
  35. package/api/src/libs/session.ts +1 -1
  36. package/api/src/libs/timing.ts +35 -0
  37. package/api/src/libs/util.ts +16 -15
  38. package/api/src/libs/wallet-migration.ts +72 -53
  39. package/api/src/queues/auto-recharge.ts +1 -1
  40. package/api/src/queues/credit-consume.ts +94 -12
  41. package/api/src/queues/credit-grant.ts +4 -0
  42. package/api/src/queues/event.ts +14 -2
  43. package/api/src/queues/invoice.ts +1 -0
  44. package/api/src/queues/payment.ts +83 -15
  45. package/api/src/queues/refund.ts +84 -71
  46. package/api/src/queues/subscription.ts +1 -0
  47. package/api/src/routes/checkout-sessions.ts +82 -43
  48. package/api/src/routes/connect/change-payment.ts +2 -0
  49. package/api/src/routes/connect/change-plan.ts +2 -0
  50. package/api/src/routes/connect/pay.ts +12 -3
  51. package/api/src/routes/connect/setup.ts +3 -1
  52. package/api/src/routes/connect/shared.ts +52 -39
  53. package/api/src/routes/connect/subscribe.ts +4 -1
  54. package/api/src/routes/credit-grants.ts +25 -17
  55. package/api/src/routes/donations.ts +2 -2
  56. package/api/src/routes/meter-events.ts +16 -6
  57. package/api/src/routes/payment-links.ts +1 -1
  58. package/api/src/routes/payment-methods.ts +1 -1
  59. package/api/src/routes/settings.ts +1 -1
  60. package/api/src/routes/tax-rates.ts +1 -1
  61. package/api/src/store/models/customer.ts +23 -1
  62. package/api/src/store/models/payment-method.ts +4 -0
  63. package/api/src/store/models/price.ts +23 -14
  64. package/api/tests/libs/wallet-migration.spec.ts +4 -4
  65. package/api/tests/queues/credit-consume-batch.spec.ts +5 -2
  66. package/api/tests/queues/credit-consume.spec.ts +8 -4
  67. package/api/tests/routes/credit-grants.spec.ts +1 -0
  68. package/blocklet.yml +1 -1
  69. package/cloudflare/MIGRATION-CHALLENGES.md +676 -0
  70. package/cloudflare/MIGRATION-RUNBOOK.md +777 -0
  71. package/cloudflare/README.md +499 -0
  72. package/cloudflare/STAGING-MIGRATION-GUIDE.md +602 -0
  73. package/cloudflare/build.ts +151 -0
  74. package/cloudflare/did-connect-auth.ts +527 -0
  75. package/cloudflare/docs/2026-04-22-sdk-1.30.9-upgrade-retro.md +324 -0
  76. package/cloudflare/docs/2026-04-24-queue-ops-followup.md +218 -0
  77. package/cloudflare/docs/cf-queues-ops-alert-analysis.md +663 -0
  78. package/cloudflare/docs/cf-workers-local-dev-and-fixes.md +284 -0
  79. package/cloudflare/docs/cleanup-tasks-2026-05.md +62 -0
  80. package/cloudflare/docs/payment-kit-platform-analysis-2026-04-20.md +354 -0
  81. package/cloudflare/frontend-shims/buffer-polyfill.ts +9 -0
  82. package/cloudflare/frontend-shims/js-sdk.ts +43 -0
  83. package/cloudflare/frontend-shims/mime-types.ts +46 -0
  84. package/cloudflare/frontend-shims/session.ts +24 -0
  85. package/cloudflare/frontend-shims/vite-plugin-noop.ts +6 -0
  86. package/cloudflare/index.html +40 -0
  87. package/cloudflare/migrate-to-d1.js +252 -0
  88. package/cloudflare/migrations/0001_initial_schema.sql +82 -0
  89. package/cloudflare/migrations/0002_indexes.sql +75 -0
  90. package/cloudflare/migrations/0003_locks_and_constraints.sql +18 -0
  91. package/cloudflare/run-build.js +390 -0
  92. package/cloudflare/scripts/test-decrypt.js +102 -0
  93. package/cloudflare/shims/arcblock-ws.ts +20 -0
  94. package/cloudflare/shims/axios-http-adapter.ts +4 -0
  95. package/cloudflare/shims/axios-lite.ts +117 -0
  96. package/cloudflare/shims/blocklet-sdk/auth-service.ts +33 -0
  97. package/cloudflare/shims/blocklet-sdk/cdn.ts +3 -0
  98. package/cloudflare/shims/blocklet-sdk/component-api.ts +35 -0
  99. package/cloudflare/shims/blocklet-sdk/component.ts +18 -0
  100. package/cloudflare/shims/blocklet-sdk/config.ts +8 -0
  101. package/cloudflare/shims/blocklet-sdk/did.ts +14 -0
  102. package/cloudflare/shims/blocklet-sdk/env.ts +12 -0
  103. package/cloudflare/shims/blocklet-sdk/eventbus.ts +3 -0
  104. package/cloudflare/shims/blocklet-sdk/fallback.ts +3 -0
  105. package/cloudflare/shims/blocklet-sdk/index.ts +11 -0
  106. package/cloudflare/shims/blocklet-sdk/logger.ts +11 -0
  107. package/cloudflare/shims/blocklet-sdk/middlewares.ts +15 -0
  108. package/cloudflare/shims/blocklet-sdk/notification.ts +11 -0
  109. package/cloudflare/shims/blocklet-sdk/security.ts +53 -0
  110. package/cloudflare/shims/blocklet-sdk/session.ts +8 -0
  111. package/cloudflare/shims/blocklet-sdk/verify-sign.ts +38 -0
  112. package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +3 -0
  113. package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +6 -0
  114. package/cloudflare/shims/blocklet-sdk/wallet.ts +103 -0
  115. package/cloudflare/shims/cookie-parser.ts +3 -0
  116. package/cloudflare/shims/cors.ts +21 -0
  117. package/cloudflare/shims/cron.ts +189 -0
  118. package/cloudflare/shims/crypto-js-warn.ts +7 -0
  119. package/cloudflare/shims/did-space-js.ts +17 -0
  120. package/cloudflare/shims/did-space.ts +11 -0
  121. package/cloudflare/shims/error.ts +18 -0
  122. package/cloudflare/shims/express-compat/index.ts +80 -0
  123. package/cloudflare/shims/express-compat/types.ts +41 -0
  124. package/cloudflare/shims/fastq.ts +105 -0
  125. package/cloudflare/shims/lock.ts +115 -0
  126. package/cloudflare/shims/mime-types.ts +56 -0
  127. package/cloudflare/shims/nedb-storage.ts +9 -0
  128. package/cloudflare/shims/node-child-process.ts +9 -0
  129. package/cloudflare/shims/node-fs.ts +20 -0
  130. package/cloudflare/shims/node-http.ts +13 -0
  131. package/cloudflare/shims/node-https.ts +4 -0
  132. package/cloudflare/shims/node-misc.ts +15 -0
  133. package/cloudflare/shims/node-net.ts +8 -0
  134. package/cloudflare/shims/node-os.ts +14 -0
  135. package/cloudflare/shims/node-tty.ts +8 -0
  136. package/cloudflare/shims/node-zlib.ts +17 -0
  137. package/cloudflare/shims/noop.ts +26 -0
  138. package/cloudflare/shims/payment-vendor.ts +14 -0
  139. package/cloudflare/shims/querystring.ts +12 -0
  140. package/cloudflare/shims/queue.ts +585 -0
  141. package/cloudflare/shims/rolldown-runtime.ts +43 -0
  142. package/cloudflare/shims/sequelize-d1/datatypes.ts +24 -0
  143. package/cloudflare/shims/sequelize-d1/helpers.ts +46 -0
  144. package/cloudflare/shims/sequelize-d1/index.ts +34 -0
  145. package/cloudflare/shims/sequelize-d1/model.ts +1157 -0
  146. package/cloudflare/shims/sequelize-d1/operators.ts +293 -0
  147. package/cloudflare/shims/sequelize-d1/retry.ts +85 -0
  148. package/cloudflare/shims/sequelize-d1/sequelize-class.ts +119 -0
  149. package/cloudflare/shims/sequelize-d1/timing.ts +81 -0
  150. package/cloudflare/shims/sequelize-d1/types.ts +35 -0
  151. package/cloudflare/shims/stripe-cf.ts +29 -0
  152. package/cloudflare/shims/ws-lite.ts +103 -0
  153. package/cloudflare/shims/xss.ts +3 -0
  154. package/cloudflare/tests/shims/cron.spec.ts +210 -0
  155. package/cloudflare/tests/shims/queue-scheduled.spec.ts +186 -0
  156. package/cloudflare/vite.config.ts +162 -0
  157. package/cloudflare/worker.ts +1553 -0
  158. package/cloudflare/wrangler.json +63 -0
  159. package/cloudflare/wrangler.jsonc +69 -0
  160. package/cloudflare/wrangler.staging.json +66 -0
  161. package/cloudflare/wrangler.toml +28 -0
  162. package/jest.config.js +4 -12
  163. package/package.json +26 -22
  164. package/src/app.tsx +62 -4
  165. package/src/components/customer/link.tsx +9 -13
  166. package/src/components/customer/notification-preference.tsx +3 -2
  167. package/src/components/filter-toolbar.tsx +4 -0
  168. package/src/components/invoice/list.tsx +9 -1
  169. package/src/components/invoice-pdf/utils.ts +2 -1
  170. package/src/components/layout/admin.tsx +39 -5
  171. package/src/components/layout/user-cf.tsx +77 -0
  172. package/src/components/payment-intent/actions.tsx +23 -3
  173. package/src/components/safe-did-address.tsx +75 -0
  174. package/src/libs/patch-user-card.ts +25 -0
  175. package/src/libs/util.ts +5 -7
  176. package/src/pages/admin/billing/meter-events/index.tsx +4 -0
  177. package/src/pages/admin/customers/customers/detail.tsx +2 -2
  178. package/src/pages/admin/customers/customers/index.tsx +2 -2
  179. package/src/pages/admin/overview.tsx +3 -1
  180. package/src/pages/customer/subscription/detail.tsx +4 -4
  181. package/tsconfig.api.json +1 -6
  182. package/tsconfig.json +3 -4
  183. package/tsconfig.types.json +2 -1
  184. package/vite.config.ts +6 -1
@@ -0,0 +1,151 @@
1
+ import { build } from 'esbuild';
2
+ import path from 'path';
3
+
4
+ const cfDir = __dirname;
5
+
6
+ async function main() {
7
+ const result = await build({
8
+ entryPoints: [path.resolve(cfDir, 'worker.ts')],
9
+ bundle: true,
10
+ format: 'esm',
11
+ platform: 'node', // Use node platform — CF Workers nodejs_compat handles Node built-ins
12
+ target: 'esnext',
13
+ outdir: path.resolve(cfDir, 'dist'),
14
+ minify: true,
15
+ sourcemap: true,
16
+ metafile: true,
17
+ mainFields: ['module', 'main'], // Resolve ESM first, then CJS
18
+ conditions: ['import', 'module', 'default'],
19
+ // CF Workers runtime provides these
20
+ external: [
21
+ 'cloudflare:*',
22
+ '__STATIC_CONTENT_MANIFEST',
23
+ ],
24
+ alias: {
25
+ // === Sequelize → D1 shim ===
26
+ 'sequelize': path.resolve(cfDir, 'shims/sequelize-d1/index.ts'),
27
+ 'sqlite3': path.resolve(cfDir, 'shims/noop.ts'),
28
+ 'cls-hooked': path.resolve(cfDir, 'shims/noop.ts'),
29
+ 'express-async-errors': path.resolve(cfDir, 'shims/noop.ts'),
30
+
31
+ // === Express → shim ===
32
+ 'express': path.resolve(cfDir, 'shims/express-compat/index.ts'),
33
+ 'cors': path.resolve(cfDir, 'shims/cors.ts'),
34
+ 'cookie-parser': path.resolve(cfDir, 'shims/cookie-parser.ts'),
35
+
36
+ // === @blocklet/sdk — each sub-path needs its own alias ===
37
+ // NOTE: more specific paths MUST come before less specific ones
38
+ '@blocklet/sdk/lib/middlewares/fallback': path.resolve(cfDir, 'shims/blocklet-sdk/fallback.ts'),
39
+ '@blocklet/sdk/lib/middlewares/cdn': path.resolve(cfDir, 'shims/blocklet-sdk/cdn.ts'),
40
+ '@blocklet/sdk/lib/middlewares/session': path.resolve(cfDir, 'shims/blocklet-sdk/session.ts'),
41
+ '@blocklet/sdk/lib/middlewares': path.resolve(cfDir, 'shims/blocklet-sdk/middlewares.ts'),
42
+ '@blocklet/sdk/lib/env': path.resolve(cfDir, 'shims/blocklet-sdk/env.ts'),
43
+ '@blocklet/sdk/lib/config': path.resolve(cfDir, 'shims/blocklet-sdk/config.ts'),
44
+ '@blocklet/sdk/lib/component': path.resolve(cfDir, 'shims/blocklet-sdk/component.ts'),
45
+ '@blocklet/sdk/lib/wallet': path.resolve(cfDir, 'shims/blocklet-sdk/wallet.ts'),
46
+ '@blocklet/sdk/lib/wallet-authenticator': path.resolve(cfDir, 'shims/blocklet-sdk/wallet-authenticator.ts'),
47
+ '@blocklet/sdk/lib/wallet-handler': path.resolve(cfDir, 'shims/blocklet-sdk/wallet-handler.ts'),
48
+ '@blocklet/sdk/lib/security': path.resolve(cfDir, 'shims/blocklet-sdk/security.ts'),
49
+ '@blocklet/sdk/lib/util/verify-sign': path.resolve(cfDir, 'shims/blocklet-sdk/verify-sign.ts'),
50
+ '@blocklet/sdk/lib/util/component-api': path.resolve(cfDir, 'shims/blocklet-sdk/component-api.ts'),
51
+ '@blocklet/sdk/lib/error-handler': path.resolve(cfDir, 'shims/noop.ts'),
52
+ '@blocklet/sdk/lib/did': path.resolve(cfDir, 'shims/blocklet-sdk/did.ts'),
53
+ '@blocklet/sdk/lib/types/notification': path.resolve(cfDir, 'shims/noop.ts'),
54
+ '@blocklet/sdk/service/notification': path.resolve(cfDir, 'shims/blocklet-sdk/notification.ts'),
55
+ '@blocklet/sdk/service/eventbus': path.resolve(cfDir, 'shims/blocklet-sdk/eventbus.ts'),
56
+ '@blocklet/sdk/service/auth': path.resolve(cfDir, 'shims/blocklet-sdk/auth-service.ts'),
57
+ '@blocklet/sdk/service/blocklet': path.resolve(cfDir, 'shims/blocklet-sdk/auth-service.ts'),
58
+ // Catch-all for any other @blocklet/sdk paths
59
+ '@blocklet/sdk': path.resolve(cfDir, 'shims/blocklet-sdk/index.ts'),
60
+
61
+ // === Other @blocklet packages ===
62
+ '@blocklet/xss': path.resolve(cfDir, 'shims/xss.ts'),
63
+ '@blocklet/error': path.resolve(cfDir, 'shims/error.ts'),
64
+ '@blocklet/logger': path.resolve(cfDir, 'shims/blocklet-sdk/logger.ts'),
65
+ '@blocklet/did-space-js': path.resolve(cfDir, 'shims/did-space-js.ts'),
66
+ '@blocklet/payment-vendor': path.resolve(cfDir, 'shims/payment-vendor.ts'),
67
+
68
+ // === ArcBlock / DID packages ===
69
+ '@arcblock/did-connect-storage-nedb': path.resolve(cfDir, 'shims/nedb-storage.ts'),
70
+
71
+ // === Other dependencies ===
72
+ 'ws': path.resolve(cfDir, 'shims/ws-lite.ts'),
73
+ 'pg': path.resolve(cfDir, 'shims/noop.ts'),
74
+ 'follow-redirects': path.resolve(cfDir, 'shims/noop.ts'),
75
+ 'crypto-js': path.resolve(cfDir, 'shims/crypto-js-warn.ts'),
76
+ 'mime-types': path.resolve(cfDir, 'shims/mime-types.ts'),
77
+ '@abtnode/cron': path.resolve(cfDir, 'shims/cron.ts'),
78
+ 'querystring': path.resolve(cfDir, 'shims/querystring.ts'),
79
+ 'dotenv-flow': path.resolve(cfDir, 'shims/noop.ts'),
80
+ 'fastq': path.resolve(cfDir, 'shims/fastq.ts'),
81
+ },
82
+ banner: {
83
+ js: [
84
+ // process polyfills
85
+ 'if (typeof process !== "undefined") { if (!process.stderr) process.stderr = { fd: 2, write: function(){} }; else if (process.stderr.fd === undefined) process.stderr.fd = 2; if (!process.stdout) process.stdout = { fd: 1, write: function(){} }; else if (process.stdout.fd === undefined) process.stdout.fd = 1; }',
86
+ 'if (typeof process !== "undefined" && !process.on) { process.on = function(){}; process.once = function(){}; process.off = function(){}; process.removeListener = function(){}; process.emit = function(){}; process.exit = function(){}; }',
87
+ // CF Workers: defer timers called during global scope init until first request
88
+ 'var __deferredTimers = []; var __timersReady = false;',
89
+ 'globalThis.__flushDeferredTimers = function() { if (__timersReady) return; __timersReady = true; var q = __deferredTimers; __deferredTimers = []; q.forEach(function(entry) { try { entry.fn.apply(null, entry.args); } catch(e) { console.error("deferred timer error:", e); } }); };',
90
+ 'var _origSetTimeout = globalThis.setTimeout; globalThis.setTimeout = function() { if (!__timersReady) { var args = arguments; __deferredTimers.push({fn: function(){ _origSetTimeout.apply(globalThis, args); }, args: []}); return { unref: function(){}, ref: function(){}, [Symbol.toPrimitive]: function(){ return 0; } }; } var id = _origSetTimeout.apply(this, arguments); if (typeof id === "number") { return { unref: function(){}, ref: function(){}, [Symbol.toPrimitive]: function(){ return id; } }; } if (id && !id.unref) id.unref = function(){}; return id; };',
91
+ 'var _origSetInterval = globalThis.setInterval; globalThis.setInterval = function() { if (!__timersReady) { var args = arguments; __deferredTimers.push({fn: function(){ _origSetInterval.apply(globalThis, args); }, args: []}); return { unref: function(){}, ref: function(){}, [Symbol.toPrimitive]: function(){ return 0; } }; } var id = _origSetInterval.apply(this, arguments); if (typeof id === "number") { return { unref: function(){}, ref: function(){}, [Symbol.toPrimitive]: function(){ return id; } }; } if (id && !id.unref) id.unref = function(){}; return id; };',
92
+ 'var _origSetImmediate = globalThis.setImmediate; if (_origSetImmediate) { globalThis.setImmediate = function() { if (!__timersReady) { var args = arguments; __deferredTimers.push({fn: function(){ _origSetImmediate.apply(globalThis, args); }, args: []}); return { unref: function(){}, ref: function(){} }; } return _origSetImmediate.apply(this, arguments); }; } else { globalThis.setImmediate = function(fn) { if (!__timersReady) { __deferredTimers.push({fn: function(){ _origSetTimeout(fn, 0); }, args: []}); return { unref: function(){}, ref: function(){} }; } return _origSetTimeout(fn, 0); }; }',
93
+ 'if (typeof process !== "undefined") { var _origNextTick = process.nextTick; if (_origNextTick) { process.nextTick = function() { if (!__timersReady) { var args = Array.prototype.slice.call(arguments); var fn = args.shift(); __deferredTimers.push({fn: function(){ _origNextTick.apply(process, [fn].concat(args)); }, args: []}); return; } return _origNextTick.apply(process, arguments); }; } else { process.nextTick = function(fn) { if (!__timersReady) { __deferredTimers.push({fn: function(){ _origSetTimeout(fn, 0); }, args: []}); return; } _origSetTimeout(fn, 0); }; } }',
94
+ // require polyfill for Node built-ins
95
+ '(function() {',
96
+ ' var _origRequire = typeof globalThis.require === "function" ? globalThis.require : null;',
97
+ ' var _qsShim = { stringify: function(obj) { return new URLSearchParams(obj).toString(); }, parse: function(str) { return Object.fromEntries(new URLSearchParams(str).entries()); } };',
98
+ ' var _utilShim = { deprecate: function(fn) { return fn; }, inspect: function(obj) { return JSON.stringify(obj); }, inherits: function(ctor, superCtor) { if (superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor } }); } }, format: function() { return Array.prototype.join.call(arguments, " "); } };',
99
+ ' var _ttyShim = { isatty: function() { return false; }, ReadStream: function(){}, WriteStream: function(){} };',
100
+ ' var _emptyMod = {};',
101
+ ' var _httpShim = (function() { var _noop = function(){}; _noop.prototype = {}; return { request: _noop, get: _noop, Agent: _noop, globalAgent: { options: {} }, STATUS_CODES: {}, METHODS: [], createServer: _noop }; })();',
102
+ ' globalThis.require = function(mod) {',
103
+ ' if (mod === "querystring") return _qsShim;',
104
+ ' if (mod === "util") return _utilShim;',
105
+ ' if (mod === "tty") return _ttyShim;',
106
+ ' if (mod === "http" || mod === "https") return _httpShim;',
107
+ ' if (mod === "crypto") return { randomBytes: function(n) { var b = new Uint8Array(n); crypto.getRandomValues(b); return Buffer.from(b); }, createHash: function() { return { update: function() { return this; }, digest: function() { return ""; } }; } };',
108
+ ' if (mod === "stream" || mod === "fs" || mod === "net" || mod === "os" || mod === "path" || mod === "zlib" || mod === "tls" || mod === "child_process" || mod === "worker_threads") return _emptyMod;',
109
+ ' if (_origRequire) return _origRequire(mod);',
110
+ ' console.warn("Cannot require: " + mod);',
111
+ ' return _emptyMod;',
112
+ ' };',
113
+ '})();',
114
+ ].join('\n'),
115
+ },
116
+ plugins: [
117
+ {
118
+ name: 'rolldown-runtime-shim',
119
+ setup(b: any) {
120
+ b.onResolve({ filter: /rolldown_runtime/ }, () => ({
121
+ path: path.resolve(cfDir, 'shims/rolldown-runtime.ts'),
122
+ }));
123
+ },
124
+ },
125
+ ],
126
+ logLevel: 'warning',
127
+ });
128
+
129
+ const outputs = Object.keys(result.metafile!.outputs);
130
+ for (const o of outputs) {
131
+ const size = result.metafile!.outputs[o].bytes;
132
+ if (!o.endsWith('.map')) {
133
+ console.log(`${o}: ${(size / 1024).toFixed(1)}KB`);
134
+ }
135
+ }
136
+ console.log('BUILD SUCCESS');
137
+ }
138
+
139
+ main().catch((err) => {
140
+ console.error('BUILD FAILED');
141
+ console.error(err.message);
142
+ if (err.errors) {
143
+ for (const e of err.errors.slice(0, 30)) {
144
+ console.error(` ${e.text}`);
145
+ if (e.location) {
146
+ console.error(` at ${e.location.file}:${e.location.line}`);
147
+ }
148
+ }
149
+ }
150
+ process.exit(1);
151
+ });
@@ -0,0 +1,527 @@
1
+ /**
2
+ * DID Connect authentication for Cloudflare Workers.
3
+ *
4
+ * Since the published @arcblock/did-connect-js@1.28.0 does not include the Hono adapter,
5
+ * we port the adapter logic here. When a newer version with native Hono support ships,
6
+ * this file can be replaced by a direct import.
7
+ *
8
+ * Reference: blockchain/did/did-connect/src/adapters/hono.ts
9
+ */
10
+ import { WalletAuthenticator, WalletHandlers } from '@arcblock/did-connect-js';
11
+ // abt-wallet 4.20+ is dual-decode (both CBOR + protobuf), so we can go back
12
+ // to @ocap/client/encode's CBOR-only txEncoder. Drops ~300KB google-protobuf
13
+ // runtime from the worker bundle. Requires abt-wallet >= 4.20.
14
+ // fetchContext is also exported so we can pre-warm the module-level cache
15
+ // at isolate init time — avoids the first-request 8s merchant timeout that
16
+ // comes from fetchContext hitting beta chain (2 GraphQL queries in parallel).
17
+ import { createTxEncoder, fetchContext } from '@ocap/client/encode';
18
+ import * as Mcrypto from '@ocap/mcrypto';
19
+ import { fromSecretKey } from '@ocap/wallet';
20
+ import { EventEmitter } from 'events';
21
+
22
+ // Import original connect handlers — esbuild aliases handle all dependency replacements
23
+ import collectHandlers from '../api/src/routes/connect/collect';
24
+ import collectBatchHandlers from '../api/src/routes/connect/collect-batch';
25
+ import payHandlers from '../api/src/routes/connect/pay';
26
+ import setupHandlers from '../api/src/routes/connect/setup';
27
+ import subscribeHandlers from '../api/src/routes/connect/subscribe';
28
+ import changePaymentHandlers from '../api/src/routes/connect/change-payment';
29
+ import changePlanHandlers from '../api/src/routes/connect/change-plan';
30
+ import changePayerHandlers from '../api/src/routes/connect/change-payer';
31
+ import rechargeHandlers from '../api/src/routes/connect/recharge';
32
+ import rechargeAccountHandlers from '../api/src/routes/connect/recharge-account';
33
+ import delegationHandlers from '../api/src/routes/connect/delegation';
34
+ import overdraftProtectionHandlers from '../api/src/routes/connect/overdraft-protection';
35
+ import reStakeHandlers from '../api/src/routes/connect/re-stake';
36
+ import autoRechargeAuthHandlers from '../api/src/routes/connect/auto-recharge-auth';
37
+
38
+ const walletType = {
39
+ role: Mcrypto.types.RoleType.ROLE_APPLICATION,
40
+ pk: Mcrypto.types.KeyType.ED25519,
41
+ hash: Mcrypto.types.HashType.SHA3,
42
+ };
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // CloudflareKVStorage – ported from blockchain/did/did-connect/src/storage/kv.ts
46
+ // Not yet published in @arcblock/did-connect-js@1.28.0
47
+ // ---------------------------------------------------------------------------
48
+
49
+ // D1-based storage for DID Connect tokens.
50
+ // Replaces KV which is eventually consistent across datacenters (up to 60s delay).
51
+ // D1 is strongly consistent — writes are immediately visible to all readers.
52
+ // This fixes mobile wallet scanning: wallet POST auth writes to datacenter A,
53
+ // browser status polling reads from datacenter B, and sees the update immediately.
54
+ class CloudflareD1Storage extends EventEmitter {
55
+ private ttl: number;
56
+
57
+ constructor(options: { ttl?: number } = {}) {
58
+ super();
59
+ this.ttl = options.ttl ?? 300;
60
+ }
61
+
62
+ private _getDB() {
63
+ const env = (globalThis as any).__CF_ENV__;
64
+ const db = env?.DB;
65
+ if (!db) {
66
+ console.error('[D1Storage] DB not available - __CF_ENV__ missing or no DB binding');
67
+ return db;
68
+ }
69
+ return db.withSession('first-primary');
70
+ }
71
+
72
+ async create(token: string, status = 'created') {
73
+ try {
74
+ const db = this._getDB();
75
+ if (!db) throw new Error('DB not available');
76
+ const record = { token, status };
77
+ const expiresAt = Math.floor(Date.now() / 1000) + this.ttl;
78
+ await db
79
+ .prepare('INSERT OR REPLACE INTO _did_connect_tokens (token, data, expires_at) VALUES (?, ?, ?)')
80
+ .bind(token, JSON.stringify(record), expiresAt)
81
+ .run();
82
+ this.emit('create', record);
83
+ return record;
84
+ } catch (err: any) {
85
+ console.error('[D1Storage] create failed:', token, err?.message || err);
86
+ throw err;
87
+ }
88
+ }
89
+
90
+ async read(token: string) {
91
+ try {
92
+ const db = this._getDB();
93
+ if (!db) return null;
94
+ const now = Math.floor(Date.now() / 1000);
95
+ const row = await db
96
+ .prepare('SELECT data FROM _did_connect_tokens WHERE token = ? AND expires_at > ?')
97
+ .bind(token, now)
98
+ .first();
99
+ if (!row) return null;
100
+ return JSON.parse(row.data as string);
101
+ } catch (err: any) {
102
+ console.error('[D1Storage] read failed:', token, err?.message || err);
103
+ return null;
104
+ }
105
+ }
106
+
107
+ update(token: string, updates: Record<string, any>) {
108
+ // did-connect-js's handlers/util.ts onProcessError calls tokenStorage.update
109
+ // without awaiting it (fire-and-forget). On CF Workers a non-awaited Promise
110
+ // is terminated when the request handler returns, so the UPDATE never hits
111
+ // D1 and the token stays at "scanned" forever — the wallet UI then loops on
112
+ // `status: scanned` and shows "validating..." indefinitely.
113
+ //
114
+ // We wrap the real write in a promise, and register it via the global
115
+ // ctx.waitUntil (exposed as __cfWaitUntil__ in worker.ts) so the runtime
116
+ // keeps the isolate alive until the D1 write finishes, regardless of
117
+ // whether the caller awaits us.
118
+ const promise = (async () => {
119
+ try {
120
+ const existing = await this.read(token);
121
+ if (!existing) return null;
122
+
123
+ delete updates.token;
124
+ const merged = { ...existing, ...updates };
125
+ const expiresAt = Math.floor(Date.now() / 1000) + this.ttl;
126
+ const db = this._getDB();
127
+ if (!db) throw new Error('DB not available');
128
+ await db
129
+ .prepare('UPDATE _did_connect_tokens SET data = ?, expires_at = ? WHERE token = ?')
130
+ .bind(JSON.stringify(merged), expiresAt, token)
131
+ .run();
132
+ this.emit('update', merged);
133
+ return merged;
134
+ } catch (err: any) {
135
+ console.error('[D1Storage] update failed:', token, err?.message || err);
136
+ throw err;
137
+ }
138
+ })();
139
+
140
+ const waitUntil = (globalThis as any).__cfWaitUntil__ as ((p: Promise<any>) => void) | undefined;
141
+ if (typeof waitUntil === 'function') {
142
+ // Swallow rejection in waitUntil — the inner promise will still reject
143
+ // for any caller that does await us.
144
+ waitUntil(promise.catch(() => {}));
145
+ }
146
+ return promise;
147
+ }
148
+
149
+ async delete(token: string) {
150
+ try {
151
+ const existing = await this.read(token);
152
+ if (existing) {
153
+ this.emit('destroy', existing);
154
+ }
155
+ const db = this._getDB();
156
+ if (!db) return;
157
+ await db.prepare('DELETE FROM _did_connect_tokens WHERE token = ?').bind(token).run();
158
+ } catch (err: any) {
159
+ console.error('[D1Storage] delete failed:', token, err?.message || err);
160
+ }
161
+ }
162
+
163
+ async exist(token: string, did?: string) {
164
+ const record = await this.read(token);
165
+ if (!record) return false;
166
+ if (did) return record.did === did;
167
+ return true;
168
+ }
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // Hono adapter – wraps Express-style (req, res, next) handlers for Hono
173
+ // ---------------------------------------------------------------------------
174
+
175
+ function parseCookieHeader(header: string): Record<string, string> {
176
+ const cookies: Record<string, string> = {};
177
+ if (!header) return cookies;
178
+ for (const pair of header.split(';')) {
179
+ const idx = pair.indexOf('=');
180
+ if (idx > 0) {
181
+ const key = pair.slice(0, idx).trim();
182
+ const val = pair.slice(idx + 1).trim();
183
+ cookies[key] = decodeURIComponent(val);
184
+ }
185
+ }
186
+ return cookies;
187
+ }
188
+
189
+ function negotiateLanguage(acceptHeader: string, ...langs: string[]): string | false {
190
+ if (!acceptHeader || langs.length === 0) return langs[0] || false;
191
+ const lower = acceptHeader.toLowerCase();
192
+ for (const lang of langs) {
193
+ const prefix = lang.toLowerCase().split('-')[0];
194
+ if (lower.includes(lang.toLowerCase()) || lower.includes(prefix)) {
195
+ return lang;
196
+ }
197
+ }
198
+ return langs[0] || false;
199
+ }
200
+
201
+ function createHonoRequest(c: any, bodyCache: any = {}): any {
202
+ const url = new URL(c.req.url);
203
+ const rawHeaders: Record<string, string> = {};
204
+ if (c.req.raw?.headers) {
205
+ c.req.raw.headers.forEach((value: string, key: string) => {
206
+ rawHeaders[key.toLowerCase()] = value;
207
+ });
208
+ }
209
+
210
+ return {
211
+ body: bodyCache,
212
+ query: Object.fromEntries(url.searchParams.entries()),
213
+ params: typeof c.req.param === 'function' ? c.req.param() : {},
214
+ headers: rawHeaders,
215
+ cookies: parseCookieHeader(rawHeaders.cookie || ''),
216
+ protocol: url.protocol.replace(':', ''),
217
+ originalUrl: url.pathname + url.search,
218
+ get(name: string) {
219
+ return rawHeaders[name.toLowerCase()];
220
+ },
221
+ header(name: string) {
222
+ return rawHeaders[name.toLowerCase()];
223
+ },
224
+ acceptsLanguages(...langs: string[]) {
225
+ return negotiateLanguage(rawHeaders['accept-language'] || '', ...langs);
226
+ },
227
+ raw: c,
228
+ };
229
+ }
230
+
231
+ function createHonoResponse(): any {
232
+ let result: { statusCode: number; body: any } | null = null;
233
+
234
+ return {
235
+ jsonp(data: any) {
236
+ result = { statusCode: 200, body: data };
237
+ },
238
+ json(data: any) {
239
+ result = { statusCode: 200, body: data };
240
+ },
241
+ status(code: number) {
242
+ return {
243
+ json(data: any) {
244
+ result = { statusCode: code, body: data };
245
+ },
246
+ };
247
+ },
248
+ _getResult() {
249
+ return result;
250
+ },
251
+ };
252
+ }
253
+
254
+ /**
255
+ * Wrap a chain of Express-style middlewares + handler into a single Hono handler.
256
+ */
257
+ function wrapHandler(
258
+ middlewares: Array<(req: any, res: any, next: () => void) => any>,
259
+ handler: (req: any, res: any) => Promise<void>
260
+ ) {
261
+ return async (c: any) => {
262
+ // Ensure __CF_ENV__ is available for D1Storage
263
+ if (!(globalThis as any).__CF_ENV__) {
264
+ (globalThis as any).__CF_ENV__ = c.env;
265
+ }
266
+ let body = {};
267
+ try {
268
+ const text = await c.req.text();
269
+ if (text) {
270
+ const contentType = c.req.header('content-type') || '';
271
+ if (contentType.includes('json')) {
272
+ body = JSON.parse(text);
273
+ } else if (contentType.includes('urlencoded')) {
274
+ body = Object.fromEntries(new URLSearchParams(text).entries());
275
+ }
276
+ }
277
+ } catch {
278
+ // Body parsing failed
279
+ }
280
+
281
+ const req = createHonoRequest(c, body);
282
+ const res = createHonoResponse();
283
+
284
+ // Run middlewares
285
+ for (const mw of middlewares) {
286
+ let nextCalled = false;
287
+ await mw(req, res, () => {
288
+ nextCalled = true;
289
+ });
290
+ if (!nextCalled) {
291
+ const r = res._getResult();
292
+ if (r) return c.json(r.body, r.statusCode);
293
+ return c.json({ error: 'Unknown error' }, 500);
294
+ }
295
+ }
296
+
297
+ // Run handler
298
+ try {
299
+ await handler(req, res);
300
+ } catch (err: any) {
301
+ console.error(
302
+ '[DID Connect] handler error:',
303
+ err?.message || err,
304
+ err?.stack?.split('\n').slice(0, 5).join('\n')
305
+ );
306
+ // Write error to D1 for debugging (console.error may not be visible in tail)
307
+ try {
308
+ const db = (globalThis as any).__CF_ENV__?.DB;
309
+ if (db) {
310
+ await db
311
+ .prepare('INSERT OR REPLACE INTO _locks (name, owner, expires_at) VALUES (?, ?, 0)')
312
+ .bind('debug-did-error', `${err?.message || err}`.substring(0, 200))
313
+ .run();
314
+ }
315
+ } catch {
316
+ /* ignore */
317
+ }
318
+ return c.json({ error: err?.message || 'Internal server error' }, 500);
319
+ }
320
+ const r = res._getResult();
321
+ return c.json(r?.body ?? {}, r?.statusCode ?? 200);
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Monkey-patch the WalletHandlers.attach method to support Hono apps.
327
+ *
328
+ * The published @arcblock/did-connect-js expects Express. We intercept attach()
329
+ * and register routes on the Hono app directly using wrapHandler().
330
+ */
331
+ function createHonoCompatHandlers(opts: { tokenStorage: any; authenticator: any }): WalletHandlers {
332
+ const handlers = new WalletHandlers(opts);
333
+ const originalAttach = handlers.attach.bind(handlers);
334
+
335
+ // Override attach to use Hono-compatible route registration
336
+ (handlers as any).attach = function attachHono(config: any) {
337
+ const { app, action, ...rest } = config;
338
+
339
+ // Create a fake Express-like app that collects routes
340
+ const routes: Array<{
341
+ method: string;
342
+ path: string;
343
+ middlewares: Function[];
344
+ handler: Function;
345
+ }> = [];
346
+
347
+ const fakeApp: any = {
348
+ get(path: string, ...fns: Function[]) {
349
+ routes.push({ method: 'get', path, middlewares: fns.slice(0, -1), handler: fns[fns.length - 1] });
350
+ },
351
+ post(path: string, ...fns: Function[]) {
352
+ routes.push({ method: 'post', path, middlewares: fns.slice(0, -1), handler: fns[fns.length - 1] });
353
+ },
354
+ use(_path: string, _middleware: any) {
355
+ // CORS is handled by Hono's cors middleware already
356
+ },
357
+ };
358
+
359
+ // Call original attach with fake app to collect routes
360
+ originalAttach({ app: fakeApp, action, ...rest });
361
+
362
+ // Now register collected routes on the real Hono app
363
+ const prefix = (handlers as any).options?.prefix || '/api/did';
364
+
365
+ // Add CORS for DID Connect routes
366
+ app.use(`${prefix}/${action}/*`, async (c: any, next: () => Promise<void>) => {
367
+ c.header('Access-Control-Allow-Origin', '*');
368
+ c.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
369
+ c.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
370
+ if (c.req.method === 'OPTIONS') {
371
+ return c.text('', 204);
372
+ }
373
+ await next();
374
+ });
375
+
376
+ for (const route of routes) {
377
+ const wrapped = wrapHandler(route.middlewares as any[], route.handler as any);
378
+ if (route.method === 'get') {
379
+ app.get(route.path, wrapped);
380
+ } else if (route.method === 'post') {
381
+ app.post(route.path, wrapped);
382
+ }
383
+ }
384
+ };
385
+
386
+ return handlers;
387
+ }
388
+
389
+ // ---------------------------------------------------------------------------
390
+ // Public API
391
+ // ---------------------------------------------------------------------------
392
+
393
+ export function initAuth(kv: KVNamespace, appSK: string) {
394
+ const wallet = fromSecretKey(appSK, walletType);
395
+ const tokenStorage = new CloudflareD1Storage({ ttl: 300 });
396
+ // Trailing slash is REQUIRED — without it the host 50% times out (verified 2026-04-20).
397
+ // Matches the format used by D1 payment_methods.settings.arcblock.api_host.
398
+ const chainHost = 'https://beta.abtnetwork.io/api/';
399
+
400
+ // Pre-warm @ocap/client/encode's module-level context cache. The first
401
+ // createTxEncoder()(...) call internally awaits fetchContext, which POSTs
402
+ // getChainInfo + getForgeState to beta — ~1-3s on warm isolates, more on
403
+ // cold. When `genRequestedClaims` is called by the merchant and triggers
404
+ // the encoder, merchant's did-connect-js aborts after 8s. Firing the fetch
405
+ // here at init (module load) lets the cache populate while the worker sets
406
+ // up the rest of the request pipeline.
407
+ fetchContext(chainHost).catch((err) => {
408
+ // Non-fatal: on failure the first real request will retry via the
409
+ // encoder's own fetchContext call.
410
+ console.warn('[initAuth] pre-warm chainInfo failed:', err?.message);
411
+ });
412
+
413
+ const authenticator = new WalletAuthenticator({
414
+ wallet: wallet.toJSON(),
415
+ appInfo: ({ baseUrl }: { baseUrl: string }) => ({
416
+ name: 'Payment Kit',
417
+ description: 'Payment Kit on Cloudflare Workers',
418
+ icon: 'https://www.arcblock.io/favicon.ico',
419
+ link: baseUrl,
420
+ }),
421
+ chainInfo: {
422
+ type: 'arcblock',
423
+ host: chainHost,
424
+ id: 'beta',
425
+ },
426
+ txEncoder: createTxEncoder(),
427
+ // CF Workers: chain RPC to beta.abtnetwork.io + getContext warm-up can exceed
428
+ // the default 8s. Extend to 30s to match Node.js payment-kit behavior.
429
+ timeout: 30000,
430
+ });
431
+
432
+ // did-connect-js 4.0.0 natively detects Hono apps via isHonoApp()
433
+ const handlers = new WalletHandlers({ tokenStorage, authenticator });
434
+
435
+ return { wallet, authenticator, handlers, tokenStorage };
436
+ }
437
+
438
+ /**
439
+ * Attach DID Connect routes to a Hono app.
440
+ *
441
+ * Registers the login action with full profile handling, and all payment-related
442
+ * DID Connect actions using the original handler implementations from
443
+ * api/src/routes/connect/*.ts. The handlers execute real on-chain operations
444
+ * (transfers, delegations, staking) via @ocap/client HTTP RPC calls, which
445
+ * work in CF Workers. If a handler fails to attach, it falls back to a stub.
446
+ *
447
+ * Payment actions registered:
448
+ * collect, collect-batch, payment, subscription, setup,
449
+ * change-payment, change-plan, change-payer,
450
+ * recharge, recharge-account,
451
+ * delegation, overdraft-protection, re-stake,
452
+ * auto-recharge-auth
453
+ */
454
+ export function attachDIDConnectRoutes(app: any, kv: KVNamespace, appSK: string) {
455
+ const { handlers, tokenStorage } = initAuth(kv, appSK);
456
+
457
+ // ---- Login action (full implementation) ----
458
+ handlers.attach({
459
+ app,
460
+ action: 'login',
461
+ claims: {
462
+ profile: () => ({
463
+ description: 'Please provide your profile to login',
464
+ fields: ['fullName', 'email', 'avatar'],
465
+ }),
466
+ },
467
+ onAuth: async ({ userDid, userPk, claims, updateSession }: any) => {
468
+ const claim = claims.find((x: any) => x.type === 'profile');
469
+ const { type: _type, signature: _sig, ...rest } = claim || {};
470
+ await updateSession({
471
+ result: {
472
+ ...rest,
473
+ did: userDid,
474
+ pk: userPk,
475
+ },
476
+ });
477
+ },
478
+ });
479
+
480
+ // ---- Payment DID Connect actions (real implementations) ----
481
+ // These use the original handlers from api/src/routes/connect/*.ts.
482
+ // esbuild aliases handle all dependency replacements (@blocklet/sdk, sequelize, etc.)
483
+ // so that @ocap/client HTTP-based RPC calls work in CF Workers.
484
+ const paymentHandlerList = [
485
+ collectHandlers,
486
+ collectBatchHandlers,
487
+ payHandlers,
488
+ setupHandlers,
489
+ subscribeHandlers,
490
+ changePaymentHandlers,
491
+ changePlanHandlers,
492
+ changePayerHandlers,
493
+ rechargeHandlers,
494
+ rechargeAccountHandlers,
495
+ delegationHandlers,
496
+ overdraftProtectionHandlers,
497
+ reStakeHandlers,
498
+ autoRechargeAuthHandlers,
499
+ ];
500
+
501
+ for (const handler of paymentHandlerList) {
502
+ try {
503
+ handlers.attach({ app, ...handler });
504
+ console.log(`[CF DID Connect] Attached real handler: ${handler.action}`);
505
+ } catch (err: any) {
506
+ console.error(`[CF DID Connect] Failed to attach handler ${handler.action}, using stub:`, err?.message);
507
+ // Fallback to stub if real handler fails to attach
508
+ handlers.attach({
509
+ app,
510
+ action: handler.action,
511
+ claims: {
512
+ profile: () => ({
513
+ description: `Please connect your wallet to proceed with ${handler.action}`,
514
+ fields: ['fullName', 'email'],
515
+ }),
516
+ },
517
+ onAuth: async ({ userDid, userPk, updateSession }: any) => {
518
+ await updateSession({
519
+ result: { did: userDid, pk: userPk },
520
+ });
521
+ },
522
+ });
523
+ }
524
+ }
525
+
526
+ return { handlers, tokenStorage };
527
+ }