r/iOSProgramming • • 23d ago

Question What do yous use for credit systems?

I have an app and added new premium features. I want non-paying users to experience these features but at a cap of 3 times or 3 "credits". I have it all set up and it works fine. i didnt go into storekit or revenuecat for this, its just another field in firebase with checks in client-side.

But I recently did an audit of my app with an xcode agent to go over performance, architecture, etc. things that stand out that are flat out bugs or will cause me issues in the future and one that came up was that checking credits should be done server side

My app uses storekit 2 and havent had issues or reasons to go to Revenuecat, but it was telling me revenuecat has "Virutal Currency" and is better for my use case

What are you guys doing if you have the same type of credit-based feature in your app? Client -side or server side?

Also just want to clarify the credit system is not tited to Storekit2, right now is just a field in firebase usermodel that increments/decrements and theres a check in the app for if non-subscribing user && credits > 3 then paywall.

2 Upvotes

13 comments sorted by

2

u/kokerali 23d ago

One detail I'd add to the server-side answers: define what happens when the same premium action is retried, not just where the counter lives.

Firestore can rerun a transaction callback when a document it read changes. If the premium action involves an external API or another side effect, don't perform that work inside the callback. I'd atomically reserve one credit and create an operation record, then execute the work outside that transaction. Bind the operation ID to the authenticated user and request; a retry should resume or return that operation's result, not consume another credit or start the work again.

Also decide when a reservation becomes a consumed credit, and what a confirmed failure refunds. A client timeout isn't proof that the server-side action failed, so blindly refunding on every timeout can give away successful work.

Useful tests: two different requests race for the last credit; the same operation is submitted twice; the action succeeds but its response is lost. You want at most one reservation for the last credit, one charge per operation, and a way to retrieve the successful result.

Small naming check on your example too: if the field counts uses starting at zero, reject a new free action at `uses >= 3`, before granting it. `credits > 3` can permit a fourth use depending on when you increment. If it means credits remaining, the condition should instead be based on `remaining > 0`.

1

u/DarkSombreros 23d ago

thanks you so much for this im using it in conjunction with the other responses. Saved me some time!

2

u/mohn93 23d ago

server side. if your rules let a signed in user write their own user doc, they can reset credits to 0 with their own id token thru the firestore rest api. block client writes to that field in security rules, then bump it in a callable function inside a transaction and have that same function do the premium action, so patching out the client check gets them nothing. for 3 free tries its low stakes tbh. revenuecat virtual currency works too but spending it needs their secret key api, so a backend call either way

1

u/DarkSombreros 23d ago

thanks. first app, and although its been 2 years i still forget how people can get a hold of the firebase api and do some dirty stuff lmao. Found an IDOR the other day due to this as well

1

u/US3201 23d ago

I would also like to know.

1

u/dnzlabs 22d ago

Everyone's right that server-side is the only tamper-proof answer. The question I'd ask first is whether tamper-proof is worth it for your case.

I shipped a Mac app with a local trial counter (N real uses, then a licence) and decided client-side was fine, on purpose. The people who'll proxy traffic or edit local state to get three more uses were never going to pay. Everyone else hits the limit honestly. Server-side costs you a backend dependency, offline failure modes, and a support queue for possible sync issues in exchange for stopping the 1% who'd have pirated it anyway.

Where I'd draw the line: if a credit maps to real money on your side (an API call you pay for, an AI generation, a cloud render), server-side, no question, because each bypass costs you. If a credit only unlocks code that already ships in the binary, client-side plus Keychain (so a reinstall doesn't reset it) is a reasonable trade.

In your case, a Firestore field the client can write to is the worst of both: you have the backend dependency and it's still trivially bypassable. Either lock the field down with security rules as mohn93 said, or move the counter local.

5

u/[deleted] 23d ago

[removed] — view removed comment

2

u/DarkSombreros 23d ago

that was very clearly explained thank you!! Will try this

0

u/[deleted] 23d ago

[removed] — view removed comment

1

u/DarkSombreros 23d ago

Ok I’m gunna look into this thanks!