@paykit-sdk/core
Version:
The Payment Toolkit for Typescript
535 lines (516 loc) • 14.9 kB
JavaScript
// src/webhook-provider.ts
var Webhook = class {
handlers = /* @__PURE__ */ new Map();
config = null;
setup(config) {
this.config = config;
return this;
}
on(eventType, handler) {
if (!this.config) throw new Error("Webhook not configured. Call setup() first.");
if (!this.handlers.has(eventType)) this.handlers.set(eventType, []);
this.handlers.get(eventType)?.push(handler);
return this;
}
async handle(dto) {
if (!this.config) throw new Error("Webhook not configured. Call setup() first.");
const { webhookSecret, provider } = this.config;
const event = await provider.handleWebhook({ ...dto, webhookSecret });
const handlers = this.handlers.get(event.type);
if (handlers && handlers.length > 0) {
await Promise.all(handlers.map((handler) => handler(event)));
}
}
};
// src/resources/checkout.ts
import { z as z2 } from "zod";
// src/resources/metadata.ts
import { z } from "zod";
var metadataSchema = z.record(z.string(), z.string());
// src/resources/checkout.ts
var billingModeSchema = z2.enum(["one_time", "recurring"]);
var createCheckoutSchema = z2.object({
/**
* The ID of the customer.
*/
customer_id: z2.string(),
/**
* The metadata of the checkout.
*/
metadata: metadataSchema,
/**
* The mode of the checkout.
*/
session_type: billingModeSchema,
/**
* The item ID of the checkout.
*/
item_id: z2.string(),
/**
* Extra information to be sent to the provider e.g tax, trial days, etc.
*/
provider_metadata: z2.record(z2.string(), z2.unknown()).optional()
});
var retrieveCheckoutSchema = z2.object({
id: z2.string()
});
// src/resources/customer.ts
import { z as z3 } from "zod";
var createCustomerSchema = z3.object({
/**
* The email of the customer.
*/
email: z3.string().email(),
/**
* The name of the customer.
*/
name: z3.string().optional(),
/**
* The metadata of the customer.
*/
metadata: metadataSchema.optional()
});
var updateCustomerSchema = z3.object({
/**
* The email of the customer.
*/
email: z3.string().email().optional(),
/**
* The name of the customer.
*/
name: z3.string().optional(),
/**
* The metadata of the customer.
*/
metadata: metadataSchema.optional()
});
var retrieveCustomerSchema = z3.object({
id: z3.string()
});
// src/resources/subscription.ts
import { z as z4 } from "zod";
var updateSubscriptionSchema = z4.object({
metadata: metadataSchema.optional()
});
var retrieveSubscriptionSchema = z4.object({
id: z4.string()
});
// src/resources/webhook.ts
var toPaykitEvent = (event) => event;
// src/tools/error.ts
var HTTPError = class extends Error {
cause;
name = "HTTPError";
constructor(message, opts) {
let msg = message;
if (opts?.cause) {
msg += `: ${opts.cause}`;
}
super(msg, opts);
if (typeof this.cause === "undefined") {
this.cause = opts?.cause;
}
}
};
var UnauthorizedError = class extends HTTPError {
name = "PaykitUnauthorizedError";
};
var ConnectionError = class extends HTTPError {
name = "PaykitConnectionError";
};
var AbortedError = class extends HTTPError {
name = "PaykitAbortedError";
};
var TimeoutError = class extends HTTPError {
name = "PaykitTimeoutError";
};
var ValidationError = class extends HTTPError {
name = "PaykitValidationError";
};
var UnknownError = class extends HTTPError {
name = "PaykitUnknownError";
};
// src/tools/try-catch.ts
async function tryCatchAsync(promise) {
try {
const data = await promise;
return [data, void 0];
} catch (error) {
return [void 0, error];
}
}
function tryCatchSync(fn) {
try {
const data = fn();
return [data, void 0];
} catch (error) {
return [void 0, error];
}
}
// src/tools/fp.ts
var OK = (value) => ({ ok: true, value });
var ERR = (error) => ({ ok: false, error });
var unwrapAsync = async (pr) => {
const r = await pr;
if (!r.ok) throw r.error;
return r.value;
};
function safeParse(rawValue, fn, errorMessage) {
const [result, error] = tryCatchSync(() => fn(rawValue));
if (error) return ERR(new ValidationError(errorMessage, { cause: error }));
return OK(result);
}
var safeEncode = (value) => {
return safeParse(value, (value2) => Buffer.from(JSON.stringify(value2)).toString("base64"), "Failed to encode value");
};
var safeDecode = (value) => {
return safeParse(value, (value2) => JSON.parse(Buffer.from(value2, "base64").toString("utf-8")), "Failed to decode value");
};
// src/tools/http.ts
function isConnectionError(err) {
if (typeof err !== "object" || err == null) {
return false;
}
const isBrowserErr = err instanceof TypeError && err.message.toLowerCase().startsWith("failed to fetch");
const isNodeErr = err instanceof TypeError && err.message.toLowerCase().startsWith("fetch failed");
const isBunErr = "name" in err && err.name === "ConnectionError";
const isGenericErr = "code" in err && typeof err.code === "string" && err.code.toLowerCase() === "econnreset";
return isBrowserErr || isNodeErr || isGenericErr || isBunErr;
}
function isTimeoutError(err) {
if (typeof err !== "object" || err == null) {
return false;
}
const isNative = "name" in err && err.name === "TimeoutError";
const isLegacyNative = "code" in err && err.code === 23;
const isGenericErr = "code" in err && typeof err.code === "string" && err.code.toLowerCase() === "econnaborted";
return isNative || isLegacyNative || isGenericErr;
}
function isAbortError(err) {
if (typeof err !== "object" || err == null) {
return false;
}
const isNative = "name" in err && err.name === "AbortError";
const isLegacyNative = "code" in err && err.code === 20;
const isGenericErr = "code" in err && typeof err.code === "string" && err.code.toLowerCase() === "econnaborted";
return isNative || isLegacyNative || isGenericErr;
}
function isUnauthorizedError(err) {
if (typeof err !== "object" || err == null) {
return false;
}
return err instanceof Error && ["unauthorized", "401"].includes(err.message.toLowerCase());
}
// src/tools/webhook.ts
var headersExtractor = (headers, requiredHeaders) => {
const extractedHeaders = [];
for (const key of requiredHeaders) {
const value = headers[key] || headers[key.toLowerCase()];
if (value) {
extractedHeaders.push({ key, value: Array.isArray(value) ? value : [value] });
}
}
return extractedHeaders;
};
// src/tools/is-client.ts
var __IS_CLIENT__ = typeof window !== "undefined";
// src/tools/delay.ts
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// src/tools/string.ts
var truncate = (str, num, suffix = "...") => {
if (!str) return "";
if (str.length < num) return str;
return str.slice(0, num) + suffix;
};
var stringifyObjectValues = (obj) => {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, JSON.stringify(value)]));
};
// src/tools/validate-env.ts
var validateEnvVars = (requiredKeys, source) => {
const missingKeys = [];
const result = {};
for (const key of requiredKeys) {
const value = source[key];
if (!value) {
missingKeys.push(key);
} else {
result[key] = value;
}
}
if (missingKeys.length > 0) {
const missingKeysList = missingKeys.join(", ");
throw new Error(`Missing required environment variables: ${missingKeysList}`);
}
return result;
};
// src/logger.ts
import chalk from "chalk";
var Logger = class {
silent;
verbose;
spinnerInterval = null;
spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
currentSpinnerFrame = 0;
constructor(options = {}) {
this.silent = options.silent || false;
this.verbose = options.verbose || false;
}
/**
* Success messages (dark green)
*/
success(message) {
if (this.silent) return;
console.log(chalk.hex("#166534")(`\u2714 ${message}`));
}
/**
* Info messages (dark green)
*/
info(message) {
if (this.silent) return;
console.log(chalk.hex("#15803d")(`\u2139 ${message}`));
}
/**
* Warning messages (amber)
*/
warn(message) {
if (this.silent) return;
console.log(chalk.hex("#d97706")(`\u26A0 ${message}`));
}
/**
* Error messages (red)
*/
error(message) {
if (this.silent) return;
console.error(chalk.hex("#dc2626")(`\u2716 ${message}`));
}
/**
* Debug messages (gray, only in verbose mode)
*/
debug(message) {
if (this.silent || !this.verbose) return;
console.log(chalk.gray(`\u{1F50D} ${message}`));
}
/**
* Tip messages (dark green)
*/
tip(message) {
if (this.silent) return;
console.log(chalk.hex("#16a34a")(`\u{1F4A1} ${message}`));
}
/**
* Code block styling
*/
code(code) {
if (this.silent) return;
console.log(chalk.bgGray.white(` ${code} `));
}
/**
* Section headers
*/
section(title) {
if (this.silent) return;
console.log(chalk.hex("#166534").bold.underline(`
${title}`));
}
/**
* Progress indicator with spinning animation
*/
progress(message) {
if (this.silent) return;
this.stopSpinner();
this.startSpinner(message);
}
/**
* Start the spinner animation
*/
startSpinner(message) {
const updateSpinner = () => {
process.stdout.clearLine(0);
process.stdout.cursorTo(0);
const spinner = this.spinnerFrames[this.currentSpinnerFrame];
process.stdout.write(chalk.hex("#15803d")(`${spinner} ${message}... `));
this.currentSpinnerFrame = (this.currentSpinnerFrame + 1) % this.spinnerFrames.length;
};
updateSpinner();
this.spinnerInterval = setInterval(updateSpinner, 80);
}
/**
* Stop the spinner animation
*/
stopSpinner() {
if (this.spinnerInterval) {
clearInterval(this.spinnerInterval);
this.spinnerInterval = null;
}
}
/**
* Clear progress line
*/
clearProgress() {
if (this.silent) return;
this.stopSpinner();
process.stdout.clearLine(0);
process.stdout.cursorTo(0);
}
/**
* Table-like output for key-value pairs
*/
table(data) {
if (this.silent) return;
const maxKeyLength = Math.max(...Object.keys(data).map((k) => k.length));
Object.entries(data).forEach(([key, value]) => {
const paddedKey = key.padEnd(maxKeyLength);
console.log(chalk.gray(`${paddedKey}: `) + chalk.white(value));
});
}
/**
* List items
*/
list(items, bullet = "\u2022") {
if (this.silent) return;
items.forEach((item) => {
console.log(chalk.hex("#16a34a")(`${bullet} `) + chalk.white(item));
});
}
/**
* Divider line
*/
divider(char = "\u2500", length = 50) {
if (this.silent) return;
console.log(chalk.hex("#16a34a")(char.repeat(length)));
}
/**
* Spacer
*/
spacer(lines = 1) {
if (this.silent) return;
for (let i = 0; i < lines; i++) {
console.log("");
}
}
/**
* Brand header
*/
brand() {
if (this.silent) return;
console.log(chalk.hex("#166534").bold("PayKit CLI"));
this.divider();
}
};
var logger = new Logger();
// src/http.ts
var HTTPClient = class {
constructor(config) {
this.config = config;
}
errorHandler = (err) => {
switch (true) {
case isUnauthorizedError(err):
return ERR(new UnauthorizedError("Unauthorized", { cause: err }));
case isConnectionError(err):
return ERR(new ConnectionError("Connection error", { cause: err }));
case isTimeoutError(err):
return ERR(new TimeoutError("Timeout error", { cause: err }));
case isAbortError(err):
return ERR(new AbortedError("Aborted error", { cause: err }));
default:
return ERR(new UnknownError("Unknown error", { cause: err }));
}
};
getFullUrl(endpoint) {
const cleanEndpoint = endpoint.startsWith("/") ? endpoint.slice(1) : endpoint;
return `${this.config.baseUrl}/${cleanEndpoint}`;
}
getRequestOptions(options) {
return { headers: { "Content-Type": "application/json", ...this.config.headers, ...options?.headers }, ...options };
}
async get(endpoint, options) {
const url = this.getFullUrl(endpoint);
const requestOptions = this.getRequestOptions(options);
return await fetch(url, { method: "GET", ...requestOptions }).then((res) => OK(res.json())).catch((err) => this.errorHandler(err));
}
async post(endpoint, options) {
const url = this.getFullUrl(endpoint);
const requestOptions = this.getRequestOptions(options);
return await fetch(url, { method: "POST", ...requestOptions }).then((res) => OK(res.json())).catch((err) => this.errorHandler(err));
}
async delete(endpoint, options) {
const url = this.getFullUrl(endpoint);
const requestOptions = this.getRequestOptions(options);
return await fetch(url, { method: "DELETE", ...requestOptions }).then((res) => OK(res.json())).catch((err) => this.errorHandler(err));
}
async put(endpoint, options) {
const url = this.getFullUrl(endpoint);
const requestOptions = this.getRequestOptions(options);
return await fetch(url, { method: "PUT", ...requestOptions }).then((res) => OK(res.json())).catch((err) => this.errorHandler(err));
}
async patch(endpoint, options) {
const url = this.getFullUrl(endpoint);
const requestOptions = this.getRequestOptions(options);
return await fetch(url, { method: "PATCH", ...requestOptions }).then((res) => OK(res.json())).catch((err) => this.errorHandler(err));
}
};
// src/index.ts
var PayKit = class {
constructor(provider) {
this.provider = provider;
}
checkouts = {
create: (params) => this.provider.createCheckout(params),
retrieve: (id) => this.provider.retrieveCheckout(id)
};
customers = {
create: (params) => this.provider.createCustomer(params),
update: (id, params) => this.provider.updateCustomer(id, params),
retrieve: (id) => this.provider.retrieveCustomer(id)
};
subscriptions = {
update: (id, params) => this.provider.updateSubscription(id, params),
cancel: (id) => this.provider.cancelSubscription(id)
};
webhooks = {
setup: (config) => new Webhook().setup({ ...config, provider: this.provider })
};
};
export {
AbortedError,
ConnectionError,
ERR,
HTTPClient,
HTTPError,
Logger,
OK,
PayKit,
TimeoutError,
UnauthorizedError,
UnknownError,
ValidationError,
Webhook,
__IS_CLIENT__,
billingModeSchema,
createCheckoutSchema,
createCustomerSchema,
delay,
headersExtractor,
isAbortError,
isConnectionError,
isTimeoutError,
isUnauthorizedError,
logger,
metadataSchema,
retrieveCheckoutSchema,
retrieveCustomerSchema,
retrieveSubscriptionSchema,
safeDecode,
safeEncode,
safeParse,
stringifyObjectValues,
toPaykitEvent,
truncate,
tryCatchAsync,
tryCatchSync,
unwrapAsync,
updateCustomerSchema,
updateSubscriptionSchema,
validateEnvVars
};