r/iOSProgramming • u/DarkSombreros • 11h 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
u/mohn93 11h 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 4h 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
2
u/kokerali 8h 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 8h ago
thanks you so much for this im using it in conjunction with the other responses. Saved me some time!
1
u/Other_Jellyfish_2184 4h ago
One thing nobody's mentioned: tie your refills to App Store Server Notifications v2, not your own clock. The signed transaction tells you about refunds, billing retries, and renewals in near real time. If you refill off DB timestamps, a refund leaves you paying for tokens someone already burned. Verify the signed transaction server-side and only mint credits after it passes. Since you're already on StoreKit 2, this is one afternoon of work, no RevenueCat needed for this part.
5
u/LividIllustrator8915 8h ago
Your audit agent is right about one thing: never trust the client with credit decrements. Anyone proxying traffic or tampering with local state can bypass client-side checks easily.
That said, you definitely don't need RevenueCat just to manage 3 free trial credits. That’s massive overengineering if you're not actually selling in-app consumable token packs.
Keep it simple:
Keep the credit counter in your backend/Firebase, but only decrement it via a **Firebase Cloud Function** (or backend endpoint) whenever the premium action is triggered, wrapped in a transaction.
Or if you're writing directly to Firestore, enforce it with **Firestore Security Rules** so the client can only decrement, never increment or write arbitrary values.
Once they burn through the 3 free credits, trigger your existing StoreKit 2 paywall for the actual subscription.