The Problem Under the Paid Stamp

A PayPal IPN was reporting a successful payment without updating the registration record that received it, so a team that had paid still showed up on the roster as pending. The money had arrived, the status said otherwise, and nothing in the system flagged the mismatch. That was one of three unrelated problems that looked fine from the outside: a bulk delete that quietly did nothing, and page files that could be edited while a customer was already loading them.

That kind of mistake is easy to miss because the important part looked fine. The payment had gone through. The money had arrived. But a team that had paid could still appear on a roster as pending, as if somebody had left a form half-finished on the kitchen table.

There was no single dramatic break. On April 21, two quick fixes landed on the same day, alongside a third problem. The three problems touched registration, schedule management, and the way page files were released. They did not share a common cause. They only shared a talent for hiding in ordinary-looking work.

The payment problem had been sitting there for a while. A PayPal IPN, which is simply PayPal’s little message saying a payment succeeded, was not always updating the registration record that received it. The result was a paid customer and an unpaid-looking record living side by side.

I do not think a fix counts if it only helps the next person through the door. So the repair had two jobs. First, future successful payments needed to correct the registration status on their own. Second, the older wrong records in production needed attention too. We added a cleanup tool with an admin screen, a confirmation step, and a dry-run option. A dry run is like laying out all the ingredients before turning on the stove. It shows what would change without changing it yet.

That mattered because the bad rows were real. Leaving them behind would have meant the system kept telling a false story about people who had already paid. A forward-only fix can make a dashboard look healthier while the old mess stays under the rug.

The next issue taught a different lesson. Bulk deletion was not working when somebody started from the broadest account level. I first thought it was a permissions problem. That was the wrong drawer to open. It cost the first part of the investigation, looking for a lock that was not there.

The actual problem was simpler, once we found it. The delete request was being sent with an organization-level address, but the receiving part of the system only understood smaller addresses, such as a particular league or team. It was like trying to mail a letter to “the apartment building” when the mail slot only accepts apartment numbers. The request did not cause a spectacle. It just quietly did nothing.

The correction was to walk down from the broad address to the smaller one before asking for the deletion. That sounds small, but it changed the question we needed to test. The user sees, “delete does not work.” The real question is, “did we hand this part of the system an address it can use?” Those are not the same problem.

Rather than spend hours guessing through an old application, an AI assistant traced where that address was built, followed the route to the delete request, and checked which address fields were accepted. I still had to decide what the evidence meant and check the model of the problem. The assistant did the reading and left a trail to the exact places that mattered. On a day with several unrelated problems, that is a useful division of labor.

The third problem was about releasing updated page files. During a release, some pages were still pointing straight at the working copies of the files that control how a page looks and behaves. That meant an edit happening during the release could reach a customer who was already loading a page.

The safer fix was not only to change the page logic. We made dated copies of those files and pointed the pages at the dated copies instead. Think of it as serving dinner from the dish on the table, not from the chopping board while someone is still cutting vegetables. The point was to make the release process itself less likely to recreate the problem.

I keep coming back to how different these three issues looked from the outside. One was a paid registration that looked unfinished. One was a delete button that did nothing. One was a page that could change under somebody’s feet. It would have been tempting to call the day one big system failure and reach for one big explanation. That would have been wrong.

What connected them was not the code. It was the habit of stopping at the first reassuring answer. The payment fix was not done until both new payments and old records were covered. The delete fix was not done until the correct kind of address was passed along. The release fix was not done until the release method itself stopped reopening the risk.

Three problems, and each one was declared finished too early by a signal that looked fine. The payment message that never updated the registration left a paid team showing as pending, and the fix only counted once the dry-run cleanup had also dealt with the old rows still saying the wrong thing in production. The delete that silently did nothing was never a permissions failure, only an organization-level address handed to a part of the system that accepts league and team addresses. The dated copies of the page files meant a release could no longer be edited out from under a customer mid-load. In all three, the real test was not whether the screen looked right, but whether the old records, the address that was actually sent, and the release method itself had stopped telling the same false story.