JFJames FolajimiSOFTWARE ENGINEERING & DESIGNContact
← Projects

SELECTED PROJECTS / 01

VSL Market

A multichannel social commerce web application connecting sellers, buyers and AI agents through the conversations they already use.

Open application
Read the complete technical analysis ↓
Public web application preview of VSL Market
WEB APPLICATION PREVIEWVSL / PROJECT STUDY
PROJECT OVERVIEW

Technical Founder & Engineering Lead

VSL Market is a multichannel social commerce web application. It connects sellers, buyers and AI agents through WhatsApp chats, Facebook chats, VSL Conversations and compatible AI assistant conversations, using these existing conversation channels as points of sale. I built the application to carry enquiries, product information, quotes, orders, payments and fulfilment through the same commerce core. As Technical Founder and Engineering Lead, I work on its architecture, application development and systems and API integrations.

Agentic commerce

Permissioned AI tools connect assistants to business context and commerce operations. Discovery and preparation use the same seller and transaction records as the web application.

Social commerce

WhatsApp and Facebook adapters preserve seller, customer and conversation context as enquiries become quotes, orders and payment requests.

Payment orchestration

Fiat and crypto provider integrations connect payment intents, verification and commercial records. Provider confirmation governs payment status; conversation text is never proof of settlement.

MCP integrationsConversational commercePHP / SQLREST / webhooks

Screenshot of the live web application, captured October 2026.

How the AI commerce system works

02 / INSIDE VSL MARKET

THE PROJECT IN MORE DETAIL

From an enquiry
to an order.

Selling starts in the conversation. VSL connects WhatsApp chats, Facebook chats, VSL Conversations and compatible AI assistants to a shared commerce workflow. Sellers and buyers can continue through their existing channels while product information, enquiries, orders and payment requests stay connected.

Open VSL Market
vsl.market
VSL Market web application
Social and agentic commerce, connected to one commerce core.

AI THAT CAN WORK WITH A BUSINESS

Giving AI assistants
useful tools to work with.

I use the Model Context Protocol (MCP) to connect compatible AI assistants to VSL’s tools and business records. Each connection has defined permissions, so the assistant’s access stays within the seller’s choices.

Commerce Passport makes seller and product information easier for assistants to understand. With the right permissions, an assistant can help with enquiries and prepare commerce requests through VSL’s existing workflows.

MCP servers & toolsLLM tool callingScoped permissions
View the connection guide
INTEGRATION MODEL / CONCEPTUAL
ChatGPTClaudeGeminiCompatible clients
tools · context · permissions
VSL commerce coreApplication authority & existing workflows
Seller & catalogConversationsQuotes & ordersPayment requests

Client capabilities depend on the configured connection and permissions.

My role: product concept, architecture, hands-on engineering, integrations and UX/UI.

Read the technical case study

Engineering case study / 01

VSL Market

The architecture of a connected
commerce system

How I connected conversations, payments and fulfilment in VSL, and the engineering decisions behind that work.

01

The system in context

I built VSL Market to connect seller identity, product discovery, conversations, payments and fulfilment in one system. As Technical Founder and Engineering Lead, I conceived the product, designed its architecture and led the hands-on implementation across web, mobile, WhatsApp, Facebook and compatible AI environments.

The problem I set out to solve was continuity. A buyer could discover a product in one place, negotiate in another and pay through a third service. The seller then had to reconstruct what was agreed, who paid and what should happen next. I wanted VSL to carry that context through the transaction.

Here I explain how I approached that problem: where I separated channel behaviour from commerce logic, which records I kept authoritative, how I handled external events and where I placed limits on AI-assisted actions.

My responsibility
Technical Founder & Engineering Lead
Implementation
PHP · SQL · JavaScript · CSS · REST · JSON
Integration domains
Meta messaging · payment providers · OAuth · MCP
Case-study snapshot
September 2026
02

Problem, concept and operating model

Start with how sellers already work

I started with informal and social commerce in Nigeria. A seller might have a WhatsApp conversation, a Facebook Page, a product listing and a bank-transfer receipt without a single record connecting them. I wanted the system to preserve the relationship between those activities.

I treated this as a systems problem rather than a requirement for another storefront. The core needs to preserve the seller, customer, requested item, agreed amount, payment evidence and fulfilment outcome even when the interface changes.

A commercial case file

I think of the transaction as a case file that accumulates evidence. Seller identity establishes the parties. A request captures intent. A quote or invoice records terms. Provider confirmation establishes payment. Delivery and dispute records describe what happened afterwards. Different interfaces contribute to that file, but I do not give them equal authority to change it.

I use that analogy to explain the relationships, not to suggest that VSL stores everything in one document or an immutable ledger. A message expresses intent; it does not prove settlement. A tracking update expresses progress; it does not automatically prove receipt.

ConstraintArchitectural consequence
Conversations start outside VSLChannel adapters carry origin and participant context into the commerce core.
Payment evidence comes from another systemProvider verification governs the paid transition.
Sellers use several interfacesIdentity and commerce records are reused across interfaces.
AI output is not transaction evidenceAI can discover and prepare within permissions; protected execution remains in VSL.
The platform is already liveNarrow bridges and helpers extend established flows without replacing stable payment or messaging logic.
03

Architecture and authority boundaries

I have grouped the architecture by responsibility below. These groups do not imply separate microservices, message brokers or independently deployed infrastructure.

