@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.
- package/LICENSE +21 -0
- package/README.md +952 -0
- package/dist/api/admin-internal.controller.d.ts +59 -0
- package/dist/api/admin-internal.controller.d.ts.map +1 -0
- package/dist/api/admin-internal.controller.js +229 -0
- package/dist/api/admin-internal.controller.js.map +1 -0
- package/dist/api/admin-onboarding.controller.d.ts +72 -0
- package/dist/api/admin-onboarding.controller.d.ts.map +1 -0
- package/dist/api/admin-onboarding.controller.js +496 -0
- package/dist/api/admin-onboarding.controller.js.map +1 -0
- package/dist/api/admin-sources.controller.d.ts +50 -0
- package/dist/api/admin-sources.controller.d.ts.map +1 -0
- package/dist/api/admin-sources.controller.js +283 -0
- package/dist/api/admin-sources.controller.js.map +1 -0
- package/dist/api/shop-api.extension.d.ts +15 -0
- package/dist/api/shop-api.extension.d.ts.map +1 -0
- package/dist/api/shop-api.extension.js +35 -0
- package/dist/api/shop-api.extension.js.map +1 -0
- package/dist/api/shop-api.resolver.d.ts +42 -0
- package/dist/api/shop-api.resolver.d.ts.map +1 -0
- package/dist/api/shop-api.resolver.js +256 -0
- package/dist/api/shop-api.resolver.js.map +1 -0
- package/dist/api/webhook.controller.d.ts +58 -0
- package/dist/api/webhook.controller.d.ts.map +1 -0
- package/dist/api/webhook.controller.js +204 -0
- package/dist/api/webhook.controller.js.map +1 -0
- package/dist/cli/bin.d.ts +28 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +104 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/plan.d.ts +41 -0
- package/dist/cli/plan.d.ts.map +1 -0
- package/dist/cli/plan.js +115 -0
- package/dist/cli/plan.js.map +1 -0
- package/dist/cli/register-webhooks.d.ts +45 -0
- package/dist/cli/register-webhooks.d.ts.map +1 -0
- package/dist/cli/register-webhooks.js +400 -0
- package/dist/cli/register-webhooks.js.map +1 -0
- package/dist/cli/types.d.ts +75 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +10 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/constants.d.ts +35 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +40 -0
- package/dist/constants.js.map +1 -0
- package/dist/entities/index.d.ts +4 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +3 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/viva-transaction.entity.d.ts +70 -0
- package/dist/entities/viva-transaction.entity.d.ts.map +1 -0
- package/dist/entities/viva-transaction.entity.js +133 -0
- package/dist/entities/viva-transaction.entity.js.map +1 -0
- package/dist/entities/viva-webhook-event.entity.d.ts +71 -0
- package/dist/entities/viva-webhook-event.entity.d.ts.map +1 -0
- package/dist/entities/viva-webhook-event.entity.js +138 -0
- package/dist/entities/viva-webhook-event.entity.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs/process-viva-webhook.handler.d.ts +95 -0
- package/dist/jobs/process-viva-webhook.handler.d.ts.map +1 -0
- package/dist/jobs/process-viva-webhook.handler.js +530 -0
- package/dist/jobs/process-viva-webhook.handler.js.map +1 -0
- package/dist/jobs/queue-names.d.ts +18 -0
- package/dist/jobs/queue-names.d.ts.map +1 -0
- package/dist/jobs/queue-names.js +19 -0
- package/dist/jobs/queue-names.js.map +1 -0
- package/dist/jobs/retention-cleanup.handler.d.ts +31 -0
- package/dist/jobs/retention-cleanup.handler.d.ts.map +1 -0
- package/dist/jobs/retention-cleanup.handler.js +94 -0
- package/dist/jobs/retention-cleanup.handler.js.map +1 -0
- package/dist/loaders/bootstrap.d.ts +28 -0
- package/dist/loaders/bootstrap.d.ts.map +1 -0
- package/dist/loaders/bootstrap.js +90 -0
- package/dist/loaders/bootstrap.js.map +1 -0
- package/dist/migrations/1714000000000-create-viva-tables.d.ts +22 -0
- package/dist/migrations/1714000000000-create-viva-tables.d.ts.map +1 -0
- package/dist/migrations/1714000000000-create-viva-tables.js +105 -0
- package/dist/migrations/1714000000000-create-viva-tables.js.map +1 -0
- package/dist/observability/metrics-state.service.d.ts +43 -0
- package/dist/observability/metrics-state.service.d.ts.map +1 -0
- package/dist/observability/metrics-state.service.js +207 -0
- package/dist/observability/metrics-state.service.js.map +1 -0
- package/dist/payment-method-handler.d.ts +26 -0
- package/dist/payment-method-handler.d.ts.map +1 -0
- package/dist/payment-method-handler.js +693 -0
- package/dist/payment-method-handler.js.map +1 -0
- package/dist/plugin.d.ts +95 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +241 -0
- package/dist/plugin.js.map +1 -0
- package/dist/providers/viva-oauth2-strategy.provider.d.ts +41 -0
- package/dist/providers/viva-oauth2-strategy.provider.d.ts.map +1 -0
- package/dist/providers/viva-oauth2-strategy.provider.js +60 -0
- package/dist/providers/viva-oauth2-strategy.provider.js.map +1 -0
- package/dist/services/connected-accounts.service.d.ts +53 -0
- package/dist/services/connected-accounts.service.d.ts.map +1 -0
- package/dist/services/connected-accounts.service.js +108 -0
- package/dist/services/connected-accounts.service.js.map +1 -0
- package/dist/services/per-merchant-semaphore.service.d.ts +49 -0
- package/dist/services/per-merchant-semaphore.service.d.ts.map +1 -0
- package/dist/services/per-merchant-semaphore.service.js +156 -0
- package/dist/services/per-merchant-semaphore.service.js.map +1 -0
- package/dist/services/state-machine.service.d.ts +100 -0
- package/dist/services/state-machine.service.d.ts.map +1 -0
- package/dist/services/state-machine.service.js +233 -0
- package/dist/services/state-machine.service.js.map +1 -0
- package/dist/types.d.ts +286 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/dist/util/currency.d.ts +32 -0
- package/dist/util/currency.d.ts.map +1 -0
- package/dist/util/currency.js +90 -0
- package/dist/util/currency.js.map +1 -0
- package/dist/util/error-envelope.d.ts +51 -0
- package/dist/util/error-envelope.d.ts.map +1 -0
- package/dist/util/error-envelope.js +157 -0
- package/dist/util/error-envelope.js.map +1 -0
- package/dist/util/ip-allowlist.d.ts +44 -0
- package/dist/util/ip-allowlist.d.ts.map +1 -0
- package/dist/util/ip-allowlist.js +139 -0
- package/dist/util/ip-allowlist.js.map +1 -0
- package/dist/util/normalize-options.d.ts +24 -0
- package/dist/util/normalize-options.d.ts.map +1 -0
- package/dist/util/normalize-options.js +189 -0
- package/dist/util/normalize-options.js.map +1 -0
- package/dist/util/url-template.d.ts +18 -0
- package/dist/util/url-template.d.ts.map +1 -0
- package/dist/util/url-template.js +22 -0
- package/dist/util/url-template.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* currency.ts — ISO 4217 alpha ↔ numeric conversion helpers.
|
|
3
|
+
*
|
|
4
|
+
* Viva's API requires numeric currency codes (e.g. 826 for GBP).
|
|
5
|
+
* Vendure stores alpha codes (e.g. 'GBP').
|
|
6
|
+
*
|
|
7
|
+
* @see references/viva-docs/md/wh-transaction-payment-created.txt:487
|
|
8
|
+
* @see docs/plans/vendure-plugin-v0.md §"Constraints" (Currency: Numeric ISO 4217)
|
|
9
|
+
*/
|
|
10
|
+
import { asCurrencyCode } from '@sakeetech/viva-payments-core/types';
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Lookup tables
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
/**
|
|
15
|
+
* ISO 4217 alpha → numeric mapping.
|
|
16
|
+
* Extend as new currencies are onboarded.
|
|
17
|
+
*/
|
|
18
|
+
const ALPHA_TO_NUMERIC = {
|
|
19
|
+
EUR: '978',
|
|
20
|
+
GBP: '826',
|
|
21
|
+
USD: '840',
|
|
22
|
+
PLN: '985',
|
|
23
|
+
RON: '946',
|
|
24
|
+
CZK: '203',
|
|
25
|
+
HUF: '348',
|
|
26
|
+
BGN: '975',
|
|
27
|
+
DKK: '208',
|
|
28
|
+
SEK: '752',
|
|
29
|
+
NOK: '578',
|
|
30
|
+
CHF: '756',
|
|
31
|
+
AUD: '036',
|
|
32
|
+
CAD: '124',
|
|
33
|
+
JPY: '392',
|
|
34
|
+
SGD: '702',
|
|
35
|
+
HKD: '344',
|
|
36
|
+
NZD: '554',
|
|
37
|
+
MXN: '484',
|
|
38
|
+
BRL: '986',
|
|
39
|
+
ZAR: '710',
|
|
40
|
+
TRY: '949',
|
|
41
|
+
ILS: '376',
|
|
42
|
+
AED: '784',
|
|
43
|
+
THB: '764',
|
|
44
|
+
};
|
|
45
|
+
const NUMERIC_TO_ALPHA = Object.fromEntries(Object.entries(ALPHA_TO_NUMERIC).map(([alpha, numeric]) => [numeric, alpha]));
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Converters
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
/**
|
|
50
|
+
* Convert ISO 4217 alpha code to branded numeric CurrencyCode.
|
|
51
|
+
* Throws if the alpha code is unknown.
|
|
52
|
+
*
|
|
53
|
+
* @example alphaToNumeric('GBP') // → '826' (branded CurrencyCode)
|
|
54
|
+
*/
|
|
55
|
+
export function alphaToNumeric(alpha) {
|
|
56
|
+
const upper = alpha.toUpperCase();
|
|
57
|
+
const numeric = ALPHA_TO_NUMERIC[upper];
|
|
58
|
+
if (!numeric) {
|
|
59
|
+
throw new Error(`Unknown ISO 4217 alpha currency code: "${alpha}"`);
|
|
60
|
+
}
|
|
61
|
+
return asCurrencyCode(numeric);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Convert ISO 4217 numeric string to alpha code.
|
|
65
|
+
* Coerces numeric input (string or number) defensively.
|
|
66
|
+
* Throws if the numeric code is unknown.
|
|
67
|
+
*
|
|
68
|
+
* @example numericToAlpha('826') // → 'GBP'
|
|
69
|
+
* @example numericToAlpha(826) // → 'GBP'
|
|
70
|
+
*/
|
|
71
|
+
export function numericToAlpha(numeric) {
|
|
72
|
+
const key = String(numeric).padStart(3, '0');
|
|
73
|
+
const alpha = NUMERIC_TO_ALPHA[key];
|
|
74
|
+
if (!alpha) {
|
|
75
|
+
throw new Error(`Unknown ISO 4217 numeric currency code: "${numeric}"`);
|
|
76
|
+
}
|
|
77
|
+
return alpha;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Coerce a webhook payload currency value (may be string or number) to the
|
|
81
|
+
* branded CurrencyCode type. Throws on unknown code.
|
|
82
|
+
*/
|
|
83
|
+
export function coerceCurrencyCode(raw) {
|
|
84
|
+
const key = String(raw).padStart(3, '0');
|
|
85
|
+
if (!NUMERIC_TO_ALPHA[key]) {
|
|
86
|
+
throw new Error(`Unknown ISO 4217 numeric currency code: "${raw}"`);
|
|
87
|
+
}
|
|
88
|
+
return asCurrencyCode(key);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=currency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currency.js","sourceRoot":"","sources":["../../src/util/currency.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAGrE,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,gBAAgB,GAAqC;IACzD,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;CACX,CAAC;AAEF,MAAM,gBAAgB,GAAqC,MAAM,CAAC,WAAW,CAC3E,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAC7E,CAAC;AAEF,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,0CAA0C,KAAK,GAAG,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,OAAwB;IACrD,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,4CAA4C,OAAO,GAAG,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAoB;IACrD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,4CAA4C,GAAG,GAAG,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,cAAc,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* error-envelope.ts — VivaPluginError class for all plugin-level errors.
|
|
3
|
+
*
|
|
4
|
+
* One error class with static factories per error code.
|
|
5
|
+
* Mirrors the §"Error Contract" section of docs/plans/vendure-plugin-v0.md.
|
|
6
|
+
*/
|
|
7
|
+
export type VivaErrorCode = 'VIVA_AUTH_DOWN' | 'VIVA_API_ERROR' | 'VIVA_ACCOUNT_NOT_VERIFIED' | 'VIVA_ISV_AMOUNT_TOO_HIGH' | 'VIVA_CHANNEL_MISCONFIGURED' | 'VIVA_ORDER_NOT_FOUND' | 'VIVA_AMOUNT_MISMATCH' | 'VIVA_REFUND_REJECTED' | 'VIVA_FAST_REFUND_INELIGIBLE' | 'VIVA_MODE_MISMATCH' | 'VIVA_PAYMENT_ALREADY_SETTLED' | 'VIVA_PAYMENT_NOT_CANCELLABLE' | 'VIVA_ALREADY_ONBOARDED' | 'VIVA_RESELLER_CREDENTIALS_MISSING' | 'VIVA_SOURCE_CREATION_FAILED' | 'VIVA_INTERNAL_ERROR';
|
|
8
|
+
export interface VivaPluginErrorOptions {
|
|
9
|
+
code: VivaErrorCode;
|
|
10
|
+
message: string;
|
|
11
|
+
retryable: boolean;
|
|
12
|
+
vivaErrorCode?: number;
|
|
13
|
+
vivaErrorMessage?: string;
|
|
14
|
+
cause?: unknown;
|
|
15
|
+
}
|
|
16
|
+
export declare class VivaPluginError extends Error {
|
|
17
|
+
readonly code: VivaErrorCode;
|
|
18
|
+
readonly retryable: boolean;
|
|
19
|
+
readonly vivaErrorCode: number | undefined;
|
|
20
|
+
readonly vivaErrorMessage: string | undefined;
|
|
21
|
+
readonly cause: unknown;
|
|
22
|
+
constructor(opts: VivaPluginErrorOptions);
|
|
23
|
+
toJSON(): Record<string, unknown>;
|
|
24
|
+
static authDown(message: string, cause?: unknown): VivaPluginError;
|
|
25
|
+
static apiError(opts: {
|
|
26
|
+
message: string;
|
|
27
|
+
vivaErrorCode?: number;
|
|
28
|
+
vivaErrorMessage?: string;
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
}): VivaPluginError;
|
|
31
|
+
static accountNotVerified(message?: string): VivaPluginError;
|
|
32
|
+
static isvAmountTooHigh(isvAmount: number, amount: number): VivaPluginError;
|
|
33
|
+
static channelMisconfigured(message?: string): VivaPluginError;
|
|
34
|
+
static orderNotFound(message?: string): VivaPluginError;
|
|
35
|
+
static amountMismatch(expected: bigint | number, actual: bigint | number): VivaPluginError;
|
|
36
|
+
static refundRejected(message: string, cause?: unknown): VivaPluginError;
|
|
37
|
+
static fastRefundIneligible(message?: string, cause?: unknown): VivaPluginError;
|
|
38
|
+
static modeMismatch(message: string, cause?: unknown): VivaPluginError;
|
|
39
|
+
static paymentAlreadySettled(): VivaPluginError;
|
|
40
|
+
static paymentNotCancellable(reason?: string): VivaPluginError;
|
|
41
|
+
static alreadyOnboarded(accountId: string): VivaPluginError;
|
|
42
|
+
static resellerCredentialsMissing(message?: string): VivaPluginError;
|
|
43
|
+
static sourceCreationFailed(opts: {
|
|
44
|
+
message: string;
|
|
45
|
+
vivaStatus?: number;
|
|
46
|
+
vivaErrorCode?: number;
|
|
47
|
+
cause?: unknown;
|
|
48
|
+
}): VivaPluginError;
|
|
49
|
+
static internalError(message: string, cause?: unknown): VivaPluginError;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=error-envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-envelope.d.ts","sourceRoot":"","sources":["../../src/util/error-envelope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* error-envelope.ts — VivaPluginError class for all plugin-level errors.
|
|
3
|
+
*
|
|
4
|
+
* One error class with static factories per error code.
|
|
5
|
+
* Mirrors the §"Error Contract" section of docs/plans/vendure-plugin-v0.md.
|
|
6
|
+
*/
|
|
7
|
+
export class VivaPluginError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
retryable;
|
|
10
|
+
vivaErrorCode;
|
|
11
|
+
vivaErrorMessage;
|
|
12
|
+
cause;
|
|
13
|
+
constructor(opts) {
|
|
14
|
+
super(opts.message);
|
|
15
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
16
|
+
this.name = 'VivaPluginError';
|
|
17
|
+
this.code = opts.code;
|
|
18
|
+
this.retryable = opts.retryable;
|
|
19
|
+
this.vivaErrorCode = opts.vivaErrorCode;
|
|
20
|
+
this.vivaErrorMessage = opts.vivaErrorMessage;
|
|
21
|
+
this.cause = opts.cause;
|
|
22
|
+
if (Error.captureStackTrace) {
|
|
23
|
+
Error.captureStackTrace(this, new.target);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
toJSON() {
|
|
27
|
+
return {
|
|
28
|
+
code: this.code,
|
|
29
|
+
message: this.message,
|
|
30
|
+
retryable: this.retryable,
|
|
31
|
+
...(this.vivaErrorCode !== undefined ? { vivaErrorCode: this.vivaErrorCode } : {}),
|
|
32
|
+
...(this.vivaErrorMessage !== undefined ? { vivaErrorMessage: this.vivaErrorMessage } : {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Static factories
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
static authDown(message, cause) {
|
|
39
|
+
return new VivaPluginError({ code: 'VIVA_AUTH_DOWN', message, retryable: true, cause });
|
|
40
|
+
}
|
|
41
|
+
static apiError(opts) {
|
|
42
|
+
const o = {
|
|
43
|
+
code: 'VIVA_API_ERROR',
|
|
44
|
+
message: opts.message,
|
|
45
|
+
retryable: false,
|
|
46
|
+
};
|
|
47
|
+
if (opts.vivaErrorCode !== undefined)
|
|
48
|
+
o.vivaErrorCode = opts.vivaErrorCode;
|
|
49
|
+
if (opts.vivaErrorMessage !== undefined)
|
|
50
|
+
o.vivaErrorMessage = opts.vivaErrorMessage;
|
|
51
|
+
if (opts.cause !== undefined)
|
|
52
|
+
o.cause = opts.cause;
|
|
53
|
+
return new VivaPluginError(o);
|
|
54
|
+
}
|
|
55
|
+
static accountNotVerified(message) {
|
|
56
|
+
return new VivaPluginError({
|
|
57
|
+
code: 'VIVA_ACCOUNT_NOT_VERIFIED',
|
|
58
|
+
message: message ?? 'Viva payouts not enabled for this channel. Complete merchant verification first.',
|
|
59
|
+
retryable: false,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
static isvAmountTooHigh(isvAmount, amount) {
|
|
63
|
+
return new VivaPluginError({
|
|
64
|
+
code: 'VIVA_ISV_AMOUNT_TOO_HIGH',
|
|
65
|
+
message: `ISV amount (${isvAmount}) must be strictly less than order amount (${amount}).`,
|
|
66
|
+
retryable: false,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
static channelMisconfigured(message) {
|
|
70
|
+
return new VivaPluginError({
|
|
71
|
+
code: 'VIVA_CHANNEL_MISCONFIGURED',
|
|
72
|
+
message: message ?? 'Channel is missing vivaMerchantId configuration.',
|
|
73
|
+
retryable: false,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
static orderNotFound(message) {
|
|
77
|
+
return new VivaPluginError({
|
|
78
|
+
code: 'VIVA_ORDER_NOT_FOUND',
|
|
79
|
+
message: message ?? 'Viva order or transaction not found.',
|
|
80
|
+
retryable: false,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
static amountMismatch(expected, actual) {
|
|
84
|
+
return new VivaPluginError({
|
|
85
|
+
code: 'VIVA_AMOUNT_MISMATCH',
|
|
86
|
+
message: `Amount mismatch: expected ${expected}, got ${actual} from Viva.`,
|
|
87
|
+
retryable: false,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
static refundRejected(message, cause) {
|
|
91
|
+
return new VivaPluginError({ code: 'VIVA_REFUND_REJECTED', message, retryable: false, cause });
|
|
92
|
+
}
|
|
93
|
+
static fastRefundIneligible(message, cause) {
|
|
94
|
+
return new VivaPluginError({
|
|
95
|
+
code: 'VIVA_FAST_REFUND_INELIGIBLE',
|
|
96
|
+
message: message ??
|
|
97
|
+
"Fast Refund ineligible (HTTP 403). Configured strategy 'fast' does not fall back. " +
|
|
98
|
+
"Set refundStrategy='auto' to enable automatic Standard-refund fallback.",
|
|
99
|
+
retryable: false,
|
|
100
|
+
cause,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
static modeMismatch(message, cause) {
|
|
104
|
+
return new VivaPluginError({
|
|
105
|
+
code: 'VIVA_MODE_MISMATCH',
|
|
106
|
+
message,
|
|
107
|
+
retryable: false,
|
|
108
|
+
cause,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
static paymentAlreadySettled() {
|
|
112
|
+
return new VivaPluginError({
|
|
113
|
+
code: 'VIVA_PAYMENT_ALREADY_SETTLED',
|
|
114
|
+
message: 'Payment is already settled.',
|
|
115
|
+
retryable: false,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
static paymentNotCancellable(reason) {
|
|
119
|
+
return new VivaPluginError({
|
|
120
|
+
code: 'VIVA_PAYMENT_NOT_CANCELLABLE',
|
|
121
|
+
message: reason ?? 'Payment cannot be cancelled in its current state.',
|
|
122
|
+
retryable: false,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
static alreadyOnboarded(accountId) {
|
|
126
|
+
return new VivaPluginError({
|
|
127
|
+
code: 'VIVA_ALREADY_ONBOARDED',
|
|
128
|
+
message: `Channel already has accountId ${accountId}. Use the reconcile endpoint to refresh.`,
|
|
129
|
+
retryable: false,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
static resellerCredentialsMissing(message) {
|
|
133
|
+
return new VivaPluginError({
|
|
134
|
+
code: 'VIVA_RESELLER_CREDENTIALS_MISSING',
|
|
135
|
+
message: message ??
|
|
136
|
+
'POST /viva/admin/connected-accounts/:id/sources requires reseller credentials. ' +
|
|
137
|
+
'Set options.reseller = { resellerId, merchantId, resellerApiKey } in VivaPaymentPlugin.init().',
|
|
138
|
+
retryable: false,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
static sourceCreationFailed(opts) {
|
|
142
|
+
const o = {
|
|
143
|
+
code: 'VIVA_SOURCE_CREATION_FAILED',
|
|
144
|
+
message: opts.message,
|
|
145
|
+
retryable: false,
|
|
146
|
+
};
|
|
147
|
+
if (opts.vivaErrorCode !== undefined)
|
|
148
|
+
o.vivaErrorCode = opts.vivaErrorCode;
|
|
149
|
+
if (opts.cause !== undefined)
|
|
150
|
+
o.cause = opts.cause;
|
|
151
|
+
return new VivaPluginError(o);
|
|
152
|
+
}
|
|
153
|
+
static internalError(message, cause) {
|
|
154
|
+
return new VivaPluginError({ code: 'VIVA_INTERNAL_ERROR', message, retryable: false, cause });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=error-envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-envelope.js","sourceRoot":"","sources":["../../src/util/error-envelope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* util/ip-allowlist.ts — IP allowlist helpers for the webhook receiver.
|
|
3
|
+
*
|
|
4
|
+
* Viva Wallet does not sign webhook bodies (no HMAC). Instead it publishes
|
|
5
|
+
* a set of source IPs/CIDRs that all webhook POSTs will originate from.
|
|
6
|
+
* We reject anything outside the allowlist.
|
|
7
|
+
*
|
|
8
|
+
* Source: references/viva-docs/md/webhooks-for-payments.txt lines 272–301.
|
|
9
|
+
*
|
|
10
|
+
* @see docs/plans/vendure-plugin-v0.md §D16
|
|
11
|
+
* @see docs/VENDURE-CONTRACT.MD §4 "Webhook receiver"
|
|
12
|
+
*/
|
|
13
|
+
import type { IncomingMessage } from 'node:http';
|
|
14
|
+
/**
|
|
15
|
+
* Default allowlist = demo + production combined.
|
|
16
|
+
* A single deployment receives both (environment flag is for outbound calls only).
|
|
17
|
+
*/
|
|
18
|
+
export declare const DEFAULT_VIVA_IP_ALLOWLIST: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Determine whether the given source IP is permitted by the allowlist.
|
|
21
|
+
*
|
|
22
|
+
* Allowlist behaviour:
|
|
23
|
+
* - `false` → allowlist disabled (dev/test convenience); always returns true.
|
|
24
|
+
* - `[]` → empty list; behaves the same as disabled (always true) so that
|
|
25
|
+
* accidentally passing an empty array doesn't lock out every call.
|
|
26
|
+
* - `undefined` → use Viva's published demo + production CIDRs.
|
|
27
|
+
* - `string[]` → custom list; enforce it.
|
|
28
|
+
*
|
|
29
|
+
* Each entry may be a single IPv4 address or an IPv4 CIDR range.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isIpAllowed(sourceIp: string, allowlist: string[] | false | undefined): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Extract the source IP from an incoming request.
|
|
34
|
+
*
|
|
35
|
+
* Delegates to the core `extractClientIp` helper which walks `X-Forwarded-For`
|
|
36
|
+
* from the **rightmost** end, counting back `trustedProxyDepth` hops. The old
|
|
37
|
+
* "take leftmost X-F-F" behaviour was vulnerable to header spoofing (CSO
|
|
38
|
+
* Finding #2); see `docs/TODO-CSO.md`.
|
|
39
|
+
*
|
|
40
|
+
* @param trustedProxyDepth — number of trailing X-F-F hops set by trusted
|
|
41
|
+
* proxies. 0 = ignore X-F-F (use socket); 1 = one reverse proxy; 2 = CDN+LB.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getSourceIp(req: IncomingMessage, trustedProxyDepth?: number): string;
|
|
44
|
+
//# sourceMappingURL=ip-allowlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip-allowlist.d.ts","sourceRoot":"","sources":["../../src/util/ip-allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAqCjD;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EAAE,MAAM,EAG7C,CAAC;AAyDF;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EAAE,GAAG,KAAK,GAAG,SAAS,GACtC,OAAO,CAYT;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,eAAe,EAAE,iBAAiB,SAAI,GAAG,MAAM,CAE/E"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* util/ip-allowlist.ts — IP allowlist helpers for the webhook receiver.
|
|
3
|
+
*
|
|
4
|
+
* Viva Wallet does not sign webhook bodies (no HMAC). Instead it publishes
|
|
5
|
+
* a set of source IPs/CIDRs that all webhook POSTs will originate from.
|
|
6
|
+
* We reject anything outside the allowlist.
|
|
7
|
+
*
|
|
8
|
+
* Source: references/viva-docs/md/webhooks-for-payments.txt lines 272–301.
|
|
9
|
+
*
|
|
10
|
+
* @see docs/plans/vendure-plugin-v0.md §D16
|
|
11
|
+
* @see docs/VENDURE-CONTRACT.MD §4 "Webhook receiver"
|
|
12
|
+
*/
|
|
13
|
+
import { extractClientIp } from '@sakeetech/viva-payments-core/webhooks';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Viva's published CIDRs (webhooks-for-payments.txt lines 274–301)
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* Production IP allowlist.
|
|
19
|
+
* Source: webhooks-for-payments.txt lines 276–287.
|
|
20
|
+
*/
|
|
21
|
+
const VIVA_PRODUCTION_IPS = [
|
|
22
|
+
'51.138.37.238',
|
|
23
|
+
'13.80.70.181',
|
|
24
|
+
'13.80.71.223',
|
|
25
|
+
'13.79.28.70',
|
|
26
|
+
'40.127.253.112/28',
|
|
27
|
+
'51.105.129.192/28',
|
|
28
|
+
'20.54.89.16',
|
|
29
|
+
'4.223.76.50',
|
|
30
|
+
'51.12.157.0/28',
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Demo IP allowlist.
|
|
34
|
+
* Source: webhooks-for-payments.txt lines 291–301.
|
|
35
|
+
*/
|
|
36
|
+
const VIVA_DEMO_IPS = [
|
|
37
|
+
'20.50.240.57',
|
|
38
|
+
'40.74.20.78',
|
|
39
|
+
'195.167.87.181',
|
|
40
|
+
'195.167.87.180',
|
|
41
|
+
'20.13.195.185',
|
|
42
|
+
'135.225.16.50',
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Default allowlist = demo + production combined.
|
|
46
|
+
* A single deployment receives both (environment flag is for outbound calls only).
|
|
47
|
+
*/
|
|
48
|
+
export const DEFAULT_VIVA_IP_ALLOWLIST = [
|
|
49
|
+
...VIVA_PRODUCTION_IPS,
|
|
50
|
+
...VIVA_DEMO_IPS,
|
|
51
|
+
];
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// CIDR / IP parsing helpers (zero-dep)
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
/**
|
|
56
|
+
* Parse an IPv4 address string into a 32-bit unsigned integer.
|
|
57
|
+
* Returns NaN on malformed input.
|
|
58
|
+
*/
|
|
59
|
+
function ipv4ToInt(ip) {
|
|
60
|
+
const parts = ip.trim().split('.');
|
|
61
|
+
if (parts.length !== 4)
|
|
62
|
+
return NaN;
|
|
63
|
+
let result = 0;
|
|
64
|
+
for (const part of parts) {
|
|
65
|
+
const n = Number(part);
|
|
66
|
+
if (!Number.isInteger(n) || n < 0 || n > 255)
|
|
67
|
+
return NaN;
|
|
68
|
+
result = (result << 8) | n;
|
|
69
|
+
}
|
|
70
|
+
// Force unsigned 32-bit.
|
|
71
|
+
return result >>> 0;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check whether `ip` falls within the CIDR block `cidr`.
|
|
75
|
+
* Handles both single-host notation (`"1.2.3.4"`) and range notation
|
|
76
|
+
* (`"1.2.3.0/24"`).
|
|
77
|
+
*/
|
|
78
|
+
function isInCidr(ip, cidr) {
|
|
79
|
+
const slashIdx = cidr.indexOf('/');
|
|
80
|
+
if (slashIdx === -1) {
|
|
81
|
+
// Single IP comparison.
|
|
82
|
+
return ip.trim() === cidr.trim();
|
|
83
|
+
}
|
|
84
|
+
const networkIp = cidr.slice(0, slashIdx);
|
|
85
|
+
const prefixLen = Number(cidr.slice(slashIdx + 1));
|
|
86
|
+
if (!Number.isInteger(prefixLen) || prefixLen < 0 || prefixLen > 32)
|
|
87
|
+
return false;
|
|
88
|
+
const ipInt = ipv4ToInt(ip);
|
|
89
|
+
const netInt = ipv4ToInt(networkIp);
|
|
90
|
+
if (isNaN(ipInt) || isNaN(netInt))
|
|
91
|
+
return false;
|
|
92
|
+
if (prefixLen === 0)
|
|
93
|
+
return true; // 0.0.0.0/0 = all
|
|
94
|
+
const mask = prefixLen === 32 ? 0xffffffff : ~((1 << (32 - prefixLen)) - 1);
|
|
95
|
+
const maskU = mask >>> 0;
|
|
96
|
+
return (ipInt & maskU) === (netInt & maskU);
|
|
97
|
+
}
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Public API
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
/**
|
|
102
|
+
* Determine whether the given source IP is permitted by the allowlist.
|
|
103
|
+
*
|
|
104
|
+
* Allowlist behaviour:
|
|
105
|
+
* - `false` → allowlist disabled (dev/test convenience); always returns true.
|
|
106
|
+
* - `[]` → empty list; behaves the same as disabled (always true) so that
|
|
107
|
+
* accidentally passing an empty array doesn't lock out every call.
|
|
108
|
+
* - `undefined` → use Viva's published demo + production CIDRs.
|
|
109
|
+
* - `string[]` → custom list; enforce it.
|
|
110
|
+
*
|
|
111
|
+
* Each entry may be a single IPv4 address or an IPv4 CIDR range.
|
|
112
|
+
*/
|
|
113
|
+
export function isIpAllowed(sourceIp, allowlist) {
|
|
114
|
+
// Disabled check — dev/test bypass.
|
|
115
|
+
if (allowlist === false || (Array.isArray(allowlist) && allowlist.length === 0)) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
const list = allowlist ?? DEFAULT_VIVA_IP_ALLOWLIST;
|
|
119
|
+
for (const entry of list) {
|
|
120
|
+
if (isInCidr(sourceIp, entry))
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Extract the source IP from an incoming request.
|
|
127
|
+
*
|
|
128
|
+
* Delegates to the core `extractClientIp` helper which walks `X-Forwarded-For`
|
|
129
|
+
* from the **rightmost** end, counting back `trustedProxyDepth` hops. The old
|
|
130
|
+
* "take leftmost X-F-F" behaviour was vulnerable to header spoofing (CSO
|
|
131
|
+
* Finding #2); see `docs/TODO-CSO.md`.
|
|
132
|
+
*
|
|
133
|
+
* @param trustedProxyDepth — number of trailing X-F-F hops set by trusted
|
|
134
|
+
* proxies. 0 = ignore X-F-F (use socket); 1 = one reverse proxy; 2 = CDN+LB.
|
|
135
|
+
*/
|
|
136
|
+
export function getSourceIp(req, trustedProxyDepth = 0) {
|
|
137
|
+
return extractClientIp(req, trustedProxyDepth);
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=ip-allowlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip-allowlist.js","sourceRoot":"","sources":["../../src/util/ip-allowlist.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* util/normalize-options.ts — input validation + back-compat handling for
|
|
3
|
+
* `VivaPaymentPlugin.init()`.
|
|
4
|
+
*
|
|
5
|
+
* Takes the loose `VivaPaymentPluginInitInput` shape (which accepts deprecated
|
|
6
|
+
* field aliases and an optional `mode`) and returns the strict discriminated
|
|
7
|
+
* `VivaPaymentPluginOptions` union.
|
|
8
|
+
*
|
|
9
|
+
* Responsibilities:
|
|
10
|
+
* 1. Resolve deprecated `isvClientId`/`isvClientSecret` → `clientId`/`clientSecret`,
|
|
11
|
+
* with one-time deprecation warnings.
|
|
12
|
+
* 2. Resolve `mode`: default to `'merchant'` (with a warning), or auto-detect
|
|
13
|
+
* `'isv'` when ISV-only fields are present (no warning — strong hint).
|
|
14
|
+
* 3. Warn (don't throw) when merchant mode is paired with ISV-only resolvers.
|
|
15
|
+
* 4. Throw a single aggregated error if required fields are missing.
|
|
16
|
+
*
|
|
17
|
+
* Mirrors `medusa-payment-viva/src/config.ts` defaulting + back-compat behaviour
|
|
18
|
+
* (multi-mode-v0 plan §5, §10).
|
|
19
|
+
*/
|
|
20
|
+
import type { VivaPaymentPluginInitInput, VivaPaymentPluginOptions } from '../types.js';
|
|
21
|
+
/** @internal Test-only: reset all one-time warning latches. */
|
|
22
|
+
export declare function _resetInitNoticesForTesting(): void;
|
|
23
|
+
export declare function normalizePluginOptions(input: VivaPaymentPluginInitInput): VivaPaymentPluginOptions;
|
|
24
|
+
//# sourceMappingURL=normalize-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-options.d.ts","sourceRoot":"","sources":["../../src/util/normalize-options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAIV,0BAA0B,EAC1B,wBAAwB,EACzB,MAAM,aAAa,CAAC;AAWrB,+DAA+D;AAC/D,wBAAgB,2BAA2B,IAAI,IAAI,CAKlD;AAqBD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,0BAA0B,GAChC,wBAAwB,CAgK1B"}
|