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.

Thales & Claude | March 25, 2026 9 min 0fee
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