01 / Entry surfaces
Web & mobile marketplaceWhatsApp & FacebookCompatible AI applications
02 / Access and interpretation
Profiles & Commerce PassportChannel routing & sessionsOAuth & capability controls
03 / VSL commerce authority
Requests · quotes · invoicesPayment intents · orders · inventoryDelivery · disputes · history
04 / External evidence
Payment-provider confirmationDelivery activity & proofParty responses & refund evidence
Figure 1. Channels access a shared commerce core. External evidence supports controlled state changes.

One owner for each consequential fact

I keep seller, request, order, inventory and delivery records under VSL’s control. Integrated providers supply payment confirmation. Messaging platforms transport events and have their own connection requirements. Connected AI applications operate within the access granted to them. This separation helps me distinguish a successful interaction from a completed commercial event.

Extending a working application

I use PHP and relational SQL for application logic and data, JavaScript and CSS for the interfaces, and REST integrations with JSON exchanges. I connect newer conversational and agent-assisted flows through bridge and helper layers because the existing notification, payment and invoice paths already carry production responsibilities.

The trade-off I accept is coordination. A narrow bridge limits the scope of a change, but linked records still need to converge on the same outcome. When I add an interface, it needs to reuse the existing paid transition and ownership rules.

04

Domain structure and data continuity

When I reason about the data, I start with the relationships between records. The table below explains the responsibilities I need those relationships to preserve; it is not a proposed SQL schema.

Domain recordWhat it representsContinuity requirement
Seller and Commerce PassportIdentity, approved catalog, contact routes and available capabilities.Public discovery must respect seller visibility and permission choices.
Conversation contextChannel, seller connection, customer identity and originating request.Retain origin after the interaction leaves the chat interface.
Request, quote and invoiceCustomer intent and the commercial terms prepared from it.Keep parties, amount, currency and purpose associated.
Payment intent and provider referenceA request to pay and the external evidence used to verify it.Correlate the provider outcome to the intended transaction.
Order and inventoryThe commercial commitment and its effects on stock.Use canonical commerce transitions rather than message text.
Delivery job and proofRecipient details, assignment, tracking and fulfilment evidence.Retain the link to the relevant order or offline sale.
Dispute and refund recordParty responses, case visibility and resolution evidence.Distinguish a claim, an acknowledgement and a verified outcome.

Identity is contextual

I distinguish a channel address from a seller account and a customer record. A WhatsApp business phone-number identifier helps resolve the receiving seller connection. The incoming sender identifies the customer in that channel, and a persisted commerce session carries the relationship forward. Treating those identities as interchangeable leads to routing errors.

Origin survives the payment link

I need a payment request to retain enough context to return its result to the right conversation. If I lose the origin during creation, the payment can exist correctly while its button or confirmation reaches the wrong place. That makes origin persistence part of the commerce flow, not just the interface.

Events and effects

I separate the incoming event, the accepted state change and its downstream effects. A callback is an observation to validate and associate with an internal record. The state transition accepts that observation. Notifications and other follow-up work are effects of the transition. This distinction is how I reason about retries.

05

Payment orchestration and verification

I integrated Fincra, NOWPayments and Busha across fiat, cryptocurrency and conversion-related workflows. VSL supports cards, NGN bank transfer, Bitcoin and USDT TRC20, alongside payment links, virtual POS and Naira virtual accounts. I connect these distinct provider paths to the same commerce journey.

From intent to confirmed payment

  1. Prepare the commercial context. Associate the seller, customer where known, amount, currency, purpose and origin with the payment request.
  2. Open the supported provider path. The customer completes the relevant payment process. A redirect or chat response is not sufficient evidence of payment.
  3. Verify the provider event. Validate the callback through the provider-specific verification flow and correlate it to the intended transaction.
  4. Apply the canonical paid transition. Repeated delivery of the same event must not create another payment outcome or duplicate downstream effects.
  5. Continue the commerce journey. Linked orders, inventory, delivery actions and customer or seller notifications follow the authoritative outcome.

Two separate reliability problems

I treat deduplication and idempotency as separate responsibilities. Deduplication recognises repeated events. Idempotency makes repeated processing safe. Recognising a duplicate callback is useful, but I also need the commercial effects to remain safe when processing is retried.

I keep payment confirmation separate from message delivery. A failed notification must not turn a verified payment into an unpaid one. Equally, delivering a payment button successfully does not mean the customer has paid.

ConditionRequired interpretation
Customer returns to a success-looking screenWait for authoritative payment verification.
Provider repeats a callbackPreserve the existing verified outcome and avoid duplicate effects.
Confirmation message failsPayment truth remains independent of the notification outcome.
Payment is confirmed but fulfilment is incompleteThe order still needs its own delivery evidence.
A refund is claimed in conversationA claim alone does not establish a verified refund.

Economics without confusing volume and revenue

VSL earns a 2% commission on completed customer payments. I distinguish that revenue from the money customers pay sellers. Our first-campaign payment value of ₦933,000 corresponds to ₦18,660 in commission, before operating costs or any assessment of profitability.

I designed VSL around a non-custodial operating model, with funds forwarded through configured bank or cryptocurrency payout arrangements. I am describing the product model here, not asserting a regulatory classification or settlement guarantee.

06

WhatsApp and Facebook integration

Platform configuration is part of the system

My Meta integration work went beyond parsing messages. I worked through business and WABA configuration, phone-number identifiers, Facebook Page connectivity, webhook subscriptions, verification, credential handling, permissions and connection-state behaviour. I cannot treat a saved connection as proof that messages are arriving.

