Having depreciated all stores but Google and Apple, we are writing our own. The new redesign has many good points but it is hard to understand some of the intent. Samples would help. Flow diagrams would be great.
Few item I’m stuck on.
If I implement IPurchaseService, I can’t seem to figure out how Subscription will work as I can not call OnCheckEntitlement because I can’t create the needed Entitlement since it’s constructor is internal? When should I Implement I<Purchase, Product>Service beyond just deriving from Store?
UnityIAPServices.AddNewCustomStore( new MyCustomStore() ) should go where. It works called from Awake of a GameObject but in IAP 4.0 we did not need an object in the Scene. Recommendations?
It seems the Store is responsible for keeping/persisting its lists of Orders, confirmed, deferred, and pending and presenting them back to the service via the OnPurchasesFetched callback. Is this correct? A ‘deferred’ order has no ‘transactionId’ or ‘receipt’, a ‘pending’ order has no ‘receipt’ and a ‘confirmed’ order has both. Is this correct?
In the Backend sample it says, “If the app is closed during this time, ProcessPurchase will be called again for the same purchase once the app is opened again.“ Is this true for custom stores?
All your sample just use the first order product in an Order. IStore.Purchase product takes ICart with many Products. But IPurchaseService.PurchaseProduct provides a single product. Any store can call either? In processing orders, we should handle ALL products in an order, not just the first?
Thank you for the feedback, we do plan to release more samples and keep improving the documentation!
The I<Purchase, Product>Service are used to access the stores, but you shouldn’t implement them, just the Store. For the CheckEntitlement, you want to invoke the Store.EntitlementCallback?.OnCheckEntitlement which only requires the EntitlementStatus. This will create the Entitlement internally and invoke the IPurchaseService.OnCheckEntitlement.
You could make this call right before connecting to the store with UnityIAPServices.Store(storeName).Connect().
DeferredOrder has no transactionId and no receipt, because it hasn’t been paid for yet. However, a PendingOrder has been paid, so it will have a transactionId and a receipt. A PendingOrder is when it’s been paid, but not rewarded yet. A ConfirmedOrder is when the order has been paid and has been rewarded.
Yes, PendingOrders will be processed when fetching purchases on startup by default to match the bevahiour on IAP 4.13.0. We added a way to toggle this off if needed with IPurchaseService.ProcessPendingOrdersOnPurchasesFetched(bool). The sample should say OnPurchasePending instead of ProcessPurchase.
The stores we currently support only allow purchasing 1 product at a time, but the new IAP architecture purchases an entire cart. For the stores we support, this cart will have only have 1 CartItem for that 1 product.
You can choose to buy multiple products at once if your stores allows it with the IPurchaseService.Purchase(ICart).
You are correct that you should go over every items in your Cart and not just the first one if you are expecting to have multiple items.
It seems PendingOrders processing that you mentioned is a function built for Google and Apple stores. Implementing my own store, I must build the FetchPurchases implementation; Is this correct? If so, I can not see how as I must pass into PurchaseFetchCallback.OnAllPurchasesRetrieved a list of orders.
To create an Order we need an ICart which require a Product but there are no public constructors on Product preventing the creation of both a CartItem and thus an Order.
Even more fundamental is the need to call, OnPurchaseSucceeded which also needs a PendingOrder. The fulfilment of a purchase may happen during a different session. As such I need to persist the PendingOrders. None of these classes are marked Serializable to aide in this. But the missing constructor on Product seems to make deserialization not possible or difficult anyway.
In IAP 4 we could return success for a purchase in IStoreCallback.OnPurchaseSucceeded without the need for specific object construction or deserialization. Please explain how this should be done now?
Since the interface is now there, the users/clients of my store may use it. So I will deal with multi-item carts. Is there a reason that CartItem.Quantity is limited to 1?
Correct, you have to implement the FetchPurchases which will invoke the IStorePurchaseFetchCallback with the orders. For the Product, we missed this and will be fixing it shortly by exposing the Products that were fetched. In the meantime, you could use UnityIAPServices.Product(storeName).GetProducts(); to obtain them.
For the persisting of Orders, could you give us more details on your use case? Are you trying to persist them locally or by fetching them from a backend?
Good catch, the constructor intended for multi-quantity was left internal. We will fix this, but in the meantime, you can use CartItem.Quantity to edit it after it was been constructed.
Once I start a transaction, returned to the user/client as a PendingOrder, it may take minutes to finalize payment. In the meantime the game may exit, etc… At startup, I need to continue waiting for the transactions to complete and report to the user when it has with a receipt. I encrypt and persist locally. In 4.0 this was simple. Now with the whole cart hierarchy I need a solution?
I have some idea, but I’ll not prejudice the team with my limited view.
I think what he is referring to is the Transaction Log. Is this still supported in Unity IAP 5?
It would keep pending transaction identifiers locally and retry them on app launch automatically, at least before Unity IAP 5. Actually there was no need to store transactions on our own, even for custom stores, because the Transaction Log did it until they were confirmed.
I have Debug.Log on the Store implementation methods and the callbacks in the client. I am still not following as to when and how IAP 5 will persist transactions and return them to the Store implementation for further processing if the purchase flow is interrupted.
I have the client call storeController.ProcessPendingOrdersOnPurchasesFetched( true ) even though this should be the default. But my store.FetchPurchases() is never called. If I call it myself inside the store and synthesize some orders as you descried using UnityIAPServices.Product(), then return these via PurchaseFetchCallback.OnAllPurchasesRetrieved, nothing else seems to happen. I thought storeController.OnPurchasesFetched would be handed these but it is never called.
Additionally, I tried killing my app at various Purchase processing steps, (after OnPurchasePending but before ConfirmPurchase; after ConfirmPurchase but before ConfirmCallback, etc…). None of these interruptions caused a subsequent run of the client and store to have any additional callback with orders that needed more processing to complete.
Again, I am just guessing at what the flow might be based upon the method signatures. Here flow diagram would be worth 1000 words as they say. If it is suppose to automagically happens, I’m not sure what I am not doing to make this order persistence work?
This is why I thought I had to do my own persistence.
What triggers IAP to persist each order type? I was thinking after the store calls ConfirmCallback.OnConfirmOrderSucceeded, PurchaseCallback.OnPurchaseSucceeded, …?
Sorry for the misunderstanding, IAP does not persist any data.
The useTransactionLog was the exception and it only kept track on transactionIDs. It was used because we had no way to tell between a PendingOrder or a ConfirmedOrder on IAP 4, so this would avoid sending purchases that were already confirmed to the ProcessPurchase.
The transactions that are processed on launch were sent back by the store (when fetching purchases for Google and automatically, as new purchases, through the transaction observer for Apple).
When you implement a custom store, your Store.FetchPurchases has to obtain and recreate the Orders to pass them to the IStore.PurchaseCallback. This is usually done by fetching them from an external source, but you have to do your own persistence.
Once those Orders are sent to the PurchaseCallback, IAP will be handling them and replaying the Pending ones to the OnPurchasePending (if ProcessPendingOrdersOnPurchasesFetched is true).
On the client side, you want to call the IPurchaseService.FetchPurchases which will call your store.FetchPurchases.
Then the API is designed so that the client can ‘expect’ the store to maintain a list of purchases for the user. These should then be returned to the client by the store on a call to IPurchaseSevice.FetchPurchases through the OnPurchasesConfirmed callback.
It is up to the custom store to fulfill this expectation?
Currently, my custom store replays pending purchases on store initialization. It seems if I rely on IAP to replay them, then I am also relying on the client to both set ProcessPendingOrdersOnPurchasesFetched to true AND to also call FetchPurchases? Is this correct?
Since the default is true on the former case and they may be relying on the Store to persist the purchases, it seems likely that the client would make the call ti FetchPurchases. I just want to insure my understanding is correct. The case where ProcessPendingOrdersOnPurchasesFetched would be to set to false by the client is ?
Correct, the custom store should fulfill this expectation.
To clarify what the “replay” is, when ProcessPendingOrdersOnPurchasesFetched is set to true (default), we take the PendingOrders that were returned OnPurchasesFetched and send them to the OnPurchasePending to recreate the behaviour from IAP 4.13.0.
If ProcessPendingOrdersOnPurchasesFetched is false, you have to handle the PendingOrders in the OnPurchasesFetched to handle the PendingOrders that were not completed previously (or that happened while the application wasn’t active).
You might also want to go over the ConfirmedOrder coming to the OnPurchasesFetched if you use the store as source of truth. If you have a backend that handles entitlements as the source of truth, then those have already been rewarded and you can skip validating the ConfirmedOrder again.
Thank you for the clarifications. I have it working as you described and can see that ProcessPendingOrdersOnPurchasesFetched will control if the client gets a OnPurchasesPending message or not.
My one concern is that this process relies upon the client calling storeController.FetchPurchases. If they do not, I will not monitor pending transactions as that is now initiated in Store.FetchPurchase.
I can not know if/when the client StoreController may call FetchPurchases. If they don’t, pending purchases will be lost. If I always process them at startup instead of relying on FetchPurchases, then FetchPurchases will return the ‘current’ state as some pending purchases may have been fulfilled before the client called it. Is that the intent?
The demo code did not call FetchPurchases which made this case come to light. Is it safe to assume stores will always call FetchPurchase?
Is it also correct that, FetchPurchase will never return via OnAllPurchasesRetrieved any ConfirmedOrders that have Consumables? (but Consumables may be seen as PendingOrders)
Or to put it another way, all ConfirmedOrders are either Non-consumables or Subscriptions?
As such, ConfirmedOrders should all have unique product IDs because their Cart.Quantiy can only be 1 and they can’t be repurchased?
If you’re worried they don’t call the FetchPurchases, your custom store could invoke the same callback as FetchPurchases in the Connect or FetchProducts.
FetchPurchases is useful if you rely on the Store telling you what you own, but if you have your own backend as the source of truth, then it isn’t necessary. This is up to the developer integrating Unity IAP. The same is true for your custom store, so this shouldn’t make their implementation any different.
For Google and Apple, that is correct, FetchPurchases will not return consumables because they expect you to persist them once consumed.
For an incoming PendingOrder, when it is confirmed, it will be sent to IPurchaseService.OnPurchaseConfirmed even for consumable. If you call IPurchaseService.GetPurchases, you will be able to see the ConfirmedOrder again for the current session or until a new FetchPurchases is called.
There are cases where IPurchaseService.GetPurchases could have multiple ConfirmedOrder with the same product id. For example, if you buy a subscription and it renews, then you will have 2 of them. Same if you purchase multiple of the same Consumable in the same session.
If you own a non-consumable or a subscription that is still active, it shouldn’t be possible to purchase it again.
Hello @bladerunner69008
If you want a simpler alternative to meet the new Android Billing requirement, we also released IAP 4.13.0 which supports Google Play Billing Library v7.
If you are using IAP 5, make sure to register your callback on OnPurchasePending.
This upgrade guide might also be helpful to transition from IAP 4 to 5.
I did register callback OnPurchasePending but it was not triggered.
The guide could be a little bit more explicit when it comes to the purchasing process using code.
For the time being I will try the 4.13 version for a smoother transition.
Why do you have the 2 concurring version. Which one is recommended for long term support ? There is no indication in the Packaga Manager
Thanks.
public async void InitializeIAP(List<ProductDefinition> products)
{
LogUtil.LogMessage("Store Manager InitializeIAP()");
GD.hud.ShowMessage("test", "Store Manager InitializeIAP()", null, null);
this.controller = UnityIAPServices.StoreController();
await this.controller.Connect();
this.controller.OnProductsFetched += OnProductsFetched;
this.controller.OnPurchasesFetched += OnPurchasesFetched;
this.controller.OnPurchasesFetchFailed += (p) => { OnPurchaseFailed(p); };
this.controller.OnPurchasePending += (p) => { OnPurchasePending(p); };
this.controller.FetchProducts(products);
}
private void OnPurchasePending(Order order)
{
GD.hud.ShowMessage("Store Manager", "OnPurchasePending", null, null);
// Process purchases, e.g. check for entitlements from completed orders
//this.RecordPuchase(order);
}
public void OnPurchaseFailed(PurchasesFetchFailureDescription p)
{
LogUtil.LogMessage("Store Manager Purchase failure :" + p.message);
GD.hud.ShowMessage("", "Store Manager Purchase KO", null, null);
GD.hud.ShowMessage("Store Manager Purchase failure", p.message, null, null);
//Send an event to be taken care of by the purchasing screen
GE.GetOrCreate<StoreGameEvent>().PurchaseFailed(p.message);
}
Your code seems correct, but it might be the purchase itself failing. Make sure to add your OnPurchaseFailed on this.controller. OnPurchaseFailed += (p) => { OnPurchaseFailed(p); };
You currently have it on the OnPurchasesFetchFailed, but this one is used when the FetchPurchase fails.
IAP 5 will be the version supported long term, but we made the decision to keep supporting 4.x for now to make sure we don’t disrupt any games relying on IAP.
Your reply here, to confirm, is suggesting that I manufacture a PendingOrder if I want to Confirm a purchase after I no longer have access to the original PendingOrder returned by the StoreController during a FetchPurchases?
Is there a functional reason that I need anything aside from the transactionId? This seems like the critical piece of information. If this is true, a signature taking only the transactionId would be in order.
Now that the IAP API has changed again in a big way, how about a full example that does everything? I don’t want to waste any time on upgrading this again by collecting bits and pieces from here and there…