> ## Documentation Index
> Fetch the complete documentation index at: https://paystack-sdk.efobi.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Type Safety

> Understanding TypeScript and runtime validation in the Paystack SDK

The Paystack TypeScript SDK provides comprehensive type safety through a combination of TypeScript's compile-time checking and Zod's runtime validation. This dual-layer approach ensures your code is both type-safe and validates data at runtime.

## Why Type Safety Matters

Type safety prevents entire classes of bugs before they reach production:

<CardGroup cols={2}>
  <Card title="Catch Errors Early" icon="bug-slash">
    TypeScript catches type mismatches during development, not in production.
  </Card>

  <Card title="Better IntelliSense" icon="lightbulb">
    Your IDE provides accurate autocomplete and inline documentation.
  </Card>

  <Card title="Refactor Safely" icon="rotate">
    Change APIs with confidence knowing TypeScript will flag breaking changes.
  </Card>

  <Card title="Runtime Validation" icon="shield-check">
    Zod validates data at runtime, protecting against invalid API responses.
  </Card>
</CardGroup>

## TypeScript Benefits

### Full Type Coverage

Every SDK method has complete TypeScript definitions:

```typescript theme={null}
import { Paystack } from '@efobi/paystack';

const paystack = new Paystack('sk_test_...');

// TypeScript knows the exact shape of input and output
const result = await paystack.transaction.initialize({
  email: 'customer@example.com',  // string (validated as email)
  amount: '50000',                 // string (required)
  currency: 'NGN',                 // 'NGN' | 'USD' | 'GHS' | 'ZAR' | 'KES' | 'XOF'
  reference: 'ref_123',            // string (optional)
  callback_url: 'https://...',     // string (validated as URL, optional)
  metadata: { userId: 123 }        // any (optional)
});

// TypeScript knows result has 'data' and 'error' properties
if (result.data) {
  const url = result.data.data.authorization_url; // string
  const code = result.data.data.access_code;      // string
  const ref = result.data.data.reference;         // string
}
```

### IntelliSense Support

Your IDE provides intelligent code completion:

```typescript theme={null}
paystack.transaction.  // IDE shows: initialize, verify, list, getTransactionById, etc.

paystack.transaction.initialize({
  email: '',  // IDE suggests: string (email format)
  amount: '', // IDE suggests: string (required)
  // Press Ctrl+Space to see all available options
});
```

<Tip>
  Hover over any method or property in your IDE to see detailed documentation extracted from JSDoc comments.
</Tip>

### Compile-Time Error Detection

TypeScript catches mistakes before runtime:

```typescript theme={null}
// ❌ TypeScript Error: Type 'number' is not assignable to type 'string'
const result = await paystack.transaction.initialize({
  email: 'customer@example.com',
  amount: 50000  // Should be a string: '50000'
});

// ❌ TypeScript Error: Property 'invalid' does not exist
const result = await paystack.transaction.initialize({
  email: 'customer@example.com',
  amount: '50000',
  invalid: true  // Unknown property
});

// ✅ Correct
const result = await paystack.transaction.initialize({
  email: 'customer@example.com',
  amount: '50000'
});
```

## Zod Runtime Validation

While TypeScript provides compile-time safety, Zod validates data at runtime. This is crucial for:

* API responses that might not match expected schemas
* User input that bypasses TypeScript
* Third-party data from webhooks or external sources

### How Zod Works

Zod schemas define the shape and validation rules for data. Here's an example from `src/zod/transaction.ts:15-85`:

```typescript src/zod/transaction.ts theme={null}
export const txnInitializeInput = z.object({
  amount: z.string(),
  email: z.email(),
  currency: z.optional(currency),
  reference: z.string().optional(),
  callback_url: z.url().optional(),
  plan: z.string().optional(),
  invoice_limit: z.number().optional(),
  metadata: z.any().optional(),
  channels: z
    .array(
      z.enum([
        "card",
        "bank",
        "apple_pay",
        "ussd",
        "qr",
        "mobile_money",
        "bank_transfer",
        "eft",
      ])
    )
    .optional(),
  split_code: z.string().optional(),
  subaccount: z.string().optional(),
  transaction_charge: z.string().optional(),
  bearer: z.enum(["account", "subaccount"]).optional(),
});
```