I designed the WhatsApp flow to accommodate a shared VSL sender and seller-connected business accounts. Routing needs to resolve the seller for the relevant mode, then retain the customer and conversation context. For Facebook, that depends on the corresponding Page connection and usable credentials.

Inbound processing

I distinguish endpoint verification from authentication of incoming payloads. GET verification supports webhook setup; HMAC validation addresses payload authenticity. After that, event deduplication, routing and session persistence determine which commerce flow receives the message.

I connected commands such as pay, amount-specific requests and seller instructions such as “send”, “charge” or “collect” to payment preparation. The parser needs to interpret abbreviated amounts and currency context, but I do not let it determine whether payment has completed.

Outbound processing

I use the channel layer to return payment links, seller-profile routes and relevant transaction updates. Recipient resolution, consent and STOP/START handling all matter. Template and direct-message paths affect delivery; they must not change the commercial outcome underneath.

Connection health is operational evidence

During the September Facebook investigation, the callback endpoint and subscriptions appeared healthy, but message delivery remained blocked. Page reconnection had not returned a usable access token. That reinforced the need to trace the whole chain: configuration, credentials, event arrival, routing, processing and delivery.

I include Facebook workflows in this account because they are part of the platform I built. I keep that separate from whether every connected Page is operational at a particular moment.

07

Commerce Passport and discovery

I built Commerce Passport as the seller-controlled, machine-readable identity and capability layer. It brings together approved merchant information, VSL catalog data, contact routes, permitted actions and relevant commercial evidence. My aim is to make a seller understandable across interfaces while preserving the boundaries around private records.

Publication and execution are different concerns

I separate publication, authenticated access and execution. A public descriptor explains what a merchant offers. An authenticated connection determines what a client may access or prepare. A checkout or other protected flow governs consequential execution. An assistant may present these as one conversation, but the application still needs those boundaries.

Passport concernEngineering purpose
Seller identityResolve the business behind a listing or request.
Catalog informationExpose approved products and current business information.
Contact and action routesConnect discovery to the seller’s permitted next steps.
CapabilitiesDescribe the operations available within the relevant permissions.
Commercial evidenceConnect discovery to transaction and fulfilment history where publication is permitted.

Privacy must be consistent across representations

I need the same privacy decision to hold across the public profile, Passport, discovery output and connected tools. A review identified a conflict where the existence of settings could override private-profile intent. The follow-up approach was a shared, fail-closed visibility decision. I keep that work separate from a verified release claim or security certification.

My rule is that creating settings must not silently grant public visibility. A seller cannot be private in one interface and become public through another.

Discovery is not guaranteed indexing

I use machine-readable information to give compatible consumers a clearer structure. I do not treat that as a guarantee of search indexing or inclusion in an assistant’s results. I also distinguish a standards reference from conformance: I am not claiming W3C Verifiable Credentials 2.0 compliance for VSL.

08

AI applications, agents and controlled access

I build applications, agents and LLM tools that connect through Model Context Protocol (MCP). In VSL, that work connects discovery and transaction preparation to existing seller permissions and commerce records.

Two uses of AI

I use AI commerce to describe discovery, comparison and purchase preparation using approved seller and product information. With agentic commerce, a seller can authorise assistance with replies, order drafts, payment requests and follow-ups. In both cases, I keep VSL authoritative over its commercial state.

Permissions attach to the client and the action

My integration work includes OAuth authorisation, client registration, capability scopes, consent and ownership checks. I moved toward provider-independent access so that the commerce model would not depend on one assistant vendor. Discovering a tool still does not give a client permission to execute every operation.

ActivityAuthority boundary
Read merchant or catalog informationOnly approved public or permissioned information is available.
Prepare a reply or order draftPreparation is constrained by the seller’s permitted capabilities.
Prepare a payment requestThe request uses the protected commerce flow and applicable approval controls.
Declare a transaction paidAn AI response cannot replace provider verification.
Declare delivery complete or release fundsA generated response cannot create the required operational evidence or bypass controls.

Why this boundary matters

I assume that a language model can interpret a request and still make a wrong assumption. Product selection, pricing, ownership and execution permissions therefore need to be checked against application state. Natural-language convenience cannot become another route around authorisation.

The pattern I use is a controlled entry point into the existing system: expose useful capabilities, enforce access in the application and route consequential actions through established flows. I still need to validate compatibility for each client. Protocol support alone does not prove universal assistant support.

09

Orders, delivery and accountability

Payment is a milestone, not the end of the record

I carry the commerce record beyond payment into order handling, inventory, delivery and exceptions. VSL connects delivery details, jobs, assignments, tracking and proof to the order. I also included offline sales so that the operational record does not depend on a transaction starting in the marketplace.

With pay-on-delivery, the sequence differs from prepaid commerce. VSL excludes external couriers for that flow. I keep payment evidence and delivery evidence distinct even when they are collected close together.

A dispute has its own lifecycle

I give buyer and seller a private resolution period. Publication depends on elapsed time and whether the case has been resolved. If resolution happens after publication, the public history records the outcome rather than erasing the earlier case.

Case conditionVisibilityMeaning
Opened, within 24 hoursPrivateThe parties can respond and supply evidence.
Resolved before 24 hoursNot publishedResolution remains in the private record.
Unresolved after 24 hoursLimited public historyA redacted case status becomes visible.
Resolved after publicationPublic, marked resolvedThe record reflects the later outcome.

