DEV Community

Roberto Luna
Roberto Luna

Posted on

Implementing Split Payments with Mercado Pago in a Next.js Marketplace

Implementing Split Payments with Mercado Pago in a Next.js Marketplace

TL;DR: I added Mercado Pago split‑payment support so a provider’s fee is automatically divided among the provider, their accountant, and a lawyer. This required schema changes, new env variables, a webhook handler, and admin UI tweaks to configure the percentages.


The Problem

Our marketplace originally offered two payment modes:

  1. Disabled – no payment processing.
  2. Transfer – a full‑amount bank transfer to the provider after the service is marked complete.

When we introduced legal‑backed services (accounting and legal advice) we needed a third mode: Mercado Pago split. The idea is simple on paper—take the total amount a client pays, keep a configurable commission, then automatically split the remainder into three shares:

Share Recipient
Provider Service provider
Accountant Assigned accountant
Lawyer Assigned lawyer

In practice the lack of split support caused two concrete issues:

  • Payment failures – the old transfer endpoint tried to send the full amount to the provider, then manually debit the accountant and lawyer, which Mercado Pago rejected with Error: split_amounts_total does not equal payment amount.
  • Missing audit trail – we had no persisted record of how the split percentages were defined, making it impossible to generate the required legal documents for the accountant and lawyer.

The symptom was a 500 error on the checkout API and a “payment rejected” entry in the logs:

2026-11-09T14:32:11.842Z ERROR  webhook.handler - MercadoPago error: split_amounts_total does not equal payment amount
Enter fullscreen mode Exit fullscreen mode

What I Tried First

My first attempt was to keep the existing processPayment function in src/app/api/payments/route.ts and calculate the three shares on the client, then send them as part of the request body:

// client side (bad idea)
const split = {
  provider: amount * 0.70,
  accountant: amount * 0.20,
  lawyer: amount * 0.10,
};
await fetch('/api/payments', { method: 'POST', body: JSON.stringify({ amount, split }) });
Enter fullscreen mode Exit fullscreen mode

The server simply forwarded the payload to Mercado Pago’s /checkout/preferences endpoint. This approach broke for two reasons:

  1. Rounding errors – floating‑point math caused the sum of the three shares to be a few cents off, triggering the API error above.
  2. Security risk – anyone could tamper with the split percentages in the request body.

I rolled back that change and decided to move the split logic entirely to the backend, persisting the percentages in the database and letting the webhook verify the final distribution.


The Implementation

Below is a step‑by‑step of the files that changed, why they changed, and the key code snippets.

1. Environment configuration

/.env.example got a new variable for the Mercado Pago access token and a flag to enable split mode:

+ MERCADO_PAGO_ACCESS_TOKEN=your_mp_access_token_here
+ MERCADO_PAGO_SPLIT_ENABLED=true
Enter fullscreen mode Exit fullscreen mode

The flag is read in src/config/payment.ts to guard the new route.

2. Database schema

We needed to store the split percentages per provider. I added three new columns to the User model (providers are also users) in prisma/schema.prisma:

model User {
  id                Int      @id @default(autoincrement())
  email             String   @unique
  role              Role
  // Existing fields …

+ // Split percentages (values between 0 and 1)
+ providerShare     Float    @default(0.7)
+ accountantShare   Float    @default(0.2)
+ lawyerShare       Float    @default(0.1)

  // Relations
  accountant        Accountant? @relation(fields: [accountantId], references: [id])
  lawyer            Lawyer?     @relation(fields: [lawyerId], references: [id])
}
Enter fullscreen mode Exit fullscreen mode

Running prisma migrate dev generated prisma/migrations/20261108000000_revisiones_profesionales/migration.sql with the following SQL:

ALTER TABLE "User"
  ADD COLUMN "providerShare" DOUBLE PRECISION NOT NULL DEFAULT 0.7,
  ADD COLUMN "accountantShare" DOUBLE PRECISION NOT NULL DEFAULT 0.2,
  ADD COLUMN "lawyerShare" DOUBLE PRECISION NOT NULL DEFAULT 0.1;
Enter fullscreen mode Exit fullscreen mode

3. Seed data

To keep the local dev environment functional, I updated prisma/seed/index.ts:

await prisma.user.upsert({
  where: { email: 'provider@example.com' },
  update: {},
  create: {
    email: 'provider@example.com',
    role: 'PROVIDER',
    providerShare: 0.65,
    accountantShare: 0.25,
    lawyerShare: 0.10,
  },
});
Enter fullscreen mode Exit fullscreen mode

4. Admin UI – configuring splits

The admin configuration page (src/app/admin/configuracion/page.tsx) now shows three numeric inputs bound to the new fields:

// src/app/admin/configuracion/page.tsx
<FormField label="Provider share (%)">
  <input
    type="number"
    step="0.01"
    value={providerShare}
    onChange={e => setProviderShare(parseFloat(e.target.value) / 100)}
  />
</FormField>
<FormField label="Accountant share (%)">
  <input
    type="number"
    step="0.01"
    value={accountantShare}
    onChange={e => setAccountantShare(parseFloat(e.target.value) / 100)}
  />
</FormField>
<FormField label="Lawyer share (%)">
  <input
    type="number"
    step="0.01"
    value={lawyerShare}
    onChange={e => setLawyerShare(parseFloat(e.target.value) / 100)}
  />
</FormField>
<Button onClick={save}>Save</Button>
Enter fullscreen mode Exit fullscreen mode

The save handler calls PUT /api/admin/configuracion which updates the User record.

5. Backend – creating the split preference

The core of the new flow lives in src/app/api/webhooks/payments/[provider]/route.ts. The route receives a request to create a payment preference and builds the split array based on the provider’s stored percentages:


ts
// src/app/api/webhooks/payments

---

*Part of my [Build in Public](https://dev.to/zaerohell) series — sharing the real process of building SaaS projects from Playa del Carmen, México.*

*Repo: `zaerohell/home-services-marketplace` · 2026-11-09*

\#playadev #buildinpublic
Enter fullscreen mode Exit fullscreen mode

Top comments (0)