@sakeetech/vendure-payment-viva 0.2.1

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 (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +952 -0
  3. package/dist/api/admin-internal.controller.d.ts +59 -0
  4. package/dist/api/admin-internal.controller.d.ts.map +1 -0
  5. package/dist/api/admin-internal.controller.js +229 -0
  6. package/dist/api/admin-internal.controller.js.map +1 -0
  7. package/dist/api/admin-onboarding.controller.d.ts +72 -0
  8. package/dist/api/admin-onboarding.controller.d.ts.map +1 -0
  9. package/dist/api/admin-onboarding.controller.js +496 -0
  10. package/dist/api/admin-onboarding.controller.js.map +1 -0
  11. package/dist/api/admin-sources.controller.d.ts +50 -0
  12. package/dist/api/admin-sources.controller.d.ts.map +1 -0
  13. package/dist/api/admin-sources.controller.js +283 -0
  14. package/dist/api/admin-sources.controller.js.map +1 -0
  15. package/dist/api/shop-api.extension.d.ts +15 -0
  16. package/dist/api/shop-api.extension.d.ts.map +1 -0
  17. package/dist/api/shop-api.extension.js +35 -0
  18. package/dist/api/shop-api.extension.js.map +1 -0
  19. package/dist/api/shop-api.resolver.d.ts +42 -0
  20. package/dist/api/shop-api.resolver.d.ts.map +1 -0
  21. package/dist/api/shop-api.resolver.js +256 -0
  22. package/dist/api/shop-api.resolver.js.map +1 -0
  23. package/dist/api/webhook.controller.d.ts +58 -0
  24. package/dist/api/webhook.controller.d.ts.map +1 -0
  25. package/dist/api/webhook.controller.js +204 -0
  26. package/dist/api/webhook.controller.js.map +1 -0
  27. package/dist/cli/bin.d.ts +28 -0
  28. package/dist/cli/bin.d.ts.map +1 -0
  29. package/dist/cli/bin.js +104 -0
  30. package/dist/cli/bin.js.map +1 -0
  31. package/dist/cli/plan.d.ts +41 -0
  32. package/dist/cli/plan.d.ts.map +1 -0
  33. package/dist/cli/plan.js +115 -0
  34. package/dist/cli/plan.js.map +1 -0
  35. package/dist/cli/register-webhooks.d.ts +45 -0
  36. package/dist/cli/register-webhooks.d.ts.map +1 -0
  37. package/dist/cli/register-webhooks.js +400 -0
  38. package/dist/cli/register-webhooks.js.map +1 -0
  39. package/dist/cli/types.d.ts +75 -0
  40. package/dist/cli/types.d.ts.map +1 -0
  41. package/dist/cli/types.js +10 -0
  42. package/dist/cli/types.js.map +1 -0
  43. package/dist/constants.d.ts +35 -0
  44. package/dist/constants.d.ts.map +1 -0
  45. package/dist/constants.js +40 -0
  46. package/dist/constants.js.map +1 -0
  47. package/dist/entities/index.d.ts +4 -0
  48. package/dist/entities/index.d.ts.map +1 -0
  49. package/dist/entities/index.js +3 -0
  50. package/dist/entities/index.js.map +1 -0
  51. package/dist/entities/viva-transaction.entity.d.ts +70 -0
  52. package/dist/entities/viva-transaction.entity.d.ts.map +1 -0
  53. package/dist/entities/viva-transaction.entity.js +133 -0
  54. package/dist/entities/viva-transaction.entity.js.map +1 -0
  55. package/dist/entities/viva-webhook-event.entity.d.ts +71 -0
  56. package/dist/entities/viva-webhook-event.entity.d.ts.map +1 -0
  57. package/dist/entities/viva-webhook-event.entity.js +138 -0
  58. package/dist/entities/viva-webhook-event.entity.js.map +1 -0
  59. package/dist/index.d.ts +27 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +23 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/jobs/process-viva-webhook.handler.d.ts +95 -0
  64. package/dist/jobs/process-viva-webhook.handler.d.ts.map +1 -0
  65. package/dist/jobs/process-viva-webhook.handler.js +530 -0
  66. package/dist/jobs/process-viva-webhook.handler.js.map +1 -0
  67. package/dist/jobs/queue-names.d.ts +18 -0
  68. package/dist/jobs/queue-names.d.ts.map +1 -0
  69. package/dist/jobs/queue-names.js +19 -0
  70. package/dist/jobs/queue-names.js.map +1 -0
  71. package/dist/jobs/retention-cleanup.handler.d.ts +31 -0
  72. package/dist/jobs/retention-cleanup.handler.d.ts.map +1 -0
  73. package/dist/jobs/retention-cleanup.handler.js +94 -0
  74. package/dist/jobs/retention-cleanup.handler.js.map +1 -0
  75. package/dist/loaders/bootstrap.d.ts +28 -0
  76. package/dist/loaders/bootstrap.d.ts.map +1 -0
  77. package/dist/loaders/bootstrap.js +90 -0
  78. package/dist/loaders/bootstrap.js.map +1 -0
  79. package/dist/migrations/1714000000000-create-viva-tables.d.ts +22 -0
  80. package/dist/migrations/1714000000000-create-viva-tables.d.ts.map +1 -0
  81. package/dist/migrations/1714000000000-create-viva-tables.js +105 -0
  82. package/dist/migrations/1714000000000-create-viva-tables.js.map +1 -0
  83. package/dist/observability/metrics-state.service.d.ts +43 -0
  84. package/dist/observability/metrics-state.service.d.ts.map +1 -0
  85. package/dist/observability/metrics-state.service.js +207 -0
  86. package/dist/observability/metrics-state.service.js.map +1 -0
  87. package/dist/payment-method-handler.d.ts +26 -0
  88. package/dist/payment-method-handler.d.ts.map +1 -0
  89. package/dist/payment-method-handler.js +693 -0
  90. package/dist/payment-method-handler.js.map +1 -0
  91. package/dist/plugin.d.ts +95 -0
  92. package/dist/plugin.d.ts.map +1 -0
  93. package/dist/plugin.js +241 -0
  94. package/dist/plugin.js.map +1 -0
  95. package/dist/providers/viva-oauth2-strategy.provider.d.ts +41 -0
  96. package/dist/providers/viva-oauth2-strategy.provider.d.ts.map +1 -0
  97. package/dist/providers/viva-oauth2-strategy.provider.js +60 -0
  98. package/dist/providers/viva-oauth2-strategy.provider.js.map +1 -0
  99. package/dist/services/connected-accounts.service.d.ts +53 -0
  100. package/dist/services/connected-accounts.service.d.ts.map +1 -0
  101. package/dist/services/connected-accounts.service.js +108 -0
  102. package/dist/services/connected-accounts.service.js.map +1 -0
  103. package/dist/services/per-merchant-semaphore.service.d.ts +49 -0
  104. package/dist/services/per-merchant-semaphore.service.d.ts.map +1 -0
  105. package/dist/services/per-merchant-semaphore.service.js +156 -0
  106. package/dist/services/per-merchant-semaphore.service.js.map +1 -0
  107. package/dist/services/state-machine.service.d.ts +100 -0
  108. package/dist/services/state-machine.service.d.ts.map +1 -0
  109. package/dist/services/state-machine.service.js +233 -0
  110. package/dist/services/state-machine.service.js.map +1 -0
  111. package/dist/types.d.ts +286 -0
  112. package/dist/types.d.ts.map +1 -0
  113. package/dist/types.js +23 -0
  114. package/dist/types.js.map +1 -0
  115. package/dist/util/currency.d.ts +32 -0
  116. package/dist/util/currency.d.ts.map +1 -0
  117. package/dist/util/currency.js +90 -0
  118. package/dist/util/currency.js.map +1 -0
  119. package/dist/util/error-envelope.d.ts +51 -0
  120. package/dist/util/error-envelope.d.ts.map +1 -0
  121. package/dist/util/error-envelope.js +157 -0
  122. package/dist/util/error-envelope.js.map +1 -0
  123. package/dist/util/ip-allowlist.d.ts +44 -0
  124. package/dist/util/ip-allowlist.d.ts.map +1 -0
  125. package/dist/util/ip-allowlist.js +139 -0
  126. package/dist/util/ip-allowlist.js.map +1 -0
  127. package/dist/util/normalize-options.d.ts +24 -0
  128. package/dist/util/normalize-options.d.ts.map +1 -0
  129. package/dist/util/normalize-options.js +189 -0
  130. package/dist/util/normalize-options.js.map +1 -0
  131. package/dist/util/url-template.d.ts +18 -0
  132. package/dist/util/url-template.d.ts.map +1 -0
  133. package/dist/util/url-template.js +22 -0
  134. package/dist/util/url-template.js.map +1 -0
  135. package/package.json +75 -0
@@ -0,0 +1,283 @@
1
+ /**
2
+ * api/admin-sources.controller.ts — ISV-only admin REST for /api/sources wrapping.
3
+ *
4
+ * Single route:
5
+ *
6
+ * POST /viva/admin/connected-accounts/:id/sources
7
+ * Creates a payment source (ecommerce or physical) on a connected
8
+ * merchant's Viva account via POST /api/sources on the legacy host,
9
+ * authenticated with Reseller Basic auth (per AUTH.md §1.2).
10
+ *
11
+ * Flow:
12
+ * 1. Mode gate: 404 in merchant mode (mirrors Medusa _mode-gate.ts).
13
+ * 2. Reseller gate: 412 VIVA_RESELLER_CREDENTIALS_MISSING when
14
+ * options.reseller is absent.
15
+ * 3. Body parse: 400 on missing/invalid fields or unknown kind.
16
+ * 4. Account lookup: IsvAccounts.retrieveConnectedAccount(:id).
17
+ * → 409 VIVA_ACCOUNT_NOT_VERIFIED when verified !== true or
18
+ * merchantId is null.
19
+ * 5. BasicAuthClient {authVariant:'reseller'} built with the connected
20
+ * merchant's UUID (account.merchantId — NOT config.reseller.merchantId).
21
+ * 6. IsvSources.createEcommerceSource / createPhysicalSource.
22
+ * 7. 201 with {sourceCode, name, kind}.
23
+ *
24
+ * Viva 4xx on /api/sources → 422 VIVA_SOURCE_CREATION_FAILED.
25
+ * Viva 5xx / auth errors → 503 VIVA_AUTH_DOWN (existing envelope rule).
26
+ *
27
+ * @see docs/plans/multi-mode-v0.md §6 (admin REST table)
28
+ * @see docs/AUTH.md §1.2 (Reseller Basic)
29
+ * @see docs/ENDPOINTS.md §5.1
30
+ * @see packages/medusa-payment-viva/src/api/viva/admin/connected-accounts/[id]/sources/route.ts
31
+ */
32
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
33
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
34
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
35
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
36
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
37
+ };
38
+ var __metadata = (this && this.__metadata) || function (k, v) {
39
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
40
+ };
41
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
42
+ return function (target, key) { decorator(target, key, paramIndex); }
43
+ };
44
+ import { Controller, Post, Param, Body, Res, Inject, UseGuards, } from '@nestjs/common';
45
+ import { Allow, Permission, Logger, AuthGuard } from '@vendure/core';
46
+ import { IsvAccounts, IsvHttpClient, IsvSources, } from '@sakeetech/viva-payments-core/isv';
47
+ import { BasicAuthClient } from '@sakeetech/viva-payments-core/legacy';
48
+ import { VivaApiError, VivaAuthError, VivaValidationError, } from '@sakeetech/viva-payments-core/errors';
49
+ import { VivaPluginError } from '../util/error-envelope.js';
50
+ import { VIVA_PLUGIN_OPTIONS, VIVA_OAUTH2_STRATEGY_TOKEN, VIVA_LOG_CONTEXT, } from '../constants.js';
51
+ function parseBody(raw) {
52
+ const body = (raw ?? {});
53
+ const kind = body.kind;
54
+ if (kind !== 'ecommerce' && kind !== 'physical') {
55
+ return {
56
+ ok: false,
57
+ status: 400,
58
+ message: "body.kind must be 'ecommerce' or 'physical'",
59
+ };
60
+ }
61
+ const sourceCode = body.sourceCode;
62
+ if (sourceCode !== undefined && typeof sourceCode !== 'number') {
63
+ return {
64
+ ok: false,
65
+ status: 400,
66
+ message: 'sourceCode must be a number',
67
+ };
68
+ }
69
+ if (kind === 'physical') {
70
+ if (typeof body.name !== 'string' || body.name.trim() === '') {
71
+ return {
72
+ ok: false,
73
+ status: 400,
74
+ message: 'name is required for physical sources',
75
+ };
76
+ }
77
+ const input = {
78
+ name: body.name,
79
+ ...(sourceCode !== undefined ? { sourceCode: sourceCode } : {}),
80
+ };
81
+ return { ok: true, kind, input };
82
+ }
83
+ if (typeof body.domain !== 'string' || body.domain.trim() === '') {
84
+ return { ok: false, status: 400, message: 'domain is required for ecommerce sources' };
85
+ }
86
+ if (typeof body.pathSuccess !== 'string' || body.pathSuccess.trim() === '') {
87
+ return { ok: false, status: 400, message: 'pathSuccess is required for ecommerce sources' };
88
+ }
89
+ if (typeof body.pathFail !== 'string' || body.pathFail.trim() === '') {
90
+ return { ok: false, status: 400, message: 'pathFail is required for ecommerce sources' };
91
+ }
92
+ const ecomInput = {
93
+ domain: body.domain,
94
+ pathSuccess: body.pathSuccess,
95
+ pathFail: body.pathFail,
96
+ ...(typeof body.name === 'string' ? { name: body.name } : {}),
97
+ ...(typeof body.isSecure === 'boolean' ? { isSecure: body.isSecure } : {}),
98
+ ...(sourceCode !== undefined ? { sourceCode: sourceCode } : {}),
99
+ };
100
+ return { ok: true, kind, input: ecomInput };
101
+ }
102
+ // ---------------------------------------------------------------------------
103
+ // Helpers
104
+ // ---------------------------------------------------------------------------
105
+ function writeError(res, status, err) {
106
+ res.writeHead(status, { 'Content-Type': 'application/json' });
107
+ res.end(JSON.stringify(err.toJSON()));
108
+ }
109
+ // ---------------------------------------------------------------------------
110
+ // Controller
111
+ // ---------------------------------------------------------------------------
112
+ let AdminSourcesController = class AdminSourcesController {
113
+ options;
114
+ oauth2;
115
+ constructor(options, oauth2) {
116
+ this.options = options;
117
+ this.oauth2 = oauth2;
118
+ }
119
+ /**
120
+ * Narrow options to ISV mode + reseller block.
121
+ * Returns:
122
+ * - { ok: true, isv } on success.
123
+ * - { ok: false, status, err } on mode mismatch (404) or missing reseller (412).
124
+ */
125
+ requireIsvWithReseller() {
126
+ if (this.options.mode !== 'isv') {
127
+ return {
128
+ ok: false,
129
+ status: 404,
130
+ err: VivaPluginError.channelMisconfigured('POST /viva/admin/connected-accounts/:id/sources is ISV-only — not available in merchant mode.'),
131
+ };
132
+ }
133
+ if (!this.options.reseller) {
134
+ return {
135
+ ok: false,
136
+ status: 412,
137
+ err: VivaPluginError.resellerCredentialsMissing(),
138
+ };
139
+ }
140
+ return {
141
+ ok: true,
142
+ isv: this.options,
143
+ };
144
+ }
145
+ /** Build the OAuth2-backed IsvAccounts client (platform creds). */
146
+ buildIsvAccounts() {
147
+ const client = new IsvHttpClient({
148
+ environment: this.options.environment,
149
+ authStrategy: this.oauth2,
150
+ });
151
+ return new IsvAccounts(client);
152
+ }
153
+ // -------------------------------------------------------------------------
154
+ // POST /viva/admin/connected-accounts/:id/sources
155
+ // -------------------------------------------------------------------------
156
+ async createSource(id, body, res) {
157
+ if (!id) {
158
+ writeError(res, 400, VivaPluginError.channelMisconfigured('id is required'));
159
+ return;
160
+ }
161
+ // 1. Mode + reseller gate
162
+ const gate = this.requireIsvWithReseller();
163
+ if (!gate.ok) {
164
+ writeError(res, gate.status, gate.err);
165
+ return;
166
+ }
167
+ const { isv } = gate;
168
+ // 2. Parse body
169
+ const parsed = parseBody(body);
170
+ if (!parsed.ok) {
171
+ writeError(res, parsed.status, VivaPluginError.channelMisconfigured(parsed.message));
172
+ return;
173
+ }
174
+ // 3. Resolve connected account
175
+ let account;
176
+ try {
177
+ const accounts = this.buildIsvAccounts();
178
+ account = await accounts.retrieveConnectedAccount(id);
179
+ }
180
+ catch (err) {
181
+ if (err instanceof VivaAuthError || (err instanceof VivaApiError && (err.httpStatus ?? 0) >= 500)) {
182
+ writeError(res, 503, VivaPluginError.authDown(err instanceof Error ? err.message : 'Viva service unavailable', err));
183
+ return;
184
+ }
185
+ if (err instanceof VivaApiError) {
186
+ const status = err.httpStatus ?? 502;
187
+ writeError(res, status >= 400 && status < 600 ? status : 502, VivaPluginError.apiError({
188
+ message: err.message,
189
+ vivaErrorMessage: err.message,
190
+ cause: err,
191
+ }));
192
+ return;
193
+ }
194
+ Logger.error(`[AdminSources] retrieveConnectedAccount failed for id=${id}: ${String(err)}`, VIVA_LOG_CONTEXT);
195
+ writeError(res, 500, VivaPluginError.internalError('Unexpected error', err));
196
+ return;
197
+ }
198
+ if (account.verified !== true || account.merchantId == null) {
199
+ const err = VivaPluginError.accountNotVerified(`Connected account ${id} is not verified yet (verified=${String(account.verified)}, ` +
200
+ `merchantId=${account.merchantId == null ? 'null' : 'set'}). ` +
201
+ `Wait for webhook 8194 or POST /viva/admin/connected-accounts/${id}/reconcile.`);
202
+ writeError(res, 409, err);
203
+ return;
204
+ }
205
+ // 4. Build Reseller Basic client scoped to THIS connected merchant.
206
+ // Per AUTH.md §1.2: the MerchantId slot of Reseller Basic is the
207
+ // connected merchant's UUID (account.merchantId), NOT the platform's
208
+ // merchant id (isv.reseller.merchantId — unused here despite the name).
209
+ const basic = new BasicAuthClient({
210
+ authVariant: 'reseller',
211
+ environment: isv.environment,
212
+ resellerId: isv.reseller.resellerId,
213
+ merchantId: account.merchantId,
214
+ resellerApiKey: isv.reseller.resellerApiKey,
215
+ });
216
+ const sources = new IsvSources(basic);
217
+ // 5. Dispatch on kind.
218
+ try {
219
+ const response = parsed.kind === 'ecommerce'
220
+ ? await sources.createEcommerceSource(parsed.input)
221
+ : await sources.createPhysicalSource(parsed.input);
222
+ res.writeHead(201, { 'Content-Type': 'application/json' });
223
+ res.end(JSON.stringify({
224
+ sourceCode: response.sourceCode,
225
+ name: response.name ?? parsed.input.name,
226
+ kind: parsed.kind,
227
+ }));
228
+ }
229
+ catch (err) {
230
+ if (err instanceof VivaValidationError) {
231
+ writeError(res, 400, VivaPluginError.channelMisconfigured(err.message));
232
+ return;
233
+ }
234
+ if (err instanceof VivaApiError) {
235
+ const status = err.httpStatus;
236
+ if (typeof status === 'number' && status >= 400 && status < 500) {
237
+ const rawCode = err.vivaCode !== undefined ? Number(err.vivaCode) : undefined;
238
+ const opts = {
239
+ message: err.message,
240
+ vivaStatus: status,
241
+ cause: err,
242
+ };
243
+ if (rawCode !== undefined && !Number.isNaN(rawCode)) {
244
+ opts.vivaErrorCode = rawCode;
245
+ }
246
+ writeError(res, 422, VivaPluginError.sourceCreationFailed(opts));
247
+ return;
248
+ }
249
+ if (typeof status === 'number' && status >= 500) {
250
+ writeError(res, 503, VivaPluginError.authDown(err.message, err));
251
+ return;
252
+ }
253
+ writeError(res, 502, VivaPluginError.apiError({
254
+ message: err.message,
255
+ vivaErrorMessage: err.message,
256
+ cause: err,
257
+ }));
258
+ return;
259
+ }
260
+ Logger.error(`[AdminSources] createSource failed for id=${id}: ${String(err)}`, VIVA_LOG_CONTEXT);
261
+ writeError(res, 500, VivaPluginError.internalError('Unexpected error', err));
262
+ }
263
+ }
264
+ };
265
+ __decorate([
266
+ Post(':id/sources'),
267
+ Allow(Permission.SuperAdmin),
268
+ __param(0, Param('id')),
269
+ __param(1, Body()),
270
+ __param(2, Res()),
271
+ __metadata("design:type", Function),
272
+ __metadata("design:paramtypes", [String, Object, Function]),
273
+ __metadata("design:returntype", Promise)
274
+ ], AdminSourcesController.prototype, "createSource", null);
275
+ AdminSourcesController = __decorate([
276
+ Controller('viva/admin/connected-accounts'),
277
+ UseGuards(AuthGuard),
278
+ __param(0, Inject(VIVA_PLUGIN_OPTIONS)),
279
+ __param(1, Inject(VIVA_OAUTH2_STRATEGY_TOKEN)),
280
+ __metadata("design:paramtypes", [Object, Object])
281
+ ], AdminSourcesController);
282
+ export { AdminSourcesController };
283
+ //# sourceMappingURL=admin-sources.controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-sources.controller.js","sourceRoot":"","sources":["../../src/api/admin-sources.controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * api/shop-api.extension.ts — GraphQL Shop API schema extension.
3
+ *
4
+ * Adds the `cancelPayment(paymentId)` mutation to the Shop API.
5
+ * `Order` is Vendure's existing built-in entity; `ErrorResult` interface
6
+ * is already in scope from @vendure/core so `CancelPaymentError implements
7
+ * ErrorResult` is valid without re-declaring the interface.
8
+ *
9
+ * Wired into plugin via `shopApiExtensions.schema` (see plugin.ts).
10
+ *
11
+ * @see docs/plans/vendure-plugin-v0.md §"API Surface — Shop API extension"
12
+ * @see docs/plans/vendure-plugin-v0.md §"Build Plan — V8"
13
+ */
14
+ export declare const shopApiExtensions: import("graphql").DocumentNode;
15
+ //# sourceMappingURL=shop-api.extension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shop-api.extension.d.ts","sourceRoot":"","sources":["../../src/api/shop-api.extension.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,eAAO,MAAM,iBAAiB,gCAmB5B,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * api/shop-api.extension.ts — GraphQL Shop API schema extension.
3
+ *
4
+ * Adds the `cancelPayment(paymentId)` mutation to the Shop API.
5
+ * `Order` is Vendure's existing built-in entity; `ErrorResult` interface
6
+ * is already in scope from @vendure/core so `CancelPaymentError implements
7
+ * ErrorResult` is valid without re-declaring the interface.
8
+ *
9
+ * Wired into plugin via `shopApiExtensions.schema` (see plugin.ts).
10
+ *
11
+ * @see docs/plans/vendure-plugin-v0.md §"API Surface — Shop API extension"
12
+ * @see docs/plans/vendure-plugin-v0.md §"Build Plan — V8"
13
+ */
14
+ import { parse } from 'graphql';
15
+ export const shopApiExtensions = parse(`
16
+ extend type Mutation {
17
+ """
18
+ Cancel a Vendure payment by ID. Voids the Viva-side authorization
19
+ (DELETE /checkout/v2/orders/{orderCode}) AND transitions the order back
20
+ to AddingItems. Storefront calls this on ?paymentCancelled=1.
21
+ Permission: order owner (active customer or active anonymous order).
22
+ """
23
+ cancelPayment(paymentId: ID!): CancelPaymentResult!
24
+ }
25
+
26
+ union CancelPaymentResult = Order | CancelPaymentError
27
+
28
+ type CancelPaymentError implements ErrorResult {
29
+ errorCode: String!
30
+ message: String!
31
+ vivaErrorCode: Int
32
+ vivaErrorMessage: String
33
+ }
34
+ `);
35
+ //# sourceMappingURL=shop-api.extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shop-api.extension.js","sourceRoot":"","sources":["../../src/api/shop-api.extension.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;;;;;;;;;;;;;;;;;;;CAmBtC,CAAC,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * api/shop-api.resolver.ts — VivaShopApiResolver
3
+ *
4
+ * Resolves the `cancelPayment(paymentId)` Shop API mutation.
5
+ *
6
+ * Authorization pattern (D5 + D8, §3 cancel ownership):
7
+ * - Authenticated customer: ctx.activeUserId must match order.customer.user.id.
8
+ * - Anonymous order: the session's active order (looked up via
9
+ * OrderService.getActiveOrderForUser or by active-order token) must have the
10
+ * same id as the payment's order. We load the active order for the current
11
+ * session (ctx) and compare order ids — if they match, the caller owns it.
12
+ * Returns CancelPaymentError { errorCode: 'AUTHORIZATION_FAILED' } when neither
13
+ * condition is satisfied. Does NOT throw — returns the union error member per
14
+ * Vendure GraphQL conventions.
15
+ *
16
+ * Idempotency on re-cancel:
17
+ * The row-status guard (step 6) fires before the Viva DELETE call.
18
+ * If viva_transaction.status is already 'cancelled', we return
19
+ * CancelPaymentError('VIVA_PAYMENT_NOT_CANCELLABLE') immediately — no Viva
20
+ * network call is made. If the row is NOT yet 'cancelled' but Viva 4xx with
21
+ * "already cancelled", the handler's error map converts it to
22
+ * CancelPaymentError('VIVA_API_ERROR'). Both paths are tested.
23
+ *
24
+ * @see docs/plans/vendure-plugin-v0.md §"API Surface — Shop API extension"
25
+ * @see docs/plans/vendure-plugin-v0.md §"Build Plan — V8"
26
+ * @see docs/plans/vendure-plugin-v0.md §D5, §D8
27
+ * @see docs/VENDURE-CONTRACT.MD §3
28
+ */
29
+ import { RequestContext, OrderService, PaymentService, TransactionalConnection } from '@vendure/core';
30
+ import { StateMachineService } from '../services/state-machine.service.js';
31
+ import type { VivaPaymentPluginOptions } from '../types.js';
32
+ export declare class VivaShopApiResolver {
33
+ private readonly orderService;
34
+ private readonly paymentService;
35
+ private readonly connection;
36
+ private readonly stateMachine;
37
+ private readonly options;
38
+ constructor(orderService: OrderService, paymentService: PaymentService, connection: TransactionalConnection, stateMachine: StateMachineService, options: VivaPaymentPluginOptions);
39
+ cancelPayment(paymentId: string, ctx: RequestContext): Promise<object>;
40
+ private _isOrderOwner;
41
+ }
42
+ //# sourceMappingURL=shop-api.resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shop-api.resolver.d.ts","sourceRoot":"","sources":["../../src/api/shop-api.resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,EAEL,cAAc,EACd,YAAY,EACZ,cAAc,EACd,uBAAuB,EAExB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAG3E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAgC5D,qBACa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,YAAY;IACA,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAJpC,YAAY,EAAE,YAAY,EAC1B,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,uBAAuB,EACnC,YAAY,EAAE,mBAAmB,EACJ,OAAO,EAAE,wBAAwB;IAI3E,aAAa,CACE,SAAS,EAAE,MAAM,EAC7B,GAAG,EAAE,cAAc,GACzB,OAAO,CAAC,MAAM,CAAC;YAqJJ,aAAa;CA4B5B"}
@@ -0,0 +1,256 @@
1
+ /**
2
+ * api/shop-api.resolver.ts — VivaShopApiResolver
3
+ *
4
+ * Resolves the `cancelPayment(paymentId)` Shop API mutation.
5
+ *
6
+ * Authorization pattern (D5 + D8, §3 cancel ownership):
7
+ * - Authenticated customer: ctx.activeUserId must match order.customer.user.id.
8
+ * - Anonymous order: the session's active order (looked up via
9
+ * OrderService.getActiveOrderForUser or by active-order token) must have the
10
+ * same id as the payment's order. We load the active order for the current
11
+ * session (ctx) and compare order ids — if they match, the caller owns it.
12
+ * Returns CancelPaymentError { errorCode: 'AUTHORIZATION_FAILED' } when neither
13
+ * condition is satisfied. Does NOT throw — returns the union error member per
14
+ * Vendure GraphQL conventions.
15
+ *
16
+ * Idempotency on re-cancel:
17
+ * The row-status guard (step 6) fires before the Viva DELETE call.
18
+ * If viva_transaction.status is already 'cancelled', we return
19
+ * CancelPaymentError('VIVA_PAYMENT_NOT_CANCELLABLE') immediately — no Viva
20
+ * network call is made. If the row is NOT yet 'cancelled' but Viva 4xx with
21
+ * "already cancelled", the handler's error map converts it to
22
+ * CancelPaymentError('VIVA_API_ERROR'). Both paths are tested.
23
+ *
24
+ * @see docs/plans/vendure-plugin-v0.md §"API Surface — Shop API extension"
25
+ * @see docs/plans/vendure-plugin-v0.md §"Build Plan — V8"
26
+ * @see docs/plans/vendure-plugin-v0.md §D5, §D8
27
+ * @see docs/VENDURE-CONTRACT.MD §3
28
+ */
29
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
30
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
31
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
32
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
33
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
34
+ };
35
+ var __metadata = (this && this.__metadata) || function (k, v) {
36
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
37
+ };
38
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
39
+ return function (target, key) { decorator(target, key, paramIndex); }
40
+ };
41
+ import { Resolver, Mutation, Args } from '@nestjs/graphql';
42
+ import { Inject } from '@nestjs/common';
43
+ import { Ctx, RequestContext, OrderService, PaymentService, TransactionalConnection, Logger, } from '@vendure/core';
44
+ import { StateMachineService } from '../services/state-machine.service.js';
45
+ import { VivaPluginError } from '../util/error-envelope.js';
46
+ import { VIVA_PLUGIN_OPTIONS, VIVA_LOG_CONTEXT } from '../constants.js';
47
+ import { vivaPaymentMethodHandler } from '../payment-method-handler.js';
48
+ // ---------------------------------------------------------------------------
49
+ // Terminal statuses that cannot be cancelled
50
+ // ---------------------------------------------------------------------------
51
+ const TERMINAL_STATUSES = new Set(['captured', 'refunded', 'partially_refunded', 'failed', 'cancelled']);
52
+ // ---------------------------------------------------------------------------
53
+ // GraphQL union return types (plain objects with __typename for Vendure)
54
+ // ---------------------------------------------------------------------------
55
+ function cancelPaymentError(opts) {
56
+ return {
57
+ __typename: 'CancelPaymentError',
58
+ errorCode: opts.errorCode,
59
+ message: opts.message,
60
+ ...(opts.vivaErrorCode !== undefined ? { vivaErrorCode: opts.vivaErrorCode } : {}),
61
+ ...(opts.vivaErrorMessage !== undefined ? { vivaErrorMessage: opts.vivaErrorMessage } : {}),
62
+ };
63
+ }
64
+ // ---------------------------------------------------------------------------
65
+ // Resolver
66
+ // ---------------------------------------------------------------------------
67
+ let VivaShopApiResolver = class VivaShopApiResolver {
68
+ orderService;
69
+ paymentService;
70
+ connection;
71
+ stateMachine;
72
+ options;
73
+ constructor(orderService, paymentService, connection, stateMachine, options) {
74
+ this.orderService = orderService;
75
+ this.paymentService = paymentService;
76
+ this.connection = connection;
77
+ this.stateMachine = stateMachine;
78
+ this.options = options;
79
+ }
80
+ async cancelPayment(paymentId, ctx) {
81
+ // -------------------------------------------------------------------------
82
+ // Step 1: load the Vendure Payment (with relations)
83
+ // -------------------------------------------------------------------------
84
+ let payment;
85
+ try {
86
+ payment = await this.paymentService.findOneOrThrow(ctx, paymentId, ['order']);
87
+ }
88
+ catch {
89
+ // Payment not found
90
+ }
91
+ if (!payment) {
92
+ return cancelPaymentError({
93
+ errorCode: 'VIVA_PAYMENT_NOT_CANCELLABLE',
94
+ message: 'Payment not found.',
95
+ });
96
+ }
97
+ // -------------------------------------------------------------------------
98
+ // Step 2: load the Order (full record with customer.user relation)
99
+ // -------------------------------------------------------------------------
100
+ const orderStub = payment.order;
101
+ if (!orderStub) {
102
+ return cancelPaymentError({
103
+ errorCode: 'VIVA_PAYMENT_NOT_CANCELLABLE',
104
+ message: 'Payment is not associated with an order.',
105
+ });
106
+ }
107
+ const order = await this.orderService.findOne(ctx, orderStub.id, ['customer', 'customer.user']);
108
+ if (!order) {
109
+ return cancelPaymentError({
110
+ errorCode: 'VIVA_PAYMENT_NOT_CANCELLABLE',
111
+ message: 'Order not found.',
112
+ });
113
+ }
114
+ // -------------------------------------------------------------------------
115
+ // Step 3: Authorization — order owner check
116
+ //
117
+ // Case A — Authenticated customer: ctx.activeUserId matches order.customer.user.id
118
+ // Case B — Anonymous order: order matches the session's active order
119
+ // (Vendure stores activeOrderId / activeOrder.code on session)
120
+ // -------------------------------------------------------------------------
121
+ const isAuthorized = await this._isOrderOwner(ctx, order);
122
+ if (!isAuthorized) {
123
+ return cancelPaymentError({
124
+ errorCode: 'AUTHORIZATION_FAILED',
125
+ message: 'You may only cancel your own payments.',
126
+ });
127
+ }
128
+ // -------------------------------------------------------------------------
129
+ // Step 4: load viva_transaction row
130
+ // -------------------------------------------------------------------------
131
+ const vivaRow = await this.stateMachine.getVivaTransaction(ctx, ctx.channelId, paymentId);
132
+ // -------------------------------------------------------------------------
133
+ // Step 5: row missing or no vivaOrderCode
134
+ // -------------------------------------------------------------------------
135
+ if (!vivaRow || !vivaRow.vivaOrderCode) {
136
+ return cancelPaymentError({
137
+ errorCode: 'VIVA_PAYMENT_NOT_CANCELLABLE',
138
+ message: 'No Viva order code found — payment may not have been initiated.',
139
+ });
140
+ }
141
+ // -------------------------------------------------------------------------
142
+ // Step 6: already-terminal status guard (row-level, no Viva call needed)
143
+ // -------------------------------------------------------------------------
144
+ if (TERMINAL_STATUSES.has(vivaRow.status)) {
145
+ return cancelPaymentError({
146
+ errorCode: 'VIVA_PAYMENT_NOT_CANCELLABLE',
147
+ message: `Payment is already in terminal state: ${vivaRow.status}.`,
148
+ });
149
+ }
150
+ // -------------------------------------------------------------------------
151
+ // Step 7: invoke the payment handler's cancelPayment (voids Viva auth +
152
+ // updates viva_transaction.status = 'cancelled')
153
+ // -------------------------------------------------------------------------
154
+ try {
155
+ // cancelPayment on the handler throws VivaPluginError on failure
156
+ await vivaPaymentMethodHandler.cancelPaymentFn(ctx, order, payment, {});
157
+ }
158
+ catch (err) {
159
+ if (err instanceof VivaPluginError) {
160
+ return cancelPaymentError({
161
+ errorCode: err.code,
162
+ message: err.message,
163
+ ...(err.vivaErrorCode !== undefined ? { vivaErrorCode: err.vivaErrorCode } : {}),
164
+ ...(err.vivaErrorMessage !== undefined ? { vivaErrorMessage: err.vivaErrorMessage } : {}),
165
+ });
166
+ }
167
+ // Unexpected error — surface as internal
168
+ const msg = err instanceof Error ? err.message : String(err);
169
+ Logger.error(`[cancelPayment] Unexpected error for paymentId=${paymentId}: ${msg}`, VIVA_LOG_CONTEXT);
170
+ return cancelPaymentError({
171
+ errorCode: 'VIVA_INTERNAL_ERROR',
172
+ message: 'An unexpected error occurred while cancelling the payment.',
173
+ });
174
+ }
175
+ // -------------------------------------------------------------------------
176
+ // Step 8: transition Order back to AddingItems
177
+ // -------------------------------------------------------------------------
178
+ const refreshedOrder = await this.orderService.findOne(ctx, order.id);
179
+ if (refreshedOrder && refreshedOrder.state !== 'AddingItems') {
180
+ try {
181
+ await this.orderService.transitionToState(ctx, order.id, 'AddingItems');
182
+ }
183
+ catch (err) {
184
+ // Race with webhook sweep — if already AddingItems treat as idempotent success
185
+ const msg = err instanceof Error ? err.message : String(err);
186
+ Logger.warn(`[cancelPayment] transitionToState('AddingItems') failed for order ${String(order.id)}: ${msg}. ` +
187
+ `Treating as idempotent (order may already be AddingItems).`, VIVA_LOG_CONTEXT);
188
+ // Re-check current state
189
+ const recheckOrder = await this.orderService.findOne(ctx, order.id);
190
+ if (recheckOrder && recheckOrder.state !== 'AddingItems') {
191
+ return cancelPaymentError({
192
+ errorCode: 'VIVA_INTERNAL_ERROR',
193
+ message: `Failed to transition order to AddingItems: ${msg}`,
194
+ });
195
+ }
196
+ }
197
+ }
198
+ // -------------------------------------------------------------------------
199
+ // Step 9: return the updated Order
200
+ // -------------------------------------------------------------------------
201
+ const finalOrder = await this.orderService.findOne(ctx, order.id);
202
+ return { ...(finalOrder ?? order), __typename: 'Order' };
203
+ }
204
+ // ---------------------------------------------------------------------------
205
+ // Private: authorization helper
206
+ //
207
+ // Vendure anonymous-order pattern:
208
+ // OrderService.getActiveOrderForUser(ctx) returns the session's current
209
+ // active order (keyed by session token, not customer). We compare by id.
210
+ // For authenticated users, ctx.activeUserId is available.
211
+ // ---------------------------------------------------------------------------
212
+ async _isOrderOwner(ctx, order) {
213
+ // Case A: authenticated customer
214
+ if (ctx.activeUserId) {
215
+ const orderCustomerUserId = order.customer?.user?.id;
216
+ if (orderCustomerUserId !== undefined && orderCustomerUserId !== null) {
217
+ return String(ctx.activeUserId) === String(orderCustomerUserId);
218
+ }
219
+ // Customer exists but user relation missing — fall through to active-order check
220
+ }
221
+ // Case B: anonymous order — compare with session's active order
222
+ try {
223
+ const activeOrder = await this.orderService.getActiveOrderForUser(ctx, ctx.session?.user?.id);
224
+ if (activeOrder && String(activeOrder.id) === String(order.id)) {
225
+ return true;
226
+ }
227
+ }
228
+ catch {
229
+ // getActiveOrderForUser may throw if no session — treat as unauthorized
230
+ }
231
+ // Case B fallback: check activeOrderId on session (Vendure stores this on CachedSession)
232
+ const sessionActiveOrderId = ctx.session?.activeOrderId;
233
+ if (sessionActiveOrderId !== undefined && sessionActiveOrderId !== null) {
234
+ return String(sessionActiveOrderId) === String(order.id);
235
+ }
236
+ return false;
237
+ }
238
+ };
239
+ __decorate([
240
+ Mutation(),
241
+ __param(0, Args('paymentId')),
242
+ __param(1, Ctx()),
243
+ __metadata("design:type", Function),
244
+ __metadata("design:paramtypes", [String, RequestContext]),
245
+ __metadata("design:returntype", Promise)
246
+ ], VivaShopApiResolver.prototype, "cancelPayment", null);
247
+ VivaShopApiResolver = __decorate([
248
+ Resolver(),
249
+ __param(4, Inject(VIVA_PLUGIN_OPTIONS)),
250
+ __metadata("design:paramtypes", [OrderService,
251
+ PaymentService,
252
+ TransactionalConnection,
253
+ StateMachineService, Object])
254
+ ], VivaShopApiResolver);
255
+ export { VivaShopApiResolver };
256
+ //# sourceMappingURL=shop-api.resolver.js.map