@jokoor/sdk
Version:
Official Jokoor API SDK for JavaScript/TypeScript - SMS, Payments, and more
1,823 lines (1,803 loc) • 83.8 kB
JavaScript
// src/http-client.ts
import axios from "axios";
import axiosRetry from "axios-retry";
// src/types/result.ts
function ok(data) {
return { data, error: null };
}
function err(error) {
return { data: null, error };
}
function isOk(result) {
return result.error === null;
}
function isErr(result) {
return result.error !== null;
}
function unwrap(result) {
if (isErr(result)) {
throw new Error(result.error);
}
return result.data;
}
function unwrapOr(result, defaultValue) {
if (isErr(result)) {
return defaultValue;
}
return result.data;
}
function map(result, fn) {
if (isErr(result)) {
return result;
}
return ok(fn(result.data));
}
function chain(result, fn) {
if (isErr(result)) {
return result;
}
return fn(result.data);
}
async function fromPromise(promise) {
try {
const data = await promise;
return ok(data);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return err(message);
}
}
function all(results) {
const data = [];
for (const result of results) {
if (isErr(result)) {
return result;
}
data.push(result.data);
}
return ok(data);
}
// src/utils/case-converter.ts
function toSnakeCase(str) {
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
}
function toCamelCase(str) {
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}
function keysToSnakeCase(obj) {
if (obj === null || obj === void 0) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => keysToSnakeCase(item));
}
if (typeof obj === "object" && obj !== null) {
const converted = {};
for (const [key, value] of Object.entries(obj)) {
const snakeKey = toSnakeCase(key);
converted[snakeKey] = keysToSnakeCase(value);
}
return converted;
}
return obj;
}
function keysToCamelCase(obj) {
if (obj === null || obj === void 0) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => keysToCamelCase(item));
}
if (typeof obj === "object" && obj !== null) {
const converted = {};
for (const [key, value] of Object.entries(obj)) {
const camelKey = toCamelCase(key);
converted[camelKey] = keysToCamelCase(value);
}
return converted;
}
return obj;
}
// src/http-client.ts
var DEFAULT_BASE_URL = "https://api.jokoor.com/v1";
var DEFAULT_TIMEOUT = 3e4;
var DEFAULT_MAX_RETRIES = 3;
var HttpClient = class {
client;
debug;
constructor(config) {
this.debug = config.debug || false;
this.client = axios.create({
baseURL: config.baseURL || DEFAULT_BASE_URL,
timeout: config.timeout || DEFAULT_TIMEOUT,
headers: {
Authorization: `Bearer ${config.apiKey}`,
"Content-Type": "application/json",
"User-Agent": "@jokoor/sdk/1.0.0",
"X-SDK-Version": "1.0.0",
"X-SDK-Language": "typescript"
}
});
axiosRetry(this.client, {
retries: config.maxRetries || DEFAULT_MAX_RETRIES,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: (error) => {
return axiosRetry.isNetworkOrIdempotentRequestError(error) || error.response?.status !== void 0 && error.response.status >= 500;
},
onRetry: (retryCount, error) => {
if (this.debug) {
console.warn(`Retry attempt ${retryCount} for ${error.config?.url}`);
}
}
});
this.client.interceptors.request.use(
(config2) => {
if (config2.data && typeof config2.data === "object" && !(config2.data instanceof FormData)) {
config2.data = keysToSnakeCase(config2.data);
}
if (this.debug) {
console.log(
`[Request] ${config2.method?.toUpperCase()} ${config2.url}`
);
if (config2.data && !(config2.data instanceof FormData)) {
console.log("[Request Body]", JSON.stringify(config2.data, null, 2));
} else if (config2.data instanceof FormData) {
console.log("[Request Body] FormData (file upload)");
}
}
return config2;
},
(error) => {
if (this.debug) {
console.error("[Request Error]", error);
}
return Promise.reject(error);
}
);
if (this.debug) {
this.client.interceptors.response.use(
(response) => {
console.log(`[Response] ${response.status} ${response.config.url}`);
if (response.data) {
console.log(
"[Response Body]",
JSON.stringify(response.data, null, 2)
);
}
return response;
},
(error) => {
if (error.response) {
console.error(
`[Response Error] ${error.response.status} ${error.config?.url}`
);
console.error("[Error Body]", error.response.data);
} else {
console.error("[Network Error]", error.message);
}
return Promise.reject(error);
}
);
}
}
/**
* Make a GET request
*/
async get(url, config) {
return this.request({ method: "GET", url, ...config });
}
/**
* Make a POST request
*/
async post(url, data, config) {
return this.request({ method: "POST", url, data, ...config });
}
/**
* Make a PUT request
*/
async put(url, data, config) {
return this.request({ method: "PUT", url, data, ...config });
}
/**
* Make a DELETE request
*/
async delete(url, config) {
return this.request({ method: "DELETE", url, ...config });
}
/**
* Generic request method with error handling
*/
async request(config) {
try {
const response = await this.client.request(config);
if (response.data.error) {
return err(response.data.error);
}
if (response.data.data !== void 0) {
const camelCaseData2 = keysToCamelCase(response.data.data);
return ok(camelCaseData2);
}
const camelCaseData = keysToCamelCase(response.data);
return ok(camelCaseData);
} catch (error) {
return err(this.extractErrorMessage(error));
}
}
/**
* Extract error message from axios error
*/
extractErrorMessage(error) {
if (axios.isAxiosError(error)) {
const axiosError = error;
if (axiosError.response?.data?.error) {
return String(axiosError.response.data.error);
}
const status = axiosError.response?.status;
if (status === 404) {
return "Resource not found";
} else if (status === 401) {
return "Authentication failed - invalid API key";
} else if (status === 403) {
return "Permission denied";
} else if (status === 429) {
return "Rate limit exceeded";
} else if (status === 400) {
return axiosError.response?.data?.error || "Bad request - invalid parameters";
} else if (status === 500) {
return "Internal server error";
} else if (status === 502 || status === 503 || status === 504) {
return "Service temporarily unavailable";
} else if (status) {
const statusText = axiosError.response?.statusText || "Unknown error";
return `API Error: ${status} ${statusText}`;
}
if (axiosError.code === "ECONNREFUSED") {
return "Unable to connect to API server";
} else if (axiosError.code === "ENOTFOUND") {
return "API server not found";
} else if (axiosError.code === "ETIMEDOUT") {
return "Request timed out";
} else if (axiosError.code === "ECONNRESET") {
return "Connection was reset";
}
return axiosError.message || "An unexpected error occurred";
}
if (error instanceof Error) {
return error.message;
}
return String(error);
}
};
// src/errors.ts
var JokoorError = class _JokoorError extends Error {
constructor(message) {
super(message);
this.name = "JokoorError";
if (Error.captureStackTrace) {
Error.captureStackTrace(this, _JokoorError);
}
}
};
var JokoorAPIError = class extends JokoorError {
statusCode;
code;
response;
constructor(message, statusCode, code, response) {
super(message);
this.name = "JokoorAPIError";
this.statusCode = statusCode;
this.code = code;
this.response = response;
}
};
var JokoorAuthenticationError = class extends JokoorError {
constructor(message = "Authentication failed") {
super(message);
this.name = "JokoorAuthenticationError";
}
};
var JokoorPermissionError = class extends JokoorError {
constructor(message = "Permission denied") {
super(message);
this.name = "JokoorPermissionError";
}
};
var JokoorNotFoundError = class extends JokoorError {
constructor(message = "Resource not found") {
super(message);
this.name = "JokoorNotFoundError";
}
};
var JokoorRateLimitError = class extends JokoorError {
constructor(message = "Rate limit exceeded") {
super(message);
this.name = "JokoorRateLimitError";
}
};
var JokoorValidationError = class extends JokoorError {
errors;
constructor(message = "Validation failed", errors) {
super(message);
this.name = "JokoorValidationError";
this.errors = errors;
}
};
var JokoorNetworkError = class extends JokoorError {
constructor(message = "Network error occurred") {
super(message);
this.name = "JokoorNetworkError";
}
};
var JokoorTimeoutError = class extends JokoorError {
constructor(message = "Request timed out") {
super(message);
this.name = "JokoorTimeoutError";
}
};
function isJokoorError(error) {
return error instanceof JokoorError;
}
// src/resources/base.ts
var BaseResource = class {
client;
constructor(client) {
this.client = client;
}
/**
* Extract paginated data from API response
*/
extractPaginatedData(result) {
if (result.error) {
return { data: null, error: result.error };
}
const data = result.data;
return {
data: {
items: data.items || [],
count: data.count || 0,
offset: data.offset || 0,
limit: data.limit || 20
},
error: null
};
}
};
// src/resources/sms.ts
var SMS = class extends BaseResource {
/**
* Send an SMS message
*
* @param params - SMS parameters
* @returns SMS message details
*
* @example
* ```typescript
* const { data, error } = await jokoor.sms.send({
* recipientPhone: '+2207123456',
* messageBody: 'Hello from Jokoor!'
* });
* ```
*/
async send(params) {
if (!params.recipientPhone && !params.contactId) {
return {
data: null,
error: "Either recipientPhone or contactId must be provided"
};
}
if (!params.messageBody && !params.templateId) {
return {
data: null,
error: "Either messageBody or templateId must be provided"
};
}
return this.client.post("/sms", params);
}
/**
* Get SMS message details
*
* @param id - SMS message ID
* @returns SMS message details
*/
async get(id) {
if (!id) {
return { data: null, error: "SMS message ID is required" };
}
return this.client.get(`/sms/${id}`);
}
/**
* List SMS messages
*
* @param options - List options
* @returns Paginated list of SMS messages
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0)
params.set("offset", String(options.offset));
if (options?.limit !== void 0)
params.set("limit", String(options.limit));
if (options?.status) params.set("status", options.status);
if (options?.contactId) params.set("contact_id", options.contactId);
if (options?.startDate) params.set("start_date", options.startDate);
if (options?.endDate) params.set("end_date", options.endDate);
const query = params.toString();
const url = query ? `/sms?${query}` : "/sms";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Resend a failed SMS message
*
* @param id - SMS message ID
* @returns Updated SMS message
*/
async resend(id) {
if (!id) {
return { data: null, error: "SMS message ID is required" };
}
return this.client.post(`/sms/${id}/resend`);
}
/**
* Send a draft SMS message
*
* @param id - Draft SMS message ID
* @param scheduledAt - Optional scheduled time
* @returns SMS message details
*/
async sendDraft(id, scheduledAt) {
if (!id) {
return { data: null, error: "SMS message ID is required" };
}
const body = scheduledAt ? { scheduledAt } : void 0;
return this.client.post(`/sms/${id}/send-draft`, body);
}
/**
* Batch resend failed messages
*
* @param messageIds - Array of message IDs to resend
* @returns Batch resend results
*/
async resendBatch(messageIds) {
if (!messageIds || messageIds.length === 0) {
return { data: null, error: "At least one message ID is required" };
}
return this.client.post("/sms/resend-failed", { messageIds });
}
};
// src/resources/templates.ts
var SMSTemplates = class extends BaseResource {
/**
* Create a new SMS template
*
* @param params - Template parameters
* @returns Created template
*
* @example
* ```typescript
* const { data, error } = await jokoor.smsTemplates.create({
* name: 'Welcome Message',
* body: 'Hello {{name}}, welcome to our service!',
* description: 'Welcome message for new users'
* });
* ```
*/
async create(params) {
if (!params.name) {
return { data: null, error: "Template name is required" };
}
if (!params.body) {
return { data: null, error: "Template body is required" };
}
return this.client.post("/sms/templates", params);
}
/**
* Get template details
*
* @param id - Template ID
* @returns Template details
*/
async get(id) {
if (!id) {
return { data: null, error: "Template ID is required" };
}
return this.client.get(`/sms/templates/${id}`);
}
/**
* List SMS templates
*
* @param options - List options
* @returns Paginated list of templates
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0) params.set("offset", String(options.offset));
if (options?.limit !== void 0) params.set("limit", String(options.limit));
const query = params.toString();
const url = query ? `/sms/templates?${query}` : "/sms/templates";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Update a template
*
* @param id - Template ID
* @param params - Update parameters
* @returns Updated template
*/
async update(id, params) {
if (!id) {
return { data: null, error: "Template ID is required" };
}
if (Object.keys(params).length === 0) {
return { data: null, error: "At least one field must be provided for update" };
}
return this.client.put(`/sms/templates/${id}`, params);
}
/**
* Delete a template
*
* @param id - Template ID
* @returns Success result
*/
async delete(id) {
if (!id) {
return { data: null, error: "Template ID is required" };
}
return this.client.delete(`/sms/templates/${id}`);
}
};
// src/resources/contacts.ts
var SMSContacts = class extends BaseResource {
/**
* Create a new contact
*
* @param params - Contact parameters
* @returns Created contact
*
* @example
* ```typescript
* const { data, error } = await jokoor.smsContacts.create({
* phoneNumber: '+2207123456',
* firstName: 'John',
* lastName: 'Doe',
* email: 'john@example.com'
* });
* ```
*/
async create(params) {
if (!params.phoneNumber) {
return { data: null, error: "Phone number is required" };
}
return this.client.post("/sms/contacts", params);
}
/**
* Get contact details
*
* @param id - Contact ID
* @returns Contact details
*/
async get(id) {
if (!id) {
return { data: null, error: "Contact ID is required" };
}
return this.client.get(`/sms/contacts/${id}`);
}
/**
* List contacts
*
* @param options - List options
* @returns Paginated list of contacts
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0)
params.set("offset", String(options.offset));
if (options?.limit !== void 0)
params.set("limit", String(options.limit));
if (options?.groupId) params.set("group_id", options.groupId);
const query = params.toString();
const url = query ? `/sms/contacts?${query}` : "/sms/contacts";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Update a contact
*
* @param id - Contact ID
* @param params - Update parameters
* @returns Updated contact
*/
async update(id, params) {
if (!id) {
return { data: null, error: "Contact ID is required" };
}
if (Object.keys(params).length === 0) {
return {
data: null,
error: "At least one field must be provided for update"
};
}
return this.client.put(`/sms/contacts/${id}`, params);
}
/**
* Delete a contact
*
* @param id - Contact ID
* @returns Success result
*/
async delete(id) {
if (!id) {
return { data: null, error: "Contact ID is required" };
}
return this.client.delete(`/sms/contacts/${id}`);
}
};
// src/resources/contact-groups.ts
var SMSContactGroups = class extends BaseResource {
/**
* Create a new contact group
*
* @param params - Group parameters
* @returns Created group
*
* @example
* ```typescript
* const { data, error } = await jokoor.smsContactGroups.create({
* name: 'VIP Customers',
* description: 'High-value customers'
* });
* ```
*/
async create(params) {
if (!params.name) {
return { data: null, error: "Group name is required" };
}
return this.client.post("/sms/groups", params);
}
/**
* Get group details
*
* @param id - Group ID
* @returns Group details
*/
async get(id) {
if (!id) {
return { data: null, error: "Group ID is required" };
}
return this.client.get(`/sms/groups/${id}`);
}
/**
* List contact groups
*
* @param options - List options
* @returns Paginated list of groups
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0)
params.set("offset", String(options.offset));
if (options?.limit !== void 0)
params.set("limit", String(options.limit));
const query = params.toString();
const url = query ? `/sms/groups?${query}` : "/sms/groups";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Update a group
*
* @param id - Group ID
* @param params - Update parameters
* @returns Updated group
*/
async update(id, params) {
if (!id) {
return { data: null, error: "Group ID is required" };
}
if (Object.keys(params).length === 0) {
return {
data: null,
error: "At least one field must be provided for update"
};
}
return this.client.put(`/sms/groups/${id}`, params);
}
/**
* Delete a group
*
* @param id - Group ID
* @returns Success result
*/
async delete(id) {
if (!id) {
return { data: null, error: "Group ID is required" };
}
return this.client.delete(`/sms/groups/${id}`);
}
/**
* Add contacts to a group
*
* @param groupId - Group ID
* @param contactIds - Array of contact IDs to add
* @returns Success result
*/
async addContacts(groupId, contactIds) {
if (!groupId) {
return { data: null, error: "Group ID is required" };
}
if (!contactIds || contactIds.length === 0) {
return { data: null, error: "At least one contact ID is required" };
}
return this.client.post(`/sms/groups/${groupId}/contacts`, {
contactIds
});
}
/**
* Remove contacts from a group
*
* @param groupId - Group ID
* @param contactIds - Array of contact IDs to remove
* @returns Success result
*/
async removeContacts(groupId, contactIds) {
if (!groupId) {
return { data: null, error: "Group ID is required" };
}
if (!contactIds || contactIds.length === 0) {
return { data: null, error: "At least one contact ID is required" };
}
return this.client.delete(`/sms/groups/${groupId}/contacts`, {
data: { contactIds }
});
}
};
// src/resources/sender-ids.ts
var SMSSenderIDs = class extends BaseResource {
/**
* Apply for a custom sender ID
*
* @param params - Sender ID parameters
* @returns Created sender ID application
*
* @example
* ```typescript
* const { data, error } = await jokoor.smsSenderIds.create({
* senderId: 'MYCOMPANY',
* purpose: 'Business notifications',
* useCase: 'Sending transaction alerts to customers'
* });
* ```
*/
async create(params) {
if (!params.senderId) {
return { data: null, error: "Sender ID is required" };
}
if (params.senderId.length < 3 || params.senderId.length > 11) {
return {
data: null,
error: "Sender ID must be between 3 and 11 characters"
};
}
return this.client.post("/sms/senders", params);
}
/**
* Get sender ID details
*
* @param id - Sender ID record ID
* @returns Sender ID details
*/
async get(id) {
if (!id) {
return { data: null, error: "Sender ID is required" };
}
return this.client.get(`/sms/senders/${id}`);
}
/**
* List sender IDs
*
* @param options - List options
* @returns Paginated list of sender IDs
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0)
params.set("offset", String(options.offset));
if (options?.limit !== void 0)
params.set("limit", String(options.limit));
if (options?.status) params.set("status", options.status);
const query = params.toString();
const url = query ? `/sms/senders?${query}` : "/sms/senders";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Update a sender ID application
*
* @param id - Sender ID record ID
* @param params - Update parameters
* @returns Updated sender ID
*/
async update(id, params) {
if (!id) {
return { data: null, error: "Sender ID is required" };
}
if (Object.keys(params).length === 0) {
return {
data: null,
error: "At least one field must be provided for update"
};
}
return this.client.put(`/sms/senders/${id}`, params);
}
/**
* Delete a sender ID
*
* @param id - Sender ID record ID
* @returns Success result
*/
async delete(id) {
if (!id) {
return { data: null, error: "Sender ID is required" };
}
return this.client.delete(`/sms/senders/${id}`);
}
/**
* Set a sender ID as default
*
* @param id - Sender ID record ID
* @returns Success result
*/
async setDefault(id) {
if (!id) {
return { data: null, error: "Sender ID is required" };
}
return this.client.post(`/sms/senders/${id}/set-default`);
}
};
// src/resources/campaigns.ts
var SMSCampaigns = class extends BaseResource {
/**
* Create a new SMS campaign
*
* @param params - Campaign parameters
* @returns Created campaign
*
* @example
* ```typescript
* const { data, error } = await jokoor.smsCampaigns.create({
* name: 'Flash Sale Alert',
* messageBody: 'Flash sale! 50% off everything today only!',
* groupIds: ['grp_123', 'grp_456']
* });
* ```
*/
async create(params) {
if (!params.name) {
return { data: null, error: "Campaign name is required" };
}
if (!params.messageBody && !params.templateId) {
return {
data: null,
error: "Either messageBody or templateId must be provided"
};
}
return this.client.post("/sms/campaigns", params);
}
/**
* Get campaign details
*
* @param id - Campaign ID
* @returns Campaign details
*/
async get(id) {
if (!id) {
return { data: null, error: "Campaign ID is required" };
}
return this.client.get(`/sms/campaigns/${id}`);
}
/**
* List campaigns
*
* @param options - List options
* @returns Paginated list of campaigns
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0)
params.set("offset", String(options.offset));
if (options?.limit !== void 0)
params.set("limit", String(options.limit));
if (options?.status) params.set("status", options.status);
const query = params.toString();
const url = query ? `/sms/campaigns?${query}` : "/sms/campaigns";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Update a campaign (only draft campaigns can be updated)
*
* @param id - Campaign ID
* @param params - Update parameters
* @returns Updated campaign
*/
async update(id, params) {
if (!id) {
return { data: null, error: "Campaign ID is required" };
}
if (Object.keys(params).length === 0) {
return {
data: null,
error: "At least one field must be provided for update"
};
}
return this.client.put(`/sms/campaigns/${id}`, params);
}
/**
* Delete a campaign (only draft campaigns can be deleted)
*
* @param id - Campaign ID
* @returns Success result
*/
async delete(id) {
if (!id) {
return { data: null, error: "Campaign ID is required" };
}
return this.client.delete(`/sms/campaigns/${id}`);
}
/**
* Send a campaign immediately or schedule it
*
* @param id - Campaign ID
* @param scheduledAt - Optional scheduled time
* @returns Updated campaign
*/
async send(id, scheduledAt) {
if (!id) {
return { data: null, error: "Campaign ID is required" };
}
const body = scheduledAt ? { scheduledAt } : void 0;
return this.client.post(`/sms/campaigns/${id}/send`, body);
}
/**
* Send a draft campaign
*
* @param id - Campaign ID
* @param scheduledAt - Optional scheduled time
* @returns Updated campaign
*/
async sendDraft(id, scheduledAt) {
if (!id) {
return { data: null, error: "Campaign ID is required" };
}
const body = scheduledAt ? { scheduledAt } : void 0;
return this.client.post(
`/sms/campaigns/${id}/send-draft`,
body
);
}
/**
* Send campaign asynchronously (for large campaigns)
*
* @param id - Campaign ID
* @param scheduledAt - Optional scheduled time
* @returns Accepted response
*/
async sendAsync(id, scheduledAt) {
if (!id) {
return { data: null, error: "Campaign ID is required" };
}
const body = scheduledAt ? { scheduledAt } : void 0;
return this.client.post(`/sms/campaigns/${id}/send-async`, body);
}
/**
* Get campaign messages
*
* @param id - Campaign ID
* @param options - List options
* @returns Paginated list of campaign messages
*/
async getMessages(id, options) {
if (!id) {
return { data: null, error: "Campaign ID is required" };
}
const params = new URLSearchParams();
if (options?.offset !== void 0)
params.set("offset", String(options.offset));
if (options?.limit !== void 0)
params.set("limit", String(options.limit));
if (options?.status) params.set("status", options.status);
const query = params.toString();
const url = query ? `/sms/campaigns/${id}/messages?${query}` : `/sms/campaigns/${id}/messages`;
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Get campaign statistics
*
* @param id - Campaign ID
* @returns Campaign statistics
*/
async getStatistics(id) {
if (!id) {
return { data: null, error: "Campaign ID is required" };
}
return this.client.get(`/sms/campaigns/${id}/statistics`);
}
/**
* Resend failed campaign messages
*
* @param id - Campaign ID
* @returns Batch resend results
*/
async resendFailed(id) {
if (!id) {
return { data: null, error: "Campaign ID is required" };
}
return this.client.post(`/sms/campaigns/${id}/resend-failed`);
}
};
// src/resources/payment-links.ts
var PaymentLinks = class extends BaseResource {
/**
* Create a new payment link
*
* @param params - Payment link parameters
* @returns Created payment link
*
* @example
* ```typescript
* const { data, error } = await jokoor.paymentLinks.create({
* title: 'Product Purchase',
* description: 'Premium subscription',
* amount: '50.00',
* currency: 'GMD',
* paymentMethods: ['wave', 'card']
* });
* ```
*/
async create(params) {
if (!params.title) {
return { data: null, error: "Title is required" };
}
if (!params.amount) {
return { data: null, error: "Amount is required" };
}
return this.client.post("/pay/payment-links", params);
}
/**
* Get payment link details
*
* @param id - Payment link ID
* @returns Payment link details
*/
async get(id) {
if (!id) {
return { data: null, error: "Payment link ID is required" };
}
return this.client.get(`/pay/payment-links/${id}`);
}
/**
* List payment links
*
* @param options - List options
* @returns Paginated list of payment links
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0)
params.set("offset", String(options.offset));
if (options?.limit !== void 0)
params.set("limit", String(options.limit));
if (options?.status) params.set("status", options.status);
const query = params.toString();
const url = query ? `/pay/payment-links?${query}` : "/pay/payment-links";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Update a payment link
*
* @param id - Payment link ID
* @param params - Update parameters
* @returns Updated payment link
*/
async update(id, params) {
if (!id) {
return { data: null, error: "Payment link ID is required" };
}
if (Object.keys(params).length === 0) {
return {
data: null,
error: "At least one field must be provided for update"
};
}
return this.client.put(`/pay/payment-links/${id}`, params);
}
/**
* Delete a payment link
*
* @param id - Payment link ID
* @returns Success result
*/
async delete(id) {
if (!id) {
return { data: null, error: "Payment link ID is required" };
}
return this.client.delete(`/pay/payment-links/${id}`);
}
};
// src/resources/invoices.ts
var Invoices = class extends BaseResource {
/**
* Create a new invoice
*
* @param params - Invoice parameters
* @returns Created invoice
*
* @example
* ```typescript
* const { data, error } = await jokoor.invoices.create({
* customerEmail: 'customer@example.com',
* customerName: 'John Doe',
* items: [
* { description: 'Consulting Services', quantity: 10, unitPrice: '50.00', amount: '500.00' },
* { description: 'Setup Fee', quantity: 1, unitPrice: '100.00', amount: '100.00' }
* ],
* currency: 'GMD',
* dueDate: '2024-12-31'
* });
* ```
*/
async create(params) {
if (!params.items || params.items.length === 0) {
return { data: null, error: "At least one invoice item is required" };
}
return this.client.post("/pay/invoices", params);
}
/**
* Get invoice details
*
* @param id - Invoice ID
* @returns Invoice details
*/
async get(id) {
if (!id) {
return { data: null, error: "Invoice ID is required" };
}
return this.client.get(`/pay/invoices/${id}`);
}
/**
* List invoices
*
* @param options - List options
* @returns Paginated list of invoices
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0)
params.set("offset", String(options.offset));
if (options?.limit !== void 0)
params.set("limit", String(options.limit));
if (options?.status) params.set("status", options.status);
const query = params.toString();
const url = query ? `/pay/invoices?${query}` : "/pay/invoices";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Update an invoice (only draft invoices can be updated)
*
* @param id - Invoice ID
* @param params - Update parameters
* @returns Updated invoice
*/
async update(id, params) {
if (!id) {
return { data: null, error: "Invoice ID is required" };
}
if (Object.keys(params).length === 0) {
return {
data: null,
error: "At least one field must be provided for update"
};
}
return this.client.put(`/pay/invoices/${id}`, params);
}
/**
* Finalize an invoice (makes it ready to send)
*
* @param id - Invoice ID
* @returns Finalized invoice
*/
async finalize(id) {
if (!id) {
return { data: null, error: "Invoice ID is required" };
}
return this.client.post(`/pay/invoices/${id}/finalize`);
}
/**
* Send an invoice to the customer
*
* @param id - Invoice ID
* @returns Success result
*/
async send(id) {
if (!id) {
return { data: null, error: "Invoice ID is required" };
}
return this.client.post(`/pay/invoices/${id}/send`);
}
/**
* Cancel an invoice
*
* @param id - Invoice ID
* @returns Cancelled invoice
*/
async cancel(id) {
if (!id) {
return { data: null, error: "Invoice ID is required" };
}
return this.client.post(`/pay/invoices/${id}/cancel`);
}
/**
* Download invoice PDF
*
* @param id - Invoice ID
* @returns PDF binary data
*/
async downloadPDF(id) {
if (!id) {
return { data: null, error: "Invoice ID is required" };
}
return this.client.get(`/pay/invoices/${id}/pdf`);
}
/**
* Record a payment against an invoice
*
* @param id - Invoice ID
* @param params - Payment recording parameters
* @returns Updated invoice with payment recorded
*
* @example
* ```typescript
* const { data, error } = await jokoor.invoices.recordPayment('inv_123', {
* amount: '500.00',
* paymentMethod: 'bank_transfer',
* transactionId: 'TXN123456',
* notes: 'Payment received via bank transfer'
* });
* ```
*/
async recordPayment(id, params) {
if (!id) {
return { data: null, error: "Invoice ID is required" };
}
if (!params.amount) {
return { data: null, error: "Payment amount is required" };
}
if (!params.paymentMethod) {
return { data: null, error: "Payment method is required" };
}
return this.client.post(`/pay/invoices/${id}/payments`, params);
}
/**
* List all payments recorded against an invoice
*
* @param id - Invoice ID
* @returns List of invoice payments
*
* @example
* ```typescript
* const { data, error } = await jokoor.invoices.listPayments('inv_123');
* ```
*/
async listPayments(id) {
if (!id) {
return { data: null, error: "Invoice ID is required" };
}
const result = await this.client.get(`/pay/invoices/${id}/payments`);
if (result.data && Array.isArray(result.data)) {
return { data: result.data, error: null };
}
return result;
}
/**
* Get all payment receipts associated with an invoice
*
* @param id - Invoice ID
* @returns List of receipts for the invoice
*
* @example
* ```typescript
* const { data, error } = await jokoor.invoices.getReceipts('inv_123');
* ```
*/
async getReceipts(id) {
if (!id) {
return { data: null, error: "Invoice ID is required" };
}
const result = await this.client.get(`/pay/invoices/${id}/receipts`);
if (result.data && Array.isArray(result.data)) {
return { data: result.data, error: null };
}
return result;
}
};
// src/resources/checkouts.ts
var Checkouts = class extends BaseResource {
/**
* Create a new checkout session
*
* @param params - Checkout parameters
* @returns Created checkout session
*
* @example
* ```typescript
* // Basic checkout (customer selects payment method)
* const { data, error } = await jokoor.checkouts.create({
* amount: '100.00',
* currency: 'GMD',
* metadata: { orderId: '12345' }
* });
*
* // Checkout with pre-selected payment method (one-step flow)
* const { data, error } = await jokoor.checkouts.create({
* amount: '100.00',
* payment_method: 'wave',
* customer_phone: '+2207654321'
* });
* // data.payment_url will be ready for payment immediately
* ```
*/
async create(params) {
if (!params.amount) {
return { data: null, error: "Amount is required" };
}
return this.client.post("/checkouts", params);
}
/**
* Get checkout session details
*
* @param id - Checkout session ID
* @returns Checkout details
*/
async get(id) {
if (!id) {
return { data: null, error: "Checkout ID is required" };
}
return this.client.get(`/checkouts/${id}`);
}
/**
* Cancel a checkout session
*
* @param id - Checkout session ID
* @returns Success result
*/
async cancel(id) {
if (!id) {
return { data: null, error: "Checkout ID is required" };
}
return this.client.post(`/checkouts/${id}/cancel`);
}
};
// src/resources/products.ts
var Products = class extends BaseResource {
/**
* Create a new product
*
* @param params - Product parameters
* @returns Created product
*
* @example
* ```typescript
* const { data, error } = await jokoor.products.create({
* name: 'Premium Subscription',
* description: 'Monthly premium features',
* price: '29.99',
* currency: 'GMD',
* active: true
* });
* ```
*/
async create(params) {
if (!params.name) {
return { data: null, error: "Product name is required" };
}
if (!params.price) {
return { data: null, error: "Price is required" };
}
return this.client.post("/pay/products", params);
}
/**
* Get product details
*
* @param id - Product ID
* @returns Product details
*/
async get(id) {
if (!id) {
return { data: null, error: "Product ID is required" };
}
return this.client.get(`/pay/products/${id}`);
}
/**
* List products
*
* @param options - List options
* @returns Paginated list of products
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0) params.set("offset", String(options.offset));
if (options?.limit !== void 0) params.set("limit", String(options.limit));
if (options?.active !== void 0) params.set("active", String(options.active));
const query = params.toString();
const url = query ? `/pay/products?${query}` : "/pay/products";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Update a product
*
* @param id - Product ID
* @param params - Update parameters
* @returns Updated product
*/
async update(id, params) {
if (!id) {
return { data: null, error: "Product ID is required" };
}
if (Object.keys(params).length === 0) {
return { data: null, error: "At least one field must be provided for update" };
}
return this.client.put(`/pay/products/${id}`, params);
}
/**
* Delete a product
*
* @param id - Product ID
* @returns Success result
*/
async delete(id) {
if (!id) {
return { data: null, error: "Product ID is required" };
}
return this.client.delete(`/pay/products/${id}`);
}
};
// src/resources/customers.ts
var Customers = class extends BaseResource {
/**
* Create or retrieve a customer
*
* @param params - Customer parameters
* @returns Customer details
*
* @example
* ```typescript
* const { data, error } = await jokoor.customers.create({
* email: 'customer@example.com',
* phone: '+2207123456',
* name: 'John Doe'
* });
* ```
*/
async create(params) {
if (!params.email && !params.phone) {
return { data: null, error: "Either email or phone must be provided" };
}
return this.client.post("/pay/customers", params);
}
/**
* Get customer details
*
* @param id - Customer ID
* @returns Customer details
*/
async get(id) {
if (!id) {
return { data: null, error: "Customer ID is required" };
}
return this.client.get(`/pay/customers/${id}`);
}
/**
* List customers
*
* @param options - List options
* @returns Paginated list of customers
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0) params.set("offset", String(options.offset));
if (options?.limit !== void 0) params.set("limit", String(options.limit));
const query = params.toString();
const url = query ? `/pay/customers?${query}` : "/pay/customers";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Update a customer
*
* @param id - Customer ID
* @param params - Update parameters
* @returns Updated customer
*/
async update(id, params) {
if (!id) {
return { data: null, error: "Customer ID is required" };
}
if (Object.keys(params).length === 0) {
return { data: null, error: "At least one field must be provided for update" };
}
return this.client.put(`/pay/customers/${id}`, params);
}
/**
* Delete a customer
*
* @param id - Customer ID
* @returns Success result
*/
async delete(id) {
if (!id) {
return { data: null, error: "Customer ID is required" };
}
return this.client.delete(`/pay/customers/${id}`);
}
};
// src/resources/transactions.ts
var Transactions = class extends BaseResource {
/**
* Get transaction details
*
* @param id - Transaction ID
* @returns Transaction details
*/
async get(id) {
if (!id) {
return { data: null, error: "Transaction ID is required" };
}
return this.client.get(`/pay/transactions/${id}`);
}
/**
* List transactions
*
* @param options - List options
* @returns Paginated list of transactions
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0)
params.set("offset", String(options.offset));
if (options?.limit !== void 0)
params.set("limit", String(options.limit));
if (options?.status) params.set("status", options.status);
if (options?.startDate) params.set("start_date", options.startDate);
if (options?.endDate) params.set("end_date", options.endDate);
const query = params.toString();
const url = query ? `/pay/transactions?${query}` : "/pay/transactions";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
};
// src/resources/refunds.ts
var Refunds = class extends BaseResource {
/**
* Create a refund for a transaction
*
* @param transactionId - Transaction ID to refund
* @param params - Refund parameters
* @returns Created refund
*
* @example
* ```typescript
* const { data, error } = await jokoor.refunds.create('txn_123', {
* amount: '50.00', // Optional, defaults to full refund
* reason: 'Customer request'
* });
* ```
*/
async create(transactionId, params) {
if (!transactionId) {
return { data: null, error: "Transaction ID is required" };
}
return this.client.post(`/pay/transactions/${transactionId}/refund`, params);
}
/**
* Get refund details
*
* @param id - Refund ID
* @returns Refund details
*/
async get(id) {
if (!id) {
return { data: null, error: "Refund ID is required" };
}
return this.client.get(`/pay/refunds/${id}`);
}
/**
* List refunds
*
* @param options - List options
* @returns Paginated list of refunds
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0) params.set("offset", String(options.offset));
if (options?.limit !== void 0) params.set("limit", String(options.limit));
const query = params.toString();
const url = query ? `/pay/refunds?${query}` : "/pay/refunds";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
};
// src/resources/subscriptions.ts
var Subscriptions = class extends BaseResource {
/**
* Create a new subscription
*
* @param params - Subscription parameters
* @returns Created subscription
*
* @example
* ```typescript
* const { data, error } = await jokoor.subscriptions.create({
* customerId: 'cus_123',
* currency: 'GMD',
* interval: 'month',
* intervalCount: 1,
* startDate: '2024-12-01',
* items: [{
* description: 'Premium Subscription',
* quantity: 1,
* unitPrice: '29.99'
* }]
* });
* ```
*/
async create(params) {
if (!params.customerId && !params.customerEmail) {
return {
data: null,
error: "Either customerId or customerEmail is required"
};
}
if (!params.currency) {
return { data: null, error: "Currency is required" };
}
if (!params.interval) {
return { data: null, error: "Interval is required" };
}
if (!params.intervalCount || params.intervalCount < 1) {
return {
data: null,
error: "Interval count is required and must be at least 1"
};
}
if (!params.startDate) {
return { data: null, error: "Start date is required" };
}
if (!params.items || params.items.length === 0) {
return { data: null, error: "At least one item is required" };
}
return this.client.post("/pay/subscriptions", params);
}
/**
* Get subscription details
*
* @param id - Subscription ID
* @returns Subscription details
*/
async get(id) {
if (!id) {
return { data: null, error: "Subscription ID is required" };
}
return this.client.get(`/pay/subscriptions/${id}`);
}
/**
* List subscriptions
*
* @param options - List options
* @returns Paginated list of subscriptions
*/
async list(options) {
const params = new URLSearchParams();
if (options?.offset !== void 0)
params.set("offset", String(options.offset));
if (options?.limit !== void 0)
params.set("limit", String(options.limit));
if (options?.status) params.set("status", options.status);
const query = params.toString();
const url = query ? `/pay/subscriptions?${query}` : "/pay/subscriptions";
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Update a subscription
*
* @param id - Subscription ID
* @param params - Update parameters
* @returns Updated subscription
*/
async update(id, params) {
if (!id) {
return { data: null, error: "Subscription ID is required" };
}
if (Object.keys(params).length === 0) {
return {
data: null,
error: "At least one field must be provided for update"
};
}
return this.client.put(`/pay/subscriptions/${id}`, params);
}
/**
* Cancel a subscription
*
* @param id - Subscription ID
* @param cancelAtPeriodEnd - If true, subscription remains active until end of current period
* @returns Cancelled subscription
*/
async cancel(id, cancelAtPeriodEnd = false) {
if (!id) {
return { data: null, error: "Subscription ID is required" };
}
return this.client.post(`/pay/subscriptions/${id}/cancel`, {
cancelAtPeriodEnd
});
}
/**
* List all invoices generated by a subscription
*
* @param id - Subscription ID
* @param options - List options
* @returns Paginated list of invoices
*
* @example
* ```typescript
* const { data, error } = await jokoor.subscriptions.listInvoices('sub_123', {
* offset: 0,
* limit: 20
* });
* ```
*/
async listInvoices(id, options) {
if (!id) {
return { data: null, error: "Subscription ID is required" };
}
const params = new URLSearchParams();
if (options?.offset !== void 0)
params.set("offset", String(options.offset));
if (options?.limit !== void 0)
params.set("limit", String(options.limit));
const query = params.toString();
const url = query ? `/pay/subscriptions/${id}/invoices?${query}` : `/pay/subscriptions/${id}/invoices`;
const result = await this.client.get(url);
return this.extractPaginatedData(result);
}
/**
* Manually trigger subscription processing
*
* This endpoint triggers processing of subscriptions that are due for billing.
* Primarily used for testing and administrative purposes.
*
* @returns Processing result with count of subscriptions processed
*
* @example
* ```typescript
* const { data, error } = await jokoor.subscriptions.triggerProcessing();
* console.log(`Processed ${data.processed} subscriptions`);
* ```
*/
async triggerProcessing() {
return this.client.post(
"/pay/subscriptions/trigger-processing"
);
}
};
// src/resources/donations.ts
var Donations = class extends BaseResource {
/**
* Create a new donation campaign
*
* @param params - Camp