@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.
- package/CHANGELOG.md +13 -0
- package/README.md +162 -0
- package/dist/cjs/adapters/fetch-transport.d.ts +7 -0
- package/dist/cjs/adapters/fetch-transport.js +34 -0
- package/dist/cjs/adapters/in-memory/fake-vivoa.d.ts +93 -0
- package/dist/cjs/adapters/in-memory/fake-vivoa.js +468 -0
- package/dist/cjs/adapters/manual-clock.d.ts +9 -0
- package/dist/cjs/adapters/manual-clock.js +20 -0
- package/dist/cjs/adapters/node-crypto.d.ts +2 -0
- package/dist/cjs/adapters/node-crypto.js +13 -0
- package/dist/cjs/adapters/system-clock.d.ts +2 -0
- package/dist/cjs/adapters/system-clock.js +7 -0
- package/dist/cjs/application/partner-client.d.ts +45 -0
- package/dist/cjs/application/partner-client.js +55 -0
- package/dist/cjs/application/resources/fleet.d.ts +7 -0
- package/dist/cjs/application/resources/fleet.js +13 -0
- package/dist/cjs/application/resources/me.d.ts +13 -0
- package/dist/cjs/application/resources/me.js +23 -0
- package/dist/cjs/application/resources/merchants.d.ts +37 -0
- package/dist/cjs/application/resources/merchants.js +92 -0
- package/dist/cjs/application/resources/webhooks.d.ts +16 -0
- package/dist/cjs/application/resources/webhooks.js +30 -0
- package/dist/cjs/application/signed-http.d.ts +41 -0
- package/dist/cjs/application/signed-http.js +105 -0
- package/dist/cjs/application/use-cases/provision-store.d.ts +50 -0
- package/dist/cjs/application/use-cases/provision-store.js +103 -0
- package/dist/cjs/domain/errors.d.ts +72 -0
- package/dist/cjs/domain/errors.js +111 -0
- package/dist/cjs/domain/merchant.d.ts +74 -0
- package/dist/cjs/domain/merchant.js +28 -0
- package/dist/cjs/domain/operator.d.ts +32 -0
- package/dist/cjs/domain/operator.js +6 -0
- package/dist/cjs/domain/signature.d.ts +21 -0
- package/dist/cjs/domain/signature.js +23 -0
- package/dist/cjs/domain/webhook.d.ts +48 -0
- package/dist/cjs/domain/webhook.js +10 -0
- package/dist/cjs/index.d.ts +25 -0
- package/dist/cjs/index.js +61 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/ports/clock.port.d.ts +5 -0
- package/dist/cjs/ports/clock.port.js +2 -0
- package/dist/cjs/ports/crypto.port.d.ts +7 -0
- package/dist/cjs/ports/crypto.port.js +2 -0
- package/dist/cjs/ports/http-transport.port.d.ts +25 -0
- package/dist/cjs/ports/http-transport.port.js +2 -0
- package/dist/cjs/ports/index.d.ts +5 -0
- package/dist/cjs/ports/index.js +5 -0
- package/dist/cjs/ports/logger.port.d.ts +6 -0
- package/dist/cjs/ports/logger.port.js +7 -0
- package/dist/cjs/testing.d.ts +12 -0
- package/dist/cjs/testing.js +16 -0
- package/dist/cjs/webhooks/verify-webhook.d.ts +21 -0
- package/dist/cjs/webhooks/verify-webhook.js +36 -0
- package/dist/esm/adapters/fetch-transport.d.ts +7 -0
- package/dist/esm/adapters/fetch-transport.js +30 -0
- package/dist/esm/adapters/in-memory/fake-vivoa.d.ts +93 -0
- package/dist/esm/adapters/in-memory/fake-vivoa.js +464 -0
- package/dist/esm/adapters/manual-clock.d.ts +9 -0
- package/dist/esm/adapters/manual-clock.js +16 -0
- package/dist/esm/adapters/node-crypto.d.ts +2 -0
- package/dist/esm/adapters/node-crypto.js +10 -0
- package/dist/esm/adapters/system-clock.d.ts +2 -0
- package/dist/esm/adapters/system-clock.js +4 -0
- package/dist/esm/application/partner-client.d.ts +45 -0
- package/dist/esm/application/partner-client.js +51 -0
- package/dist/esm/application/resources/fleet.d.ts +7 -0
- package/dist/esm/application/resources/fleet.js +9 -0
- package/dist/esm/application/resources/me.d.ts +13 -0
- package/dist/esm/application/resources/me.js +19 -0
- package/dist/esm/application/resources/merchants.d.ts +37 -0
- package/dist/esm/application/resources/merchants.js +87 -0
- package/dist/esm/application/resources/webhooks.d.ts +16 -0
- package/dist/esm/application/resources/webhooks.js +26 -0
- package/dist/esm/application/signed-http.d.ts +41 -0
- package/dist/esm/application/signed-http.js +101 -0
- package/dist/esm/application/use-cases/provision-store.d.ts +50 -0
- package/dist/esm/application/use-cases/provision-store.js +99 -0
- package/dist/esm/cli.d.ts +2 -0
- package/dist/esm/cli.js +106 -0
- package/dist/esm/domain/errors.d.ts +72 -0
- package/dist/esm/domain/errors.js +92 -0
- package/dist/esm/domain/merchant.d.ts +74 -0
- package/dist/esm/domain/merchant.js +23 -0
- package/dist/esm/domain/operator.d.ts +32 -0
- package/dist/esm/domain/operator.js +3 -0
- package/dist/esm/domain/signature.d.ts +21 -0
- package/dist/esm/domain/signature.js +19 -0
- package/dist/esm/domain/webhook.d.ts +48 -0
- package/dist/esm/domain/webhook.js +7 -0
- package/dist/esm/index.d.ts +25 -0
- package/dist/esm/index.js +33 -0
- package/dist/esm/ports/clock.port.d.ts +5 -0
- package/dist/esm/ports/clock.port.js +1 -0
- package/dist/esm/ports/crypto.port.d.ts +7 -0
- package/dist/esm/ports/crypto.port.js +1 -0
- package/dist/esm/ports/http-transport.port.d.ts +25 -0
- package/dist/esm/ports/http-transport.port.js +1 -0
- package/dist/esm/ports/index.d.ts +5 -0
- package/dist/esm/ports/index.js +1 -0
- package/dist/esm/ports/logger.port.d.ts +6 -0
- package/dist/esm/ports/logger.port.js +4 -0
- package/dist/esm/testing.d.ts +12 -0
- package/dist/esm/testing.js +11 -0
- package/dist/esm/webhooks/verify-webhook.d.ts +21 -0
- package/dist/esm/webhooks/verify-webhook.js +33 -0
- package/package.json +56 -0
package/dist/esm/cli.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* vivoa-partner — thin driving adapter over the SDK. Credentials come ONLY from
|
|
4
|
+
* the environment (never flags, so they stay out of shell history):
|
|
5
|
+
* VIVOA_PARTNER_KEY, VIVOA_PARTNER_SECRET, VIVOA_API_URL (default https://api.vivoa.app)
|
|
6
|
+
*/
|
|
7
|
+
import { parseArgs } from 'node:util';
|
|
8
|
+
import { createPartnerClient, DEFAULT_BASE_URL } from "./index.js";
|
|
9
|
+
import { VivoaError } from "./domain/errors.js";
|
|
10
|
+
const USAGE = `vivoa-partner <command> [options]
|
|
11
|
+
|
|
12
|
+
me Profile, plan and quota
|
|
13
|
+
merchants list [--search s] [--external-id id] [--page n] [--limit n]
|
|
14
|
+
merchants status <id>
|
|
15
|
+
merchants suspend <id> | reactivate <id>
|
|
16
|
+
store create --external-id id --name n --email e --countries SV,GT [--industry i] --yes
|
|
17
|
+
Create (or resume) + activate + wait until live.
|
|
18
|
+
The platform assigns the handle; --external-id is
|
|
19
|
+
your own reference and the retry-safety key.
|
|
20
|
+
fleet Fleet analytics
|
|
21
|
+
webhook get | webhook set --url u [--events a,b] | webhook test | webhook deliveries
|
|
22
|
+
|
|
23
|
+
Env: VIVOA_PARTNER_KEY, VIVOA_PARTNER_SECRET, VIVOA_API_URL (default ${DEFAULT_BASE_URL})`;
|
|
24
|
+
async function main(argv) {
|
|
25
|
+
const { values, positionals } = parseArgs({
|
|
26
|
+
args: argv,
|
|
27
|
+
allowPositionals: true,
|
|
28
|
+
options: {
|
|
29
|
+
search: { type: 'string' },
|
|
30
|
+
'external-id': { type: 'string' },
|
|
31
|
+
page: { type: 'string' },
|
|
32
|
+
limit: { type: 'string' },
|
|
33
|
+
name: { type: 'string' },
|
|
34
|
+
email: { type: 'string' },
|
|
35
|
+
countries: { type: 'string' },
|
|
36
|
+
industry: { type: 'string' },
|
|
37
|
+
url: { type: 'string' },
|
|
38
|
+
events: { type: 'string' },
|
|
39
|
+
yes: { type: 'boolean', default: false },
|
|
40
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const [command, sub, arg] = positionals;
|
|
44
|
+
if (!command || values.help) {
|
|
45
|
+
console.log(USAGE);
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const apiKey = process.env.VIVOA_PARTNER_KEY;
|
|
49
|
+
const apiSecret = process.env.VIVOA_PARTNER_SECRET;
|
|
50
|
+
if (!apiKey || !apiSecret)
|
|
51
|
+
throw new VivoaError('Set VIVOA_PARTNER_KEY and VIVOA_PARTNER_SECRET');
|
|
52
|
+
const client = createPartnerClient({ apiKey, apiSecret, baseUrl: process.env.VIVOA_API_URL || undefined });
|
|
53
|
+
const list = (v) => (v ? v.split(',').map((s) => s.trim()).filter(Boolean) : undefined);
|
|
54
|
+
switch (`${command} ${sub ?? ''}`.trim()) {
|
|
55
|
+
case 'me':
|
|
56
|
+
return client.me.get();
|
|
57
|
+
case 'fleet':
|
|
58
|
+
return client.fleet.analytics();
|
|
59
|
+
case 'merchants list':
|
|
60
|
+
return client.merchants.list({
|
|
61
|
+
search: values.search,
|
|
62
|
+
externalId: values['external-id'],
|
|
63
|
+
page: values.page ? Number(values.page) : undefined,
|
|
64
|
+
limit: values.limit ? Number(values.limit) : undefined,
|
|
65
|
+
});
|
|
66
|
+
case 'merchants status':
|
|
67
|
+
return client.merchants.status(required(arg, '<id>'));
|
|
68
|
+
case 'merchants suspend':
|
|
69
|
+
return client.merchants.suspend(required(arg, '<id>'));
|
|
70
|
+
case 'merchants reactivate':
|
|
71
|
+
return client.merchants.reactivate(required(arg, '<id>'));
|
|
72
|
+
case 'store create': {
|
|
73
|
+
if (!values.yes)
|
|
74
|
+
throw new VivoaError('store create provisions REAL infrastructure — re-run with --yes');
|
|
75
|
+
return client.provisionStore({
|
|
76
|
+
name: required(values.name, '--name'),
|
|
77
|
+
email: required(values.email, '--email'),
|
|
78
|
+
countries: list(required(values.countries, '--countries')) ?? [],
|
|
79
|
+
industry: values.industry,
|
|
80
|
+
externalId: required(values['external-id'], '--external-id'),
|
|
81
|
+
}, { onProgress: (p) => console.error(`· ${p.phase} ${p.handle} (${p.merchantId}) ${p.runtime ?? ''}`) });
|
|
82
|
+
}
|
|
83
|
+
case 'webhook get':
|
|
84
|
+
return client.webhooks.get();
|
|
85
|
+
case 'webhook set':
|
|
86
|
+
return client.webhooks.set({ url: required(values.url, '--url'), events: list(values.events) });
|
|
87
|
+
case 'webhook test':
|
|
88
|
+
return client.webhooks.test();
|
|
89
|
+
case 'webhook deliveries':
|
|
90
|
+
return client.webhooks.deliveries(values.limit ? Number(values.limit) : undefined);
|
|
91
|
+
default:
|
|
92
|
+
throw new VivoaError(`Unknown command "${positionals.join(' ')}"\n\n${USAGE}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function required(value, name) {
|
|
96
|
+
if (!value)
|
|
97
|
+
throw new VivoaError(`Missing ${name}`);
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
main(process.argv.slice(2)).then((result) => {
|
|
101
|
+
if (result !== undefined)
|
|
102
|
+
console.log(JSON.stringify(result, null, 2));
|
|
103
|
+
}, (err) => {
|
|
104
|
+
console.error(`${err.name}: ${err.message}`);
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed error hierarchy. Every failed call surfaces as a `VivoaApiError`
|
|
3
|
+
* subclass keyed by HTTP status, so partners branch on `instanceof` instead of
|
|
4
|
+
* parsing strings.
|
|
5
|
+
*/
|
|
6
|
+
export declare class VivoaError extends Error {
|
|
7
|
+
constructor(message: string, options?: {
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
/** Input rejected client-side before any request is sent. */
|
|
12
|
+
export declare class VivoaValidationError extends VivoaError {
|
|
13
|
+
}
|
|
14
|
+
/** The request never produced an HTTP response (DNS, reset, timeout). */
|
|
15
|
+
export declare class VivoaNetworkError extends VivoaError {
|
|
16
|
+
}
|
|
17
|
+
/** A long-running wait (e.g. `waitUntilSettled`) ran out of time. */
|
|
18
|
+
export declare class VivoaTimeoutError extends VivoaError {
|
|
19
|
+
}
|
|
20
|
+
export declare class VivoaApiError extends VivoaError {
|
|
21
|
+
readonly status: number;
|
|
22
|
+
readonly method: string;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
readonly body: unknown;
|
|
25
|
+
constructor(params: {
|
|
26
|
+
status: number;
|
|
27
|
+
method: string;
|
|
28
|
+
path: string;
|
|
29
|
+
message: string;
|
|
30
|
+
body: unknown;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** 400 — invalid payload or country outside allowedRegions. */
|
|
34
|
+
export declare class VivoaBadRequestError extends VivoaApiError {
|
|
35
|
+
}
|
|
36
|
+
/** 401 — bad signature, unknown key or clock skew > 300 s. */
|
|
37
|
+
export declare class VivoaAuthenticationError extends VivoaApiError {
|
|
38
|
+
}
|
|
39
|
+
/** 403 — operator not active or quota full. */
|
|
40
|
+
export declare class VivoaForbiddenError extends VivoaApiError {
|
|
41
|
+
}
|
|
42
|
+
/** 404 — merchant does not exist or is not in your fleet. */
|
|
43
|
+
export declare class VivoaNotFoundError extends VivoaApiError {
|
|
44
|
+
}
|
|
45
|
+
/** 409 — handle taken, or activation on a creating/live/suspended merchant. */
|
|
46
|
+
export declare class VivoaConflictError extends VivoaApiError {
|
|
47
|
+
}
|
|
48
|
+
/** 429 — requests-per-minute limit for the plan. */
|
|
49
|
+
export declare class VivoaRateLimitError extends VivoaApiError {
|
|
50
|
+
}
|
|
51
|
+
/** 5xx. */
|
|
52
|
+
export declare class VivoaServerError extends VivoaApiError {
|
|
53
|
+
}
|
|
54
|
+
/** Extracts NestJS's `{ message }` (string or validation array) from a body. */
|
|
55
|
+
export declare function messageFromBody(body: unknown, fallback: string): string;
|
|
56
|
+
export declare function apiErrorFor(params: {
|
|
57
|
+
status: number;
|
|
58
|
+
method: string;
|
|
59
|
+
path: string;
|
|
60
|
+
body: unknown;
|
|
61
|
+
}): VivoaApiError;
|
|
62
|
+
/** Provisioning ended in `failed`. `activate` again (or `provisionStore`) to retry. */
|
|
63
|
+
export declare class VivoaProvisioningFailedError extends VivoaError {
|
|
64
|
+
readonly merchantId: string;
|
|
65
|
+
readonly reason: string;
|
|
66
|
+
constructor(merchantId: string, reason: string, options?: {
|
|
67
|
+
cause?: unknown;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** A webhook body whose `X-Vivoa-Signature` does not verify. Reject it (401/400). */
|
|
71
|
+
export declare class VivoaWebhookSignatureError extends VivoaError {
|
|
72
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed error hierarchy. Every failed call surfaces as a `VivoaApiError`
|
|
3
|
+
* subclass keyed by HTTP status, so partners branch on `instanceof` instead of
|
|
4
|
+
* parsing strings.
|
|
5
|
+
*/
|
|
6
|
+
export class VivoaError extends Error {
|
|
7
|
+
constructor(message, options) {
|
|
8
|
+
super(message, options);
|
|
9
|
+
this.name = new.target.name;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Input rejected client-side before any request is sent. */
|
|
13
|
+
export class VivoaValidationError extends VivoaError {
|
|
14
|
+
}
|
|
15
|
+
/** The request never produced an HTTP response (DNS, reset, timeout). */
|
|
16
|
+
export class VivoaNetworkError extends VivoaError {
|
|
17
|
+
}
|
|
18
|
+
/** A long-running wait (e.g. `waitUntilSettled`) ran out of time. */
|
|
19
|
+
export class VivoaTimeoutError extends VivoaError {
|
|
20
|
+
}
|
|
21
|
+
export class VivoaApiError extends VivoaError {
|
|
22
|
+
status;
|
|
23
|
+
method;
|
|
24
|
+
path;
|
|
25
|
+
body;
|
|
26
|
+
constructor(params) {
|
|
27
|
+
super(`${params.method} ${params.path} → ${params.status}: ${params.message}`);
|
|
28
|
+
this.status = params.status;
|
|
29
|
+
this.method = params.method;
|
|
30
|
+
this.path = params.path;
|
|
31
|
+
this.body = params.body;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** 400 — invalid payload or country outside allowedRegions. */
|
|
35
|
+
export class VivoaBadRequestError extends VivoaApiError {
|
|
36
|
+
}
|
|
37
|
+
/** 401 — bad signature, unknown key or clock skew > 300 s. */
|
|
38
|
+
export class VivoaAuthenticationError extends VivoaApiError {
|
|
39
|
+
}
|
|
40
|
+
/** 403 — operator not active or quota full. */
|
|
41
|
+
export class VivoaForbiddenError extends VivoaApiError {
|
|
42
|
+
}
|
|
43
|
+
/** 404 — merchant does not exist or is not in your fleet. */
|
|
44
|
+
export class VivoaNotFoundError extends VivoaApiError {
|
|
45
|
+
}
|
|
46
|
+
/** 409 — handle taken, or activation on a creating/live/suspended merchant. */
|
|
47
|
+
export class VivoaConflictError extends VivoaApiError {
|
|
48
|
+
}
|
|
49
|
+
/** 429 — requests-per-minute limit for the plan. */
|
|
50
|
+
export class VivoaRateLimitError extends VivoaApiError {
|
|
51
|
+
}
|
|
52
|
+
/** 5xx. */
|
|
53
|
+
export class VivoaServerError extends VivoaApiError {
|
|
54
|
+
}
|
|
55
|
+
const BY_STATUS = {
|
|
56
|
+
400: VivoaBadRequestError,
|
|
57
|
+
401: VivoaAuthenticationError,
|
|
58
|
+
403: VivoaForbiddenError,
|
|
59
|
+
404: VivoaNotFoundError,
|
|
60
|
+
409: VivoaConflictError,
|
|
61
|
+
429: VivoaRateLimitError,
|
|
62
|
+
};
|
|
63
|
+
/** Extracts NestJS's `{ message }` (string or validation array) from a body. */
|
|
64
|
+
export function messageFromBody(body, fallback) {
|
|
65
|
+
if (body && typeof body === 'object' && 'message' in body) {
|
|
66
|
+
const m = body.message;
|
|
67
|
+
if (Array.isArray(m))
|
|
68
|
+
return m.join('; ');
|
|
69
|
+
if (typeof m === 'string')
|
|
70
|
+
return m;
|
|
71
|
+
}
|
|
72
|
+
if (typeof body === 'string' && body.length > 0)
|
|
73
|
+
return body;
|
|
74
|
+
return fallback;
|
|
75
|
+
}
|
|
76
|
+
export function apiErrorFor(params) {
|
|
77
|
+
const Ctor = BY_STATUS[params.status] ?? (params.status >= 500 ? VivoaServerError : VivoaApiError);
|
|
78
|
+
return new Ctor({ ...params, message: messageFromBody(params.body, `HTTP ${params.status}`) });
|
|
79
|
+
}
|
|
80
|
+
/** Provisioning ended in `failed`. `activate` again (or `provisionStore`) to retry. */
|
|
81
|
+
export class VivoaProvisioningFailedError extends VivoaError {
|
|
82
|
+
merchantId;
|
|
83
|
+
reason;
|
|
84
|
+
constructor(merchantId, reason, options) {
|
|
85
|
+
super(`Provisioning failed for merchant ${merchantId}: ${reason}`, options);
|
|
86
|
+
this.merchantId = merchantId;
|
|
87
|
+
this.reason = reason;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** A webhook body whose `X-Vivoa-Signature` does not verify. Reject it (401/400). */
|
|
91
|
+
export class VivoaWebhookSignatureError extends VivoaError {
|
|
92
|
+
}
|
|
@@ -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,23 @@
|
|
|
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 const MERCHANT_RUNTIME_STATUSES = [
|
|
7
|
+
'not_activated', // created, `activate` never called
|
|
8
|
+
'creating', // provisioning in flight
|
|
9
|
+
'live', // serving traffic
|
|
10
|
+
'suspended', // stopped by the operator / ops
|
|
11
|
+
'failed', // last provisioning attempt failed — `activate` again to retry
|
|
12
|
+
'unknown', // torn down / unexpected state
|
|
13
|
+
];
|
|
14
|
+
/** Max length the platform accepts for `CreateMerchantInput.externalId`. */
|
|
15
|
+
export const MAX_EXTERNAL_ID_LENGTH = 120;
|
|
16
|
+
/** Runtime states where the platform accepts `activate` (first run or retry). */
|
|
17
|
+
export function canActivate(runtime) {
|
|
18
|
+
return runtime === 'not_activated' || runtime === 'failed';
|
|
19
|
+
}
|
|
20
|
+
/** States that will not change without a new action from the partner. */
|
|
21
|
+
export function isSettled(runtime) {
|
|
22
|
+
return runtime !== 'creating';
|
|
23
|
+
}
|
|
@@ -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,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,19 @@
|
|
|
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 function canonicalRequestPayload(params) {
|
|
8
|
+
const path = params.path.split('?')[0];
|
|
9
|
+
return `${params.timestamp}.${params.method.toUpperCase()}.${path}.${params.bodySha256Hex}`;
|
|
10
|
+
}
|
|
11
|
+
export const PARTNER_HEADERS = {
|
|
12
|
+
key: 'x-partner-key',
|
|
13
|
+
timestamp: 'x-partner-timestamp',
|
|
14
|
+
signature: 'x-partner-signature',
|
|
15
|
+
};
|
|
16
|
+
export const WEBHOOK_SIGNATURE_HEADER = 'x-vivoa-signature';
|
|
17
|
+
export const WEBHOOK_EVENT_HEADER = 'x-vivoa-event';
|
|
18
|
+
/** Server accepts ±300 s of clock skew. */
|
|
19
|
+
export const 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,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,33 @@
|
|
|
1
|
+
import { VivoaPartnerClient } from "./application/partner-client.js";
|
|
2
|
+
import { FetchTransport } from "./adapters/fetch-transport.js";
|
|
3
|
+
import { nodeCrypto } from "./adapters/node-crypto.js";
|
|
4
|
+
import { systemClock } from "./adapters/system-clock.js";
|
|
5
|
+
import { verifyWebhook as verifyWithCrypto } from "./webhooks/verify-webhook.js";
|
|
6
|
+
export const DEFAULT_BASE_URL = 'https://api.vivoa.app';
|
|
7
|
+
/**
|
|
8
|
+
* Composition root with production adapters (global fetch, node:crypto, wall
|
|
9
|
+
* clock). Any of them can be overridden — e.g. `transport: new FakeVivoa()`.
|
|
10
|
+
*/
|
|
11
|
+
export function createPartnerClient(options) {
|
|
12
|
+
return new VivoaPartnerClient({
|
|
13
|
+
...options,
|
|
14
|
+
baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
|
|
15
|
+
transport: options.transport ?? new FetchTransport(),
|
|
16
|
+
crypto: options.crypto ?? nodeCrypto,
|
|
17
|
+
clock: options.clock ?? systemClock,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/** Standalone webhook verification (no client needed in your webhook handler). */
|
|
21
|
+
export function verifyWebhook(params) {
|
|
22
|
+
return verifyWithCrypto(nodeCrypto, params);
|
|
23
|
+
}
|
|
24
|
+
export { VivoaPartnerClient, SDK_VERSION } from "./application/partner-client.js";
|
|
25
|
+
export * from "./domain/errors.js";
|
|
26
|
+
export * from "./domain/merchant.js";
|
|
27
|
+
export * from "./domain/operator.js";
|
|
28
|
+
export * from "./domain/webhook.js";
|
|
29
|
+
export { canonicalRequestPayload, PARTNER_HEADERS, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_EVENT_HEADER } from "./domain/signature.js";
|
|
30
|
+
export * from "./ports/index.js";
|
|
31
|
+
export { FetchTransport } from "./adapters/fetch-transport.js";
|
|
32
|
+
export { nodeCrypto } from "./adapters/node-crypto.js";
|
|
33
|
+
export { systemClock } from "./adapters/system-clock.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 @@
|
|
|
1
|
+
export { silentLogger } from "./logger.port.js";
|
|
@@ -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,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';
|