Back to 0fee
0fee

Session 3: Marketing Website, 5 New SDKs, and Docker

How we built the 0fee.dev marketing website, 5 SDKs (Go, Ruby, PHP, Java, C#), and Docker stack in one session. By Juste A. Gnimavo and Claude.

Juste A. Gnimavo (Thales) & Claude | March 27, 2026 9 min 0fee
EN/ FR/ ES
session-003marketingsdksdockersolidjsgorubyphpjavacsharp

December 10, 2025. Still the same day as Session 001 and Session 002. By this point, 0fee.dev had a complete backend, 7 payment providers, a SolidJS dashboard, a checkout widget, Celery background tasks, and SDKs in TypeScript and Python. Session 003 added a Stripe-inspired marketing website, 5 new SDKs covering Go, Ruby, PHP, Java, and C#, a full Docker production stack with 7 services, and a webhook delivery service. Approximately 7,650 lines of code.

The Marketing Website

The marketing website needed to accomplish one thing: convince a developer visiting 0fee.dev for the first time that this is a serious, professional payment platform worth integrating. The design benchmark was Stripe's marketing site -- clean, developer-focused, with interactive code examples and clear pricing.

Component Architecture

The website was built with SolidJS (matching the dashboard stack) and TailwindCSS:

website/
├── package.json
├── vite.config.ts
├── tailwind.config.js
├── index.html
├── Dockerfile
├── nginx.conf
└── src/
    ├── index.tsx
    ├── App.tsx
    ├── styles/
    │   └── globals.css
    ├── components/
    │   ├── Navbar.tsx          # Mega-menu navigation
    │   ├── Footer.tsx          # Links + social
    │   ├── Hero.tsx            # Animated hero with stats
    │   ├── CodeExample.tsx     # Interactive code tabs
    │   ├── Features.tsx        # 8-feature grid
    │   ├── CountryMap.tsx      # 21-country selector
    │   ├── Pricing.tsx         # 3-tier pricing
    │   ├── Testimonials.tsx    # Customer quotes
    │   └── CTA.tsx             # Call-to-action
    └── pages/
        ├── Home.tsx            # Landing page
        ├── Products.tsx        # Product overview
        ├── Pricing.tsx         # Detailed pricing
        ├── Docs.tsx            # Documentation
        ├── About.tsx           # Team + mission
        ├── Contact.tsx         # Contact form
        ├── Login.tsx           # OTP login
        └── Register.tsx        # Registration

The Hero Section

The hero needed to communicate three things in under 5 seconds: what 0fee.dev does, how many countries it covers, and that integration is simple. The implementation used animated stat counters and a single code snippet:

tsxfunction Hero() {
    return (
        <section class="relative overflow-hidden bg-gradient-to-b from-gray-900 to-gray-800 text-white">
            <div class="max-w-7xl mx-auto px-4 py-24 sm:py-32">
                <div class="text-center">
                    <h1 class="text-5xl sm:text-7xl font-bold tracking-tight">
                        One API.{" "}
                        <span class="text-blue-400">Every payment.</span>
                    </h1>
                    <p class="mt-6 text-xl text-gray-300 max-w-3xl mx-auto">
                        Accept mobile money, cards, and wallets across
                        200+ countries with a single integration.
                        Built for Africa, ready for the world.
                    </p>

                    <div class="mt-12 grid grid-cols-3 gap-8 max-w-2xl mx-auto">
                        <StatCounter value={53} suffix="+" label="Providers" />
                        <StatCounter value={200} suffix="+" label="Countries" />
                        <StatCounter value={7} label="SDKs" />
                    </div>
                </div>
            </div>
        </section>
    );
}

Interactive Code Examples

The CodeExample component shows how to integrate 0fee.dev in four languages, with syntax-highlighted tabs that switch between TypeScript, Python, Go, and cURL:

tsxfunction CodeExample() {
    const [activeTab, setActiveTab] = createSignal("typescript");

    const examples = {
        typescript: `import { ZeroFee } from "zerofee";

const zf = new ZeroFee("sk_live_...");

const payment = await zf.payments.create({
  amount: 5000,
  payment_method: "PAYIN_ORANGE_CI",
  customer: { phone: "+2250709757296" },
});`,
        python: `from zerofee import ZeroFee

zf = ZeroFee(api_key="sk_live_...")

payment = zf.payments.create(
    amount=5000,
    payment_method="PAYIN_ORANGE_CI",
    customer={"phone": "+2250709757296"},
)`,
        go: `client := zerofee.New("sk_live_...")

payment, err := client.Payments.Create(&zerofee.PaymentParams{
    Amount:        5000,
    PaymentMethod: "PAYIN_ORANGE_CI",
    Customer:      &zerofee.Customer{Phone: "+2250709757296"},
})`,
        curl: `curl -X POST https://api.0fee.dev/v1/payments \\
  -H "Authorization: Bearer sk_live_..." \\
  -H "Content-Type: application/json" \\
  -d '{
    "amount": 5000,
    "payment_method": "PAYIN_ORANGE_CI",
    "customer": {"phone": "+2250709757296"}
  }'`,
    };

    return (
        <section class="py-24 bg-gray-950">
            <div class="max-w-4xl mx-auto px-4">
                <h2 class="text-3xl font-bold text-white text-center mb-12">
                    Start accepting payments in minutes
                </h2>

                <div class="rounded-xl overflow-hidden border border-gray-800">
                    <div class="flex border-b border-gray-800 bg-gray-900">
                        <For each={Object.keys(examples)}>
                            {(lang) => (
                                <button
                                    onClick={() => setActiveTab(lang)}
                                    class={`px-4 py-3 text-sm font-medium ${
                                        activeTab() === lang
                                            ? "text-blue-400 border-b-2 border-blue-400"
                                            : "text-gray-400"
                                    }`}
                                >
                                    {lang}
                                </button>
                            )}
                        </For>
                    </div>
                    <pre class="p-6 bg-gray-950 text-gray-300 overflow-x-auto">
                        <code>{examples[activeTab()]}</code>
                    </pre>
                </div>
            </div>
        </section>
    );
}

Country Map

The CountryMap component is an interactive selector showing which countries 0fee.dev supports. Clicking a country reveals which payment methods are available there:

tsxfunction CountryMap() {
    const [selected, setSelected] = createSignal<string | null>(null);

    const countries = [
        { code: "CI", name: "Ivory Coast", methods: ["Orange", "MTN", "Wave", "Moov"] },
        { code: "SN", name: "Senegal", methods: ["Orange", "Wave", "Free"] },
        { code: "GH", name: "Ghana", methods: ["MTN", "Vodafone", "AirtelTigo"] },
        { code: "KE", name: "Kenya", methods: ["M-Pesa", "Airtel"] },
        { code: "NG", name: "Nigeria", methods: ["Card", "Bank Transfer"] },
        // ... 16 more countries
    ];

    return (
        <section class="py-24">
            <h2 class="text-3xl font-bold text-center mb-12">
                Payments across Africa and beyond
            </h2>
            <div class="grid grid-cols-3 sm:grid-cols-5 md:grid-cols-7 gap-4 max-w-5xl mx-auto">
                <For each={countries}>
                    {(country) => (
                        <button
                            onClick={() => setSelected(country.code)}
                            class={`p-4 rounded-lg text-center transition-all ${
                                selected() === country.code
                                    ? "bg-blue-600 text-white scale-105"
                                    : "bg-gray-100 hover:bg-gray-200"
                            }`}
                        >
                            <span class="text-2xl">{countryFlag(country.code)}</span>
                            <span class="block text-xs mt-1">{country.name}</span>
                        </button>
                    )}
                </For>
            </div>

            <Show when={selected()}>
                <div class="mt-8 p-6 bg-gray-50 rounded-xl max-w-2xl mx-auto">
                    <h3 class="font-semibold mb-4">
                        Available methods in {getCountryName(selected()!)}
                    </h3>
                    <div class="flex flex-wrap gap-2">
                        <For each={getMethodsForCountry(selected()!)}>
                            {(method) => (
                                <span class="px-3 py-1 bg-white rounded-full text-sm border">
                                    {method}
                                </span>
                            )}
                        </For>
                    </div>
                </div>
            </Show>
        </section>
    );
}

Design Choices

The marketing site used several design patterns from modern developer-focused products:

  • Dark hero, light content. The hero section uses a dark gradient to create visual impact, while feature and pricing sections use light backgrounds for readability.
  • Glassmorphism effects. Semi-transparent backgrounds with blur effects on navigation and floating elements.
  • Mobile-first responsive design. Every component adapts from single-column mobile to multi-column desktop.
  • Dark/light mode toggle. Persisted in localStorage, because developer tools should respect system preferences.

Five New SDKs

Session 003 added SDKs in Go, Ruby, PHP, Java, and C#, bringing the total to 7 languages. Each SDK followed the same API design: a client initialized with an API key, resource-based methods (payments, apps, checkout), and consistent error handling.

Go SDK

gopackage main

import "github.com/0feedev/zerofee-go"

func main() {
    client := zerofee.New("sk_live_your_key_here")

    payment, err := client.Payments.Create(&zerofee.PaymentParams{
        Amount:        5000,
        PaymentMethod: "PAYIN_ORANGE_CI",
        Customer: &zerofee.Customer{
            Phone: "+2250709757296",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Payment ID: %s\n", payment.ID)
    fmt.Printf("Status: %s\n", payment.Status)
}

The Go SDK used the standard net/http client with struct-based parameters. Error handling followed Go conventions: every method returns (result, error).

Ruby SDK

rubyrequire "zerofee"

ZeroFee.api_key = "sk_live_your_key_here"

payment = ZeroFee::Payment.create(
  amount: 5000,
  payment_method: "PAYIN_ORANGE_CI",
  customer: { phone: "+2250709757296" }
)

puts payment.id        # "txn_abc123..."
puts payment.status    # "pending"

Built on Faraday for HTTP with a gem structure ready for RubyGems publishing.

PHP SDK

php<?php
require_once 'vendor/autoload.php';

\ZeroFee\ZeroFee::setApiKey('sk_live_your_key_here');

$payment = \ZeroFee\Payment::create([
    'amount' => 5000,
    'payment_method' => 'PAYIN_ORANGE_CI',
    'customer' => ['phone' => '+2250709757296'],
]);

echo $payment->id;      // "txn_abc123..."
echo $payment->status;  // "pending"

The PHP SDK used Guzzle for HTTP requests and followed Stripe's PHP SDK pattern with static class methods. Composer package with PSR-4 autoloading.

Java SDK

javaimport dev.zerofee.ZeroFee;
import dev.zerofee.model.Payment;
import dev.zerofee.model.PaymentParams;
import dev.zerofee.model.Customer;

public class Example {
    public static void main(String[] args) {
        ZeroFee zf = new ZeroFee("sk_live_your_key_here");

        Payment payment = zf.payments().create(
            new PaymentParams.Builder()
                .amount(5000)
                .paymentMethod("PAYIN_ORANGE_CI")
                .customer(new Customer.Builder()
                    .phone("+2250709757296")
                    .build())
                .build()
        );

        System.out.println(payment.getId());
        System.out.println(payment.getStatus());
    }
}

The Java SDK used OkHttp with Gson serialization and the builder pattern for request parameters. Gradle build with Kotlin DSL.

C# SDK

csharpusing ZeroFee;

var client = new ZeroFeeClient("sk_live_your_key_here");

var payment = await client.Payments.CreateAsync(new PaymentParams
{
    Amount = 5000,
    PaymentMethod = "PAYIN_ORANGE_CI",
    Customer = new Customer
    {
        Phone = "+2250709757296"
    }
});

Console.WriteLine(payment.Id);      // "txn_abc123..."
Console.WriteLine(payment.Status);  // "pending"

The C# SDK targeted .NET 8.0 with System.Text.Json serialization and async/await throughout.

SDK Line Counts

SDKLanguageApproximate LinesHTTP Client
GoGo~800net/http
RubyRuby~400Faraday
PHPPHP~500Guzzle
JavaJava~1,200OkHttp + Gson
C#C#~600HttpClient

Java is notably larger because of the language's verbosity -- builder patterns, separate model classes, and explicit type declarations add significant line count compared to dynamic languages.

Docker Configuration

The Docker stack defines 7 services that constitute the complete 0fee.dev deployment:

yaml# docker-compose.yml
version: "3.8"

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    environment:
      - DATABASE_PATH=/data/0fee.db
      - CACHE_URL=redis://dragonfly:6379
    volumes:
      - api-data:/data
    depends_on:
      - dragonfly

  worker:
    build:
      context: .
      dockerfile: Dockerfile
    command: celery -A tasks.celery_app worker -l info
    environment:
      - CACHE_URL=redis://dragonfly:6379
    depends_on:
      - dragonfly
      - api

  scheduler:
    build:
      context: .
      dockerfile: Dockerfile
    command: celery -A tasks.celery_app beat -l info
    environment:
      - CACHE_URL=redis://dragonfly:6379
    depends_on:
      - dragonfly
      - api

  dragonfly:
    image: docker.dragonflydb.io/dragonflydb/dragonfly
    ports:
      - "6379:6379"
    volumes:
      - dragonfly-data:/data

  dashboard:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "3000:80"

  website:
    build:
      context: ./website
      dockerfile: Dockerfile
    ports:
      - "3001:80"

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - api
      - dashboard
      - website
    profiles:
      - production

volumes:
  api-data:
  dragonfly-data:

Service Responsibilities

ServiceRoleNotes
apiFastAPI backendMain application server
workerCelery workerProcesses webhook retries, reconciliation
schedulerCelery beatTriggers scheduled tasks
dragonflyCache + message brokerSessions, rate limiting, Celery broker
dashboardMerchant dashboardSolidJS app served by nginx
websiteMarketing websiteSolidJS app served by nginx
nginxReverse proxyRoutes traffic, SSL termination (production only)

The nginx service uses a production profile, meaning it only starts when explicitly requested with docker compose --profile production up. During development, services are accessed directly on their individual ports.

Webhook Delivery Service

The last component built in Session 003 was backend/services/webhook_delivery.py -- the service responsible for delivering payment events to merchant endpoints.

Key features:

  • HMAC-SHA256 signature generation for every delivery, allowing merchants to verify authenticity.
  • Exponential backoff retries with 5 attempts.
  • Delivery logging recording every attempt, response code, and response time.
  • Queue-based async processing via Celery integration.
  • Timeout handling with a 30-second maximum wait per delivery attempt.

Event types covered:

payment.created
payment.completed
payment.failed
payment.expired
payment.cancelled
refund.created
refund.completed
refund.failed
checkout.completed
checkout.expired

Session 003 By the Numbers

MetricValue
Marketing website components9
Marketing website pages8
New SDKs5 (Go, Ruby, PHP, Java, C#)
Total SDKs7
Docker services7
Lines of code added~7,650
Time elapsedOne session

Three sessions completed on December 10, 2025. The first day of development produced a payment orchestration platform with a backend, 7 providers, a dashboard, a checkout widget, background tasks, 7 SDKs in 7 languages, a marketing website, Docker deployment, and a webhook delivery service. Session 004 would add the CLI tool, hosted checkout pages, and comprehensive API documentation.


This article is part of the "How We Built 0fee.dev" series. 0fee.dev is a payment orchestrator covering 53+ providers across 200+ countries, built by Juste A. GNIMAVO and Claude from Abidjan with zero human engineers. Follow the series for the complete build story.

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude deblo

Step Zero Wasn’t Enough: How Validating A Constructor But Not The Runtime Took Down Every Déblo Voice Session The Hour We Shipped Real-Time Camera Streaming

Phase 14 shipped Déblo Eyes — real-time camera streaming over LiveKit to Gemini Live native audio. The first deploy took down every voice session in production within ninety seconds because our Step 0 had validated the constructor without exercising the runtime path. The build log of how Déblo got eyes, what an incomplete pre-flight check cost us, and which polish items we shipped versus deferred.

30 min May 20, 2026
debloclaude-opus-4.7claude-codegemini-live +25
Thales & Claude deblo

The Em-Dash That Killed Production: How One Marketing Tagline In An HTTP Header Took Down Déblo’s Chat For 24 Hours

Two days before App Store submission, Déblo’s entire chat product silently broke. No spinner, no toast, no error in the UI — just dead air. The 24-hour outage came down to a single « é » in an HTTP header value raising UnicodeEncodeError before any request to OpenRouter ever left the backend. The post-mortem of a false hypothesis, a Sentry trace, and a 6-line fix that unblocked the launch.

27 min May 19, 2026
debloclaude-opus-4.7claude-codeincident +19
Thales & Claude deblo

Six Hours From Empty Page to Apple Review — How We Submitted Déblo to the App Store, Live

Live walkthrough of submitting Déblo to the iOS App Store in six hours: what Apple’s validators rejected (a Unicode superscript), what we corrected (a Promotional Text wasted on third-party brands), and the iOS ASO mechanics almost everyone gets wrong.

27 min May 13, 2026
debloclaude-opus-4.7claude-codeapp-store +16