mavunta 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chainwaka Technologies
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # mavunta
2
+
3
+ Official TypeScript and JavaScript SDK for the Mavunta API.
4
+
5
+ Mavunta helps merchants accept **M-Pesa, card, PayPal, Mavunta Balance, and crypto** payments, with settlement to **USDT, USDC, BTC, ETH, or SOL**. Create a payment intent, send the customer to hosted checkout, then confirm the result with signed webhooks.
6
+
7
+ > Server-side only. This package uses your **secret** (`cwk_…_sk_`) or **restricted** (`cwk_…_rk_`) key and must never run in a browser.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install mavunta
13
+ ```
14
+
15
+ `npm install mavunta` also works (it re-exports this package).
16
+
17
+ ## Quick start
18
+
19
+ ```ts
20
+ import Mavunta from 'mavunta'
21
+
22
+ const mavunta = new Mavunta({
23
+ secretKey: process.env.MAVUNTA_SECRET_KEY!,
24
+ environment: 'sandbox', // informational; the key prefix selects the mode
25
+ })
26
+ ```
27
+
28
+ ## Create a payment intent
29
+
30
+ ```ts
31
+ const intent = await mavunta.paymentIntents.create({
32
+ amount: '2500',
33
+ currency: 'KES',
34
+ settlement_currency: 'USDT',
35
+ payment_methods: ['mpesa', 'card', 'paypal', 'mavunta_balance'],
36
+ customer: { email: 'customer@example.com', phone: '+254712345678' },
37
+ metadata: { orderId: 'ORD-1001' },
38
+ })
39
+
40
+ // Redirect the customer to complete payment:
41
+ return intent.checkout_url
42
+ ```
43
+
44
+ ## Payment links and QR
45
+
46
+ ```ts
47
+ const link = await mavunta.paymentLinks.create({
48
+ title: 'Order #1001',
49
+ amount: '2500',
50
+ currency: 'KES',
51
+ settlement_currency: 'USDT',
52
+ })
53
+ // link.url, link.qr_code_url
54
+ ```
55
+
56
+ ## Verify webhooks
57
+
58
+ Payment results are asynchronous (M-Pesa, card redirects, crypto confirmations), so confirm them with signed webhooks. Always use the **raw** request body.
59
+
60
+ ```ts
61
+ import express from 'express'
62
+ import Mavunta from 'mavunta'
63
+
64
+ const mavunta = new Mavunta({ secretKey: process.env.MAVUNTA_SECRET_KEY! })
65
+
66
+ app.post('/mavunta/webhook', express.raw({ type: 'application/json' }), (req, res) => {
67
+ let event
68
+ try {
69
+ event = mavunta.webhooks.verify({
70
+ payload: req.body, // raw Buffer
71
+ signature: req.headers['mavunta-signature'] as string,
72
+ timestamp: req.headers['mavunta-timestamp'] as string,
73
+ secret: process.env.MAVUNTA_WEBHOOK_SECRET!,
74
+ })
75
+ } catch {
76
+ return res.sendStatus(400)
77
+ }
78
+
79
+ if (event.type === 'payment_intent.paid') {
80
+ // fulfil the order (idempotently)
81
+ }
82
+ res.sendStatus(200)
83
+ })
84
+ ```
85
+
86
+ Only need verification? Import it standalone:
87
+
88
+ ```ts
89
+ import { verifyWebhook } from 'mavunta/webhooks'
90
+ ```
91
+
92
+ ## Sandbox and live mode
93
+
94
+ The key prefix selects the mode (`cwk_test_…` vs `cwk_live_…`); responses carry `environment` and `livemode`. In sandbox you can simulate events:
95
+
96
+ ```ts
97
+ await mavunta.sandbox.triggerWebhook({ type: 'payment_intent.paid' })
98
+ ```
99
+
100
+ ## API keys
101
+
102
+ | Key | Where | Can do |
103
+ | --- | --- | --- |
104
+ | `cwk_…_sk_` (secret) | Backend | Create and read objects |
105
+ | `cwk_…_rk_` (restricted) | Backend | Scoped subset of the above |
106
+
107
+ Never embed a secret key in a browser, mobile app, or public repo.
108
+
109
+ ## Idempotency
110
+
111
+ Every money-moving create is retry-safe. Pass your own key, or let the SDK generate one per request:
112
+
113
+ ```ts
114
+ await mavunta.paymentIntents.create(params, { idempotencyKey: 'order_1001_attempt_1' })
115
+ ```
116
+
117
+ ## Errors
118
+
119
+ ```ts
120
+ import { MavuntaError, MavuntaValidationError } from 'mavunta'
121
+
122
+ try {
123
+ await mavunta.paymentIntents.create(params)
124
+ } catch (err) {
125
+ if (err instanceof MavuntaValidationError) {
126
+ console.log(err.code, err.param)
127
+ } else if (err instanceof MavuntaError) {
128
+ console.log(err.code, err.requestId, err.statusCode)
129
+ }
130
+ }
131
+ ```
132
+
133
+ Classes: `MavuntaError`, `MavuntaAPIError`, `MavuntaAuthenticationError`, `MavuntaPermissionError`, `MavuntaValidationError`, `MavuntaIdempotencyError`, `MavuntaRateLimitError`, `MavuntaConnectionError`, `MavuntaTimeoutError`, `MavuntaWebhookSignatureError`.
134
+
135
+ ## Resources
136
+
137
+ `auth`, `rates`, `quotes`, `paymentIntents`, `paymentLinks`, `customers`, `refunds`, `balances`, `settlements`, `reports`, `webhookEndpoints`, `webhookEvents`, `sandbox`, `webhooks` (verify).
138
+
139
+ ## Security
140
+
141
+ - Keep secret keys on the server and out of version control.
142
+ - Verify every webhook signature before acting; return `2xx` quickly.
143
+ - Treat confirmed crypto and payout events as final.
144
+
145
+ ## Support
146
+
147
+ - Website: https://www.mavunta.com
148
+ - Developers: https://developers.mavunta.com
149
+ - Status: https://status.mavunta.com
150
+ - Support: support@mavunta.com
151
+ - Security: security@mavunta.com
152
+
153
+ ## License
154
+
155
+ MIT © Chainwaka Technologies.
@@ -0,0 +1,108 @@
1
+ import { createHmac, timingSafeEqual } from 'crypto';
2
+
3
+ // src/webhooks/verify.ts
4
+
5
+ // src/errors.ts
6
+ var MavuntaError = class extends Error {
7
+ type;
8
+ code;
9
+ param;
10
+ requestId;
11
+ statusCode;
12
+ docsUrl;
13
+ constructor(message, init = {}) {
14
+ super(message);
15
+ this.name = this.constructor.name;
16
+ this.type = init.type ?? "api_error";
17
+ this.code = init.code ?? "unknown";
18
+ this.param = init.param ?? null;
19
+ this.requestId = init.requestId ?? null;
20
+ this.statusCode = init.statusCode ?? null;
21
+ this.docsUrl = init.docsUrl ?? null;
22
+ }
23
+ };
24
+ var MavuntaAPIError = class extends MavuntaError {
25
+ };
26
+ var MavuntaAuthenticationError = class extends MavuntaError {
27
+ };
28
+ var MavuntaPermissionError = class extends MavuntaError {
29
+ };
30
+ var MavuntaValidationError = class extends MavuntaError {
31
+ };
32
+ var MavuntaIdempotencyError = class extends MavuntaError {
33
+ };
34
+ var MavuntaConnectionError = class extends MavuntaError {
35
+ };
36
+ var MavuntaTimeoutError = class extends MavuntaError {
37
+ };
38
+ var MavuntaWebhookSignatureError = class extends MavuntaError {
39
+ };
40
+ var MavuntaRateLimitError = class extends MavuntaError {
41
+ retryAfterMs;
42
+ constructor(message, init = {}) {
43
+ super(message, init);
44
+ this.retryAfterMs = init.retryAfterMs ?? null;
45
+ }
46
+ };
47
+ function errorFromResponse(status, body, retryAfterMs = null) {
48
+ const init = {
49
+ type: body?.type,
50
+ code: body?.code,
51
+ param: body?.param ?? null,
52
+ requestId: body?.request_id ?? null,
53
+ statusCode: status,
54
+ docsUrl: body?.docs_url ?? null
55
+ };
56
+ const message = body?.message ?? `Mavunta API error (HTTP ${status})`;
57
+ if (status === 401) return new MavuntaAuthenticationError(message, init);
58
+ if (status === 403) return new MavuntaPermissionError(message, init);
59
+ if (status === 429) return new MavuntaRateLimitError(message, { ...init, retryAfterMs });
60
+ if (status === 409 || body?.type === "idempotency_error")
61
+ return new MavuntaIdempotencyError(message, init);
62
+ if (status === 400 || status === 422 || body?.type === "validation_error")
63
+ return new MavuntaValidationError(message, init);
64
+ return new MavuntaAPIError(message, init);
65
+ }
66
+
67
+ // src/webhooks/verify.ts
68
+ function fail(message) {
69
+ throw new MavuntaWebhookSignatureError(message, { type: "webhook_signature_error", code: "invalid_signature" });
70
+ }
71
+ function verifyWebhook(params) {
72
+ const { signature, timestamp, secret } = params;
73
+ if (!signature) fail("Missing Mavunta-Signature header.");
74
+ if (!timestamp) fail("Missing Mavunta-Timestamp header.");
75
+ if (!secret) fail("Missing webhook signing secret.");
76
+ const raw = typeof params.payload === "string" ? params.payload : params.payload.toString("utf8");
77
+ const tolerance = params.toleranceSec ?? 300;
78
+ const ts = Number(timestamp);
79
+ if (!Number.isFinite(ts)) fail("Invalid Mavunta-Timestamp header.");
80
+ if (Math.abs(Date.now() / 1e3 - ts) > tolerance) fail("Timestamp outside the tolerance window.");
81
+ const expected = createHmac("sha256", secret).update(`${timestamp}.${raw}`).digest("hex");
82
+ let a;
83
+ let b;
84
+ try {
85
+ a = Buffer.from(expected, "hex");
86
+ b = Buffer.from(signature, "hex");
87
+ } catch {
88
+ fail("Malformed signature.");
89
+ }
90
+ if (a.length !== b.length || !timingSafeEqual(a, b)) fail("Signature mismatch.");
91
+ return JSON.parse(raw);
92
+ }
93
+
94
+ // src/webhooks/index.ts
95
+ var WebhooksResource = class {
96
+ /** Verify a webhook signature and return the parsed event, or throw. */
97
+ verify(params) {
98
+ return verifyWebhook(params);
99
+ }
100
+ /** Alias of {@link verify}, matching the wording in some platform docs. */
101
+ constructEvent(params) {
102
+ return verifyWebhook(params);
103
+ }
104
+ };
105
+
106
+ export { MavuntaAPIError, MavuntaAuthenticationError, MavuntaConnectionError, MavuntaError, MavuntaIdempotencyError, MavuntaPermissionError, MavuntaRateLimitError, MavuntaTimeoutError, MavuntaValidationError, MavuntaWebhookSignatureError, WebhooksResource, errorFromResponse, verifyWebhook };
107
+ //# sourceMappingURL=chunk-DNIDKPOQ.js.map
108
+ //# sourceMappingURL=chunk-DNIDKPOQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/webhooks/verify.ts","../src/webhooks/index.ts"],"names":[],"mappings":"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","file":"chunk-DNIDKPOQ.js","sourcesContent":["/**\r\n * Error hierarchy for the Mavunta SDK.\r\n *\r\n * Every failure surfaces as a {@link MavuntaError} subclass so callers can\r\n * branch on the type and read `code` / `requestId` for support. The shape\r\n * mirrors the API's error envelope (spec §24):\r\n *\r\n * { error: { type, code, message, param, request_id, docs_url } }\r\n */\r\n\r\nexport interface MavuntaErrorBody {\r\n type?: string\r\n code?: string\r\n message?: string\r\n param?: string | null\r\n request_id?: string\r\n docs_url?: string\r\n}\r\n\r\nexport interface MavuntaErrorInit {\r\n type?: string\r\n code?: string\r\n param?: string | null\r\n requestId?: string | null\r\n statusCode?: number | null\r\n docsUrl?: string | null\r\n}\r\n\r\n/** Base class for everything thrown by the SDK. */\r\nexport class MavuntaError extends Error {\r\n readonly type: string\r\n readonly code: string\r\n readonly param: string | null\r\n readonly requestId: string | null\r\n readonly statusCode: number | null\r\n readonly docsUrl: string | null\r\n\r\n constructor(message: string, init: MavuntaErrorInit = {}) {\r\n super(message)\r\n this.name = this.constructor.name\r\n this.type = init.type ?? 'api_error'\r\n this.code = init.code ?? 'unknown'\r\n this.param = init.param ?? null\r\n this.requestId = init.requestId ?? null\r\n this.statusCode = init.statusCode ?? null\r\n this.docsUrl = init.docsUrl ?? null\r\n }\r\n}\r\n\r\n/** A non-2xx response that does not map to a more specific class. */\r\nexport class MavuntaAPIError extends MavuntaError {}\r\n/** 401: missing, invalid, or revoked API key. */\r\nexport class MavuntaAuthenticationError extends MavuntaError {}\r\n/** 403: the key is valid but lacks the scope for this action. */\r\nexport class MavuntaPermissionError extends MavuntaError {}\r\n/** 400 / 422: the request was malformed or failed validation. */\r\nexport class MavuntaValidationError extends MavuntaError {}\r\n/** 409 on a money-moving create with a reused idempotency key. */\r\nexport class MavuntaIdempotencyError extends MavuntaError {}\r\n/** A network failure before a response was received. */\r\nexport class MavuntaConnectionError extends MavuntaError {}\r\n/** The request exceeded the configured timeout. */\r\nexport class MavuntaTimeoutError extends MavuntaError {}\r\n/** Webhook signature verification failed. */\r\nexport class MavuntaWebhookSignatureError extends MavuntaError {}\r\n\r\n/** 429: too many requests. `retryAfterMs` is parsed from `retry-after`. */\r\nexport class MavuntaRateLimitError extends MavuntaError {\r\n readonly retryAfterMs: number | null\r\n constructor(message: string, init: MavuntaErrorInit & { retryAfterMs?: number | null } = {}) {\r\n super(message, init)\r\n this.retryAfterMs = init.retryAfterMs ?? null\r\n }\r\n}\r\n\r\n/**\r\n * Build the right error subclass from an HTTP status + the parsed body. Used by\r\n * the client for every non-2xx response.\r\n */\r\nexport function errorFromResponse(\r\n status: number,\r\n body: MavuntaErrorBody | null,\r\n retryAfterMs: number | null = null,\r\n): MavuntaError {\r\n const init: MavuntaErrorInit = {\r\n type: body?.type,\r\n code: body?.code,\r\n param: body?.param ?? null,\r\n requestId: body?.request_id ?? null,\r\n statusCode: status,\r\n docsUrl: body?.docs_url ?? null,\r\n }\r\n const message = body?.message ?? `Mavunta API error (HTTP ${status})`\r\n\r\n if (status === 401) return new MavuntaAuthenticationError(message, init)\r\n if (status === 403) return new MavuntaPermissionError(message, init)\r\n if (status === 429) return new MavuntaRateLimitError(message, { ...init, retryAfterMs })\r\n if (status === 409 || body?.type === 'idempotency_error')\r\n return new MavuntaIdempotencyError(message, init)\r\n if (status === 400 || status === 422 || body?.type === 'validation_error')\r\n return new MavuntaValidationError(message, init)\r\n return new MavuntaAPIError(message, init)\r\n}\r\n","import { createHmac, timingSafeEqual } from 'node:crypto'\r\nimport { MavuntaWebhookSignatureError } from '../errors.js'\r\nimport type { WebhookEvent } from '../types.js'\r\n\r\nexport interface VerifyWebhookParams {\r\n /** The raw request body, exactly as received (string or Buffer). Never the\r\n * parsed JSON object: re-serializing changes the bytes and breaks the HMAC. */\r\n payload: string | Buffer\r\n /** The `Mavunta-Signature` header (hex HMAC-SHA256). */\r\n signature: string\r\n /** The `Mavunta-Timestamp` header (unix seconds). */\r\n timestamp: string\r\n /** The endpoint's signing secret (`whsec_…`). */\r\n secret: string\r\n /** Reject timestamps older/newer than this many seconds (default 300). */\r\n toleranceSec?: number\r\n}\r\n\r\nfunction fail(message: string): never {\r\n throw new MavuntaWebhookSignatureError(message, { type: 'webhook_signature_error', code: 'invalid_signature' })\r\n}\r\n\r\n/**\r\n * Verify a webhook signature and return the parsed event, or throw a\r\n * {@link MavuntaWebhookSignatureError}.\r\n *\r\n * The signature is HMAC-SHA256 (hex) over `\"<timestamp>.<rawBody>\"`, where\r\n * `timestamp` is the `Mavunta-Timestamp` header and the digest is the\r\n * `Mavunta-Signature` header. Always pass the raw body, not parsed JSON.\r\n */\r\nexport function verifyWebhook(params: VerifyWebhookParams): WebhookEvent {\r\n const { signature, timestamp, secret } = params\r\n if (!signature) fail('Missing Mavunta-Signature header.')\r\n if (!timestamp) fail('Missing Mavunta-Timestamp header.')\r\n if (!secret) fail('Missing webhook signing secret.')\r\n\r\n const raw = typeof params.payload === 'string' ? params.payload : params.payload.toString('utf8')\r\n const tolerance = params.toleranceSec ?? 300\r\n\r\n const ts = Number(timestamp)\r\n if (!Number.isFinite(ts)) fail('Invalid Mavunta-Timestamp header.')\r\n if (Math.abs(Date.now() / 1000 - ts) > tolerance) fail('Timestamp outside the tolerance window.')\r\n\r\n const expected = createHmac('sha256', secret).update(`${timestamp}.${raw}`).digest('hex')\r\n let a: Buffer\r\n let b: Buffer\r\n try {\r\n a = Buffer.from(expected, 'hex')\r\n b = Buffer.from(signature, 'hex')\r\n } catch {\r\n fail('Malformed signature.')\r\n }\r\n if (a.length !== b.length || !timingSafeEqual(a, b)) fail('Signature mismatch.')\r\n\r\n return JSON.parse(raw) as WebhookEvent\r\n}\r\n","import { verifyWebhook, type VerifyWebhookParams } from './verify.js'\nimport type { WebhookEvent } from '../types.js'\n\nexport { verifyWebhook } from './verify.js'\nexport type { VerifyWebhookParams } from './verify.js'\n\n/**\n * Webhook helpers, available both as `mavunta.webhooks` and standalone via the\n * `mavunta/webhooks` subpath (handy when you only need verification and\n * not a full client).\n */\nexport class WebhooksResource {\n /** Verify a webhook signature and return the parsed event, or throw. */\n verify(params: VerifyWebhookParams): WebhookEvent {\n return verifyWebhook(params)\n }\n /** Alias of {@link verify}, matching the wording in some platform docs. */\n constructEvent(params: VerifyWebhookParams): WebhookEvent {\n return verifyWebhook(params)\n }\n}\n"]}
@@ -0,0 +1,192 @@
1
+ /** Shared types for the Mavunta SDK. Resource payloads are pragmatic subsets:
2
+ * unknown fields pass through, so a forward-compatible API never breaks a build. */
3
+ type MavuntaEnvironment = 'sandbox' | 'live';
4
+ interface MavuntaOptions {
5
+ /** Your secret (`cwk_…_sk_`) or restricted (`cwk_…_rk_`) key. Never a public key. */
6
+ secretKey: string;
7
+ /** Informational; the key prefix selects test vs live server-side. */
8
+ environment?: MavuntaEnvironment;
9
+ /** Override the API base URL (default `https://api.mavunta.com/v1`). */
10
+ baseUrl?: string;
11
+ /** Per-request timeout in ms (default 30000). */
12
+ timeoutMs?: number;
13
+ /** Max automatic retries for safe/idempotent requests (default 2). */
14
+ maxRetries?: number;
15
+ }
16
+ /** Per-call options for money-moving create requests. */
17
+ interface RequestOptions {
18
+ /** Replaying the same key returns the original result instead of acting twice. */
19
+ idempotencyKey?: string;
20
+ }
21
+ interface ListResponse<T> {
22
+ object: 'list';
23
+ data: T[];
24
+ has_more: boolean;
25
+ }
26
+ interface EnvelopeFields {
27
+ request_id?: string;
28
+ environment?: MavuntaEnvironment;
29
+ livemode?: boolean;
30
+ }
31
+ type SettlementCurrency = 'USDT' | 'USDC' | 'BTC' | 'ETH' | 'SOL';
32
+ type PaymentMethod = 'mavunta_balance' | 'mpesa' | 'card' | 'paypal' | 'external_wallet';
33
+ interface PaymentIntent extends EnvelopeFields {
34
+ id: string;
35
+ object?: 'payment_intent';
36
+ status: string;
37
+ amount: string;
38
+ currency: string;
39
+ settlement_currency: string;
40
+ pay_amount: string | null;
41
+ pay_asset?: string;
42
+ checkout_url: string;
43
+ merchant_reference: string | null;
44
+ description: string | null;
45
+ metadata: Record<string, unknown> | null;
46
+ expires_at: string;
47
+ }
48
+ interface CreatePaymentIntentParams {
49
+ amount: string;
50
+ currency: string;
51
+ settlement_currency?: SettlementCurrency;
52
+ description?: string;
53
+ merchant_reference?: string;
54
+ payment_methods?: PaymentMethod[];
55
+ customer?: {
56
+ id?: string;
57
+ email?: string;
58
+ phone?: string;
59
+ };
60
+ metadata?: Record<string, unknown>;
61
+ expires_in_minutes?: number;
62
+ }
63
+ interface PaymentLink extends EnvelopeFields {
64
+ id: string;
65
+ object: 'payment_link';
66
+ url: string;
67
+ qr_code_url?: string;
68
+ status: string;
69
+ paid_count: number;
70
+ max_payments: number | null;
71
+ }
72
+ interface CreatePaymentLinkParams {
73
+ title: string;
74
+ currency: string;
75
+ amount?: string;
76
+ settlement_currency?: SettlementCurrency;
77
+ description?: string;
78
+ max_payments?: number;
79
+ expires_at?: string;
80
+ metadata?: Record<string, unknown>;
81
+ }
82
+ interface Refund extends EnvelopeFields {
83
+ id: string;
84
+ object: 'refund';
85
+ payment_intent: string;
86
+ amount: string;
87
+ asset: string;
88
+ status: 'requested' | 'succeeded' | 'failed';
89
+ reason: string | null;
90
+ created_at: string;
91
+ }
92
+ interface Customer extends EnvelopeFields {
93
+ id: string;
94
+ object: 'customer';
95
+ email: string | null;
96
+ phone: string | null;
97
+ name: string | null;
98
+ metadata: Record<string, unknown> | null;
99
+ created_at: string;
100
+ }
101
+ interface CreateCustomerParams {
102
+ email?: string;
103
+ phone?: string;
104
+ name?: string;
105
+ metadata?: Record<string, unknown>;
106
+ }
107
+ interface Balance extends EnvelopeFields {
108
+ object: 'balance';
109
+ available: Array<{
110
+ asset: string;
111
+ amount: string;
112
+ }>;
113
+ pending: Array<{
114
+ asset: string;
115
+ amount: string;
116
+ }>;
117
+ }
118
+ interface Settlement extends EnvelopeFields {
119
+ id: string;
120
+ object: 'settlement';
121
+ asset: string;
122
+ amount: string;
123
+ status: string;
124
+ created_at: string;
125
+ }
126
+ interface WebhookEndpoint extends EnvelopeFields {
127
+ id: string;
128
+ object: 'webhook_endpoint';
129
+ url: string;
130
+ enabled_events: string[];
131
+ status: string;
132
+ secret?: string;
133
+ created_at: string;
134
+ }
135
+ interface CreateWebhookEndpointParams {
136
+ url: string;
137
+ enabled_events?: string[];
138
+ description?: string;
139
+ }
140
+ interface WebhookEvent extends EnvelopeFields {
141
+ id: string;
142
+ object: 'event';
143
+ type: string;
144
+ api_version: string;
145
+ created_at: string;
146
+ data: Record<string, unknown>;
147
+ }
148
+ interface AuthVerifyResult extends EnvelopeFields {
149
+ merchant_id: string;
150
+ app_id: string | null;
151
+ key_id: string;
152
+ scopes: string[];
153
+ ip_allowed: boolean;
154
+ status: string;
155
+ }
156
+
157
+ interface VerifyWebhookParams {
158
+ /** The raw request body, exactly as received (string or Buffer). Never the
159
+ * parsed JSON object: re-serializing changes the bytes and breaks the HMAC. */
160
+ payload: string | Buffer;
161
+ /** The `Mavunta-Signature` header (hex HMAC-SHA256). */
162
+ signature: string;
163
+ /** The `Mavunta-Timestamp` header (unix seconds). */
164
+ timestamp: string;
165
+ /** The endpoint's signing secret (`whsec_…`). */
166
+ secret: string;
167
+ /** Reject timestamps older/newer than this many seconds (default 300). */
168
+ toleranceSec?: number;
169
+ }
170
+ /**
171
+ * Verify a webhook signature and return the parsed event, or throw a
172
+ * {@link MavuntaWebhookSignatureError}.
173
+ *
174
+ * The signature is HMAC-SHA256 (hex) over `"<timestamp>.<rawBody>"`, where
175
+ * `timestamp` is the `Mavunta-Timestamp` header and the digest is the
176
+ * `Mavunta-Signature` header. Always pass the raw body, not parsed JSON.
177
+ */
178
+ declare function verifyWebhook(params: VerifyWebhookParams): WebhookEvent;
179
+
180
+ /**
181
+ * Webhook helpers, available both as `mavunta.webhooks` and standalone via the
182
+ * `mavunta/webhooks` subpath (handy when you only need verification and
183
+ * not a full client).
184
+ */
185
+ declare class WebhooksResource {
186
+ /** Verify a webhook signature and return the parsed event, or throw. */
187
+ verify(params: VerifyWebhookParams): WebhookEvent;
188
+ /** Alias of {@link verify}, matching the wording in some platform docs. */
189
+ constructEvent(params: VerifyWebhookParams): WebhookEvent;
190
+ }
191
+
192
+ export { type AuthVerifyResult as A, type Balance as B, type CreatePaymentIntentParams as C, type ListResponse as L, type MavuntaOptions as M, type PaymentIntent as P, type RequestOptions as R, type Settlement as S, type VerifyWebhookParams as V, type WebhookEndpoint as W, type CreatePaymentLinkParams as a, type PaymentLink as b, type CreateCustomerParams as c, type Customer as d, type Refund as e, type CreateWebhookEndpointParams as f, type WebhookEvent as g, WebhooksResource as h, type MavuntaEnvironment as i, type PaymentMethod as j, type SettlementCurrency as k, verifyWebhook as v };
@@ -0,0 +1,192 @@
1
+ /** Shared types for the Mavunta SDK. Resource payloads are pragmatic subsets:
2
+ * unknown fields pass through, so a forward-compatible API never breaks a build. */
3
+ type MavuntaEnvironment = 'sandbox' | 'live';
4
+ interface MavuntaOptions {
5
+ /** Your secret (`cwk_…_sk_`) or restricted (`cwk_…_rk_`) key. Never a public key. */
6
+ secretKey: string;
7
+ /** Informational; the key prefix selects test vs live server-side. */
8
+ environment?: MavuntaEnvironment;
9
+ /** Override the API base URL (default `https://api.mavunta.com/v1`). */
10
+ baseUrl?: string;
11
+ /** Per-request timeout in ms (default 30000). */
12
+ timeoutMs?: number;
13
+ /** Max automatic retries for safe/idempotent requests (default 2). */
14
+ maxRetries?: number;
15
+ }
16
+ /** Per-call options for money-moving create requests. */
17
+ interface RequestOptions {
18
+ /** Replaying the same key returns the original result instead of acting twice. */
19
+ idempotencyKey?: string;
20
+ }
21
+ interface ListResponse<T> {
22
+ object: 'list';
23
+ data: T[];
24
+ has_more: boolean;
25
+ }
26
+ interface EnvelopeFields {
27
+ request_id?: string;
28
+ environment?: MavuntaEnvironment;
29
+ livemode?: boolean;
30
+ }
31
+ type SettlementCurrency = 'USDT' | 'USDC' | 'BTC' | 'ETH' | 'SOL';
32
+ type PaymentMethod = 'mavunta_balance' | 'mpesa' | 'card' | 'paypal' | 'external_wallet';
33
+ interface PaymentIntent extends EnvelopeFields {
34
+ id: string;
35
+ object?: 'payment_intent';
36
+ status: string;
37
+ amount: string;
38
+ currency: string;
39
+ settlement_currency: string;
40
+ pay_amount: string | null;
41
+ pay_asset?: string;
42
+ checkout_url: string;
43
+ merchant_reference: string | null;
44
+ description: string | null;
45
+ metadata: Record<string, unknown> | null;
46
+ expires_at: string;
47
+ }
48
+ interface CreatePaymentIntentParams {
49
+ amount: string;
50
+ currency: string;
51
+ settlement_currency?: SettlementCurrency;
52
+ description?: string;
53
+ merchant_reference?: string;
54
+ payment_methods?: PaymentMethod[];
55
+ customer?: {
56
+ id?: string;
57
+ email?: string;
58
+ phone?: string;
59
+ };
60
+ metadata?: Record<string, unknown>;
61
+ expires_in_minutes?: number;
62
+ }
63
+ interface PaymentLink extends EnvelopeFields {
64
+ id: string;
65
+ object: 'payment_link';
66
+ url: string;
67
+ qr_code_url?: string;
68
+ status: string;
69
+ paid_count: number;
70
+ max_payments: number | null;
71
+ }
72
+ interface CreatePaymentLinkParams {
73
+ title: string;
74
+ currency: string;
75
+ amount?: string;
76
+ settlement_currency?: SettlementCurrency;
77
+ description?: string;
78
+ max_payments?: number;
79
+ expires_at?: string;
80
+ metadata?: Record<string, unknown>;
81
+ }
82
+ interface Refund extends EnvelopeFields {
83
+ id: string;
84
+ object: 'refund';
85
+ payment_intent: string;
86
+ amount: string;
87
+ asset: string;
88
+ status: 'requested' | 'succeeded' | 'failed';
89
+ reason: string | null;
90
+ created_at: string;
91
+ }
92
+ interface Customer extends EnvelopeFields {
93
+ id: string;
94
+ object: 'customer';
95
+ email: string | null;
96
+ phone: string | null;
97
+ name: string | null;
98
+ metadata: Record<string, unknown> | null;
99
+ created_at: string;
100
+ }
101
+ interface CreateCustomerParams {
102
+ email?: string;
103
+ phone?: string;
104
+ name?: string;
105
+ metadata?: Record<string, unknown>;
106
+ }
107
+ interface Balance extends EnvelopeFields {
108
+ object: 'balance';
109
+ available: Array<{
110
+ asset: string;
111
+ amount: string;
112
+ }>;
113
+ pending: Array<{
114
+ asset: string;
115
+ amount: string;
116
+ }>;
117
+ }
118
+ interface Settlement extends EnvelopeFields {
119
+ id: string;
120
+ object: 'settlement';
121
+ asset: string;
122
+ amount: string;
123
+ status: string;
124
+ created_at: string;
125
+ }
126
+ interface WebhookEndpoint extends EnvelopeFields {
127
+ id: string;
128
+ object: 'webhook_endpoint';
129
+ url: string;
130
+ enabled_events: string[];
131
+ status: string;
132
+ secret?: string;
133
+ created_at: string;
134
+ }
135
+ interface CreateWebhookEndpointParams {
136
+ url: string;
137
+ enabled_events?: string[];
138
+ description?: string;
139
+ }
140
+ interface WebhookEvent extends EnvelopeFields {
141
+ id: string;
142
+ object: 'event';
143
+ type: string;
144
+ api_version: string;
145
+ created_at: string;
146
+ data: Record<string, unknown>;
147
+ }
148
+ interface AuthVerifyResult extends EnvelopeFields {
149
+ merchant_id: string;
150
+ app_id: string | null;
151
+ key_id: string;
152
+ scopes: string[];
153
+ ip_allowed: boolean;
154
+ status: string;
155
+ }
156
+
157
+ interface VerifyWebhookParams {
158
+ /** The raw request body, exactly as received (string or Buffer). Never the
159
+ * parsed JSON object: re-serializing changes the bytes and breaks the HMAC. */
160
+ payload: string | Buffer;
161
+ /** The `Mavunta-Signature` header (hex HMAC-SHA256). */
162
+ signature: string;
163
+ /** The `Mavunta-Timestamp` header (unix seconds). */
164
+ timestamp: string;
165
+ /** The endpoint's signing secret (`whsec_…`). */
166
+ secret: string;
167
+ /** Reject timestamps older/newer than this many seconds (default 300). */
168
+ toleranceSec?: number;
169
+ }
170
+ /**
171
+ * Verify a webhook signature and return the parsed event, or throw a
172
+ * {@link MavuntaWebhookSignatureError}.
173
+ *
174
+ * The signature is HMAC-SHA256 (hex) over `"<timestamp>.<rawBody>"`, where
175
+ * `timestamp` is the `Mavunta-Timestamp` header and the digest is the
176
+ * `Mavunta-Signature` header. Always pass the raw body, not parsed JSON.
177
+ */
178
+ declare function verifyWebhook(params: VerifyWebhookParams): WebhookEvent;
179
+
180
+ /**
181
+ * Webhook helpers, available both as `mavunta.webhooks` and standalone via the
182
+ * `mavunta/webhooks` subpath (handy when you only need verification and
183
+ * not a full client).
184
+ */
185
+ declare class WebhooksResource {
186
+ /** Verify a webhook signature and return the parsed event, or throw. */
187
+ verify(params: VerifyWebhookParams): WebhookEvent;
188
+ /** Alias of {@link verify}, matching the wording in some platform docs. */
189
+ constructEvent(params: VerifyWebhookParams): WebhookEvent;
190
+ }
191
+
192
+ export { type AuthVerifyResult as A, type Balance as B, type CreatePaymentIntentParams as C, type ListResponse as L, type MavuntaOptions as M, type PaymentIntent as P, type RequestOptions as R, type Settlement as S, type VerifyWebhookParams as V, type WebhookEndpoint as W, type CreatePaymentLinkParams as a, type PaymentLink as b, type CreateCustomerParams as c, type Customer as d, type Refund as e, type CreateWebhookEndpointParams as f, type WebhookEvent as g, WebhooksResource as h, type MavuntaEnvironment as i, type PaymentMethod as j, type SettlementCurrency as k, verifyWebhook as v };