@sakeetech/medusa-payment-viva 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +816 -0
  3. package/dist/api/index.d.ts +15 -0
  4. package/dist/api/index.d.ts.map +1 -0
  5. package/dist/api/index.js +22 -0
  6. package/dist/api/index.js.map +1 -0
  7. package/dist/api/middlewares.d.ts +27 -0
  8. package/dist/api/middlewares.d.ts.map +1 -0
  9. package/dist/api/middlewares.js +62 -0
  10. package/dist/api/middlewares.js.map +1 -0
  11. package/dist/api/viva/admin/_admin-auth.d.ts +26 -0
  12. package/dist/api/viva/admin/_admin-auth.d.ts.map +1 -0
  13. package/dist/api/viva/admin/_admin-auth.js +49 -0
  14. package/dist/api/viva/admin/_admin-auth.js.map +1 -0
  15. package/dist/api/viva/admin/_mode-gate.d.ts +28 -0
  16. package/dist/api/viva/admin/_mode-gate.d.ts.map +1 -0
  17. package/dist/api/viva/admin/_mode-gate.js +45 -0
  18. package/dist/api/viva/admin/_mode-gate.js.map +1 -0
  19. package/dist/api/viva/admin/connected-accounts/[id]/reconcile/route.d.ts +21 -0
  20. package/dist/api/viva/admin/connected-accounts/[id]/reconcile/route.d.ts.map +1 -0
  21. package/dist/api/viva/admin/connected-accounts/[id]/reconcile/route.js +93 -0
  22. package/dist/api/viva/admin/connected-accounts/[id]/reconcile/route.js.map +1 -0
  23. package/dist/api/viva/admin/connected-accounts/[id]/route.d.ts +18 -0
  24. package/dist/api/viva/admin/connected-accounts/[id]/route.d.ts.map +1 -0
  25. package/dist/api/viva/admin/connected-accounts/[id]/route.js +59 -0
  26. package/dist/api/viva/admin/connected-accounts/[id]/route.js.map +1 -0
  27. package/dist/api/viva/admin/connected-accounts/[id]/sources/route.d.ts +34 -0
  28. package/dist/api/viva/admin/connected-accounts/[id]/sources/route.d.ts.map +1 -0
  29. package/dist/api/viva/admin/connected-accounts/[id]/sources/route.js +234 -0
  30. package/dist/api/viva/admin/connected-accounts/[id]/sources/route.js.map +1 -0
  31. package/dist/api/viva/admin/connected-accounts/route.d.ts +19 -0
  32. package/dist/api/viva/admin/connected-accounts/route.d.ts.map +1 -0
  33. package/dist/api/viva/admin/connected-accounts/route.js +78 -0
  34. package/dist/api/viva/admin/connected-accounts/route.js.map +1 -0
  35. package/dist/api/viva/internal/auth-status/route.d.ts +19 -0
  36. package/dist/api/viva/internal/auth-status/route.d.ts.map +1 -0
  37. package/dist/api/viva/internal/auth-status/route.js +91 -0
  38. package/dist/api/viva/internal/auth-status/route.js.map +1 -0
  39. package/dist/api/viva/internal/metrics/route.d.ts +13 -0
  40. package/dist/api/viva/internal/metrics/route.d.ts.map +1 -0
  41. package/dist/api/viva/internal/metrics/route.js +48 -0
  42. package/dist/api/viva/internal/metrics/route.js.map +1 -0
  43. package/dist/api/viva/webhook/health/route.d.ts +16 -0
  44. package/dist/api/viva/webhook/health/route.d.ts.map +1 -0
  45. package/dist/api/viva/webhook/health/route.js +27 -0
  46. package/dist/api/viva/webhook/health/route.js.map +1 -0
  47. package/dist/api/viva/webhook/route.d.ts +57 -0
  48. package/dist/api/viva/webhook/route.d.ts.map +1 -0
  49. package/dist/api/viva/webhook/route.js +269 -0
  50. package/dist/api/viva/webhook/route.js.map +1 -0
  51. package/dist/cli/bin.d.ts +12 -0
  52. package/dist/cli/bin.d.ts.map +1 -0
  53. package/dist/cli/bin.js +78 -0
  54. package/dist/cli/bin.js.map +1 -0
  55. package/dist/cli/index.d.ts +12 -0
  56. package/dist/cli/index.d.ts.map +1 -0
  57. package/dist/cli/index.js +14 -0
  58. package/dist/cli/index.js.map +1 -0
  59. package/dist/cli/plan.d.ts +51 -0
  60. package/dist/cli/plan.d.ts.map +1 -0
  61. package/dist/cli/plan.js +128 -0
  62. package/dist/cli/plan.js.map +1 -0
  63. package/dist/cli/register-webhooks.d.ts +54 -0
  64. package/dist/cli/register-webhooks.d.ts.map +1 -0
  65. package/dist/cli/register-webhooks.js +366 -0
  66. package/dist/cli/register-webhooks.js.map +1 -0
  67. package/dist/cli/types.d.ts +62 -0
  68. package/dist/cli/types.d.ts.map +1 -0
  69. package/dist/cli/types.js +12 -0
  70. package/dist/cli/types.js.map +1 -0
  71. package/dist/config.d.ts +158 -0
  72. package/dist/config.d.ts.map +1 -0
  73. package/dist/config.js +236 -0
  74. package/dist/config.js.map +1 -0
  75. package/dist/index.d.ts +21 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +29 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/loaders/viva-oauth2-strategy.d.ts +26 -0
  80. package/dist/loaders/viva-oauth2-strategy.d.ts.map +1 -0
  81. package/dist/loaders/viva-oauth2-strategy.js +58 -0
  82. package/dist/loaders/viva-oauth2-strategy.js.map +1 -0
  83. package/dist/migrations/Migration_20260425000001_init_viva_payments.d.ts +19 -0
  84. package/dist/migrations/Migration_20260425000001_init_viva_payments.d.ts.map +1 -0
  85. package/dist/migrations/Migration_20260425000001_init_viva_payments.js +136 -0
  86. package/dist/migrations/Migration_20260425000001_init_viva_payments.js.map +1 -0
  87. package/dist/migrations/Migration_20260425000002_allow_null_order_code.d.ts +31 -0
  88. package/dist/migrations/Migration_20260425000002_allow_null_order_code.d.ts.map +1 -0
  89. package/dist/migrations/Migration_20260425000002_allow_null_order_code.js +71 -0
  90. package/dist/migrations/Migration_20260425000002_allow_null_order_code.js.map +1 -0
  91. package/dist/migrations/Migration_20260425000003_webhook_retry_count.d.ts +18 -0
  92. package/dist/migrations/Migration_20260425000003_webhook_retry_count.d.ts.map +1 -0
  93. package/dist/migrations/Migration_20260425000003_webhook_retry_count.js +42 -0
  94. package/dist/migrations/Migration_20260425000003_webhook_retry_count.js.map +1 -0
  95. package/dist/migrations/Migration_20260425000004_webhook_error_and_nullable_merchant.d.ts +29 -0
  96. package/dist/migrations/Migration_20260425000004_webhook_error_and_nullable_merchant.d.ts.map +1 -0
  97. package/dist/migrations/Migration_20260425000004_webhook_error_and_nullable_merchant.js +74 -0
  98. package/dist/migrations/Migration_20260425000004_webhook_error_and_nullable_merchant.js.map +1 -0
  99. package/dist/models/index.d.ts +7 -0
  100. package/dist/models/index.d.ts.map +1 -0
  101. package/dist/models/index.js +10 -0
  102. package/dist/models/index.js.map +1 -0
  103. package/dist/models/viva-tenant-merchant.d.ts +11 -0
  104. package/dist/models/viva-tenant-merchant.d.ts.map +1 -0
  105. package/dist/models/viva-tenant-merchant.js +54 -0
  106. package/dist/models/viva-tenant-merchant.js.map +1 -0
  107. package/dist/models/viva-transaction.d.ts +34 -0
  108. package/dist/models/viva-transaction.d.ts.map +1 -0
  109. package/dist/models/viva-transaction.js +104 -0
  110. package/dist/models/viva-transaction.js.map +1 -0
  111. package/dist/models/viva-webhook-event.d.ts +32 -0
  112. package/dist/models/viva-webhook-event.d.ts.map +1 -0
  113. package/dist/models/viva-webhook-event.js +88 -0
  114. package/dist/models/viva-webhook-event.js.map +1 -0
  115. package/dist/observability/config.d.ts +34 -0
  116. package/dist/observability/config.d.ts.map +1 -0
  117. package/dist/observability/config.js +57 -0
  118. package/dist/observability/config.js.map +1 -0
  119. package/dist/observability/index.d.ts +8 -0
  120. package/dist/observability/index.d.ts.map +1 -0
  121. package/dist/observability/index.js +15 -0
  122. package/dist/observability/index.js.map +1 -0
  123. package/dist/observability/prom-metrics.d.ts +41 -0
  124. package/dist/observability/prom-metrics.d.ts.map +1 -0
  125. package/dist/observability/prom-metrics.js +219 -0
  126. package/dist/observability/prom-metrics.js.map +1 -0
  127. package/dist/providers/payment-provider.d.ts +19 -0
  128. package/dist/providers/payment-provider.d.ts.map +1 -0
  129. package/dist/providers/payment-provider.js +24 -0
  130. package/dist/providers/payment-provider.js.map +1 -0
  131. package/dist/resolvers/auth-strategy-factory.d.ts +42 -0
  132. package/dist/resolvers/auth-strategy-factory.d.ts.map +1 -0
  133. package/dist/resolvers/auth-strategy-factory.js +60 -0
  134. package/dist/resolvers/auth-strategy-factory.js.map +1 -0
  135. package/dist/resolvers/tenant-resolver.d.ts +104 -0
  136. package/dist/resolvers/tenant-resolver.d.ts.map +1 -0
  137. package/dist/resolvers/tenant-resolver.js +118 -0
  138. package/dist/resolvers/tenant-resolver.js.map +1 -0
  139. package/dist/service.d.ts +200 -0
  140. package/dist/service.d.ts.map +1 -0
  141. package/dist/service.js +1003 -0
  142. package/dist/service.js.map +1 -0
  143. package/dist/subscribers/index.d.ts +5 -0
  144. package/dist/subscribers/index.d.ts.map +1 -0
  145. package/dist/subscribers/index.js +10 -0
  146. package/dist/subscribers/index.js.map +1 -0
  147. package/dist/subscribers/viva-webhook-event.d.ts +38 -0
  148. package/dist/subscribers/viva-webhook-event.d.ts.map +1 -0
  149. package/dist/subscribers/viva-webhook-event.js +133 -0
  150. package/dist/subscribers/viva-webhook-event.js.map +1 -0
  151. package/dist/workflows/cleanup-old-webhook-events.d.ts +39 -0
  152. package/dist/workflows/cleanup-old-webhook-events.d.ts.map +1 -0
  153. package/dist/workflows/cleanup-old-webhook-events.js +68 -0
  154. package/dist/workflows/cleanup-old-webhook-events.js.map +1 -0
  155. package/dist/workflows/index.d.ts +14 -0
  156. package/dist/workflows/index.d.ts.map +1 -0
  157. package/dist/workflows/index.js +19 -0
  158. package/dist/workflows/index.js.map +1 -0
  159. package/dist/workflows/per-tenant-semaphore.d.ts +47 -0
  160. package/dist/workflows/per-tenant-semaphore.d.ts.map +1 -0
  161. package/dist/workflows/per-tenant-semaphore.js +89 -0
  162. package/dist/workflows/per-tenant-semaphore.js.map +1 -0
  163. package/dist/workflows/process-webhook-event.d.ts +80 -0
  164. package/dist/workflows/process-webhook-event.d.ts.map +1 -0
  165. package/dist/workflows/process-webhook-event.js +280 -0
  166. package/dist/workflows/process-webhook-event.js.map +1 -0
  167. package/dist/workflows/reprocess-unresolved-tenants.d.ts +58 -0
  168. package/dist/workflows/reprocess-unresolved-tenants.d.ts.map +1 -0
  169. package/dist/workflows/reprocess-unresolved-tenants.js +121 -0
  170. package/dist/workflows/reprocess-unresolved-tenants.js.map +1 -0
  171. package/package.json +63 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * route.ts — Viva Wallet webhook endpoint.