### Input Validation

Zod automatically validates all inputs before API requests:

```typescript theme={null}
const result = await paystack.transaction.initialize({
  email: 'not-an-email',  // ❌ Will fail Zod validation
  amount: '50000'
});

if (result.error) {
  console.log(result.error.issues);
  // [
  //   {
  //     validation: 'email',
  //     code: 'invalid_string',
  //     message: 'Invalid email',
  //     path: ['email']
  //   }
  // ]
}
```

### Output Validation

Zod also validates API responses to ensure they match the expected schema:

```typescript src/main/transaction.ts theme={null}
if (!response.ok) {
  const { data, error } = await genericResponse.safeParseAsync(raw);
  return { data, error };
}
const { data, error } = await txnInitializeSuccess.safeParseAsync(raw);
return { data, error };
```

This protects your application from unexpected API changes or malformed responses.

## Type Inference

Zod schemas can be converted to TypeScript types using `z.infer`. From `src/zod/transaction.ts:1503-1517`:

```typescript src/zod/transaction.ts theme={null}
export type TxnInitializeInput = z.infer<typeof txnInitializeInput>;
export type TxnInitializeSuccess = z.infer<typeof txnInitializeSuccess>;
export type TransactionShared = z.infer<typeof transactionShared>;
export type TxnVerifySuccess = z.infer<typeof txnVerifySuccess>;
export type TxnListInput = z.infer<typeof txnListInput>;
export type TxnSingleSuccess = z.infer<typeof txnSingleSuccess>;
export type TxnListSuccess = z.infer<typeof txnListSuccess>;
export type TxnChargeInput = z.infer<typeof txnChargeInput>;
export type TxnChargeSuccess = z.infer<typeof txnChargeSuccess>;
export type TxnTimelineSuccess = z.infer<typeof txnTimelineSuccess>;
export type TxnTotalsSuccess = z.infer<typeof txnTotalsSuccess>;
export type TxnExportInput = z.infer<typeof txnExportInput>;
export type TxnExportSuccess = z.infer<typeof txnExportSuccess>;
export type TxnPartialDebitInput = z.infer<typeof txnPartialDebitInput>;
export type TxnPartialDebitSuccess = z.infer<typeof txnPartialDebitSuccess>;
```

This ensures TypeScript types and runtime validation stay in sync.

## Importing Types

You can import types for use in your application:

```typescript theme={null}
import type { 
  TxnInitializeInput,
  TxnInitializeSuccess,
  TxnVerifySuccess,
  TransactionShared
} from '@efobi/paystack/transaction';

// Use types in your code
function createTransaction(data: TxnInitializeInput) {
  return paystack.transaction.initialize(data);
}

function handleSuccess(response: TxnInitializeSuccess) {
  const { authorization_url, access_code, reference } = response.data;
  // TypeScript knows these properties exist
}
```

### Common Types

The SDK exports many reusable types from `src/zod/index.ts:228-237`:

```typescript src/zod/index.ts theme={null}
export type Meta = z.infer<typeof meta>;
export type GenericResponse = z.infer<typeof genericResponse>;
export type Currency = z.infer<typeof currency>;
export type History = z.infer<typeof history>;
export type Log = z.infer<typeof log>;
export type Authorization = z.infer<typeof authorization>;
export type Customer = z.infer<typeof customer>;
export type GenericInput = z.infer<typeof genericInput>;
export type Subaccount = z.infer<typeof subaccount>;
export type Plan = z.infer<typeof plan>;
```

Import and use these in your application:

```typescript theme={null}
import type { Currency, Customer, Authorization } from '@efobi/paystack';

interface PaymentData {
  currency: Currency;        // 'NGN' | 'USD' | 'GHS' | 'ZAR' | 'KES' | 'XOF'
  customer: Customer;        // Full customer object
  authorization: Authorization; // Card authorization details
}
```

