npm package: @jokoor/sdk
Description: Official Jokoor API SDK for JavaScript/TypeScript - SMS, Payments, and more
Author: Jokoor
Latest version: 1.3.0

# Jokoor SDK for TypeScript/JavaScript

Official TypeScript/JavaScript SDK for the [Jokoor API](https://jokoor.com) - Send SMS, accept payments, manage payouts, and more.

[![npm version](https://badge.fury.io/js/%40jokoor%2Fsdk.svg)](https://www.npmjs.com/package/@jokoor/sdk)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Features

- 🚀 **Type-safe** - Full TypeScript support with comprehensive type definitions
- 📦 **Tree-shakeable** - Only bundle what you use
- 🔄 **Automatic retries** - Built-in retry logic with exponential backoff
- ✅ **Result pattern** - No exceptions thrown, explicit error handling with `{data, error}`
- 🎯 **Modern** - ES2022+, ESM and CommonJS support
- 🔀 **Auto case conversion** - Seamless camelCase (JS) ↔ snake_case (API) conversion
- 📝 **Well-documented** - Comprehensive JSDoc comments and examples

## Installation

```bash
npm install @jokoor/sdk
```

```bash
yarn add @jokoor/sdk
```

```bash
pnpm add @jokoor/sdk
```

## Quick Start

```typescript
import { Jokoor } from "@jokoor/sdk";

// Initialize the client with your API key
const jokoor = new Jokoor("sk_test_your_api_key_here");

// Send an SMS
const { data, error } = await jokoor.sms.send({
  recipientPhone: "+2207123456",
  messageBody: "Hello from Jokoor!",
});

if (error) {
  console.error("Error:", error);
} else {
  console.log("SMS sent:", data.id);
}
```

## Authentication

Get your API keys from the [Jokoor Dashboard](https://dashboard.jokoor.com/settings/api-keys).

### API Key Types

- **Secret keys** (`sk_test_xxx`, `sk_live_xxx`) - Full API access, use server-side only
- **Publishable keys** (`pk_test_xxx`, `pk_live_xxx`) - Limited access for client-side operations

### Test vs Live Mode

- **Test keys** (`_test_`) - For development and testing, no real charges
- **Live keys** (`_live_`) - For production use with real transactions

```typescript
// Test mode
const jokoor = new Jokoor("sk_test_xxx");
console.log(jokoor.isTestMode()); // true

// Live mode
const jokoorLive = new Jokoor("sk_live_xxx");
console.log(jokoorLive.isTestMode()); // false
```

## Configuration

```typescript
import { Jokoor } from "@jokoor/sdk";

const jokoor = new Jokoor("sk_test_xxx", {
  baseURL: "https://api.jokoor.com/v1", // API base URL
  timeout: 30000, // Request timeout (30s default)
  maxRetries: 3, // Max retry attempts (3 default)
  debug: false, // Enable debug logging
});
```

## API Reference

### SMS

#### Send SMS

```typescript
const { data, error } = await jokoor.sms.send({
  recipientPhone: "+2207123456",
  messageBody: "Your verification code is 123456",
  senderId: "MyApp", // Optional
  scheduledAt: "2024-12-25T10:00:00Z", // Optional
  isDraft: false, // Optional
});
```

#### Get SMS Message

```typescript
const { data, error } = await jokoor.sms.get("msg_123");
```

#### List SMS Messages

```typescript
const { data, error } = await jokoor.sms.list({
  offset: 0,
  limit: 20,
  status: "delivered",
  startDate: "2024-01-01T00:00:00Z",
  endDate: "2024-12-31T23:59:59Z",
});

if (data) {
  console.log(`Total: ${data.count}`);
  data.items.forEach((sms) => console.log(sms.id));
}
```

### Payment Links

#### Create Payment Link

```typescript
const { data, error } = await jokoor.paymentLinks.create({
  title: "Premium Subscription",
  description: "Monthly premium features",
  amount: "500.00",
  currency: "GMD",
  successUrl: "https://example.com/success",
  failureUrl: "https://example.com/cancel",
});

if (data) {
  // Share this URL with customers
  console.log("Payment URL:", data.paymentUrl);
  // Example: https://pay.jokoor.com/pay/pl_abc123
}
```

**Note:** Payment links have hosted payment pages. Customers visit the `paymentUrl` to complete payment - no need to call the initialize endpoint.

#### List Payment Links

```typescript
const { data, error } = await jokoor.paymentLinks.list({
  limit: 20,
  status: "active",
});
```

### Checkouts

Checkouts support **both hosted pages and custom integration**.

#### Option 1: Hosted Checkout Page (Simple)

```typescript
const { data, error } = await jokoor.checkouts.create({
  amount: "100.00",
  currency: "GMD",
  description: "Service payment",
});

if (data) {
  // Share paymentUrl with customer
  console.log("Send customer to:", data.paymentUrl);
  // https://pay.jokoor.com/checkout/chk_abc123
  // Customer completes payment on hosted page
}
```

#### Option 2: Custom SDK Integration (Full Control)

```typescript
// Step 1: Create checkout
const { data: checkout, error: createError } = await jokoor.checkouts.create({
  amount: "100.00",
  currency: "GMD",
  description: "Service payment",
});

if (createError) return;

// Step 2: Initialize with client_secret
const { data: session, error } = await jokoor.payments.initialize({
  clientSecret: checkout.clientSecret,
  paymentMethod: "wave",
  customerPhone: "+2207654321",
  customerEmail: "customer@example.com",
});

if (error) return;

// Step 3: Redirect to payment provider
window.location.href = session.paymentUrl;
```

### Initialize Payment (Embedded Checkout)

Use this endpoint when building a custom payment UI with checkouts.

```typescript
// After creating a checkout with client_secret
const { data, error } = await jokoor.payments.initialize({
  clientSecret: checkout.clientSecret, // From checkout creation
  paymentMethod: "wave",
  customerPhone: "+2207654321",
  customerEmail: "customer@example.com",
  customerName: "John Doe",
});

if (data) {
  // Redirect customer to payment provider
  window.location.href = data.paymentUrl;
}
```

**When to use:**

- ✅ Embedded checkout integrations (custom payment UI)
- ❌ NOT for payment links, donations, or invoices (they have hosted pages)

### Invoices

#### Create Invoice

```typescript
const { data, error } = await jokoor.invoices.create({
  customerEmail: "customer@example.com",
  customerName: "John Doe",
  items: [
    {
      description: "Consulting Services",
      quantity: 10,
      unitPrice: "50.00",
    },
  ],
  currency: "GMD",
  dueDate: "2024-12-31T23:59:59Z",
  taxRate: 15, // Optional: 15% tax
});

if (data) {
  console.log("Invoice Number:", data.invoiceNumber);
  console.log("Payment URL:", data.paymentUrl); // Customer pays here
  console.log("PDF URL:", data.pdfUrl); // Download PDF
}
```

#### Record Offline Payment

For cash, bank transfers, checks, etc. (NOT Wave/Afrimoney/card):

```typescript
const { data, error } = await jokoor.invoices.recordPayment("inv_123", {
  amount: "1150.00",
  paymentMethod: "bank_transfer",
  transactionId: "BANK-REF-123456",
  notes: "Received via wire transfer",
});
```

**Two ways to pay invoices:**

- **Online:** Customer visits `paymentUrl` (Wave, Afrimoney, card)
- **Offline:** Use `recordPayment()` for cash, bank transfers, checks

### Donation Campaigns

```typescript
const { data, error } = await jokoor.donations.create({
  title: "Help Build a School",
  description: "Raising funds to build a new school",
  targetAmount: "50000.00", // Optional goal
  currency: "GMD",
  slug: "school-building-fund", // Custom URL slug
});

if (data) {
  console.log("Donation URL:", data.donationUrl);
  // Example: https://donate.jokoor.com/school-building-fund
  console.log("Slug:", data.slug);
  console.log("Progress:", `${data.progressPercentage}%`);
}
```

### Customers

```typescript
const { data, error } = await jokoor.customers.create({
  email: "customer@example.com",
  phone: "+2207123456",
  name: "John Doe",
});
```

### Products

```typescript
const { data, error } = await jokoor.products.create({
  name: "Premium Subscription",
  description: "Monthly premium features",
  price: "29.99",
  currency: "GMD",
  active: true,
});
```

### Transactions

```typescript
const { data, error } = await jokoor.transactions.list({
  offset: 0,
  limit: 20,
  status: "completed",
  startDate: "2024-01-01T00:00:00Z",
  endDate: "2024-12-31T23:59:59Z",
});

if (data) {
  data.items.forEach((txn) => {
    console.log(`${txn.id}: ${txn.amount} ${txn.currency}`);
  });
}
```

### Refunds

```typescript
// Full refund
const { data, error } = await jokoor.refunds.create("txn_123", {
  reason: "Customer request",
});

// Partial refund
const { data, error } = await jokoor.refunds.create("txn_123", {
  amount: "50.00",
  reason: "Partial refund for damaged item",
});
```

### Subscriptions

```typescript
const { data, error } = await jokoor.subscriptions.create({
  customerId: "cus_123",
  amount: "29.99",
  currency: "GMD",
  interval: "month",
  intervalCount: 1,
  startDate: "2024-12-01",
  items: [
    {
      description: "Premium Subscription",
      quantity: 1,
      unitPrice: "29.99",
    },
  ],
});
```

### Payouts

#### Get Balance

```typescript
const { data, error } = await jokoor.payouts.getBalance();
if (data) {
  console.log(`Available: ${data.availableBalance} ${data.currency}`);
  console.log(`Pending: ${data.pendingBalance} ${data.currency}`);
}
```

#### List Bank Accounts

```typescript
const { data, error } = await jokoor.bankAccounts.list();
```

**Note:** Creating/updating bank accounts requires OTP and must be done via the web dashboard.

#### Send Payout to Recipient (Wave B2P)

```typescript
const { data, error } = await jokoor.payoutRecipients.sendPayout({
  recipientId: "recip_123",
  amount: "500.00",
  reference: "Salary payment",
  // Note: OTP required - must be obtained via dashboard
});
```

### Webhooks

#### Create Webhook Endpoint

```typescript
const { data, error } = await jokoor.webhooks.create({
  url: "https://myapp.com/webhooks/jokoor",
  enabledEvents: [
    "payment.succeeded",
    "payment.failed",
    "sms.delivered",
    "sms.failed",
  ],
});

if (data) {
  // Secret is only shown once - store it securely
  console.log("Webhook Secret:", data.secret);
}
```

#### List Webhook Events

```typescript
const { data, error } = await jokoor.webhookEvents.list({
  offset: 0,
  limit: 20,
  type: "payment.succeeded",
  startDate: "2024-01-01T00:00:00Z",
});
```

## Error Handling

The SDK uses a **Result pattern** instead of throwing exceptions. All methods return `{data, error}`:

```typescript
// Success case
const { data, error } = await jokoor.sms.send({...});
if (error) {
  console.error('Error:', error);  // error is a string
  return;
}
console.log('Success:', data);      // data is the response
```

## TypeScript Support

The SDK is written in TypeScript and provides comprehensive type definitions:

```typescript
import type {
  SMS,
  PaymentLink,
  Checkout,
  Invoice,
  Transaction,
  DonationCampaign,
} from "@jokoor/sdk";

// All response types are fully typed
const handlePaymentLink = (link: PaymentLink) => {
  console.log(link.paymentUrl); // TypeScript knows this field exists
  console.log(link.livemode); // Full autocomplete support
};
```

## Complete Examples

### Accept Payment with Hosted Page

```typescript
import { Jokoor } from "@jokoor/sdk";

const jokoor = new Jokoor("sk_test_xxx");

// Create a payment link
const { data, error } = await jokoor.paymentLinks.create({
  title: "Product Purchase",
  amount: "500.00",
  currency: "GMD",
});

if (error) {
  console.error("Error:", error);
} else {
  // Share paymentUrl with customer
  console.log("Send customer to:", data.paymentUrl);
  // Customer visits URL and completes payment on hosted page
}
```

### Custom Payment Integration

```typescript
import { Jokoor } from "@jokoor/sdk";

const jokoor = new Jokoor("sk_test_xxx");

// 1. Create checkout
const checkout = await jokoor.checkouts.create({
  amount: "100.00",
  currency: "GMD",
  description: "Service payment",
});

if (checkout.error) return;

// 2. Initialize payment with custom UI
const payment = await jokoor.payments.initialize({
  clientSecret: checkout.data.clientSecret,
  paymentMethod: "wave",
  customerPhone: "+2207654321",
  customerEmail: "customer@example.com",
});

if (payment.error) return;

// 3. Redirect to payment provider
window.location.href = payment.data.paymentUrl;
```

### Create and Send Invoice

```typescript
// Create invoice
const { data: invoice, error } = await jokoor.invoices.create({
  customerEmail: "customer@example.com",
  customerName: "John Doe",
  items: [
    {
      description: "Web Development",
      quantity: 1,
      unitPrice: "1000.00",
    },
  ],
  currency: "GMD",
  dueDate: "2024-12-31T23:59:59Z",
  taxRate: 15,
});

if (error) return;

// Send invoice to customer
await jokoor.invoices.send(invoice.id);

// Customer can pay online at:
console.log("Payment URL:", invoice.paymentUrl);

// Or record offline payment (cash, bank transfer):
await jokoor.invoices.recordPayment(invoice.id, {
  amount: "1150.00",
  paymentMethod: "bank_transfer",
  transactionId: "BANK-123",
  notes: "Received via wire transfer",
});
```

### Bulk SMS Campaign

```typescript
// 1. Create contact group
const { data: group } = await jokoor.smsContactGroups.create({
  name: "Campaign Recipients",
});

// 2. Add contacts
await jokoor.smsContactGroups.addContacts(group.id, [
  "contact_1",
  "contact_2",
  "contact_3",
]);

// 3. Create and send campaign
const { data: campaign } = await jokoor.smsCampaigns.create({
  name: "Product Launch",
  messageBody: "Check out our new product!",
  groupIds: [group.id],
});

// Send immediately
await jokoor.smsCampaigns.send(campaign.id);

// Or send asynchronously (recommended for large campaigns)
await jokoor.smsCampaigns.sendAsync(campaign.id);
```

## Rate Limiting

The Jokoor API implements rate limiting. When rate limited, the SDK will:

1. Automatically retry with exponential backoff (up to `maxRetries`)
2. Return an error if all retries fail

```typescript
const jokoor = new Jokoor("sk_test_xxx", {
  maxRetries: 5, // Increase retries for rate-limited endpoints
});
```

## Debugging

Enable debug logging to see detailed request/response information:

```typescript
const jokoor = new Jokoor("sk_test_xxx", {
  debug: true, // Logs all requests and responses
});
```

## Migration from v1.x

### Field Name Changes

The SDK now automatically converts between camelCase (JavaScript) and snake_case (API). Update your code:

```typescript
// OLD (v1.x)
sms.send({ recipient_phone: "...", message_body: "..." });

// NEW (v2.0)
sms.send({ recipientPhone: "...", messageBody: "..." });

// OLD
link.url;

// NEW
link.paymentUrl; // Clearer naming
```

### Payment Links

```typescript
// OLD
const link = await jokoor.paymentLinks.create({...});
console.log(link.url);

// NEW
const { data } = await jokoor.paymentLinks.create({...});
console.log(data.paymentUrl);  // Now includes full hosted page URL
```

### Donation Campaigns

```typescript
// OLD
campaign.goalAmount;
campaign.raisedAmount;

// NEW
campaign.targetAmount; // Renamed for API consistency
campaign.currentAmount; // Renamed for API consistency
campaign.donationUrl; // New: Public donation page URL
campaign.slug; // New: SEO-friendly URL slug
```

## Support

- **Documentation**: https://docs.jokoor.com
- **API Reference**: https://docs.jokoor.com/api
- **Email**: hello@jokoor.com

## License

MIT License - see [LICENSE](LICENSE) file for details.