I do not treat a complaint as proof of wrongdoing. Contact details, payment credentials and uploaded evidence stay out of the public summary. I also distinguish refund acknowledgement and verification from an unsupported refund claim.

I approach this as a temporal access-control problem as well as an interface problem. The visibility rule needs to hold wherever the record appears, including seller profiles and Commerce Passport.

10

Engineering investigations and trade-offs

Investigation 01

Payment-button failures in WhatsApp

What I was fixing. A seller instruction could create or relate to a payment request without producing the expected customer-facing payment action.

What the investigation exposed. Transaction origin, repeated-event handling, recipient resolution and instruction parsing interacted across the flow. The symptom appeared in chat, while the underlying problem involved persisted commerce context.

What I changed. I preserved origin, corrected idempotency behaviour, resolved the customer through commerce sessions and expanded handling of repeated seller instructions. WhatsApp operation was subsequently confirmed.

What I took from it. I need to trace identity and context across the flow before adding another delivery mechanism. A payment action needs the right transaction, recipient and channel, not just valid message formatting.

Investigation 02

Provider-independent AI access

The problem I wanted to avoid. Provider-specific assumptions would make the shared commerce integration harder to extend and reason about.

What I changed. I moved the AI connection work toward generic MCP access, client-specific capability controls and corrected ownership filtering. I kept the shared commerce services authoritative.

The trade-off I accept. A common interface reduces vendor coupling, but I still need to validate each client and bound its permissions. Abstraction does not remove that testing work.

Investigation 03

Preserving production behaviour while adding channels

My constraint. I could not replace working payment, invoice and notification paths simply to accommodate a new interface.

My approach. I use bridge and helper layers to carry additional context into established services. I keep changes narrow enough to review against the latest working files.

The trade-off I accept. Additive integration limits disruption, but I still need to coordinate linked records and keep ownership of state transitions explicit.

11

Formal model and correctness conditions

The question I keep returning to is whether a commerce action stays correct when events are duplicated, participants change interfaces or an external dependency fails. I need more than a happy-path diagram to answer it.

Model a transaction as several related states

I cannot describe the whole transaction with one “complete” flag. Payment can be verified while delivery remains open, and a paid order can later enter a dispute. I separate those dimensions in the analytical model below.

Model 1. Independent dimensions of a commerce record Explanatory pseudocode
Commerce = (Identity, Request, Payment, Fulfilment, Dispute)

Payment     ∈ {awaiting_evidence, verified, unsuccessful}
Fulfilment  ∈ {not_started, in_progress, evidenced}
Dispute     ∈ {none, private_open, public_open, resolved}

verify_payment(evidence) changes Payment
record_delivery(evidence) changes Fulfilment
resolve_case(evidence) changes Dispute

Payment = verified does not imply Fulfilment = evidenced

I use these labels to explain the state model, not to reproduce VSL database enum values. The distinction I want to make is that completion in one subsystem does not prove completion in another.

Invariants define what must stay true

InvariantFormal statementPractical consequence
Payment authoritypaid(t) ⇒ accepted_provider_evidence(t)A chat message or redirect cannot establish payment.
Ownershippermitted(actor, action, record) must hold at executionKnowing a record identifier is not permission to use it.
Retry safetyapply(apply(S, e), e) = apply(S, e)A repeated event preserves the accepted business outcome.
Origin continuityorigin(request) remains associated with its downstream actionThe response can return to the intended channel and customer.
Privacypublic(record) ⇒ publication_allowed(record)A new settings row cannot substitute for permission.
Evidence separationpayment_verified ≠ delivery_proven ≠ refund_verifiedEach assertion needs the appropriate evidence.

In the idempotency equation, I am comparing business state. Log entries and retry counters can change when the same event is observed again; the commercial effect should remain singular.

Preconditions, transitions and postconditions

I analyse a verified-payment transition in three parts. Its preconditions cover authenticity, correlation and acceptable provider status. The transition changes authoritative payment state. Its postcondition is that the linked journey can observe that result without repeating commercial effects. An HTTP success response alone does not establish those properties.

12

Webhook algorithm and concurrent retries

Authenticate before interpreting authority

My Meta integration includes HMAC verification. In this model, I authenticate the exact received payload before accepting claims extracted from it. I keep endpoint setup verification separate from payload authentication because they solve different problems.

Algorithm 1. Signature boundary Explanatory pseudocode
function authenticate_event(raw_bytes, signature, secret):
    if signature is absent:
        reject

    expected = HMAC_SHA256(secret, raw_bytes)
    supplied = decode_expected_signature_format(signature)

    if supplied is invalid:
        reject
    if not constant_time_equal(expected, supplied):
        reject

    return parse_json(raw_bytes)

Why byte identity matters. Parsing JSON and serialising it again can change whitespace or key order. Authenticating that reconstructed representation can disagree with the sender’s signature. I keep signature checking ahead of semantic interpretation in this model.

Boundary of this example. I use this example to explain Meta HMAC verification, not as a universal payment-provider verifier. Each provider path must follow its own verification contract. The comparison routine must be supplied by a suitable cryptographic library rather than handwritten string comparison.

Prevent a check-then-write race

Two workers may receive the same valid event concurrently. Both can observe “not processed” before either writes an outcome. An in-memory check or an ordinary read followed by an insert is not enough to establish exclusive processing.

Algorithm 2. Atomic acceptance of a payment event Explanatory pseudocode
event = authenticate_and_interpret_provider_event(input)
intent = correlate_with_expected_payment(event)
validate_provider_specific_payment_evidence(event, intent)

