Skip to main content

Overview

The PayNow SDK provides two specialized clients, each designed for different use cases:
  • Storefront Client: For public-facing store operations
  • Management Client: For administrative store management
Both clients share the same underlying architecture, providing a consistent developer experience with full TypeScript support.

Client Types

The Storefront Client handles customer-facing operations like browsing products and creating orders.
import { createStorefrontClient } from '@paynow-gg/typescript-sdk';

const storefront = createStorefrontClient(
  'your-store-id',
  'optional-customer-token'
);

// Access operations through grouped methods
const store = await storefront.store.getStorefrontStore();
const products = await storefront.products.listProducts();

Available Operation Groups

  • store - Store information and configuration
  • products - Product catalog and variants
  • orders - Order creation and management
  • customers - Customer profiles and authentication

Proxy-Based Method Routing

The SDK uses JavaScript Proxy objects to dynamically route method calls to the correct API endpoints. This enables a clean, intuitive API without hardcoding every possible operation.

How It Works

// From src/client.ts:110-159
return new Proxy({} as GroupedOperations<T>, {
  get: (_, prop: string) => {
    return new Proxy(
      {},
      {
        get: (_, methodName: string) => {
          const prefix = prop.charAt(0).toUpperCase() + prop.slice(1);
          const method = methodName.charAt(0).toUpperCase() + methodName.slice(1);
          const operationId = `${prefix}_${method}`;

          return async (config?: any) => {
            const mapping = operationMappings[operationId];
            
            if (!mapping) {
              throw new Error(`Operation ${operationId} not found`);
            }

            // ... URL construction and request execution
          };
        },
      },
    );
  },
});

Method Name Mapping

When you call storefront.products.listProducts(), the SDK:
  1. Takes the group name: products
  2. Takes the method name: listProducts
  3. Converts to operation ID: Products_ListProducts
  4. Looks up the HTTP method and path from operation mappings
  5. Executes the request
This pattern means you get autocomplete and type safety for all operations without bloating the SDK bundle size.

Type Safety

The SDK provides comprehensive TypeScript types derived from the OpenAPI specification.

Operation Groups

Operations are automatically grouped based on their naming convention:
// From src/client.ts:72-88
type GroupedOperations<T extends Record<string, any>> = {
  [K in keyof T as K extends `${infer Prefix}_${string}`
    ? Uncapitalize<Prefix>
    : never]: {
    [Op in keyof T as Op extends `${Capitalize<K extends `${infer P}_${string}` ? P : never>}_${infer Method}`
      ? Uncapitalize<Method>
      : never]: Op extends keyof T
      ? AllOptional<MapToAxiosConfig<T[Op]>> extends true
        ? (config?: MapToAxiosConfig<T[Op]>) => Promise<AxiosResponse<SuccessType<T[Op]>>>
        : (config: MapToAxiosConfig<T[Op]>) => Promise<AxiosResponse<SuccessType<T[Op]>>>
      : never;
  };
};
This complex type transformation provides:
  • Grouped operation methods (e.g., products.list, products.get)
  • Optional vs required config parameters
  • Correct return types for each operation

Response Type Extraction

The SDK automatically extracts the success response type from OpenAPI specs:
// From src/client.ts:7-17
type SuccessType<T> = T extends {
  responses: { 200: { content: { "application/json": infer R } } };
}
  ? R
  : T extends {
        responses: { 201: { content: { "application/json": infer R } } };
      }
    ? R
    : T extends { responses: { 204: any } }
      ? void
      : never;
This means when you call an operation, TypeScript knows the exact shape of response.data:
// TypeScript knows the exact type of store
const response = await storefront.store.getStorefrontStore();
const store = response.data; // Fully typed!

Request Configuration

Each operation method accepts a configuration object that maps OpenAPI parameters to Axios config:
// From src/client.ts:27-64
type MapToAxiosConfig<T extends { parameters?: any; requestBody?: any }> = Omit<
  AxiosRequestConfig,
  "params" | "headers" | "data" | "url"
> &
  (T["parameters"] extends { query?: infer Q }
    ? Q extends never
      ? { params?: any }
      : Q extends undefined
        ? { params?: any }
        : HasRequiredKeys<Q> extends true
          ? { params: Q }
          : { params?: Q }
    : { params?: any }) & {
    headers?: T["parameters"] extends { header?: infer H }
      ? H extends never
        ? AxiosRequestConfig["headers"]
        : H extends undefined
          ? AxiosRequestConfig["headers"]
          : H & AxiosRequestConfig["headers"]
      : AxiosRequestConfig["headers"];
  } // ... and more for data and path parameters

Using the Config Object

// List products with filtering
const response = await storefront.products.listProducts({
  params: {
    category: 'games',
    limit: 10,
  },
});

Path Replacement

The SDK handles dynamic path parameters automatically:
// From src/client.ts:141-146
if (config?.path) {
  url = url.replace(
    /{(\w+)}/g,
    (_, key) => config.path[key] ?? `{${key}}`,
  );
}
When you provide a path object, the SDK replaces {paramName} placeholders in the URL template with actual values.

Axios Integration

The SDK is built on top of Axios, giving you access to all Axios features:
const storefront = createStorefrontClient(
  'your-store-id',
  'customer-token',
  {
    timeout: 5000,
    headers: { 'X-Custom-Header': 'value' },
    validateStatus: (status) => status < 500,
  }
);
All responses are Axios responses, so you can access:
  • response.data - The response body
  • response.status - HTTP status code
  • response.headers - Response headers
  • response.config - Request configuration

Default Configuration

The SDK applies sensible defaults to all clients:
// From src/client.ts:96-108
const client = axios.create({
  baseURL: "https://api.paynow.gg",
  ...options,
  headers: {
    "Content-Type": "application/json",
    Accept: "application/json",
    ...options?.headers,
    ...defaultHeaders,
  },
  paramsSerializer: {
    indexes: null,
  },
});
The paramsSerializer.indexes: null setting ensures array parameters are serialized as key=value1&key=value2 instead of key[0]=value1&key[1]=value2.

Next Steps

Authentication

Learn about customer tokens and API keys

Error Handling

Handle API errors gracefully with type-safe helpers

Build docs developers (and LLMs) love