lomi.
ReferenceSdks

Next.js SDK

Official Next.js SDK with React hooks for lomi. payments API.

Official Next.js SDK for the lomi. payments API. Built specifically for Next.js 13+ with App Router, providing React hooks and seamless server/client integration.

The Next.js SDK extends the TypeScript SDK with React-specific features like hooks and server component utilities.

Installation

npm install @lomi./sdk-next
# or
pnpm add @lomi./sdk-next

Quick Start

Server-side Setup

// lib/lomi.ts
import { LomiSDK } from '@lomi./sdk-next';

export const lomi = new LomiSDK({
  apiKey: process.env.LOMI_API_KEY!,
  environment: process.env.NODE_ENV === 'development' ? 'test' : 'live',
});

Client-side Usage

'use client';

import { useLomiRequest } from '@lomi./sdk-next';

Server Components

Fetch Customers

// app/customers/page.tsx
import { lomi } from '@/lib/lomi';

export default async function CustomersPage() {
  const customers = await lomi.customers.list();

  return (
    <div>
      <h1>Customers ({customers.length})</h1>
      <ul>
        {customers.map((customer) => (
          <li key={customer.id}>{customer.name} - {customer.email}</li>
        ))}
      </ul>
    </div>
  );
}

Fetch Transactions with Filters

// app/transactions/page.tsx
import { lomi } from '@/lib/lomi';

export default async function TransactionsPage() {
  const transactions = await lomi.transactions.list({
    status: 'completed',
    pageSize: 50,
  });

  return (
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Amount</th>
          <th>Status</th>
          <th>Provider</th>
        </tr>
      </thead>
      <tbody>
        {transactions.map((tx) => (
          <tr key={tx.id}>
            <td>{tx.id}</td>
            <td>{tx.gross_amount} {tx.currency_code}</td>
            <td>{tx.status}</td>
            <td>{tx.provider_code}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

Get Organization Metrics

// app/dashboard/page.tsx
import { lomi } from '@/lib/lomi';

export default async function DashboardPage() {
  const metrics = await lomi.organizations.getMetrics();

  return (
    <div className="grid grid-cols-3 gap-4">
      <div>
        <h3>MRR</h3>
        <p>{metrics.mrr.toLocaleString()} {metrics.currency_code}</p>
      </div>
      <div>
        <h3>ARR</h3>
        <p>{metrics.arr.toLocaleString()} {metrics.currency_code}</p>
      </div>
      <div>
        <h3>Customers</h3>
        <p>{metrics.total_customers}</p>
      </div>
    </div>
  );
}

Client Components (Hooks)

useLomiRequest Hook

'use client';

import { useLomiRequest } from '@lomi./sdk-next';
import { lomi } from '@/lib/lomi';

export function CustomerList() {
  const { data, loading, error, execute } = useLomiRequest(
    () => lomi.customers.list()
  );

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <button onClick={() => execute()}>Refresh</button>
      <ul>
        {data?.map((customer) => (
          <li key={customer.id}>{customer.name}</li>
        ))}
      </ul>
    </div>
  );
}

Create Customer Form

'use client';

import { useState } from 'react';
import { useLomiRequest } from '@lomi./sdk-next';
import { lomi } from '@/lib/lomi';

export function CreateCustomerForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    phone_number: '',
  });

  const { loading, error, execute } = useLomiRequest(
    () => lomi.customers.create(formData),
    { manual: true }
  );

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const result = await execute();
    if (result) {
      alert(`Customer created: ${result.id}`);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Name"
        value={formData.name}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
      />
      <input
        type="email"
        placeholder="Email"
        value={formData.email}
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
      />
      <input
        type="tel"
        placeholder="Phone"
        value={formData.phone_number}
        onChange={(e) => setFormData({ ...formData, phone_number: e.target.value })}
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create Customer'}
      </button>
      {error && <p>Error: {error.message}</p>}
    </form>
  );
}

API Routes

Create Checkout Session

// app/api/checkout/route.ts
import { NextResponse } from 'next/server';
import { lomi } from '@/lib/lomi';

export async function POST(request: Request) {
  const body = await request.json();

  const session = await lomi.checkoutSessions.create({
    amount: body.amount,
    currency_code: 'XOF',
    title: body.title || 'Payment',
    success_url: `${process.env.NEXT_PUBLIC_URL}/success`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/cancel`,
    customer_email: body.email,
    metadata: {
      user_id: body.userId,
      order_id: body.orderId,
    },
  });

  return NextResponse.json({ url: session.checkout_url });
}

Webhook Handler

// app/api/webhooks/lomi/route.ts
import { NextResponse } from 'next/server';
import crypto from 'crypto';

export async function POST(request: Request) {
  const signature = request.headers.get('x-lomi-signature') || '';
  const body = await request.text();

  // Verify signature
  const expectedSignature = crypto
    .createHmac('sha256', process.env.LOMI_WEBHOOK_SECRET!)
    .update(body)
    .digest('hex');

  if (signature !== expectedSignature) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
  }

  const event = JSON.parse(body);

  switch (event.type) {
    case 'PAYMENT_SUCCEEDED':
      console.log('Payment succeeded:', event.data.gross_amount);
      // Update order status in database
      break;



    case 'SUBSCRIPTION_RENEWED':
      console.log('Subscription renewed:', event.data.id);
      // Extend access
      break;

    case 'REFUND_COMPLETED':
      console.log('Refund processed:', event.data.id);
      // Handle refund
      break;

    default:
      console.log('Unhandled event:', event.type);
  }

  return NextResponse.json({ received: true });
}

Checkout Button Component

'use client';

import { useState } from 'react';

interface CheckoutButtonProps {
  amount: number;
  productName: string;
  email?: string;
}

export function CheckoutButton({ amount, productName, email }: CheckoutButtonProps) {
  const [loading, setLoading] = useState(false);

  const handleCheckout = async () => {
    setLoading(true);

    try {
      const response = await fetch('/api/checkout', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ 
          amount, 
          title: productName,
          email 
        }),
      });

      const { url } = await response.json();
      window.location.href = url;
    } catch (error) {
      console.error('Checkout error:', error);
      alert('Failed to start checkout');
    } finally {
      setLoading(false);
    }
  };

  return (
    <button
      onClick={handleCheckout}
      disabled={loading}
      className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg"
    >
      {loading ? 'Loading...' : `Pay ${amount.toLocaleString()} XOF`}
    </button>
  );
}

Environment Variables

# .env.local
LOMI_API_KEY=sk_live_xxxxxxxxxxxxxxxxxxxxxx
LOMI_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_URL=https://yoursite.com

TypeScript Types

import type {
  Customer,
  CustomerCreate,
  CheckoutSession,
  CheckoutSessionCreate,
  Transaction,
  Product,
  Subscription,
  PaymentLink,
} from '@lomi./sdk-next';

Resources

On this page