begin transaction
    claim = claim_event_atomically(provider, event.identity)
    if claim already committed:
        commit
        return accepted_duplicate

    payment = read_payment_with_concurrency_control(intent)
    if payment already has this verified outcome:
        record_event_as_handled(event)
        commit
        return accepted_duplicate

    apply_allowed_payment_transition(payment, event)
    record_required_downstream_work_once(payment)
    record_event_as_handled(event)
commit

return accepted

Atomic claim. For this algorithm to hold, I need a durable uniqueness or equivalent concurrency mechanism. A unique constraint, a lock or an atomic conditional operation can support such a guarantee; their use in VSL cannot be inferred from this model.

Transaction scope. In this model, I group event acceptance, state mutation and durable downstream-work recording. A provider call or outbound message should not be assumed to participate in the same database transaction. If multiple stores are involved, this local atomicity argument no longer applies unchanged.

Crash reasoning. A crash before commit should leave the event retryable. A crash after commit but before the HTTP response should make the next delivery observe an already accepted result. A committed “processed” marker written before the actual state change would violate this reasoning.

Failure pointRiskProperty needed
Two workers accept one eventDuplicate commercial effectsAtomic claim or equivalent concurrency control.
Worker stops before commitPartially applied stateRollback or a recoverable state protocol.
Commit succeeds, response is lostProvider retries accepted workA durable record of the accepted result.
Notification fails after payment acceptanceCustomer misses the updateRetry notification work without reversing payment truth.
Older event arrives after verified paymentState regresses incorrectlyExplicitly allowed transitions, not last-message-wins.

Complexity. HMAC processing is linear in payload size, O(b). With suitable tree indexes, event and payment lookups are typically O(log n). Contention and provider latency can dominate elapsed time; these bounds are analytical, not VSL benchmark results.

13

Conversation routing and command parsing

Resolve the seller and customer independently

I separate the identity receiving a message from the identity sending it. For seller-connected WhatsApp traffic, the business phone-number identifier resolves the receiving connection, while the channel sender identifies the customer. Shared-sender traffic needs its own established routing context.

Algorithm 3. Route a seller-connected conversation Explanatory pseudocode
message = authenticated_channel_message(input)

seller_connection = resolve_receiving_connection(
    channel = message.channel,
    receiving_identity = message.business_identity
)
require seller_connection is unambiguous

customer = normalise_channel_sender(message.sender)
session = resolve_or_create_context(
    seller_connection,
    customer,
    message.channel
)

require session belongs_to seller_connection.seller
retain_origin(session, message.origin)
route_to_existing_commerce_flow(session, message)

Tenant boundary. The seller connection participates in the session lookup. Looking up a customer phone number alone can confuse that customer’s conversations with different sellers. Normalisation also needs the channel’s identity rules; it should not guess a missing country code from an arbitrary default.

Mode boundary. I have scoped this example to the seller-connected mode. It must not be applied blindly to a shared VSL sender, where the receiving business identifier alone may identify the platform rather than the intended merchant.

Parse intent without manufacturing certainty

I included seller payment instructions and abbreviated amounts in the conversational flow. To explain the parsing problem, I use “Abeg send 2k”: recognise the command, extract the number and suffix, resolve currency from explicit or trusted context, then prepare a request.

Algorithm 4. Interpret an abbreviated payment instruction Explanatory pseudocode
function interpret_payment_instruction(text, context):
    tokens = tokenise(normalise_spacing_and_case(text))
    action = match_supported_command(tokens)
    amount_token = match_unambiguous_amount(tokens)

    if action is missing or amount_token is ambiguous:
        return needs_clarification

    value = parse_decimal_exactly(amount_token.number)
    multiplier = suffix_multiplier(amount_token.suffix)
    amount = value * multiplier
    currency = explicit_currency(tokens) or trusted_currency(context)

    if currency is unknown or amount is not allowed:
        return needs_clarification

    return prepared_request(action, amount, currency, context)

Worked trace. In an illustrative session with trusted NGN context, 2k expands to 2 × 1,000 = 2,000 NGN. Without currency context, “2k” alone does not establish the currency. A parsed instruction still needs the correct recipient and execution permissions.

Precision. Monetary arithmetic should preserve decimal meaning. Binary floating-point rounding must not silently change a requested amount. The currency’s unit convention and the provider’s representation must be handled explicitly; I am not specifying VSL’s deployed numeric type in this model.

Input conditionWhy it mattersModel response
“send 2k”, trusted currency presentAbbreviation is interpretablePrepare a request for the expanded amount.
“send 2k”, currency unresolvedNumber alone is insufficientSeek clarification rather than guess.
Two competing amounts in one instructionRecipient amount is ambiguousDo not silently select the first number.
Same text, new message identityCould be a legitimate repeated requestDo not deduplicate solely by message text.
Same delivered event repeatedTransport retryReuse the accepted event outcome.

Complexity. A bounded token scan is O(m) in message length. Poorly designed regular expressions can invalidate that expectation through excessive backtracking. Routing cost depends separately on indexed connection and session lookups.

14

Capability checks and privacy predicates

Authorisation is an intersection

I model authorisation as an intersection of client capability, granted scope, ownership, seller policy and approval state. Authentication satisfies only one part of that decision. If any required predicate fails, the action must be denied.

