Metadata-Version: 2.4
Name: afrikdp
Version: 1.1.0
Summary: Official Python client for the AfriKDP Publishing API — upload books, run checkout, and handle webhooks from your backend.
Author: AfriKDP
License: MIT
Project-URL: Homepage, https://api-afrikdp.onrender.com/docs.html
Project-URL: Documentation, https://api-afrikdp.onrender.com/docs.html
Project-URL: Repository, https://github.com/afrikdp/afrikdp-python
Keywords: afrikdp,publishing,ebooks,paystack,checkout,books,africa
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28.0
Dynamic: license-file

# afrikdp

Official Node.js client for the [AfriKDP Publishing API](https://api-afrikdp.onrender.com/docs.html) — publishing infrastructure for Africa. Upload a book once, sell it everywhere, without building storage, checkout, or delivery yourself.

Requires **Node.js 18+** (uses native `fetch`, `FormData`, and `Blob` — no dependencies).

## Install

```bash
npm install afrikdp
```

## Quick start

```js
const AfriKDP = require('afrikdp');
// or: import AfriKDP from 'afrikdp';

const client = new AfriKDP({ secretKey: process.env.AFRIKDP_SECRET_KEY });

const book = await client.uploadBook({
  title: 'Ubuntu',
  description: 'A short reflection on community and shared humanity.',
  price: 2500, // whole currency units — 2500 = ₦2,500. 0 = free.
  currency: 'NGN',
  file: './ubuntu.pdf',
  cover: './cover.jpg',
});

console.log(book.book_id, book.slug);
// → bk_8937  ubuntu-8937a1c2
```

That's it — AfriKDP stores the file, generates the book's slug, and it's ready to sell.

## Two kinds of keys

| Key | Used for | Where it belongs |
|---|---|---|
| `sk_live_...` (secret) | Uploading/updating books, webhooks, distribution | Your server only |
| `pk_live_...` (public) | Listing/fetching books, checkout, verify | Safe in a browser, but this package is meant for your backend |

```js
// Full access
const client = new AfriKDP({ secretKey: 'sk_live_...' });

// Read-only — fine if this process should never be able to upload/edit
const client = new AfriKDP({ publicKey: 'pk_live_...' });
```

If you pass a `secretKey`, it's used for everything (including reads). Pass only a `publicKey` if you deliberately want a read-only client.

## Uploading a book

```js
const result = await client.uploadBook({
  title: 'Ubuntu',
  description: '...',
  price: 2500,
  currency: 'NGN',          // optional, defaults to NGN
  file: './ubuntu.pdf',     // a file path, or a Buffer
  cover: './cover.jpg',     // optional — a file path, or a Buffer
  visibility: 'public',     // 'public' | 'private' | 'unlisted'
  externalBookId: 'my-db-id-123', // optional — your own ID, for mapping back
});
```

## Updating a book

Editing a book that already exists on AfriKDP — only send the fields that changed:

```js
await client.updateBook('bk_8937', { price: 3000 });

// Replacing the file too:
await client.updateBook('bk_8937', {
  price: 3000,
  file: './ubuntu-v2.pdf',
});
```

## Listing and fetching books

```js
const books = await client.listBooks();
const one = await client.getBook('bk_8937');
```

## Author sub-accounts (enterprise)

If you're a platform with your own authors — Afara has its authors, a school has its teachers — each one can get their own AfriKDP wallet. A book uploaded under an author's `authorId` pays that author directly; the sale never touches your organization's own wallet.

```js
// Create the author once (do this when the real author signs up on your platform)
const { author } = await client.createAuthor({ name: 'Florence Cook', email: 'florence@example.com' });

// Any book uploaded with this authorId pays Florence directly
await client.uploadBook({
  title: 'Embracing Sankofa',
  description: '...',
  price: 2500,
  file: './sankofa.pdf',
  authorId: author.id,
});

// Florence's own earnings
const wallet = await client.getAuthorWallet(author.id);
console.log(wallet.balance);

// Florence requests a payout
await client.requestAuthorWithdrawal(author.id, {
  amount: 2000,
  bankName: 'GTBank',
  accountNumber: '0123456789',
  accountName: 'Florence Cook',
});
```

Don't pass `authorId` at all if you're a solo creator — every sale keeps crediting your organization's own wallet exactly as before. Nothing changes for that case.

## Checkout (server-side)

Most integrations should use the [browser SDK](https://api-afrikdp.onrender.com/docs.html#sdk) for checkout — it opens a branded modal and handles Paystack's inline popup for you. Use this only if you're building your own checkout UI:

```js
const order = await client.checkout({
  bookId: 'bk_8937',
  buyerEmail: 'reader@example.com',
  buyerCountry: 'NG', // optional
});

if (order.free) {
  // order.order_id is already paid — go straight to getDownloadLink()
} else {
  // order.reference, order.amount, order.paystack_public_key
  // → hand these to Paystack's inline.js in the browser
}
```

After the buyer pays:

```js
const result = await client.verifyPayment(order.reference);
if (result.status === 'paid') {
  const { download_url } = await client.getDownloadLink(result.order_id);
  // download_url is valid for 2 minutes
}
```

## Distribution

```js
const marketplace = await client.createMarketplace({
  marketplaceName: "Sarah's Bookstore",
  domain: 'books.sarah.com',
  visibility: 'private',
});

await client.pushDistribution({
  bookId: 'bk_8937',
  marketplaceIds: [marketplace.marketplace.id],
});
```

## Webhooks

```js
const hook = await client.registerWebhook({
  url: 'https://yoursite.com/hooks/afrikdp',
  events: ['book.created', 'sale.completed'],
});
console.log(hook.secret); // shown once — store it now
```

Verifying an incoming webhook (use the **raw** request body, not a re-parsed object):

```js
const AfriKDP = require('afrikdp');

app.post('/hooks/afrikdp', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-afrikdp-signature'];
  const valid = AfriKDP.verifyWebhookSignature(req.body, signature, process.env.AFRIKDP_WEBHOOK_SECRET);
  if (!valid) return res.status(401).send('Invalid signature');

  const event = JSON.parse(req.body);
  // handle event.event, event.data
  res.sendStatus(200);
});
```

## Error handling

Every method throws `AfriKDPError` on a non-2xx response:

```js
const { AfriKDPError } = require('afrikdp');

try {
  await client.uploadBook({ /* ... */ });
} catch (err) {
  if (err instanceof AfriKDPError) {
    console.error(err.status, err.message, err.body);
  }
}
```

## TypeScript

Type definitions are included — no `@types` package needed.

## Configuration

```js
new AfriKDP({
  secretKey: 'sk_live_...',
  apiBase: 'https://api-afrikdp.onrender.com/v1', // optional override
});
```

## License

MIT
