UNPKG

@paykit-sdk/core

Version:

The Payment Toolkit for Typescript

535 lines (516 loc) 14.9 kB
// 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 };