Algorithm 5. Authorise a prepared commerce action Explanatory pseudocode
function authorise(principal, client, action, resource):
    require principal is authenticated
    require client is enabled
    require action in client.capabilities
    require action in principal.granted_scopes
    require principal may_access resource
    require seller_policy_allows(action, resource)

    if action requires approval:
        require approval_matches(
            principal, action, resource, current_terms
        )

    return allowed

# Recheck at the execution boundary.
# Tool visibility is not execution permission.

Time-of-check versus time-of-use. Between preparing an order and executing it, price, availability, consent or permissions may change. An earlier approval must not be treated as unlimited consent to altered terms. The protected flow needs to check the relevant current state at execution.

Application authority. A model-produced statement such as “the customer has paid” is untrusted input to this boundary. It cannot satisfy the provider-evidence requirement. Prompt wording is not a replacement for server-side access controls.

A shared publication decision

The privacy review exposed a case where settings could override a private-profile decision. I explain the correction as one shared publication predicate, reused wherever a seller representation can leave the private application context.

Algorithm 6. Fail-closed publication model Explanatory pseudocode
function may_publish(profile):
    return profile explicitly permits public visibility
       and profile is active
       and profile is not deleted

function public_representation(profile, viewer):
    if not may_publish(profile):
        return unavailable

    return project_approved_fields(profile, viewer)

# A settings record existing is not a visibility grant.
# A publication grant is not permission to expose every field.

Projection after permission. Authorising publication and selecting fields are separate steps. A public profile does not imply that credentials, private evidence or customer details should be included.

Consistency risk. A correct predicate in one endpoint does not protect an older discovery response, cached representation or another public route. Cache invalidation and all publication paths would need inspection before claiming an end-to-end guarantee. I have not established those implementation details in this account.

Complexity. Checking a fixed set of loaded predicates is O(1). Projecting k approved fields is O(k). Database retrieval, nested catalog expansion and cache behaviour determine the broader cost.

15

Relational reasoning and durable effects

A relationship carries authority

When I model a relationship, I ask what it establishes and who owns it. A payment linked to an invoice must agree with the parties and terms accepted by the payment flow. A delivery job must remain attached to its order or offline-sale context. Assigning identifiers alone does not establish that consistency.

RelationshipConsistency questionFailure if ignored
Seller connection → conversation contextDoes this receiving connection resolve the intended seller?Cross-seller routing.
Conversation → payment requestAre recipient and origin preserved?Correct payment link sent to the wrong conversation.
Provider reference → payment intentDoes accepted evidence match the intended transaction?Unrelated provider event changes payment state.
Payment outcome → downstream effectHas this business effect already been applied?Repeated notifications or other duplicate commerce effects.
Order → delivery evidenceDoes the evidence belong to this order?Unrelated tracking treated as fulfilment.

Why retries need two identities

I distinguish an external event identity from a business-effect identity. Different provider events can refer to the same business result. Equally, identical text in two separate messages can represent two valid requests. Those cases need different retry decisions.

Model 2. Event identity and effect identity Explanatory pseudocode
event_identity  = (source, external_event_identity)
effect_identity = (commerce_record, effect_kind, relevant_version)

same event_identity:
    handle a transport retry safely

different events, same effect_identity:
    preserve one intended business effect

same message text, different event_identity:
    do not assume the requests are duplicates

The dual-write problem

To reason about the dual-write problem, I consider a worker that commits payment state and then calls a messaging service. A crash between the two leaves a paid transaction without its message. Reversing the order risks sending a confirmation for a transaction that never commits. Neither ordering makes the two systems atomic.

One design option I examine is a transactional outbox: commit the state and a durable work item together, then deliver the work separately. I include it here to analyse the reliability problem, not to claim that VSL currently implements an outbox.

Algorithm 7. Outbox pattern as an analytical option Explanatory pseudocode
within one local transaction:
    apply_verified_business_transition()
    insert_effect_if_absent(effect_identity, payload)
commit

worker:
    work = claim_due_effect_with_lease()
    result = deliver(work)
    if result is confirmed:
        mark_delivered(work)
    else:
        schedule_retry_with_bounded_backoff(work)

Remaining ambiguity. If delivery succeeds but the worker stops before marking the item delivered, a retry can send twice. An outbox makes work durable; it does not create exactly-once delivery across an arbitrary external service. A provider-supported idempotency mechanism can help where available. Otherwise, the application must tolerate and manage that ambiguity.

What indexes can and cannot establish

I would assess constraints and indexes against actual query predicates and key selectivity. A unique constraint can support a singular claim, while an index can reduce lookup cost. I have not reproduced DDL here or established the deployed query plans; those require a repository and schema review.

16

Temporal rules and boundary analysis

Dispute visibility depends on event order

My dispute policy gives a case 24 hours of private resolution. If it is resolved before the threshold, it stays private. If it crosses the threshold unresolved, it can enter public history. Once published, a later resolution changes the outcome without deleting that history.

Algorithm 8. Dispute publication decision Explanatory pseudocode
function visibility(case, now):
    if case.was_published:
        return public_with_current_resolution_status

    deadline = case.opened_at + 24 hours

    if case.resolved_at exists and case.resolved_at < deadline:
        return private

    if now < deadline:
        return private

    if case.resolved_at is absent or case.resolved_at > deadline:
        return eligible_for_limited_publication

    return apply_defined_equal_timestamp_policy

# Eligibility and actual publication are distinct.
# was_published represents history in this model, not a claimed column.

The exact boundary. “Before 24 hours” and “after 24 hours” leave a tie when resolution and the deadline have the same timestamp. I have not established that tie-breaking rule here. It should be explicit rather than silently chosen by database precision or worker timing.

