Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

InAppPurchase verification & separate server for game logic

Tags:

I'm developing an app using Unity (for Android and iOS). I'm using the SOOMLA plugin to allow users to purchase Gems (virtual currency) with In App Purchase.

Users and Gems and all other game logic go through my server on Azure.

I want the following procedure to take place as a single transaction in some way:

  1. User buys Gems with IAP
  2. App notifies server
  3. Server validates the purchase and updates data

But if the internet connection breaks down between step 1 and step 2 - the user payed for Gems that he did not receive (not good!)

So my current approach is this:

  1. User initiates a purchase
  2. App notifies the server
  3. Server blindly updates data accordingly
  4. User buys Gems with IAP
  5. If the purchase is cancelled, notify server to undo it

That way, the user is guaranteed to get his purchased Gems, but I am not guaranteed to get paid (not great...)

Note: I don't want to manage user Gems in the store itself. I want everything on my own server. So the SOOMLA's balance is meaningless to me. I don't care for it.

I was thinking maybe the app can store the purchase data in persistent storage until it manages to notify the server about it, and then delete it. But I was also thinking that this might be a bad solution. Hence this question.


I imagine the best solution as something that will properly handle this scenario:

  1. User buys Gems with IAP
  2. IAP succeeds
  3. Internet breaks down
  4. My own server isn't notified
  5. User uninstalls app from his device
  6. User may then install the app on other devices:

    • Either he was charged and he got the gems by some magic
    • Or he was refunded automatically, since the gems were not received

So far it seems like this is impossible by any means, which makes me disappointed with the technology of IAP's. Hoping for answers that will prove me wrong.


Seems like all I'd ever need is the ability get a user's purchase history from my server, with a secured request to Google Play or Apple Store. But that's just not part of the framework.


So what are others doing? What is the best approach?

like image 662
SimpleVar Avatar asked Feb 03 '16 20:02

SimpleVar


People also ask

How do you validate in app purchases?

When a user makes an in-app purchase, cache the details (token, order id, and product id) locally on the client (i.e the app) then send it to your API. Your API should then send the purchaseToken to the Google Play Developer API for validation.

How do you verify In app purchases on Google?

Go to Settings > Developer Account > API access. Make sure you have the necessary permissions to access all the settings in Google Play Console and Firebase project is linked to app in Google Play Console. In that API access > Service Accounts select Create Service Account.

What is receipt validation in app purchase?

The Receipt Verification Service (RVS) enables validation of purchases made by your app's users.


2 Answers

In general, you seem to suffer from the Two Generals' Problem which was

the first computer communication problem to be proved to be unsolvable.

Since everywhere in your communication protocol a message can be lost (even the acknowledgement or the acknowledgement`s acknowledgement or the ...) you cannot be 100% sure that both communication parties (the user device and your server) have agreed upon the same state. You can only say that upon a certain probability the state information has been interchanged successfully.

I would send a couple of ACKs back-and-forth and store the purchase if a sufficient number got trough. Quote from Wikipedia:

Also, the first general can send a marking on each message saying it is message 1, 2, 3 ... of n. This method will allow the second general to know how reliable the channel is and send an appropriate number of messages back to ensure a high probability of at least one message being received

For customer satisfaction I would take the odds in their favor - 1% not delivered goods will get you in a lot of trouble but 1% loss on your side is acceptable.

like image 106
PhilLab Avatar answered Nov 01 '22 16:11

PhilLab


Considering your Gems are a virtual currency, then the natural in-app product type should be consumable, i.e. they are not restorable purchases.

To consume a purchase with a Google Play purchase you will call consumePurchase. On iOS you will call finishTransaction on the SKPaymentQueue. In both marketplaces the consumable purchase will remain in an active state until they have been consumed. If the user deletes the app off their device before the purchase is consumed, they will be able to re-install, and restore their previous unconsumed purchases.

In-between the initial purchase and consumption is where you want to put your server-side validation. When a purchase is made, send the receipt or token to your server, perform the validation and respond accordingly. The app should wait for a valid response from the server before consuming the purchase.

(Note that consumed purchases will not appear in the in_app collection on an iTunes receipt. They are only present if the purchase has not been consumed yet).

If the server is timing-out or network connectivity is lost the purchases will remain in an active state and the app should continue trying to send the details periodically until it receives a response it is expecting.

Purchases for both Google Play and iOS are stored locally so you will just need to run a process that looks for unconsumed purchases once network connectivity is re-established.

You can treat the provisioning of Gems in the same way a bank handles deposits of cheques; the new balance will be immediately updated but the amount spendable will not match until the cheque (or in your case a validation) is cleared.

Some pseudo code to make the process clear:

Purchase product or Restore purchases While consumable purchases > 0   Send purchase receipt to API   If response is ok     If purchase is valid       Consume product       Allocate gems       Break     Else       Remove retroactive gem allocation       Discipline the naughty user       Break   Else     Retroactively allocate un-spendable gems     Pause process until network is re-established       Re-send receipt to API 
like image 32
Marc Greenstock Avatar answered Nov 01 '22 18:11

Marc Greenstock