## Validation Schemas

### Currency Validation

The SDK validates currency codes from `src/zod/index.ts:40-49`:

```typescript src/zod/index.ts theme={null}
export const currency = z.enum(["NGN", "USD", "GHS", "ZAR", "KES", "XOF"]).default("NGN");
```

Usage:

```typescript theme={null}
const result = await paystack.transaction.initialize({
  email: 'customer@example.com',
  amount: '50000',
  currency: 'NGN'  // ✅ Valid
  // currency: 'EUR'  // ❌ TypeScript error: Type '"EUR"' is not assignable to type Currency
});
```

### Email Validation

Emails are validated using Zod's built-in email validator:

```typescript theme={null}
email: z.email()  // Validates RFC 5322 email format
```

```typescript theme={null}
// ✅ Valid emails
const emails = [
  'user@example.com',
  'first.last@company.co.uk',
  'name+tag@domain.com'
];

// ❌ Invalid emails
const invalid = [
  'not-an-email',
  '@example.com',
  'user@',
  'user @example.com'
];
```

### URL Validation

Callback URLs are validated as proper URLs:

```typescript theme={null}
callback_url: z.url().optional()
```

```typescript theme={null}
// ✅ Valid URLs
const urls = [
  'https://example.com/callback',
  'https://app.example.com/api/webhook',
  'http://localhost:3000/callback'
];

// ❌ Invalid URLs
const invalid = [
  'not-a-url',
  'example.com',  // Missing protocol
  '/callback'     // Relative URL
];
```

### Custom Object Validation

From `src/zod/index.ts:127-147`, the SDK validates complex nested objects:

```typescript src/zod/index.ts theme={null}
export const customer = z.object({
  id: z.number(),
  first_name: z.nullable(z.string()),
  last_name: z.nullable(z.string()),
  email: z.email(),
  phone: z.nullable(z.string()),
  metadata: z.nullable(z.any()),
  customer_code: z.string(),
  risk_action: z.string(),
  international_format_phone: z.nullable(z.string()),
});
```

## Advanced Type Patterns

### Discriminated Unions

Handle different response types safely:

```typescript theme={null}
type PaystackResult<T> = 
  | { data: T; error: undefined }
  | { data: undefined; error: ZodError };

function processResult<T>(result: PaystackResult<T>): T {
  if (result.error) {
    throw new Error('Validation failed');
  }
  return result.data;  // TypeScript knows this is T, not undefined
}
```

### Generic Helpers

Create type-safe wrappers:

```typescript theme={null}
import type { Paystack } from '@efobi/paystack';

type ExtractData<T> = T extends { data: infer D } ? D : never;

type TransactionData = ExtractData<
  Awaited<ReturnType<Paystack['transaction']['initialize']>>
>;
// TransactionData is the success response type
```

### Utility Types

Use TypeScript utility types with SDK types:

```typescript theme={null}
import type { TxnInitializeInput } from '@efobi/paystack/transaction';

// Make all fields required
type RequiredTransaction = Required<TxnInitializeInput>;

// Pick specific fields
type MinimalTransaction = Pick<TxnInitializeInput, 'email' | 'amount'>;

// Omit fields
type TransactionWithoutMetadata = Omit<TxnInitializeInput, 'metadata'>;

// Make all fields optional
type PartialTransaction = Partial<TxnInitializeInput>;
```

## Schema Composition

Zod schemas can be composed and extended:

```typescript theme={null}
import { z } from 'zod';
import type { TxnInitializeInput } from '@efobi/paystack/transaction';

// Extend the base schema
const customTransactionInput = z.object({
  // Add your custom fields
  userId: z.string(),
  sessionId: z.string().optional(),
});

type CustomTransaction = TxnInitializeInput & z.infer<typeof customTransactionInput>;

function initializeWithTracking(data: CustomTransaction) {
  const { userId, sessionId, ...paystackData } = data;
  
  // Track in your system
  trackTransaction(userId, sessionId);
  
  // Initialize with Paystack
  return paystack.transaction.initialize(paystackData);
}
```

