← Selected engineering work
CASE STUDY / 02

Fintech · Payment lifecycle · Go

Invoice Payment System

A payment workflow that creates invoices, initiates hosted payment links, records callbacks, and keeps invoice status in sync.

01

Overview

The system, not just the screen.

A Go and MongoDB backend organized around domain, repository, use-case, delivery, and gateway layers, with a Next.js client for invoice and payment flows.

Problem

Invoice payments cross an external provider boundary. The application must keep its own record of intent and status while handling provider initiation failures and asynchronous callback results.

My work

Implemented the invoice and payment lifecycle, the SantimPay gateway boundary, callback-driven status updates, and persistence-backed transaction history.

02

Architecture

How data moves through the system.

  1. 01Next.js clientInvoice and payment actions
  2. 02Go + Gin APIValidation and delivery layer
  3. 03Use casesInvoice and callback state transitions
  4. 04MongoDB + SantimPayDurable records and hosted payment initiation
03

Engineering

Decisions and constraints.

Selected decisions

  1. 01

    Persist an invoice as PENDING before initiating payment so the application has a durable record of the attempt.

  2. 02

    Mark initiation failures explicitly and store the returned payment URL and provider reference on success.

  3. 03

    Isolate provider-specific payloads and signed-token handling behind a PaymentGateway interface.

  4. 04

    Give outbound payment requests an explicit 10-second HTTP client timeout.

Challenges

  • Keeping local invoice state aligned with asynchronous provider callbacks.
  • Separating provider details from core invoice rules so the domain stays testable.
  • Preserving enough payment history to inspect status changes and failed attempts.
representative_flow.go
invoice.Status = "PENDING"
if err := invoices.Create(invoice); err != nil { return err }

url, reference, err := gateway.CreatePayment(invoice)
if err != nil {
    _ = invoices.UpdateStatus(invoice.ID, "FAILED")
    return err
}
return invoices.UpdatePaymentInfo(invoice.ID, url, reference)
04

Stack & evidence

What it was built with.

  • Go
  • Gin
  • MongoDB
  • REST
  • JWT
  • Next.js
  • TypeScript