Ecommerce

Everything was cash

Our ERP register filed every card, Yape and Mercado Pago payment as cash

Nicolás Biondi

In short: The ERP recorded every payment taken at the service desk as cash, because we never sent it the real payment method. We fixed it with a six-row table and checked the inference by issuing one real receipt.

When we closed the repair service desk, the ERP register (the system where we keep inventory and invoicing) said we had collected the whole day in cash. Nobody handed us a single sol, the Peruvian currency: the money came in through the POS (the card terminal), a payment link and Yape (the phone wallet half of Lima pays with). The day's total matched sales, but the payment method was wrong on everything that wasn't cash.

Whoever closes the register has no way to guess that. They have to open each receipt, remember the customer and fix it by hand. That one was my mistake. I assumed that a field SUNAT (Peru's tax authority) doesn't require mattered to nobody else either.

The receipt doesn't ask how they paid you

For SUNAT the receipt separates two things: paid now (Contado) and on credit (Crédito). Whether you pay in cash, with a card or with a link, the XML we declare (the file that travels to SUNAT) is the same document. That legal view was right from day one.

Closing the register answers another question: which channel each sol came in through, and where it sits now. We answered the first one and took the second as solved.

The ERP filled the empty field for us

Our internal payment record does know the truth. Every payment is stored with its method: cash, card, Yape, Plin, Mercado Pago or bank transfer. When we issued the receipt, we never sent that field.

The ERP doesn't leave the field empty. It writes its default value, Cash, and its register module files whatever arrived through the POS or a payment link as money sitting in the drawer.

Analogy: a notebook next to the register where the salesperson writes down every sale as cash, including the one paid by card. The notebook total always matches the day's sales. The drawer never matches the notebook, and the difference doesn't say where it came from.

Six rows that translate the method for the ERP

The fix fits in one table, CONDICIONPAGO_POR_METODO. It translates each method in our record into the ERP's payment condition id, the numeric code the ERP uses to classify where the money came from. We look it up before issuing the receipt: six rows of mapping, no new infrastructure.

Payment in internal record

Mapped to condition

Condition id

Receipt issued

ERP cash register

Collected in drawer

Deposited to account

Before: no method

The payment method's path from our internal record to the ERP register; dotted, the old route with no method.

The catalog the API wouldn't give us

To fill those six rows I needed the ids from our electronic invoicing provider, and its API (the door a program uses to ask another one for data) doesn't publish the list of payment conditions. I tried 26 plausible endpoint names, one by one, and all 26 returned 404, the code that means "there is nothing here". The provider assumes you'll pick the condition by hand in its web panel.

We did have the panel. The conditions show up there in an ordered list, so I worked out each id by counting its position. The panel never shows the number, so each row was a guess based on that order.

Ids missing

Try 26 endpoints

All 26 return 404

Infer by position

Issue real receipt

Prints POS Culqi

Transfer untested

Flagged in docs

How we built the catalog the API doesn't hand over, and which row was left unproven.

We spent a real receipt to check it

We issued a real receipt, for a real sale, with the card method. It printed as POS Culqi (Culqi is one of the payment gateways we use), exactly as the table predicted. That receipt turned the card row into a verified fact, and the other five still rest on the panel's ordering.

Before, the six methods reached the ERP as Cash, and cash was right by accident. Now the receipt travels with its payment condition and the register splits in two: what we collected in the drawer and what was deposited into an account. Closing stopped depending on someone remembering how each customer paid.

The transfer row we couldn't verify

The record stores "transfer" without the bank attached, so every transfer falls into a single bank, by business decision. For closing the register, getting the type of movement right (money that went into an account, not into the drawer) matters more than the bank's name. That row is flagged as unconfirmed in the code and in the documentation, and we fix it the day the record stores the bank.

What we do differently now

  • If the internal record knows the payment method, it travels with the receipt, even though SUNAT doesn't ask for it
  • One real transaction taught us more than 26 endpoint attempts: issuing the receipt turned the panel inference into a verified fact
  • As in other modules, we show a dash instead of a number we can't back up, and we write down which row we never tested

More in Ecommerce

This is the only post here so far. Browse the section or all posts.