## Safe Parsing

Manually validate data using Zod schemas:

```typescript theme={null}
import { z } from 'zod';

const userInput = getUserInput(); // Unknown data

const schema = z.object({
  email: z.email(),
  amount: z.string()
});

const result = schema.safeParse(userInput);

if (result.success) {
  // Data is validated and typed
  await paystack.transaction.initialize(result.data);
} else {
  // Handle validation errors
  console.error(result.error.issues);
}
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Trust the Types" icon="1">
    Let TypeScript guide you. If something doesn't type-check, there's usually a good reason.

    ```typescript theme={null}
    // Trust TypeScript's error messages
    const result = await paystack.transaction.initialize({
      email: 123  // Error helps you fix the issue
    });
    ```
  </Card>

  <Card title="Import Types" icon="2">
    Import types for reusability and consistency across your codebase.

    ```typescript theme={null}
    import type { TxnInitializeInput } from '@efobi/paystack/transaction';

    function prepare(data: TxnInitializeInput) {
      // Type-safe function
    }
    ```
  </Card>

  <Card title="Handle Validation Errors" icon="3">
    Always check for validation errors in the result object.

    ```typescript theme={null}
    if (result.error) {
      // Zod found validation issues
      const errors = result.error.flatten();
    }
    ```
  </Card>

  <Card title="Use Strict Mode" icon="4">
    Enable strict TypeScript settings in `tsconfig.json`:

    ```json theme={null}
    {
      "compilerOptions": {
        "strict": true,
        "strictNullChecks": true
      }
    }
    ```
  </Card>
</CardGroup>

## Platform Support

The SDK is fully typed for all JavaScript runtimes:

<Tabs>
  <Tab title="Node.js">
    ```typescript theme={null}
    import { Paystack } from '@efobi/paystack';
    import type { TxnInitializeInput } from '@efobi/paystack/transaction';

    const paystack = new Paystack(process.env.PAYSTACK_SECRET_KEY!);

    const data: TxnInitializeInput = {
      email: 'customer@example.com',
      amount: '50000'
    };

    const result = await paystack.transaction.initialize(data);
    ```
  </Tab>

  <Tab title="Bun">
    ```typescript theme={null}
    import { Paystack } from '@efobi/paystack';
    import type { TxnVerifySuccess } from '@efobi/paystack/transaction';

    const paystack = new Paystack(Bun.env.PAYSTACK_SECRET_KEY!);

    const result = await paystack.transaction.verify('ref_123');

    if (result.data) {
      const verified: TxnVerifySuccess = result.data;
    }
    ```
  </Tab>

  <Tab title="Deno">
    ```typescript theme={null}
    import { Paystack } from '@efobi/paystack';
    import type { Customer } from '@efobi/paystack';

    const paystack = new Paystack(Deno.env.get('PAYSTACK_SECRET_KEY')!);

    const result = await paystack.transaction.verify('ref_123');

    if (result.data) {
      const customer: Customer = result.data.data.customer;
    }
    ```
  </Tab>

  <Tab title="Cloudflare Workers">
    ```typescript theme={null}
    import { Paystack } from '@efobi/paystack';

    export default {
      async fetch(request: Request, env: Env) {
        const paystack = new Paystack(env.PAYSTACK_SECRET_KEY);
        
        const result = await paystack.transaction.list();
        
        return Response.json(result.data);
      }
    };
    ```
  </Tab>
</Tabs>

## TypeScript Configuration

Recommended `tsconfig.json` for best experience:

```json tsconfig.json theme={null}
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020"],
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "strictNullChecks": true,
    "noUncheckedIndexedAccess": true
  }
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="API Reference" icon="book" href="/api-reference/transaction">
    Explore detailed API documentation with type information
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/core-concepts/error-handling">
    Learn how to handle validation and runtime errors
  </Card>
</CardGroup>