3
+ *
4
+ * Two handlers:
5
+ * GET /viva/webhook — challenge-response for URL registration.
6
+ * POST /viva/webhook — event ingest: IP check, INSERT + emit.
7
+ *
8
+ * Security model (plan P7):
9
+ * (a) IP allowlist — 403 on mismatch. Source IP is extracted with
10
+ * trustedProxyDepth-bounded X-Forwarded-For walking
11
+ * (CSO Finding #2). Configure via
12
+ * VIVA_TRUSTED_PROXY_DEPTH (default 0 = socket only).
13
+ * (b) DB dedup — INSERT ... ON CONFLICT (message_id) DO NOTHING.
14
+ * (c) A2 gate — only emit 'viva.webhook.received' when RETURNING non-empty.
15
+ *
16
+ * HMAC verification is intentionally not performed here. Event 7936 (the
17
+ * only HMAC-signed Viva event) is not subscribed; if it is ever added,
18
+ * route a separate VIVA_WEBHOOK_HMAC_SECRET, NOT the public
19
+ * VIVA_WEBHOOK_VERIFICATION_KEY. See CSO Finding #3.
20
+ *
21
+ * Metrics emitted (P16):
22
+ * viva_webhook_received_total{event_type_id, result}
23
+ * viva_webhook_processing_lag_seconds
24
+ * viva_tenant_resolution_failures_total
25
+ * viva_webhook_ordercode_mismatch_total
26
+ *
27
+ * @see references/viva-docs/md/webhooks-for-payments.txt:270 (IP allowlist)
28
+ * @see references/viva-docs/md/webhooks-for-payments.txt:254 (security model P7)
29
+ * @see docs/TODO-CSO.md (Findings 1, 2, 3)
30
+ */
31
+ import type { MedusaRequest, MedusaResponse } from '@medusajs/framework/http';
32
+ /**
33
+ * Challenge-response for webhook URL registration.
34
+ * Returns `{"Key": "<webhook_verification_key>"}` per Viva protocol.
35
+ * No auth gate; only exercised at deployment time.
36
+ *
37
+ * @see references/viva-docs/md/webhooks-for-payments.txt:284 (challenge response)
38
+ */
39
+ export declare const GET: (_req: MedusaRequest, res: MedusaResponse) => Promise<void>;
40
+ /**
41
+ * POST event ingest handler.
42
+ *
43
+ * Steps:
44
+ * 1. IP allowlist check (env-configured). 403 on rejection.
45
+ * 2. Read rawBody captured by vivaWebhookRawBodyMiddleware.
46
+ * 3. Parse JSON.
47
+ * 4. If EventTypeId 7936: verify HMAC. 401 on mismatch.
48
+ * 5. Resolve tenant via viva_tenant_merchant.
49
+ * 6. INSERT INTO viva_webhook_event ... ON CONFLICT (message_id) DO NOTHING RETURNING viva_webhook_event_id.
50
+ * 7. A2: only emit 'viva.webhook.received' when RETURNING produced a row.
51
+ * 8. Always respond 200.
52
+ *
53
+ * @see references/viva-docs/md/webhooks-for-payments.txt:286 (POST flow)
54
+ * @see references/viva-docs/md/wh-transaction-payment-created.txt:158 (event envelope)
55
+ */
56
+ export declare const POST: (req: MedusaRequest, res: MedusaResponse) => Promise<void>;
57
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../src/api/viva/webhook/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAiB9E;;;;;;GAMG;AACH,eAAO,MAAM,GAAG,GACd,MAAM,aAAa,EACnB,KAAK,cAAc,KAClB,OAAO,CAAC,IAAI,CAQd,CAAC;AAMF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,IAAI,GACf,KAAK,aAAa,EAClB,KAAK,cAAc,KAClB,OAAO,CAAC,IAAI,CAwMd,CAAC"}
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ /**
3
+ * route.ts — Viva Wallet webhook endpoint.
4
+ *
5
+ * Two handlers:
6
+ * GET /viva/webhook — challenge-response for URL registration.
7
+ * POST /viva/webhook — event ingest: IP check, INSERT + emit.
8
+ *
9
+ * Security model (plan P7):
10
+ * (a) IP allowlist — 403 on mismatch. Source IP is extracted with
11
+ * trustedProxyDepth-bounded X-Forwarded-For walking
12
+ * (CSO Finding #2). Configure via
13
+ * VIVA_TRUSTED_PROXY_DEPTH (default 0 = socket only).
14
+ * (b) DB dedup — INSERT ... ON CONFLICT (message_id) DO NOTHING.
15
+ * (c) A2 gate — only emit 'viva.webhook.received' when RETURNING non-empty.
16
+ *
17
+ * HMAC verification is intentionally not performed here. Event 7936 (the
18
+ * only HMAC-signed Viva event) is not subscribed; if it is ever added,
19
+ * route a separate VIVA_WEBHOOK_HMAC_SECRET, NOT the public
20
+ * VIVA_WEBHOOK_VERIFICATION_KEY. See CSO Finding #3.
21
+ *
22
+ * Metrics emitted (P16):
23
+ * viva_webhook_received_total{event_type_id, result}
24
+ * viva_webhook_processing_lag_seconds
25
+ * viva_tenant_resolution_failures_total
26
+ * viva_webhook_ordercode_mismatch_total
27
+ *
28
+ * @see references/viva-docs/md/webhooks-for-payments.txt:270 (IP allowlist)
29
+ * @see references/viva-docs/md/webhooks-for-payments.txt:254 (security model P7)
30
+ * @see docs/TODO-CSO.md (Findings 1, 2, 3)
31
+ */
32
+ var __importDefault = (this && this.__importDefault) || function (mod) {
33
+ return (mod && mod.__esModule) ? mod : { "default": mod };
34
+ };
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.POST = exports.GET = void 0;
37
+ const utils_1 = require("@medusajs/framework/utils");
38
+ const pg_1 = __importDefault(require("pg"));
39
+ const webhooks_1 = require("@sakeetech/viva-payments-core/webhooks");
40
+ const index_js_1 = require("../../../observability/index.js");
41
+ // ---------------------------------------------------------------------------
42
+ // GET — challenge-response
43
+ // ---------------------------------------------------------------------------
44
+ /**
45
+ * Challenge-response for webhook URL registration.
46
+ * Returns `{"Key": "<webhook_verification_key>"}` per Viva protocol.
47
+ * No auth gate; only exercised at deployment time.
48
+ *
49
+ * @see references/viva-docs/md/webhooks-for-payments.txt:284 (challenge response)
50
+ */
51
+ const GET = async (_req, res) => {
52
+ const key = process.env['VIVA_WEBHOOK_VERIFICATION_KEY'] ?? '';
53
+ if (!key) {
54
+ res.status(500).json({ error: 'VIVA_WEBHOOK_VERIFICATION_KEY is not configured' });
55
+ return;
56
+ }
57
+ res.setHeader('Cache-Control', 'no-store');
58
+ res.status(200).json({ Key: key });
59
+ };
60
+ exports.GET = GET;
61
+ // ---------------------------------------------------------------------------
62
+ // POST — event ingest
63
+ // ---------------------------------------------------------------------------
64
+ /**
65
+ * POST event ingest handler.
66
+ *
67
+ * Steps:
68
+ * 1. IP allowlist check (env-configured). 403 on rejection.
69
+ * 2. Read rawBody captured by vivaWebhookRawBodyMiddleware.
70
+ * 3. Parse JSON.
71
+ * 4. If EventTypeId 7936: verify HMAC. 401 on mismatch.
72
+ * 5. Resolve tenant via viva_tenant_merchant.
73
+ * 6. INSERT INTO viva_webhook_event ... ON CONFLICT (message_id) DO NOTHING RETURNING viva_webhook_event_id.
74
+ * 7. A2: only emit 'viva.webhook.received' when RETURNING produced a row.
75
+ * 8. Always respond 200.
76
+ *
77
+ * @see references/viva-docs/md/webhooks-for-payments.txt:286 (POST flow)
78
+ * @see references/viva-docs/md/wh-transaction-payment-created.txt:158 (event envelope)
79
+ */
80
+ const POST = async (req, res) => {
81
+ const environment = (process.env['VIVA_ENVIRONMENT'] ?? 'demo');
82
+ const extraAllowlist = parseExtraAllowlist(process.env['VIVA_WEBHOOK_IP_ALLOWLIST']);
83
+ const trustedProxyDepth = parseTrustedProxyDepth(process.env['VIVA_TRUSTED_PROXY_DEPTH']);
84
+ const metrics = (0, index_js_1.getSharedMetrics)();
85
+ // ---- Step 1: IP allowlist (CSO Finding #2) ----
86
+ // VIVA_TRUSTED_PROXY_DEPTH controls how many trailing X-Forwarded-For hops we
87
+ // trust. Default 0 = socket only (safe for direct exposure); set to 1 for a
88
+ // single reverse proxy, 2 for CDN+LB. Walking from the rightmost end means
89
+ // attacker-injected leftmost values are ignored.
90
+ const clientIp = (0, webhooks_1.extractClientIp)(req, trustedProxyDepth);
91
+ if (!(0, webhooks_1.isAllowedSourceIp)(clientIp, environment, extraAllowlist)) {
92
+ // Log metric for IP rejection but only 403 in non-dev environments.
93
+ // In dev/test with IP_ALLOWLIST_BYPASS=true, allow through.
94
+ if (process.env['VIVA_WEBHOOK_IP_ALLOWLIST_BYPASS'] !== 'true') {
95
+ req.scope?.resolve?.('logger')?.warn?.(`[viva] Rejected webhook from disallowed IP ${clientIp} (env=${environment})`);
96
+ metrics.counter('viva_webhook_received_total', { event_type_id: 'unknown', result: 'ip_rejected' });
97
+ res.status(403).json({ error: 'Forbidden' });
98
+ return;
99
+ }
100
+ }
101
+ // ---- Step 2: Raw body ----
102
+ const rawBody = req.rawBody;
103
+ if (!rawBody || rawBody.length === 0) {
104
+ res.status(200).end();
105
+ return;
106
+ }
107
+ // ---- Step 3: Parse JSON ----
108
+ let envelope;
109
+ try {
110
+ envelope = JSON.parse(rawBody.toString('utf-8'));
111
+ }
112
+ catch {
113
+ // Malformed JSON — return 200 per plan (Viva retries are not helpful here)
114
+ const logger = req.scope?.resolve?.('logger');
115
+ logger?.error?.('[viva] Webhook POST: failed to parse JSON body');
116
+ res.status(200).end();
117
+ return;
118
+ }
119
+ const eventTypeId = envelope.EventTypeId;
120
+ const messageId = envelope.MessageId;
121
+ // ---- Compute processing lag (P16) ----
122
+ // envelope.Created is an ISO8601 timestamp when present
123
+ const createdTs = envelope['Created'];
124
+ if (typeof createdTs === 'string') {
125
+ const createdMs = new Date(createdTs).getTime();
126
+ if (!isNaN(createdMs)) {
127
+ const lagSeconds = (Date.now() - createdMs) / 1000;
128
+ metrics.histogram('viva_webhook_processing_lag_seconds', lagSeconds);
129
+ }
130
+ }
131
+ // ---- Step 4: HMAC verification ----
132
+ // Plugin does NOT subscribe to event 7936 (Sale Transactions) — the only
133
+ // Viva event that ships an HMAC signature. The handler used to verify 7936
134
+ // signatures here, but it reused VIVA_WEBHOOK_VERIFICATION_KEY (a public,
135
+ // browser-fetchable value) as the HMAC secret, which is incorrect by
136
+ // Viva's protocol design (CSO Finding #3, dormant).
137
+ //
138
+ // If 7936 is ever added to the subscribed event list, route a SEPARATE
139
+ // VIVA_WEBHOOK_HMAC_SECRET env var, NOT VIVA_WEBHOOK_VERIFICATION_KEY.
140
+ // The `verifyHmacSignature` helper remains in
141
+ // @sakeetech/viva-payments-core/webhooks for that future wiring.
142
+ // @see docs/TODO-CSO.md "Finding 3"
143
+ // @see references/viva-docs/md/wh-sale-transactions.txt:174
144
+ // ---- Step 5: Resolve tenant ----
145
+ const connString = process.env['DATABASE_URL'] ??
146
+ `postgresql://${process.env['USER'] ?? 'postgres'}@localhost:5432/postgres`;
147
+ const pool = new pg_1.default.Pool({ connectionString: connString, max: 1 });
148
+ const logger = req.scope?.resolve?.('logger');
149
+ let tenantId = null;
150
+ let vivaMerchantId = null;
151
+ let connectedAccountId = null;
152
+ let transactionId = null;
153
+ try {
154
+ const eventData = envelope.EventData;
155
+ if ((0, webhooks_1.isTransactionEvent)(eventTypeId)) {
156
+ const txData = eventData;
157
+ vivaMerchantId = txData.MerchantId ?? null;
158
+ transactionId = txData.TransactionId ?? null;
159
+ connectedAccountId = txData.ConnectedAccountId ?? null;
160
+ if (vivaMerchantId) {
161
+ const client = await pool.connect();
162
+ try {
163
+ const row = await client.query(`SELECT tenant_id FROM viva_tenant_merchant WHERE viva_merchant_id = $1 LIMIT 1`, [vivaMerchantId]);
164
+ tenantId = row.rows[0]?.tenant_id ?? null;
165
+ }
166
+ finally {
167
+ client.release();
168
+ }
169
+ }
170
+ }
171
+ else if ((0, webhooks_1.isOnboardingEvent)(eventTypeId)) {
172
+ const onbData = eventData;
173
+ connectedAccountId = onbData.ConnectedAccountId ?? null;
174
+ if (connectedAccountId) {
175
+ const client = await pool.connect();
176
+ try {
177
+ const row = await client.query(`SELECT tenant_id FROM viva_tenant_merchant WHERE connected_account_id = $1 LIMIT 1`, [connectedAccountId]);
178
+ tenantId = row.rows[0]?.tenant_id ?? null;
179
+ }
180
+ finally {
181
+ client.release();
182
+ }
183
+ }
184
+ }
185
+ if (!tenantId) {
186
+ // A6: log metric but continue — INSERT with tenant_id=NULL
187
+ logger?.warn?.(`[viva] Tenant resolution failed for event ${eventTypeId} message ${messageId} ` +
188
+ `vivaMerchantId=${vivaMerchantId ?? 'null'} connectedAccountId=${connectedAccountId ?? 'null'}. ` +
189
+ `Metric: viva_tenant_resolution_failures_total`);
190
+ metrics.counter('viva_tenant_resolution_failures_total', { event_type_id: String(eventTypeId) });
191
+ }
192
+ // ---- Step 6: INSERT ... ON CONFLICT DO NOTHING RETURNING ----
193
+ const client = await pool.connect();
194
+ let insertedEventId = null;
195
+ try {
196
+ const result = await client.query(`INSERT INTO viva_webhook_event
197
+ (transaction_id, event_type_id, message_id, viva_merchant_id, connected_account_id, raw_payload)
198
+ VALUES ($1, $2, $3, $4, $5, $6)
199
+ ON CONFLICT (message_id) DO NOTHING
200
+ RETURNING viva_webhook_event_id`, [
201
+ transactionId,
202
+ eventTypeId,
203
+ messageId,
204
+ vivaMerchantId,
205
+ connectedAccountId,
206
+ JSON.stringify(envelope),
207
+ ]);
208
+ insertedEventId = result.rows[0]?.viva_webhook_event_id ?? null;
209
+ }
210
+ finally {
211
+ client.release();
212
+ }
213
+ // ---- Step 7: A2 dispatch gate — only emit when RETURNING returned a row ----
214
+ if (insertedEventId) {
215
+ metrics.counter('viva_webhook_received_total', {
216
+ event_type_id: String(eventTypeId),
217
+ result: tenantId ? 'accepted' : 'tenant_unresolved',
218
+ });
219
+ const eventBus = req.scope?.resolve?.(utils_1.Modules.EVENT_BUS);
220
+ if (eventBus) {
221
+ await eventBus.emit({
222
+ name: 'viva.webhook.received',
223
+ data: {
224
+ eventId: insertedEventId,
225
+ tenantId: tenantId,
226
+ eventTypeId,
227
+ transactionId,
228
+ vivaMerchantId,
229
+ connectedAccountId,
230
+ },
231
+ });
232
+ }
233
+ }
234
+ else {
235
+ // Duplicate — ON CONFLICT hit
236
+ metrics.counter('viva_webhook_received_total', {
237
+ event_type_id: String(eventTypeId),
238
+ result: 'duplicate',
239
+ });
240
+ }
241
+ }
242
+ catch (err) {
243
+ logger?.error?.(`[viva] Webhook POST error for message ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
244
+ // Still return 200 so Viva doesn't retry unnecessarily
245
+ }
246
+ finally {
247
+ await pool.end().catch(() => undefined);
248
+ }
249
+ // ---- Step 8: Always 200 ----
250
+ res.status(200).end();
251
+ };
252
+ exports.POST = POST;
253
+ // ---------------------------------------------------------------------------
254
+ // Helpers
255
+ // ---------------------------------------------------------------------------
256
+ function parseExtraAllowlist(raw) {
257
+ if (!raw)
258
+ return [];
259
+ return raw.split(',').map((s) => s.trim()).filter(Boolean);
260
+ }
261
+ function parseTrustedProxyDepth(raw) {
262
+ if (!raw)
263
+ return 0;
264
+ const n = Number.parseInt(raw, 10);
265
+ if (Number.isNaN(n) || n < 0)
266
+ return 0;
267
+ return n;
268
+ }
269
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../../../../src/api/viva/webhook/route.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bin.ts — CLI shim for viva-register-webhooks.
4
+ *
5
+ * Parses CLI flags with node:util.parseArgs (no npm dependency) and
6
+ * delegates to run() from register-webhooks.ts.
7
+ *
8
+ * @see references/viva-docs/md/isv-partner-program.txt:196 (ISV webhook setup)
9
+ * @see references/viva-docs/md/webhooks-for-payments.txt:134 (10-URL limit)
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG"}
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * bin.ts — CLI shim for viva-register-webhooks.
5
+ *
6
+ * Parses CLI flags with node:util.parseArgs (no npm dependency) and
7
+ * delegates to run() from register-webhooks.ts.
8
+ *
9
+ * @see references/viva-docs/md/isv-partner-program.txt:196 (ISV webhook setup)
10
+ * @see references/viva-docs/md/webhooks-for-payments.txt:134 (10-URL limit)
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ const node_util_1 = require("node:util");
14
+ const register_webhooks_js_1 = require("./register-webhooks.js");
15
+ const { values } = (0, node_util_1.parseArgs)({
16
+ options: {
17
+ 'dry-run': { type: 'boolean', default: false },
18
+ apply: { type: 'boolean', default: false },
19
+ 'webhook-base-url': { type: 'string' },
20
+ 'reconcile-drift': { type: 'boolean', default: false },
21
+ output: { type: 'string', default: 'human' },
22
+ help: { type: 'boolean', default: false, short: 'h' },
23
+ },
24
+ strict: true,
25
+ });
26
+ if (values.help) {
27
+ console.log(`
28
+ viva-register-webhooks — Set up V1 webhook URLs for the configured Viva account.
29
+
30
+ Mode is auto-detected from VIVA_MODE (or VIVA_RESELLER_* presence):
31
+ - ISV mode: idempotently POSTs to /isv/v1/webhooks for every event type.
32
+ - Merchant mode: prints the URLs to paste into Viva Self Care → Sales →
33
+ API Access → Webhooks and the verification key.
34
+
35
+ Usage:
36
+ viva-register-webhooks --dry-run [--output json]
37
+ viva-register-webhooks --apply [--reconcile-drift]
38
+ viva-register-webhooks --apply --webhook-base-url https://api.example.com
39
+
40
+ Environment:
41
+ VIVA_MODE merchant | isv (auto-detected if unset)
42
+ VIVA_ENVIRONMENT demo | production (default: demo)
43
+ VIVA_CLIENT_ID required (alias: VIVA_ISV_CLIENT_ID, deprecated)
44
+ VIVA_CLIENT_SECRET required (alias: VIVA_ISV_CLIENT_SECRET, deprecated)
45
+ VIVA_MERCHANT_ID required (Basic-auth username; used in both modes)
46
+ VIVA_API_KEY required (Basic-auth password; used in both modes)
47
+ VIVA_WEBHOOK_VERIFICATION_KEY required
48
+ VIVA_WEBHOOK_BASE_URL required; e.g., https://api.example.com
49
+
50
+ Notes:
51
+ --reconcile-drift is non-applicable in merchant mode (manual setup only).
52
+
53
+ Exit codes:
54
+ 0 clean / applied successfully / merchant manual setup printed
55
+ 1 plan has actions but --apply was not passed (CI gate use case — ISV mode)
56
+ 2 apply failed (HTTP/Viva error)
57
+ 3 fatal precondition (config invalid, near-limit abort)
58
+ `);
59
+ process.exit(0);
60
+ }
61
+ if (!values['dry-run'] && !values.apply) {
62
+ console.error('Specify --dry-run or --apply (or --help).');
63
+ process.exit(3);
64
+ }
65
+ const runOpts = {
66
+ dryRun: !!values['dry-run'],
67
+ apply: !!values.apply,
68
+ reconcileDrift: !!values['reconcile-drift'],
69
+ output: values.output ?? 'human',
70
+ };
71
+ if (values['webhook-base-url'] !== undefined) {
72
+ runOpts.webhookBaseUrl = values['webhook-base-url'];
73
+ }
74
+ void (async () => {
75
+ const exitCode = await (0, register_webhooks_js_1.run)(runOpts);
76
+ process.exit(exitCode);
77
+ })();
78
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";;AACA;;;;;;;;GAQG;;AAEH,yCAAsC;AACtC,iEAA6C;AAE7C,MAAM,EAAE,MAAM,EAAE,GAAG,IAAA,qBAAS,EAAC;IAC3B,OAAO,EAAE;QACP,SAAS,EAAW,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QACvD,KAAK,EAAe,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QACvD,kBAAkB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACtC,iBAAiB,EAAG,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;QACvD,MAAM,EAAc,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE;QACxD,IAAI,EAAgB,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE;KACpE;IACD,MAAM,EAAE,IAAI;CACb,CAAC,CAAC;AAEH,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+Bb,CAAC,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACxC,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,OAAO,GAA8B;IACzC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC;IAC3B,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK;IACrB,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC;IAC3C,MAAM,EAAG,MAAM,CAAC,MAA2B,IAAI,OAAO;CACvD,CAAC;AAEF,IAAI,MAAM,CAAC,kBAAkB,CAAC,KAAK,SAAS,EAAE,CAAC;IAC7C,OAAO,CAAC,cAAc,GAAG,MAAM,CAAC,kBAAkB,CAAC,CAAC;AACtD,CAAC;AAED,KAAK,CAAC,KAAK,IAAI,EAAE;IACf,MAAM,QAAQ,GAAG,MAAM,IAAA,0BAAG,EAAC,OAAO,CAAC,CAAC;IACpC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACzB,CAAC,CAAC,EAAE,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * CLI barrel — re-exports the public surface of the register-webhooks CLI.
3
+ *
4
+ * @see references/viva-docs/md/isv-partner-program.txt:196
5
+ * @see references/viva-docs/md/webhooks-for-payments.txt:134
6
+ */
7
+ export { run } from './register-webhooks.js';
8
+ export type { RunOptions } from './register-webhooks.js';
9
+ export { computePlan } from './plan.js';
10
+ export type { ComputePlanInput } from './plan.js';
11
+ export type { DesiredWebhook, WebhookPlanAction, PlanResult, } from './types.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,UAAU,GACX,MAAM,YAAY,CAAC"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ /**
3
+ * CLI barrel — re-exports the public surface of the register-webhooks CLI.
4
+ *
5
+ * @see references/viva-docs/md/isv-partner-program.txt:196
6
+ * @see references/viva-docs/md/webhooks-for-payments.txt:134
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.computePlan = exports.run = void 0;
10
+ var register_webhooks_js_1 = require("./register-webhooks.js");
11
+ Object.defineProperty(exports, "run", { enumerable: true, get: function () { return register_webhooks_js_1.run; } });
12
+ var plan_js_1 = require("./plan.js");
13
+ Object.defineProperty(exports, "computePlan", { enumerable: true, get: function () { return plan_js_1.computePlan; } });
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEH,+DAA6C;AAApC,2GAAA,GAAG,OAAA;AAEZ,qCAAwC;AAA/B,sGAAA,WAAW,OAAA"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * plan.ts — pure, IO-free webhook plan computation.
3
+ *
4
+ * Computes the diff between desired webhook state (from config) and current
5
+ * registrations (from Viva API), returning a deterministic action list.
6
+ *
7
+ * Per Viva docs, max 10 webhook URLs per event type.
8
+ *
9
+ * @see references/viva-docs/md/webhooks-for-payments.txt:134 (10-URL limit)
10
+ * @see references/viva-docs/md/isv-partner-program.txt:196 (ISV webhook setup flow)
11
+ */
12
+ import type { DesiredWebhook, PlanResult, WebhookRegistration } from './types.js';
13
+ export interface ComputePlanInput {
14
+ desired: readonly DesiredWebhook[];
15
+ current: readonly WebhookRegistration[];
16
+ /**
17
+ * Per Viva: 10 URLs per event type.
18
+ * @see references/viva-docs/md/webhooks-for-payments.txt:134
19
+ */
20
+ perEventTypeLimit?: number;
21
+ /**
22
+ * Warn when registered URLs reach this fraction of the limit.
23
+ * Default 0.8 → warn at 8.
24
+ */
25
+ nearLimitFraction?: number;
26
+ /**
27
+ * Whether to deactivate URLs that match our hostname pattern but aren't in
28
+ * `desired`. Disabled by default (safer for shared ISV accounts).
29
+ */
30
+ reconcileDrift?: boolean;
31
+ /**
32
+ * Hostname pattern that identifies "our" registrations for drift reconciliation.
33
+ * Required when reconcileDrift is true to avoid touching foreign webhooks.
34
+ */
35
+ ownedHostnamePattern?: RegExp;
36
+ }
37
+ /**
38
+ * Computes the desired-vs-current diff and returns an ordered action list.
39
+ *
40
+ * Action ordering for human readability:
41
+ * 1. REGISTER (sorted by eventTypeId)
42
+ * 2. SKIP_ALREADY_REGISTERED (sorted by eventTypeId)
43
+ * 3. DEACTIVATE_DRIFT (sorted by eventTypeId)
44
+ * 4. WARN_LIMIT_NEAR (sorted by eventTypeId)
45
+ * 5. ABORT_LIMIT_HIT (sorted by eventTypeId)
46
+ *
47
+ * This is deterministic: same logical inputs always produce the same output
48
+ * regardless of input ordering.
49
+ */
50
+ export declare function computePlan(input: ComputePlanInput): PlanResult;
51
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/cli/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,cAAc,EAEd,UAAU,EACV,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAMpB,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACxC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,UAAU,CAyH/D"}