Eligibility versus execution. A scheduler can run late. Publication eligibility should be derived from event times, while already-published history must remain distinguishable. A delayed worker should not incorrectly treat a case resolved before the deadline as unresolved simply because it sees the record later.

Concurrency. Resolution and publication can race. The publication decision needs a consistent view of resolution state at its write boundary. Reading the case once and publishing later without rechecking can disclose a case whose resolution should have kept it private.

Boundary caseExpected reasoning
Resolved at deadline minus one secondRemains private under the documented rule.
Unresolved at or beyond the thresholdEligible for limited publication.
Already published, then resolvedRemains public with the resolved outcome.
Resolved exactly at the thresholdRequires an explicit tie policy; not inferred here.
Worker processes old eligible cases lateUse event timestamps and publication history, not worker arrival order alone.

Complexity. The decision for one loaded case is O(1). Finding eligible cases at scale is a separate query problem. Without suitable access paths, a periodic scan may be O(n); no production query performance is asserted.

17

Test design, counterexamples and performance

Test properties, not just example screens

I want tests that can expose a broken invariant. Rendering a payment button does not prove recipient correctness. Returning success from a webhook does not prove that a repeated event leaves stock and notifications unchanged.

Algorithm 9. Property-oriented verification sketch Explanatory pseudocode
property replay_preserves_business_result:
    once = apply(starting_state, valid_event)
    twice = apply(once, valid_event)
    assert business_projection(twice) == business_projection(once)

property caller_cannot_cross_seller_boundary:
    assert execute(seller_A, protected_record_of_B) is denied

property message_cannot_prove_payment:
    result = process_chat("payment completed")
    assert result does not bypass provider_verification

property private_resolution_stays_private:
    case = resolved_before_publication_deadline()
    assert visibility(case, later_time) == private

I propose these properties for the explanatory model; I have not run them against the production application for this article. I keep them separate from the historical 655-pass result for the earlier validated suite.

TechniqueCounterexample to constructWhat it reveals
Concurrent executionTwo workers claim the same provider event simultaneously.Whether deduplication survives a race.
Fault injectionStop after commit but before HTTP acknowledgement.Whether retry preserves the accepted outcome.
Out-of-order deliverySend an earlier pending event after verified payment.Whether the state can regress.
Tenant isolationReuse a customer identity across two sellers.Whether routing includes the seller boundary.
Parser ambiguityProvide two amounts or omit unresolved currency.Whether interpretation invents commercial terms.
Permission changeRevoke access after preparation, before execution.Whether execution rechecks authority.
Time boundaryResolve immediately before, exactly at and after the deadline.Whether temporal policy is defined and consistent.

Separate algorithmic cost from system latency

I break webhook latency down as T = T_auth + T_lookup + T_lock + T_commit + T_response. Synchronous external work adds another term. An O(log n) lookup does not make the request fast if it is waiting on a lock or a provider.

OperationAnalytical cost under stated assumptionsLikely practical limit
Authenticate payload of b bytesO(b)Payload size and cryptographic implementation.
Scan message of m charactersO(m) with bounded token matchingParser ambiguity or regex backtracking.
Indexed lookup among n recordsTypically O(log n) with a tree indexSelectivity, storage I/O and query plan.
Check loaded fixed permissionsO(1) for a fixed predicate setAccess-data retrieval and stale authorisation.
Project k approved fieldsO(k)Catalog expansion and response size.
Evaluate one dispute deadlineO(1)Race control and batch selection.

I use these bounds to reason about cost, not to report measured VSL performance. For a performance claim, I would need to state the revision, dataset, concurrency, environment, provider dependencies and latency distribution. I have not supplied those measurements here.

What the engineering demonstrates

The difficult part of this work is preserving meaning across boundaries. A sender identity becomes a scoped conversation, an instruction becomes a prepared request, provider evidence becomes payment state, and an elapsed deadline becomes a publication decision. For each translation, I need a defined input, authority check, state transition and failure behaviour.

18

Validation and operational reasoning

My September engineering record reports 655 passing tests, zero failures and two skipped tests. I tie that result to the suite and revision validated at the time. I do not use it as a coverage percentage, a performance benchmark or proof that every subsequent change was tested.

Some later Passport work was delivered without PHP runtime or live endpoint verification. I keep its validation status separate from the earlier passing suite. A previous test result cannot establish that a later change is correct.

What must be demonstrated

BoundaryMeaningful validation question
Identity and ownershipCan one seller or client access another seller’s protected commerce state?
Webhook verificationAre invalid events rejected before consequential processing?
Repeated eventsDoes replay preserve one commercial outcome and avoid duplicate effects?
Conversation routingDoes the action reach the customer associated with the originating request?
Provider confirmationCan a redirect, message or AI response incorrectly mark a payment paid?
PrivacyDo all public representations honour the same visibility decision?
Delivery and disputesDo state and publication rules hold at their time and evidence boundaries?

What this site runs

I use this static site to explain VSL. It does not run the production application, process payments or expose credentials and live customer records.

Operational checks complement code tests

I also need operational evidence beyond code tests. External connections can fail without a source change, so credential validity, subscription state, event arrival and outbound delivery need separate checks. The Facebook reconnection investigation is an example of why application tests alone cannot establish connection health.

19

Operating evidence and analysis

Our first campaign showed that sellers and customers had used the payment path. I treat it as an early operating sample. Social-commerce services were being reused, while repeat adoption of the payment layer remained low.

