Why (Not) Build Yet Another Finance App — Part 2: Subscriptions and the App Store
Charging for an app looks like the simple part. Add a "Subscribe" button, hook up the store, get a notice when someone pays.
In our heads, it was one table and one status column.
This is Part 2 of the diary we started in Part 0. In Part 1, we covered what made Open Finance hard. Now it's time to charge for the app.
Quick context: Snabber has a free plan and paid plans. People who subscribe on the web pay through Stripe. On the iPhone, they pay Apple. On Android, they pay Google.
What we thought it was
- A "Subscribe" button in each app
- The store tells us when someone pays
- If they stop paying, they go back to the free plan
What it really was
Getting through App Store review
Before you can charge anyone on the iPhone, Apple has to approve the app. And every new version goes through review again. Someone at Apple installs the app, tests the screens and checks everything against the App Store rules.
The rules are public, and there are a lot of them. Review checks every one closely. No detail is too small to get you rejected.
In August 2026, we sent a new version with redesigned plans. It came back with three guidelines cited at once:
None of the three broke the app. All three were enough to keep it from going live.
And those were only the ones that showed up together. Other rules shaped the app before its first review. If you offer sign-in with Google, you also have to offer an option like "Sign in with Apple". If people can create an account in the app, they have to be able to delete it in the app too, not only by email or on the website.
Every rejection is a round of days: read the reviewer's message, figure out which rule was cited, fix it, build a new version, submit again and wait. Meanwhile, the version that was ready sits still, along with everything new in it.
On the web, we decide when to ship. On the iPhone, review decides.
Three stores, three truths
Getting through review is only the way in. After that, each store has its own way of saying "active", "past due", "canceled" and "in grace period". The app has to turn all of that into one answer.
Apple doesn't tell you a user canceled
When someone turns off auto-renewal on the iPhone, the subscription stays "active" until the end of the period. That makes sense for the user, who paid until then.
For us, it meant not knowing who was on their way out. For months, that information became an email in our inbox and nothing more. Only in September 2026 did we start recording it in a way we can look up.
A grace period that never ended
after it expired
When a charge fails, Apple and Stripe give a few days of grace before cutting access. We respected that. We respected it so much that one user stayed on the paid plan for 24 days after it expired, with Apple itself already saying "expired". Our code treated "in grace period" as active, with no deadline.
Card declined, customer in the dark
On Stripe, when a card was declined, we showed a notice in the app. But only people who open the app see the notice. Those who didn't found out when they lost access.
Now an email and a push notification go out with a direct link to update the card. And since Stripe sends the same event on every new charge attempt, we had to make sure nobody got five identical emails.
Losing the plan can't delete the bank
The free plan doesn't include Open Finance. So when someone goes back to free, their bank connections are removed. And there's no undo: to reconnect, the person has to authorize everything again in their bank's app (and, as we saw in Part 1, reconnecting is the most fragile moment).
Keeping someone on a paid plan by mistake is lost revenue. Deleting their connections without warning is damage you can't undo.
Between the two mistakes, we pick the first. Today nobody loses a connection without a warning first, with at least one day to subscribe again.
Shared subscriptions multiply everything
When we launched the family plan, every limit in the app (connections, AI agents, API access) had to start counting the group, not the person. In one of the first tests, four members of one plan had, together, four times the agents the plan sold.
Some of the 84 commits with "Apple" in the message, from February to September 2026 (several in Portuguese: "grace period stops protecting forever", "Apple rejection fixes: coupon on iOS, subscription modal and availability via StoreKit", "Apple webhook, too many emails").
What we did
- ✓An iPhone subscription screen designed from the start around the App Store rules.
- ✓A single "can use the paid plan" rule that translates Stripe, Apple and Google.
- ✓A grace period with a deadline, and confirmation from the store before cutting access.
- ✓A record of who turned off renewal on the iPhone.
- ✓Email and push when a charge fails, without duplicates.
- ✓A mandatory warning, with a deadline, before any bank connection is lost.
What we haven't solved yet
Every new iPhone version is still a bet. We read the rules, we test, and we still only know it passed when the reviewer's answer arrives.
Deleting a Snabber account doesn't cancel the Apple subscription. Only the user can cancel it, in the iPhone's Settings. We warn them, but there's always someone who deletes their account and keeps getting charged.
And every time a price or plan changes, all three stores have to be updated and tested separately. Apple's, with review.
If you're going to charge in more than one store
Read the App Store guidelines before you design the plans screen, not after the first rejection. And decide on day one what your source of truth is for who is a subscriber.
Store events arrive late, out of order and repeated. Treat them all as suggestions, not orders.
Want to see how it turned out? Create your Snabber account and try the free plan.
In the next part: why "the AI categorizes it" is only half the story.
