diff --git a/package.json b/package.json index d376d5b..bf5925c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@payark/sdk", - "version": "0.1.1", + "version": "0.1.6", "description": "Official TypeScript SDK for the PayArk payment gateway API.", "main": "dist/index.js", "module": "dist/index.mjs", @@ -40,7 +40,7 @@ "@types/node": "^20.0.0" }, "dependencies": { - "@effect/platform": "0.94.2", - "effect": "3.19.15" + "@effect/platform": "^0.94.5", + "effect": "^3.19.19" } } diff --git a/src/client.ts b/src/client.ts index d03f089..2319b87 100644 --- a/src/client.ts +++ b/src/client.ts @@ -17,6 +17,8 @@ import { WebhooksResource } from "./resources/webhooks"; import { Projects } from "./resources/projects"; import { CustomersResource } from "./resources/customers"; import { SubscriptionsResource } from "./resources/subscriptions"; +import { AutomationResource } from "./resources/automation"; +import { TokensResource } from "./resources/tokens"; import type { PayArkConfig } from "./types"; /** @@ -76,6 +78,8 @@ export class PayArk { private _projects?: Projects; private _customers?: CustomersResource; private _subscriptions?: SubscriptionsResource; + private _automation?: AutomationResource; + private _tokens?: TokensResource; /** * Create a new PayArk client. @@ -159,4 +163,28 @@ export class PayArk { } return this._subscriptions; } + + /** + * Automation resource. + * + * Use this to trigger reminders and reapers. + */ + get automation(): AutomationResource { + if (!this._automation) { + this._automation = new AutomationResource(this.http); + } + return this._automation; + } + + /** + * Tokens resource. + * + * Use this to manage Personal Access Tokens (PAT). + */ + get tokens(): TokensResource { + if (!this._tokens) { + this._tokens = new TokensResource(this.http); + } + return this._tokens; + } } diff --git a/src/http.ts b/src/http.ts index 5a16488..8b961c8 100644 --- a/src/http.ts +++ b/src/http.ts @@ -26,7 +26,7 @@ import { import { Cause, Exit, Duration, Option } from "effect"; /** SDK version – injected at build time for User-Agent header. */ -const SDK_VERSION = "0.1.0"; +const SDK_VERSION = "0.1.4"; /** Supported HTTP methods for the PayArk API. */ export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; @@ -218,14 +218,24 @@ export class HttpClient { err._tag === "ResponseError" ) { const responseError = err as any; + // 1. Try to parse JSON problem body (RFC 7807) or standard JSON error const errorBody = yield* responseError.response.json.pipe( - Effect.catchAll(() => Effect.succeed(undefined)), + // 2. Fall back to raw text if it's not JSON (e.g., 502 Bad Gateway from Nginx) + Effect.catchAll(() => + responseError.response.text.pipe( + Effect.map((text: string) => ({ + error: text.length > 0 ? text.slice(0, 512) : undefined, + })), + Effect.catchAll(() => Effect.succeed(undefined)), + ), + ), ); + return yield* Effect.fail( PayArkError.generate( responseError.response.status, errorBody, - errorBody?.error, + errorBody?.detail || errorBody?.error, ), ); } diff --git a/src/index.ts b/src/index.ts index 48accc0..93ed2fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,16 @@ // ── Main client ──────────────────────────────────────────────────────────── export { PayArk } from "./client"; +// ── Functional Resources ─────────────────────────────────────────────────── +export * from "./resources/checkout"; +export * from "./resources/payments"; +export * from "./resources/customers"; +export * from "./resources/subscriptions"; +export * from "./resources/automation"; +export * from "./resources/tokens"; +export * from "./resources/projects"; +export * from "./resources/webhooks"; + // ── Error classes ────────────────────────────────────────────────────────── export { PayArkError, @@ -26,19 +36,33 @@ export type { PayArkErrorCode } from "./errors"; export type { // Config PayArkConfig, + // Models + Customer, + Payment, + Subscription, + Project, + Token, + Metadata, + // Primitives + Provider, + PaymentStatus, + SubscriptionStatus, + SubscriptionInterval, // Checkout CreateCheckoutParams, CheckoutSession, + // Customers + CreateCustomerParams, + UpdateCustomerParams, + ListCustomersParams, // Payments - Payment, - PaymentStatus, ListPaymentsParams, + // Subscriptions + CreateSubscriptionParams, + ListSubscriptionsParams, + // Pagination PaginatedResponse, PaginationMeta, - // Project - Project, - // Provider - Provider, // Webhooks WebhookEvent, WebhookEventType, diff --git a/src/resources/automation.ts b/src/resources/automation.ts new file mode 100644 index 0000000..765e630 --- /dev/null +++ b/src/resources/automation.ts @@ -0,0 +1,62 @@ +// --------------------------------------------------------------------------- +// PayArk SDK – Automation Resource +// --------------------------------------------------------------------------- +// Management of automated billing tasks (reminders, reapers). +// --------------------------------------------------------------------------- + +import type { HttpClient } from "../http"; +import type { PayArkClient } from "./customers"; + +/** + * Trigger subscription reminders for upcoming renewals. + */ +export async function dispatchReminders( + client: PayArkClient, + cronSecret: string, +): Promise<{ message: string; count: number }> { + return client.http.request<{ message: string; count: number }>( + "POST", + "/v1/automation/reminders", + { + headers: { "x-cron-secret": cronSecret }, + }, + ); +} + +/** + * Trigger the subscription reaper to handle expired subscriptions. + */ +export async function runReaper( + client: PayArkClient, + cronSecret: string, +): Promise<{ message: string; count: number }> { + return client.http.request<{ message: string; count: number }>( + "POST", + "/v1/automation/reaper", + { + headers: { "x-cron-secret": cronSecret }, + }, + ); +} + +// ── Legacy Resource Class ────────────────────────────────────────────────── + +/** + * Resource class for PayArk Automation. + * @deprecated Use functional exports instead. + */ +export class AutomationResource { + constructor(public readonly http: HttpClient) {} + + async dispatchReminders( + cronSecret: string, + ): Promise<{ message: string; count: number }> { + return dispatchReminders(this, cronSecret); + } + + async runReaper( + cronSecret: string, + ): Promise<{ message: string; count: number }> { + return runReaper(this, cronSecret); + } +} diff --git a/src/resources/customers.ts b/src/resources/customers.ts index d93cde3..80dcd67 100644 --- a/src/resources/customers.ts +++ b/src/resources/customers.ts @@ -102,6 +102,32 @@ export async function deleteCustomer( ); } +/** + * High-level helper to either create a new customer or update an existing one + * based on the `merchant_customer_id`. + */ +export async function createOrUpdateCustomer( + client: PayArkClient, + params: CreateCustomerParams, +): Promise { + const existingList = await listCustomers(client, { + merchant_customer_id: params.merchant_customer_id, + limit: 1, + }); + + if (existingList.data.length > 0) { + const existing = existingList.data[0]; + return updateCustomer(client, existing.id, { + email: params.email, + name: params.name, + phone: params.phone, + metadata: params.metadata, + }); + } + + return createCustomer(client, params); +} + // ── Legacy Resource Class ────────────────────────────────────────────────── /** @@ -132,4 +158,8 @@ export class CustomersResource { async delete(id: string): Promise { return deleteCustomer(this, id); } + + async createOrUpdate(params: CreateCustomerParams): Promise { + return createOrUpdateCustomer(this, params); + } } diff --git a/src/resources/tokens.ts b/src/resources/tokens.ts new file mode 100644 index 0000000..86626d5 --- /dev/null +++ b/src/resources/tokens.ts @@ -0,0 +1,82 @@ +// --------------------------------------------------------------------------- +// PayArk SDK – Tokens Resource +// --------------------------------------------------------------------------- +// Management of Personal Access Tokens (PAT). +// --------------------------------------------------------------------------- + +import type { HttpClient } from "../http"; +import type { Token } from "../types"; +import type { PayArkClient } from "./customers"; + +/** + * Create a new personal access token. + * Requires User Session (Supabase JWT). + */ +export async function createToken( + client: PayArkClient, + params: { + name: string; + scopes?: string[]; + expires_in_days?: number; + }, + userToken: string, +): Promise { + return client.http.request("POST", "/v1/tokens", { + body: params, + headers: { Authorization: `Bearer ${userToken}` }, + }); +} + +/** + * List all tokens belonging to the authenticated user. + */ +export async function listTokens( + client: PayArkClient, + userToken: string, +): Promise { + return client.http.request("GET", "/v1/tokens", { + headers: { Authorization: `Bearer ${userToken}` }, + }); +} + +/** + * Delete a token by its ID. + */ +export async function deleteToken( + client: PayArkClient, + id: string, + userToken: string, +): Promise { + return client.http.request( + "DELETE", + `/v1/tokens/${encodeURIComponent(id)}`, + { + headers: { Authorization: `Bearer ${userToken}` }, + }, + ); +} + +// ── Legacy Resource Class ────────────────────────────────────────────────── + +/** + * Resource class for PayArk Tokens. + * @deprecated Use functional exports instead. + */ +export class TokensResource { + constructor(public readonly http: HttpClient) {} + + async create( + params: { name: string; scopes?: string[]; expires_in_days?: number }, + userToken: string, + ): Promise { + return createToken(this, params, userToken); + } + + async list(userToken: string): Promise { + return listTokens(this, userToken); + } + + async delete(id: string, userToken: string): Promise { + return deleteToken(this, id, userToken); + } +} diff --git a/src/schemas.ts b/src/schemas.ts new file mode 100644 index 0000000..ce3fbb7 --- /dev/null +++ b/src/schemas.ts @@ -0,0 +1,10 @@ +import * as S from "@payark/sdk-effect/schemas"; + +/** + * Unified Schemas for the PayArk SDK. + * + * These schemas are derived from the Effect-based industrial API + * to ensure 100% type-parity across all SDK variants. + */ + +export * from "@payark/sdk-effect/schemas"; diff --git a/src/types.ts b/src/types.ts index 62da87d..a820804 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,212 +1,65 @@ // --------------------------------------------------------------------------- // PayArk SDK – Type Definitions // --------------------------------------------------------------------------- -// Plain TypeScript types for the SDK. These are intentionally kept free of -// any runtime dependencies (no Effect, no Zod) so the standalone SDK has -// zero production dependencies. +// Unified TypeScript types for the SDK. These are re-exported from the +// industrial schemas in @payark/sdk-effect to ensure 100% type parity. // --------------------------------------------------------------------------- +import * as S from "./schemas"; + // ── Config ───────────────────────────────────────────────────────────────── -export interface PayArkConfig { - apiKey: string; - baseUrl?: string; - timeout?: number; - maxRetries?: number; - sandbox?: boolean; -} +export type PayArkConfig = S.PayArkConfig; // ── Primitives ────────────────────────────────────────────────────────────── -export type Provider = - | "esewa" - | "khalti" - | "connectips" - | "imepay" - | "fonepay" - | "sandbox"; - -export type PaymentStatus = "pending" | "success" | "failed"; - -export type SubscriptionStatus = "active" | "past_due" | "canceled" | "paused"; - -export type SubscriptionInterval = "month" | "year" | "week"; - -export type WebhookEventType = - | "payment.success" - | "payment.failed" - | "subscription.created" - | "subscription.payment_succeeded" - | "subscription.payment_failed" - | "subscription.renewal_due" - | "subscription.canceled"; +export type Provider = S.Provider; +export type PaymentStatus = S.PaymentStatus; +export type SubscriptionStatus = S.SubscriptionStatus; +export type SubscriptionInterval = S.SubscriptionInterval; +export type WebhookEventType = S.WebhookEventType; // ── Pagination ────────────────────────────────────────────────────────────── -export interface PaginationMeta { - total: number | null; - limit: number; - offset: number; -} - -export interface PaginatedResponse { +export type PaginationMeta = S.PaginationMeta; +export type PaginatedResponse = { readonly data: readonly T[]; readonly meta: PaginationMeta; -} +}; // ── Models ────────────────────────────────────────────────────────────────── -export type Metadata = Record; - -export interface Customer { - id: string; - merchant_customer_id: string; - email: string | null; - name: string | null; - phone: string | null; - project_id: string; - metadata: Metadata | null; - created_at: string; - updated_at?: string; -} - -export interface Payment { - id: string; - project_id: string; - amount: number; - currency: string; - status: PaymentStatus; - provider_ref?: string | null; - metadata_json?: Metadata | null; - gateway_response?: unknown | null; - created_at: string; - updated_at?: string; -} - -export interface Subscription { - id: string; - project_id: string; - customer_id: string; - status: SubscriptionStatus; - amount: number; - currency: string; - interval: SubscriptionInterval; - interval_count: number; - current_period_start: string; - current_period_end: string; - payment_link: string; - auto_send_link: boolean; - metadata?: Metadata | null; - canceled_at?: string | null; - created_at: string; - updated_at?: string; -} - -export interface Project { - id: string; - name: string; - api_key_secret: string; - created_at: string; -} - -export interface Token { - id: string; - name: string; - scopes: string[]; - last_used_at: string | null; - expires_at: string | null; - created_at: string; -} +export type Metadata = S.Metadata; +export type Customer = S.Customer; +export type Payment = S.Payment; +export type Subscription = S.Subscription; +export type Project = S.Project; +export type Token = S.Token; // ── Checkout ──────────────────────────────────────────────────────────────── -export interface CreateCheckoutParams { - amount: number; - currency?: string; - provider: Provider; - returnUrl: string; - cancelUrl?: string; - metadata?: Metadata; -} - -export interface CheckoutSession { - id: string; - checkout_url: string; - payment_method: { - type: Provider; - url?: string; - method?: "GET" | "POST"; - fields?: Record; - }; -} +export type CreateCheckoutParams = S.CreateCheckoutParams; +export type CheckoutSession = S.CheckoutSession; // ── Customers ─────────────────────────────────────────────────────────────── -export interface CreateCustomerParams { - merchant_customer_id: string; - email?: string; - name?: string; - phone?: string; - project_id?: string; - metadata?: Metadata; -} - -export interface UpdateCustomerParams { - email?: string; - name?: string; - phone?: string; - metadata?: Metadata; -} - -export interface ListCustomersParams { - limit?: number; - offset?: number; - email?: string; - projectId?: string; -} +export type CreateCustomerParams = S.CreateCustomerParams; +export type UpdateCustomerParams = S.UpdateCustomerParams; +export type ListCustomersParams = S.ListCustomersParams; // ── Payments ───────────────────────────────────────────────────────────────── -export interface ListPaymentsParams { - limit?: number; - offset?: number; - projectId?: string; -} +export type ListPaymentsParams = S.ListPaymentsParams; // ── Subscriptions ──────────────────────────────────────────────────────────── -export interface CreateSubscriptionParams { - customer_id: string; - amount: number; - currency?: string; - interval: SubscriptionInterval; - interval_count?: number; - project_id?: string; - auto_send_link?: boolean; - metadata?: Metadata; -} - -export interface ListSubscriptionsParams { - limit?: number; - offset?: number; - projectId?: string; - customerId?: string; - status?: SubscriptionStatus; -} +export type CreateSubscriptionParams = S.CreateSubscriptionParams; +export type ListSubscriptionsParams = S.ListSubscriptionsParams; // ── Webhooks ───────────────────────────────────────────────────────────────── -export interface WebhookEvent { - type: WebhookEventType; - id?: string; - data: Payment | Subscription | Customer | Record; - is_test: boolean; - created?: number; -} +export type WebhookEvent = S.WebhookEvent; // ── Errors ─────────────────────────────────────────────────────────────────── -export interface PayArkErrorBody { - error: string; - details?: unknown; -} +export type PayArkErrorBody = S.PayArkErrorBody; diff --git a/tests/unit/http.test.ts b/tests/unit/http.test.ts index 40d7715..52a2521 100644 --- a/tests/unit/http.test.ts +++ b/tests/unit/http.test.ts @@ -582,7 +582,7 @@ describe("HttpClient", () => { } catch (err) { expect(err).toBeInstanceOf(PayArkError); expect((err as PayArkError).code).toBe("api_error"); - expect((err as PayArkError).message).toContain("500"); + expect((err as PayArkError).message).toBe("Internal Server Error"); } });