MeasureReported valueInterpretation
Registered users450+Adoption of the platform; not a monthly-active-user count.
Completed payments130A transaction count, not a count of unique buyers.
Sellers receiving payments60–99A reported range; no precise seller count is inferred.
Customer payment value₦933,000First-campaign payment volume, not VSL revenue.
Platform commission₦18,6602% of the reported payment value; not net profit.
Mean value per completed payment≈ ₦7,177₦933,000 ÷ 130, rounded to the nearest naira.
Monthly payment adoptionApproximately 2%Reported in the pitch; cohort and denominator are not specified.

What the numbers support

I can reconcile the payment value and commission arithmetically. I calculate the average payment from the reported aggregate, not a fresh analysis of individual transactions. The seller range shows use beyond one merchant, but it does not tell me concentration, median seller volume or retention.

I cannot turn the approximately 2% figure into buyer retention, conversion or monthly active users without a defined denominator and time window. I have not inferred a revenue-growth curve or funnel from these totals.

The next measurement problem

My next measurement priority is a focused cohort in one category and location. I want to follow the complete loop: activated sellers, requests progressing to payment, repeat payments, fulfilment evidence and support or dispute resolution. These are evaluation goals, not results I have already established.

I see the engineering foundation and commercial proof as different stages of the work. Building a connected flow establishes capability. Repeated use, with measured completion and support outcomes, tells me whether that capability is serving sellers reliably.

20

Design decisions and remaining limits

The part of VSL I can carry into another system is the reasoning behind these boundaries. I would ask the same questions wherever several interfaces write into a shared operational record.

DecisionBenefitCost or remaining limit
Keep one commerce authorityConsistent prices, payments and order state across channels.Every adapter must preserve ownership and context.
Use provider-confirmed payment transitionsSeparates payment evidence from interface behaviour.Availability and reconciliation still depend on external providers.
Extend with narrow bridgesPreserves working production flows.Linked records require disciplined synchronisation and regression checks.
Expose permissioned AI capabilitiesSupports new interfaces without handing them unrestricted control.Client compatibility and access boundaries need individual validation.
Separate public evidence from private recordsAllows accountability with constrained disclosure.Privacy rules must hold across every representation.
Begin with a focused commercial cohortMakes repeat use and operating problems measurable.Broad product capability does not yet establish scaled demand.

What this case study does not establish

I have not supplied reproducible load tests, latency percentiles, uptime guarantees, infrastructure-cost benchmarks or a complete production schema in this account. I also make no claim of universal AI-client compatibility, independent security certification or registration in every market.

Applying the approach elsewhere

I would start by identifying the organisation’s authoritative records and the external evidence that can change them. Then I would map each new interface to explicit read, prepare and execute permissions, preserve origin and ownership through the workflow, and test repeated events and partial failures at those boundaries.

I would adapt the implementation to the organisation, while keeping the same discipline: retain commercial context, assign authority explicitly and validate the transition that matters.

21

My background

I’m James Folajimi, Technical Founder and Engineering Lead at VSL Market. My work spans application development, integrations, product decisions and the operational details needed to take a system from concept into use.

Before VSL, my experience included telecommunications applications and gateway connectivity at Cloud Interactive Associates, multi-location internet radio at Exodus, web applications and API work at CherryBrooks Media, and UX/UI and brand work at LifeBank Nigeria. These settings shaped how I approach systems whose interfaces, providers and operating teams depend on one another.

My technical foundation includes a BSc in Computer Science, C Programming and Core Java training, and TDWI study in analytics and data visualisation. An MSc in International Business at the National College of Ireland informs the commercial side of my product work.

Discuss a project VSL Market

22

Evidence, terminology and scope

I have written this account from my September 2026 product materials and engineering history. I distinguish what I built, what I reported at the time and the models I use to explain the design. This is my technical account, not an independent audit.

SourceUsed forScope
VSL Latest Pitch(1).pdf, September 2026Product model, Commerce Passport, workflow, payment choices, operating metrics and dispute policy.Pages 2–3, 6–8, 11 and 14–17.
Folajimi CV TECH(3).docxImplementation stack, engineering responsibilities, integrations and historical regression result.Professional profile, capabilities, VSL achievements and architecture sections.
VSL implementation and review historyRouting investigations, preservation constraints and validation limitations.Historical project account; not a fresh source-code or live-system audit.

I describe my MCP work as building applications, agents and LLM tools that connect through the protocol. The source documents I used for this account are not bundled with the website.

I wrote the formal models, pseudocode and complexity analysis to explain the system’s responsibilities. They are not extracted production code, a database migration or newly executed VSL tests. Where I discuss a mechanism such as an outbox, I identify it as a design option rather than claim it is deployed.

I have left infrastructure addresses, credentials, customer data and deployment identifiers out of the diagrams. I describe payment and delivery flows by responsibility; the tables are not executable schemas or API contracts.

Canonical transition
The established application path responsible for changing an authoritative commerce state.
Idempotency
Repeated processing preserves the intended result without repeating commercial effects.
Commerce Passport
VSL’s seller-controlled identity, catalog, contact and capability representation.
Payment intent
The context and terms of a request to pay, distinct from evidence that payment occurred.
RFQ
Request for quotation.
WABA
WhatsApp Business Account.
MCP
Model Context Protocol, used for connections between compatible AI applications and tools.
NEXT PROJECT / KEEP EXPLORINGDonia Technologies← Back to projects

Project preview

Public web application screenshot · October 2026