@vivoa/partner-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +162 -0
  3. package/dist/cjs/adapters/fetch-transport.d.ts +7 -0
  4. package/dist/cjs/adapters/fetch-transport.js +34 -0
  5. package/dist/cjs/adapters/in-memory/fake-vivoa.d.ts +93 -0
  6. package/dist/cjs/adapters/in-memory/fake-vivoa.js +468 -0
  7. package/dist/cjs/adapters/manual-clock.d.ts +9 -0
  8. package/dist/cjs/adapters/manual-clock.js +20 -0
  9. package/dist/cjs/adapters/node-crypto.d.ts +2 -0
  10. package/dist/cjs/adapters/node-crypto.js +13 -0
  11. package/dist/cjs/adapters/system-clock.d.ts +2 -0
  12. package/dist/cjs/adapters/system-clock.js +7 -0
  13. package/dist/cjs/application/partner-client.d.ts +45 -0
  14. package/dist/cjs/application/partner-client.js +55 -0
  15. package/dist/cjs/application/resources/fleet.d.ts +7 -0
  16. package/dist/cjs/application/resources/fleet.js +13 -0
  17. package/dist/cjs/application/resources/me.d.ts +13 -0
  18. package/dist/cjs/application/resources/me.js +23 -0
  19. package/dist/cjs/application/resources/merchants.d.ts +37 -0
  20. package/dist/cjs/application/resources/merchants.js +92 -0
  21. package/dist/cjs/application/resources/webhooks.d.ts +16 -0
  22. package/dist/cjs/application/resources/webhooks.js +30 -0
  23. package/dist/cjs/application/signed-http.d.ts +41 -0
  24. package/dist/cjs/application/signed-http.js +105 -0
  25. package/dist/cjs/application/use-cases/provision-store.d.ts +50 -0
  26. package/dist/cjs/application/use-cases/provision-store.js +103 -0
  27. package/dist/cjs/domain/errors.d.ts +72 -0
  28. package/dist/cjs/domain/errors.js +111 -0
  29. package/dist/cjs/domain/merchant.d.ts +74 -0
  30. package/dist/cjs/domain/merchant.js +28 -0
  31. package/dist/cjs/domain/operator.d.ts +32 -0
  32. package/dist/cjs/domain/operator.js +6 -0
  33. package/dist/cjs/domain/signature.d.ts +21 -0
  34. package/dist/cjs/domain/signature.js +23 -0
  35. package/dist/cjs/domain/webhook.d.ts +48 -0
  36. package/dist/cjs/domain/webhook.js +10 -0
  37. package/dist/cjs/index.d.ts +25 -0
  38. package/dist/cjs/index.js +61 -0
  39. package/dist/cjs/package.json +1 -0
  40. package/dist/cjs/ports/clock.port.d.ts +5 -0
  41. package/dist/cjs/ports/clock.port.js +2 -0
  42. package/dist/cjs/ports/crypto.port.d.ts +7 -0
  43. package/dist/cjs/ports/crypto.port.js +2 -0
  44. package/dist/cjs/ports/http-transport.port.d.ts +25 -0
  45. package/dist/cjs/ports/http-transport.port.js +2 -0
  46. package/dist/cjs/ports/index.d.ts +5 -0
  47. package/dist/cjs/ports/index.js +5 -0
  48. package/dist/cjs/ports/logger.port.d.ts +6 -0
  49. package/dist/cjs/ports/logger.port.js +7 -0
  50. package/dist/cjs/testing.d.ts +12 -0
  51. package/dist/cjs/testing.js +16 -0
  52. package/dist/cjs/webhooks/verify-webhook.d.ts +21 -0
  53. package/dist/cjs/webhooks/verify-webhook.js +36 -0
  54. package/dist/esm/adapters/fetch-transport.d.ts +7 -0
  55. package/dist/esm/adapters/fetch-transport.js +30 -0
  56. package/dist/esm/adapters/in-memory/fake-vivoa.d.ts +93 -0
  57. package/dist/esm/adapters/in-memory/fake-vivoa.js +464 -0
  58. package/dist/esm/adapters/manual-clock.d.ts +9 -0
  59. package/dist/esm/adapters/manual-clock.js +16 -0
  60. package/dist/esm/adapters/node-crypto.d.ts +2 -0
  61. package/dist/esm/adapters/node-crypto.js +10 -0
  62. package/dist/esm/adapters/system-clock.d.ts +2 -0
  63. package/dist/esm/adapters/system-clock.js +4 -0
  64. package/dist/esm/application/partner-client.d.ts +45 -0
  65. package/dist/esm/application/partner-client.js +51 -0
  66. package/dist/esm/application/resources/fleet.d.ts +7 -0
  67. package/dist/esm/application/resources/fleet.js +9 -0
  68. package/dist/esm/application/resources/me.d.ts +13 -0
  69. package/dist/esm/application/resources/me.js +19 -0
  70. package/dist/esm/application/resources/merchants.d.ts +37 -0
  71. package/dist/esm/application/resources/merchants.js +87 -0
  72. package/dist/esm/application/resources/webhooks.d.ts +16 -0
  73. package/dist/esm/application/resources/webhooks.js +26 -0
  74. package/dist/esm/application/signed-http.d.ts +41 -0
  75. package/dist/esm/application/signed-http.js +101 -0
  76. package/dist/esm/application/use-cases/provision-store.d.ts +50 -0
  77. package/dist/esm/application/use-cases/provision-store.js +99 -0
  78. package/dist/esm/cli.d.ts +2 -0
  79. package/dist/esm/cli.js +106 -0
  80. package/dist/esm/domain/errors.d.ts +72 -0
  81. package/dist/esm/domain/errors.js +92 -0
  82. package/dist/esm/domain/merchant.d.ts +74 -0
  83. package/dist/esm/domain/merchant.js +23 -0
  84. package/dist/esm/domain/operator.d.ts +32 -0
  85. package/dist/esm/domain/operator.js +3 -0
  86. package/dist/esm/domain/signature.d.ts +21 -0
  87. package/dist/esm/domain/signature.js +19 -0
  88. package/dist/esm/domain/webhook.d.ts +48 -0
  89. package/dist/esm/domain/webhook.js +7 -0
  90. package/dist/esm/index.d.ts +25 -0
  91. package/dist/esm/index.js +33 -0
  92. package/dist/esm/ports/clock.port.d.ts +5 -0
  93. package/dist/esm/ports/clock.port.js +1 -0
  94. package/dist/esm/ports/crypto.port.d.ts +7 -0
  95. package/dist/esm/ports/crypto.port.js +1 -0
  96. package/dist/esm/ports/http-transport.port.d.ts +25 -0
  97. package/dist/esm/ports/http-transport.port.js +1 -0
  98. package/dist/esm/ports/index.d.ts +5 -0
  99. package/dist/esm/ports/index.js +1 -0
  100. package/dist/esm/ports/logger.port.d.ts +6 -0
  101. package/dist/esm/ports/logger.port.js +4 -0
  102. package/dist/esm/testing.d.ts +12 -0
  103. package/dist/esm/testing.js +11 -0
  104. package/dist/esm/webhooks/verify-webhook.d.ts +21 -0
  105. package/dist/esm/webhooks/verify-webhook.js +33 -0
  106. package/package.json +56 -0
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VivoaWebhookSignatureError = exports.VivoaProvisioningFailedError = exports.VivoaServerError = exports.VivoaRateLimitError = exports.VivoaConflictError = exports.VivoaNotFoundError = exports.VivoaForbiddenError = exports.VivoaAuthenticationError = exports.VivoaBadRequestError = exports.VivoaApiError = exports.VivoaTimeoutError = exports.VivoaNetworkError = exports.VivoaValidationError = exports.VivoaError = void 0;
4
+ exports.messageFromBody = messageFromBody;
5
+ exports.apiErrorFor = apiErrorFor;
6
+ /**
7
+ * Typed error hierarchy. Every failed call surfaces as a `VivoaApiError`
8
+ * subclass keyed by HTTP status, so partners branch on `instanceof` instead of
9
+ * parsing strings.
10
+ */
11
+ class VivoaError extends Error {
12
+ constructor(message, options) {
13
+ super(message, options);
14
+ this.name = new.target.name;
15
+ }
16
+ }
17
+ exports.VivoaError = VivoaError;
18
+ /** Input rejected client-side before any request is sent. */
19
+ class VivoaValidationError extends VivoaError {
20
+ }
21
+ exports.VivoaValidationError = VivoaValidationError;
22
+ /** The request never produced an HTTP response (DNS, reset, timeout). */
23
+ class VivoaNetworkError extends VivoaError {
24
+ }
25
+ exports.VivoaNetworkError = VivoaNetworkError;
26
+ /** A long-running wait (e.g. `waitUntilSettled`) ran out of time. */
27
+ class VivoaTimeoutError extends VivoaError {
28
+ }
29
+ exports.VivoaTimeoutError = VivoaTimeoutError;
30
+ class VivoaApiError extends VivoaError {
31
+ status;
32
+ method;
33
+ path;
34
+ body;
35
+ constructor(params) {
36
+ super(`${params.method} ${params.path} → ${params.status}: ${params.message}`);
37
+ this.status = params.status;
38
+ this.method = params.method;
39
+ this.path = params.path;
40
+ this.body = params.body;
41
+ }
42
+ }
43
+ exports.VivoaApiError = VivoaApiError;
44
+ /** 400 — invalid payload or country outside allowedRegions. */
45
+ class VivoaBadRequestError extends VivoaApiError {
46
+ }
47
+ exports.VivoaBadRequestError = VivoaBadRequestError;
48
+ /** 401 — bad signature, unknown key or clock skew > 300 s. */
49
+ class VivoaAuthenticationError extends VivoaApiError {
50
+ }
51
+ exports.VivoaAuthenticationError = VivoaAuthenticationError;
52
+ /** 403 — operator not active or quota full. */
53
+ class VivoaForbiddenError extends VivoaApiError {
54
+ }
55
+ exports.VivoaForbiddenError = VivoaForbiddenError;
56
+ /** 404 — merchant does not exist or is not in your fleet. */
57
+ class VivoaNotFoundError extends VivoaApiError {
58
+ }
59
+ exports.VivoaNotFoundError = VivoaNotFoundError;
60
+ /** 409 — handle taken, or activation on a creating/live/suspended merchant. */
61
+ class VivoaConflictError extends VivoaApiError {
62
+ }
63
+ exports.VivoaConflictError = VivoaConflictError;
64
+ /** 429 — requests-per-minute limit for the plan. */
65
+ class VivoaRateLimitError extends VivoaApiError {
66
+ }
67
+ exports.VivoaRateLimitError = VivoaRateLimitError;
68
+ /** 5xx. */
69
+ class VivoaServerError extends VivoaApiError {
70
+ }
71
+ exports.VivoaServerError = VivoaServerError;
72
+ const BY_STATUS = {
73
+ 400: VivoaBadRequestError,
74
+ 401: VivoaAuthenticationError,
75
+ 403: VivoaForbiddenError,
76
+ 404: VivoaNotFoundError,
77
+ 409: VivoaConflictError,
78
+ 429: VivoaRateLimitError,
79
+ };
80
+ /** Extracts NestJS's `{ message }` (string or validation array) from a body. */
81
+ function messageFromBody(body, fallback) {
82
+ if (body && typeof body === 'object' && 'message' in body) {
83
+ const m = body.message;
84
+ if (Array.isArray(m))
85
+ return m.join('; ');
86
+ if (typeof m === 'string')
87
+ return m;
88
+ }
89
+ if (typeof body === 'string' && body.length > 0)
90
+ return body;
91
+ return fallback;
92
+ }
93
+ function apiErrorFor(params) {
94
+ const Ctor = BY_STATUS[params.status] ?? (params.status >= 500 ? VivoaServerError : VivoaApiError);
95
+ return new Ctor({ ...params, message: messageFromBody(params.body, `HTTP ${params.status}`) });
96
+ }
97
+ /** Provisioning ended in `failed`. `activate` again (or `provisionStore`) to retry. */
98
+ class VivoaProvisioningFailedError extends VivoaError {
99
+ merchantId;
100
+ reason;
101
+ constructor(merchantId, reason, options) {
102
+ super(`Provisioning failed for merchant ${merchantId}: ${reason}`, options);
103
+ this.merchantId = merchantId;
104
+ this.reason = reason;
105
+ }
106
+ }
107
+ exports.VivoaProvisioningFailedError = VivoaProvisioningFailedError;
108
+ /** A webhook body whose `X-Vivoa-Signature` does not verify. Reject it (401/400). */
109
+ class VivoaWebhookSignatureError extends VivoaError {
110
+ }
111
+ exports.VivoaWebhookSignatureError = VivoaWebhookSignatureError;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Merchant vocabulary — mirrors `engine/partner-api/domain/merchant-views.ts`
3
+ * on the platform. A partner speaks in merchants, never in stores/deployments.
4
+ * Pure: no I/O, no Node APIs.
5
+ */
6
+ export declare const MERCHANT_RUNTIME_STATUSES: readonly ["not_activated", "creating", "live", "suspended", "failed", "unknown"];
7
+ /** Where a merchant's runtime is, from the partner's point of view. */
8
+ export type MerchantRuntimeStatus = (typeof MERCHANT_RUNTIME_STATUSES)[number];
9
+ /** A merchant as returned by `POST /merchants` and `GET /merchants`. */
10
+ export interface Merchant {
11
+ id: string;
12
+ name: string;
13
+ /** Assigned by the platform from `name` — never chosen by the caller. */
14
+ handle: string;
15
+ /** Commercial status (active | suspended | …). */
16
+ status: string;
17
+ runtime: MerchantRuntimeStatus;
18
+ countries: string[];
19
+ plan: string;
20
+ domain: string | null;
21
+ ownerEmail: string | null;
22
+ /** Your own reference for this merchant, if you gave one at creation. */
23
+ externalId: string | null;
24
+ /** ISO-8601. */
25
+ createdAt: string;
26
+ }
27
+ /** Snapshot returned by `GET /merchants/:id/status` and `activate`. */
28
+ export interface MerchantStatus {
29
+ storeId: string;
30
+ handle: string;
31
+ status: string;
32
+ deployStatus: MerchantRuntimeStatus;
33
+ domain: string | null;
34
+ apiBaseUrl: string | null;
35
+ }
36
+ export interface CreateMerchantInput {
37
+ /** Email of the store owner (receives their own merchant account). */
38
+ email: string;
39
+ name: string;
40
+ /** ISO-3166 codes; must be a subset of the operator's allowedRegions. */
41
+ countries: string[];
42
+ industry?: string;
43
+ /**
44
+ * Your own reference for this merchant (customer id, order id…). Optional
45
+ * here, but it is the idempotency key of `merchants.create()`: replaying the
46
+ * same value returns the merchant you already created instead of a second
47
+ * one. `provisionStore()` requires it for exactly that reason. Unique per
48
+ * operator, max 120 chars.
49
+ */
50
+ externalId?: string;
51
+ }
52
+ export interface ListMerchantsQuery {
53
+ page?: number;
54
+ /** Max 100. */
55
+ limit?: number;
56
+ /** Matches name or handle. */
57
+ search?: string;
58
+ /** Exact match on your own reference, passed at creation. */
59
+ externalId?: string;
60
+ }
61
+ export interface Page<T> {
62
+ data: T[];
63
+ meta: {
64
+ total: number;
65
+ page: number;
66
+ limit: number;
67
+ };
68
+ }
69
+ /** Max length the platform accepts for `CreateMerchantInput.externalId`. */
70
+ export declare const MAX_EXTERNAL_ID_LENGTH = 120;
71
+ /** Runtime states where the platform accepts `activate` (first run or retry). */
72
+ export declare function canActivate(runtime: MerchantRuntimeStatus): boolean;
73
+ /** States that will not change without a new action from the partner. */
74
+ export declare function isSettled(runtime: MerchantRuntimeStatus): boolean;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * Merchant vocabulary — mirrors `engine/partner-api/domain/merchant-views.ts`
4
+ * on the platform. A partner speaks in merchants, never in stores/deployments.
5
+ * Pure: no I/O, no Node APIs.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.MAX_EXTERNAL_ID_LENGTH = exports.MERCHANT_RUNTIME_STATUSES = void 0;
9
+ exports.canActivate = canActivate;
10
+ exports.isSettled = isSettled;
11
+ exports.MERCHANT_RUNTIME_STATUSES = [
12
+ 'not_activated', // created, `activate` never called
13
+ 'creating', // provisioning in flight
14
+ 'live', // serving traffic
15
+ 'suspended', // stopped by the operator / ops
16
+ 'failed', // last provisioning attempt failed — `activate` again to retry
17
+ 'unknown', // torn down / unexpected state
18
+ ];
19
+ /** Max length the platform accepts for `CreateMerchantInput.externalId`. */
20
+ exports.MAX_EXTERNAL_ID_LENGTH = 120;
21
+ /** Runtime states where the platform accepts `activate` (first run or retry). */
22
+ function canActivate(runtime) {
23
+ return runtime === 'not_activated' || runtime === 'failed';
24
+ }
25
+ /** States that will not change without a new action from the partner. */
26
+ function isSettled(runtime) {
27
+ return runtime !== 'creating';
28
+ }
@@ -0,0 +1,32 @@
1
+ export type OperatorStatus = 'active' | 'pending' | 'suspended';
2
+ /** `GET /me` — the partner's own profile and quota usage. */
3
+ export interface OperatorProfile {
4
+ id: string;
5
+ name: string;
6
+ status: OperatorStatus;
7
+ plan: string;
8
+ storesUsed: number;
9
+ maxStores: number;
10
+ allowedRegions: string[];
11
+ rateLimitPerMin: number;
12
+ }
13
+ export interface RotatedApiSecret {
14
+ apiSecret: string;
15
+ }
16
+ export interface FleetAnalytics {
17
+ operatorId: string;
18
+ stores: {
19
+ total: number;
20
+ active: number;
21
+ suspended: number;
22
+ byStatus: Record<string, number>;
23
+ };
24
+ revenue: {
25
+ grossVolume: number;
26
+ currency: string;
27
+ pendingAmount: number;
28
+ completedPayments: number;
29
+ avgTicket: number;
30
+ };
31
+ }
32
+ export declare function remainingQuota(profile: OperatorProfile): number;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.remainingQuota = remainingQuota;
4
+ function remainingQuota(profile) {
5
+ return Math.max(0, profile.maxStores - profile.storesUsed);
6
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Canonical request payload signed with the operator's apiSecret. Must match
3
+ * the platform verifier (`shared/crypto/management-hmac.ts`, v2 strict):
4
+ *
5
+ * `${timestamp}.${METHOD}.${pathWithoutQuery}.${sha256hex(rawBody)}`
6
+ */
7
+ export declare function canonicalRequestPayload(params: {
8
+ timestamp: string;
9
+ method: string;
10
+ path: string;
11
+ bodySha256Hex: string;
12
+ }): string;
13
+ export declare const PARTNER_HEADERS: {
14
+ readonly key: "x-partner-key";
15
+ readonly timestamp: "x-partner-timestamp";
16
+ readonly signature: "x-partner-signature";
17
+ };
18
+ export declare const WEBHOOK_SIGNATURE_HEADER = "x-vivoa-signature";
19
+ export declare const WEBHOOK_EVENT_HEADER = "x-vivoa-event";
20
+ /** Server accepts ±300 s of clock skew. */
21
+ export declare const MAX_CLOCK_SKEW_SECONDS = 300;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_CLOCK_SKEW_SECONDS = exports.WEBHOOK_EVENT_HEADER = exports.WEBHOOK_SIGNATURE_HEADER = exports.PARTNER_HEADERS = void 0;
4
+ exports.canonicalRequestPayload = canonicalRequestPayload;
5
+ /**
6
+ * Canonical request payload signed with the operator's apiSecret. Must match
7
+ * the platform verifier (`shared/crypto/management-hmac.ts`, v2 strict):
8
+ *
9
+ * `${timestamp}.${METHOD}.${pathWithoutQuery}.${sha256hex(rawBody)}`
10
+ */
11
+ function canonicalRequestPayload(params) {
12
+ const path = params.path.split('?')[0];
13
+ return `${params.timestamp}.${params.method.toUpperCase()}.${path}.${params.bodySha256Hex}`;
14
+ }
15
+ exports.PARTNER_HEADERS = {
16
+ key: 'x-partner-key',
17
+ timestamp: 'x-partner-timestamp',
18
+ signature: 'x-partner-signature',
19
+ };
20
+ exports.WEBHOOK_SIGNATURE_HEADER = 'x-vivoa-signature';
21
+ exports.WEBHOOK_EVENT_HEADER = 'x-vivoa-event';
22
+ /** Server accepts ±300 s of clock skew. */
23
+ exports.MAX_CLOCK_SKEW_SECONDS = 300;
@@ -0,0 +1,48 @@
1
+ export declare const MERCHANT_EVENT_TYPES: readonly ["merchant.created", "merchant.live", "merchant.failed", "merchant.suspended", "merchant.reactivated"];
2
+ export type MerchantEventType = (typeof MERCHANT_EVENT_TYPES)[number];
3
+ export type WebhookEventType = MerchantEventType | 'platform.test';
4
+ export interface WebhookConfig {
5
+ configured: boolean;
6
+ url: string | null;
7
+ /** [] = all merchant events. */
8
+ events: string[];
9
+ active: boolean;
10
+ updatedAt: string | null;
11
+ }
12
+ export interface SetWebhookInput {
13
+ url: string;
14
+ /** Omit or [] to receive every merchant event. */
15
+ events?: MerchantEventType[];
16
+ }
17
+ export interface SetWebhookResult {
18
+ webhook: WebhookConfig;
19
+ /** Only on first creation. Store it — it is never readable again. */
20
+ signingSecret: string | null;
21
+ }
22
+ export interface WebhookDelivery {
23
+ id: string;
24
+ event: string;
25
+ statusCode: number | null;
26
+ success: boolean;
27
+ error: string | null;
28
+ durationMs: number | null;
29
+ attempt: number;
30
+ createdAt: string;
31
+ }
32
+ export interface MerchantEventData {
33
+ merchantId: string;
34
+ handle: string;
35
+ operatorId: string;
36
+ occurredAt: string;
37
+ /** Present on `merchant.failed`. */
38
+ reason?: string;
39
+ /** Present on `merchant.created` when the create call gave one. */
40
+ externalId?: string | null;
41
+ [key: string]: unknown;
42
+ }
43
+ /** Body POSTed to the partner's endpoint. */
44
+ export interface WebhookEvent<T extends WebhookEventType = WebhookEventType> {
45
+ event: T;
46
+ data: T extends MerchantEventType ? MerchantEventData : Record<string, unknown>;
47
+ sentAt: string;
48
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MERCHANT_EVENT_TYPES = void 0;
4
+ exports.MERCHANT_EVENT_TYPES = [
5
+ 'merchant.created',
6
+ 'merchant.live',
7
+ 'merchant.failed',
8
+ 'merchant.suspended',
9
+ 'merchant.reactivated',
10
+ ];
@@ -0,0 +1,25 @@
1
+ import { VivoaPartnerClient, type PartnerClientDeps } from './application/partner-client.ts';
2
+ import { type VerifyWebhookParams } from './webhooks/verify-webhook.ts';
3
+ import type { WebhookEvent } from './domain/webhook.ts';
4
+ export declare const DEFAULT_BASE_URL = "https://api.vivoa.app";
5
+ export type CreatePartnerClientOptions = Omit<PartnerClientDeps, 'baseUrl' | 'transport' | 'crypto' | 'clock'> & Partial<Pick<PartnerClientDeps, 'baseUrl' | 'transport' | 'crypto' | 'clock'>>;
6
+ /**
7
+ * Composition root with production adapters (global fetch, node:crypto, wall
8
+ * clock). Any of them can be overridden — e.g. `transport: new FakeVivoa()`.
9
+ */
10
+ export declare function createPartnerClient(options: CreatePartnerClientOptions): VivoaPartnerClient;
11
+ /** Standalone webhook verification (no client needed in your webhook handler). */
12
+ export declare function verifyWebhook(params: VerifyWebhookParams): Promise<WebhookEvent>;
13
+ export { VivoaPartnerClient, SDK_VERSION } from './application/partner-client.ts';
14
+ export type { PartnerClientDeps } from './application/partner-client.ts';
15
+ export type { ProvisionStoreOptions, ProvisionStoreResult, ProvisionProgress, ProvisionPhase, } from './application/use-cases/provision-store.ts';
16
+ export type { VerifyWebhookParams } from './webhooks/verify-webhook.ts';
17
+ export * from './domain/errors.ts';
18
+ export * from './domain/merchant.ts';
19
+ export * from './domain/operator.ts';
20
+ export * from './domain/webhook.ts';
21
+ export { canonicalRequestPayload, PARTNER_HEADERS, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_EVENT_HEADER } from './domain/signature.ts';
22
+ export * from './ports/index.ts';
23
+ export { FetchTransport } from './adapters/fetch-transport.ts';
24
+ export { nodeCrypto } from './adapters/node-crypto.ts';
25
+ export { systemClock } from './adapters/system-clock.ts';
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.systemClock = exports.nodeCrypto = exports.FetchTransport = exports.WEBHOOK_EVENT_HEADER = exports.WEBHOOK_SIGNATURE_HEADER = exports.PARTNER_HEADERS = exports.canonicalRequestPayload = exports.SDK_VERSION = exports.VivoaPartnerClient = exports.DEFAULT_BASE_URL = void 0;
18
+ exports.createPartnerClient = createPartnerClient;
19
+ exports.verifyWebhook = verifyWebhook;
20
+ const partner_client_ts_1 = require("./application/partner-client.js");
21
+ const fetch_transport_ts_1 = require("./adapters/fetch-transport.js");
22
+ const node_crypto_ts_1 = require("./adapters/node-crypto.js");
23
+ const system_clock_ts_1 = require("./adapters/system-clock.js");
24
+ const verify_webhook_ts_1 = require("./webhooks/verify-webhook.js");
25
+ exports.DEFAULT_BASE_URL = 'https://api.vivoa.app';
26
+ /**
27
+ * Composition root with production adapters (global fetch, node:crypto, wall
28
+ * clock). Any of them can be overridden — e.g. `transport: new FakeVivoa()`.
29
+ */
30
+ function createPartnerClient(options) {
31
+ return new partner_client_ts_1.VivoaPartnerClient({
32
+ ...options,
33
+ baseUrl: options.baseUrl ?? exports.DEFAULT_BASE_URL,
34
+ transport: options.transport ?? new fetch_transport_ts_1.FetchTransport(),
35
+ crypto: options.crypto ?? node_crypto_ts_1.nodeCrypto,
36
+ clock: options.clock ?? system_clock_ts_1.systemClock,
37
+ });
38
+ }
39
+ /** Standalone webhook verification (no client needed in your webhook handler). */
40
+ function verifyWebhook(params) {
41
+ return (0, verify_webhook_ts_1.verifyWebhook)(node_crypto_ts_1.nodeCrypto, params);
42
+ }
43
+ var partner_client_ts_2 = require("./application/partner-client.js");
44
+ Object.defineProperty(exports, "VivoaPartnerClient", { enumerable: true, get: function () { return partner_client_ts_2.VivoaPartnerClient; } });
45
+ Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return partner_client_ts_2.SDK_VERSION; } });
46
+ __exportStar(require("./domain/errors.js"), exports);
47
+ __exportStar(require("./domain/merchant.js"), exports);
48
+ __exportStar(require("./domain/operator.js"), exports);
49
+ __exportStar(require("./domain/webhook.js"), exports);
50
+ var signature_ts_1 = require("./domain/signature.js");
51
+ Object.defineProperty(exports, "canonicalRequestPayload", { enumerable: true, get: function () { return signature_ts_1.canonicalRequestPayload; } });
52
+ Object.defineProperty(exports, "PARTNER_HEADERS", { enumerable: true, get: function () { return signature_ts_1.PARTNER_HEADERS; } });
53
+ Object.defineProperty(exports, "WEBHOOK_SIGNATURE_HEADER", { enumerable: true, get: function () { return signature_ts_1.WEBHOOK_SIGNATURE_HEADER; } });
54
+ Object.defineProperty(exports, "WEBHOOK_EVENT_HEADER", { enumerable: true, get: function () { return signature_ts_1.WEBHOOK_EVENT_HEADER; } });
55
+ __exportStar(require("./ports/index.js"), exports);
56
+ var fetch_transport_ts_2 = require("./adapters/fetch-transport.js");
57
+ Object.defineProperty(exports, "FetchTransport", { enumerable: true, get: function () { return fetch_transport_ts_2.FetchTransport; } });
58
+ var node_crypto_ts_2 = require("./adapters/node-crypto.js");
59
+ Object.defineProperty(exports, "nodeCrypto", { enumerable: true, get: function () { return node_crypto_ts_2.nodeCrypto; } });
60
+ var system_clock_ts_2 = require("./adapters/system-clock.js");
61
+ Object.defineProperty(exports, "systemClock", { enumerable: true, get: function () { return system_clock_ts_2.systemClock; } });
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,5 @@
1
+ /** Driven port: time. Tests inject a manual clock to make polling instant. */
2
+ export interface Clock {
3
+ nowMs(): number;
4
+ sleep(ms: number): Promise<void>;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ /** Driven port: the three primitives the SDK needs. Node, WebCrypto or HSM. */
2
+ export interface CryptoProvider {
3
+ sha256Hex(data: string): string | Promise<string>;
4
+ hmacSha256Hex(secret: string, data: string): string | Promise<string>;
5
+ /** Constant-time comparison of two hex strings. */
6
+ timingSafeEqualHex(a: string, b: string): boolean | Promise<boolean>;
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Driven port: moves one already-signed request over the wire. The SDK core
3
+ * never touches `fetch` — swap in undici, axios, a recording proxy, or the
4
+ * in-memory fake Vivoa (`@vivoa/partner-sdk/testing`) without changing a line
5
+ * of application code.
6
+ */
7
+ export interface HttpRequest {
8
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE';
9
+ /** Absolute URL. */
10
+ url: string;
11
+ headers: Record<string, string>;
12
+ /** Exact bytes that were signed; undefined for bodyless requests. */
13
+ body?: string;
14
+ /** Per-request timeout in ms. */
15
+ timeoutMs: number;
16
+ }
17
+ export interface HttpResponse {
18
+ status: number;
19
+ headers: Record<string, string>;
20
+ /** Raw text; the core parses JSON. */
21
+ body: string;
22
+ }
23
+ export interface HttpTransport {
24
+ send(request: HttpRequest): Promise<HttpResponse>;
25
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ export type { HttpTransport, HttpRequest, HttpResponse } from './http-transport.port.ts';
2
+ export type { CryptoProvider } from './crypto.port.ts';
3
+ export type { Clock } from './clock.port.ts';
4
+ export type { SdkLogger } from './logger.port.ts';
5
+ export { silentLogger } from './logger.port.ts';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.silentLogger = void 0;
4
+ var logger_port_ts_1 = require("./logger.port.js");
5
+ Object.defineProperty(exports, "silentLogger", { enumerable: true, get: function () { return logger_port_ts_1.silentLogger; } });
@@ -0,0 +1,6 @@
1
+ /** Optional observability hook. Never receives secrets or signatures. */
2
+ export interface SdkLogger {
3
+ debug(message: string, meta?: Record<string, unknown>): void;
4
+ warn(message: string, meta?: Record<string, unknown>): void;
5
+ }
6
+ export declare const silentLogger: SdkLogger;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.silentLogger = void 0;
4
+ exports.silentLogger = {
5
+ debug: () => { },
6
+ warn: () => { },
7
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@vivoa/partner-sdk/testing` — test your integration without touching the
3
+ * platform: plug `FakeVivoa` in as the transport and a `ManualClock` so polling
4
+ * and backoff run instantly.
5
+ *
6
+ * const clock = new ManualClock();
7
+ * const vivoa = new FakeVivoa({ clock, operators: [{ apiKey: 'k', apiSecret: 's' }] });
8
+ * const client = createPartnerClient({ apiKey: 'k', apiSecret: 's', transport: vivoa, clock });
9
+ */
10
+ export { FakeVivoa } from './adapters/in-memory/fake-vivoa.ts';
11
+ export type { FakeVivoaOptions, FakeOperatorSeed, FakeActivationMode, FakeWebhookDispatch, } from './adapters/in-memory/fake-vivoa.ts';
12
+ export { ManualClock } from './adapters/manual-clock.ts';
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ManualClock = exports.FakeVivoa = void 0;
4
+ /**
5
+ * `@vivoa/partner-sdk/testing` — test your integration without touching the
6
+ * platform: plug `FakeVivoa` in as the transport and a `ManualClock` so polling
7
+ * and backoff run instantly.
8
+ *
9
+ * const clock = new ManualClock();
10
+ * const vivoa = new FakeVivoa({ clock, operators: [{ apiKey: 'k', apiSecret: 's' }] });
11
+ * const client = createPartnerClient({ apiKey: 'k', apiSecret: 's', transport: vivoa, clock });
12
+ */
13
+ var fake_vivoa_ts_1 = require("./adapters/in-memory/fake-vivoa.js");
14
+ Object.defineProperty(exports, "FakeVivoa", { enumerable: true, get: function () { return fake_vivoa_ts_1.FakeVivoa; } });
15
+ var manual_clock_ts_1 = require("./adapters/manual-clock.js");
16
+ Object.defineProperty(exports, "ManualClock", { enumerable: true, get: function () { return manual_clock_ts_1.ManualClock; } });
@@ -0,0 +1,21 @@
1
+ import type { CryptoProvider } from '../ports/crypto.port.ts';
2
+ import type { WebhookEvent } from '../domain/webhook.ts';
3
+ export interface VerifyWebhookParams {
4
+ /** The body EXACTLY as received — before any JSON parsing. */
5
+ rawBody: string | Uint8Array;
6
+ /** Value of the `X-Vivoa-Signature` header (`sha256=<hex>`). */
7
+ signatureHeader: string | null | undefined;
8
+ /** Signing secret returned by `webhooks.set()` / `webhooks.rotateSecret()`. */
9
+ signingSecret: string;
10
+ /**
11
+ * Reject events whose `sentAt` is older than this (replay window). Default
12
+ * 5 min; `0` disables the check.
13
+ */
14
+ toleranceMs?: number;
15
+ nowMs?: number;
16
+ }
17
+ /**
18
+ * Verifies and parses a Vivoa webhook. Throws `VivoaWebhookSignatureError` on
19
+ * any mismatch — respond 400/401 and do not process the event.
20
+ */
21
+ export declare function verifyWebhook(crypto: CryptoProvider, params: VerifyWebhookParams): Promise<WebhookEvent>;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyWebhook = verifyWebhook;
4
+ const errors_ts_1 = require("../domain/errors.js");
5
+ const DEFAULT_TOLERANCE_MS = 5 * 60_000;
6
+ /**
7
+ * Verifies and parses a Vivoa webhook. Throws `VivoaWebhookSignatureError` on
8
+ * any mismatch — respond 400/401 and do not process the event.
9
+ */
10
+ async function verifyWebhook(crypto, params) {
11
+ const raw = typeof params.rawBody === 'string' ? params.rawBody : new TextDecoder().decode(params.rawBody);
12
+ const header = params.signatureHeader?.trim();
13
+ if (!header?.startsWith('sha256=')) {
14
+ throw new errors_ts_1.VivoaWebhookSignatureError('Missing or malformed X-Vivoa-Signature header');
15
+ }
16
+ const expected = await crypto.hmacSha256Hex(params.signingSecret, raw);
17
+ if (!(await crypto.timingSafeEqualHex(expected, header.slice('sha256='.length)))) {
18
+ throw new errors_ts_1.VivoaWebhookSignatureError('Invalid webhook signature');
19
+ }
20
+ let event;
21
+ try {
22
+ event = JSON.parse(raw);
23
+ }
24
+ catch {
25
+ throw new errors_ts_1.VivoaWebhookSignatureError('Signed body is not valid JSON');
26
+ }
27
+ const tolerance = params.toleranceMs ?? DEFAULT_TOLERANCE_MS;
28
+ if (tolerance > 0) {
29
+ const sentAt = Date.parse(event.sentAt);
30
+ const now = params.nowMs ?? Date.now();
31
+ if (!Number.isFinite(sentAt) || Math.abs(now - sentAt) > tolerance) {
32
+ throw new errors_ts_1.VivoaWebhookSignatureError('Webhook sentAt outside the tolerance window (possible replay)');
33
+ }
34
+ }
35
+ return event;
36
+ }
@@ -0,0 +1,7 @@
1
+ import type { HttpRequest, HttpResponse, HttpTransport } from '../ports/http-transport.port.ts';
2
+ /** Default transport: the runtime's global `fetch` (Node ≥ 18, Deno, Bun, edge). */
3
+ export declare class FetchTransport implements HttpTransport {
4
+ private readonly fetchImpl;
5
+ constructor(fetchImpl?: typeof fetch);
6
+ send(request: HttpRequest): Promise<HttpResponse>;
7
+ }