Failed to purchase unknown product {0}: System.Collections.Generic.List
1[UnityEngine.AndroidJavaObject]
in log, not calling IStoreListener.OnPurchaseFailed
, after prior purchase when offline, after Initializing, until app has been restarted.IStoreController.FetchAdditionalProducts
cleared receipts inappropriately.DateTime
used CurrentCulture
instead of InvariantCulture
. Removed logs.CrossPlatformValidator.Validate
> AppleValidator.Validate
> AppleReceiptParser.Parse
) on devices set to Thai locale.StandardPurchasingModule.appStore
returns AppStore.MacAppStore
for Mac App Store, AppStore.AppleAppStore
for iOS App Store, and AppStore.WinRT
for Windows Desktop. (No change to
AppStore.SamsungApps
, AppStore.AmazonAppStore
, or AppStore.GooglePlay
.)IStoreListener.ProcessPurchase
called more than once for any purchase which is not consumed, i.e. when ProcessPurchaseResult.Pending
is returned, by fixing a race-condition.ProcessPurchase
calls after foregrounding the app, when a purchase is made outside the app (e.g. in the Play Store app), please upgrade the core package via the Package Manager to com.unity.purchasing@2.2.1
or higher.IStoreListener.OnInitialized
is now called after all purchases have been fetched from the store.IStoreListener.OnPurchaseFailed
would not be called in projects where the purchased product's ID differed from its Store Specific ID.Activity.onResume
lifecycle event, if the user had previously bought a Subscription.MissingMethodException
seen in projects using com.unity.purchasing@2.0.3
through 2.2.0
when a user launches their game after having previously purchased a Product.arm64
slice added to binary0
GoogleProductMetadata
from ProductMetadata.GetGoogleProductMetadata()
via IStoreController.products
.
GoogleProductMetadata googleMetadata = storeController.product.all[0].metadata.GetGoogleProductMetadata();
now instead of the deprecated, IGooglePlayStoreExtensions.GetProductJSONDictionary
.GetProductJSONDictionary
IGooglePlayConfiguration
changes below.IGooglePlayStoreExtensions
APIs
IGooglePlayStoreExtensions.IsOwned
IGooglePlayStoreExtensions.SetLogLevel
IGooglePlayStoreExtensions.GetProductJSONDictionary
- use the ProductMetadata
of product.metadata
from IStoreController.products
IGooglePlayStoreExtensions.FinishAdditionalTransaction
IGooglePlayStoreExtensions.UpgradeDowngradeSubscription(string oldSku, string newSku, int desiredProrationMode)
- allow refined proration mode for subscription upgrade or downgrade as described in the Google ProrationMode documentationIGooglePlayStoreExtensions.ConfirmSubscriptionPriceChange(string productId, Action<bool> callback)
- prompt users to confirm a price change for their subscriptionIGooglePlayStoreExtensions.SetDeferredPurchaseListener(Action<Product> action)
- listen for new pending out-of-app purchases, consider notifying users hereIGooglePlayStoreExtensions.SetObfuscatedAccountId(string accountId)
- to help Google detect and reduce irregular activities when making a purchaseIGooglePlayStoreExtensions.SetObfuscatedProfileId(string profileId)
- to help Google detect and reduce irregular activities when making a purchaseIGooglePlayConfiguration
APIs
IGooglePlayConfiguration.SetPublicKey
IGooglePlayConfiguration.aggressivelyRecoverLostPurchases
IGooglePlayConfiguration.UsePurchaseTokenForTransactionId
false
instead of true
product.receipt
of IStoreController.products
.aggressivelyRecoverLostPurchases = true
.aggressivelyRecoverLostPurchases = true
- switched to Google's Purchase Token from using Google's Order ID to represent all transaction IDs. Automatically sets Product.transactionID
to GooglePlay purchaseToken
when aggressivelyRecoverLostPurchases
is true
. Continues to use orderId
, otherwise. CLARIFICATION: To reinforce the preferred usage of aggressivelyRecoverLostPurchases
, a de-duplicating backend purchase verification server is recommended to be added to a game's transaction verification pipeline when using this feature. Without such a server the recovered purchases may not be easily or safely de-duplicated by a client.
bool IGooglePlayConfiguration.aggressivelyRecoverLostPurchases
, any purchases your users have made will be processed again by Unity IAP; ProcessPurchase will be called for all these purchases.purchases.products
Google Play Developer API.UsePurchaseTokenForTransactionId
, below.aggressivelyRecoverLostPurchases = true
. Always records purchaseToken in TransactionLog when a transaction is completed.Product.transactionID
and use either Google's Purchase Token, or the legacy Order ID when possible, with UsePurchaseTokenForTransactionId
.
void IGooglePlayConfiguration.UsePurchaseTokenForTransactionId(bool usePurchaseToken)
to disable the default behavior
false
to use the orderId
, when possible; this is the legacy and non-unique transaction ID behavior. This must switch to the purchaseToken when the orderId is not available from Google, which is when aggressivelyRecoverLostPurchases = true
.true
to always use the unique purchaseToken in transactionID. NOTE: this is the preferred option, and it will be the only behavior available in a future version of Unity IAP.aggressivelyRecoverLostPurchases = true
recovered purchases: the Google Play Billing history API used by this feature does not return orderId and therefore cannot be set as the Product.transactionID at that time. Historically, Unity IAP chose to prefer orderId for transactionID in its original implementation. And when the orderId was missing from purchase records (sandbox test purchases, and other no-money purchases), Unity IAP would use the purchaseToken as the transactionID.GoolgPlayTangle.cs
and AppleTangle.cs
files. The generated files have been restored to their pre-2.0.0 location.UnityEngine.Store
, IUnityChannelExtensions
, IUnityChannelConfiguration
.IGooglePlayConfiguration.aggressivelyRecoverLostPurchases == false
(default) to reward players for currently in-flight purchases only, and not historical purchases, when the player cleans their device's TransactionLog, starts and cancels a purchase, and restarts the app.packageName
. These as are seen from v1.23.2's purchase-recovery features.
packageName
is omitted by a GooglePlay historical purchase query APIs used by v1.23.2. When the RSASSA-PKCS1-v1_5 signature is valid and the receipt's packageName
is not included, the appBundleId
/ googleBundleId
input into UnityEngine.Purchasing.Security.CrossPlatformValidator
is ignored. To avoid replay attacks we encourage developers continue heuristically scrutinizing the returned purchaseTime
and productId
values found in decoded receipts.SystemInfo.deviceUniqueIdentifier
collection and sharing with ecommerce.iap.unity3d.com
server.1.23.2
, to address players deleting their game's TransactionLog and receiving multiple products for a single purchase. Opt-in to the 1.23.2
behavior with bool IGooglePlayConfiguration.aggressivelyRecoverLostPurchases = true;
, when also using a game server capable of validating each transaction and deduplicate based upon Product.transactionID
, to reward players one time for a single purchase and support quickly recovering from interrupted-purchases.
IStoreListener.OnPurchaseFailed
API, initially. Then it will query Google Play for purchase success during the current app session until network is restored, and it will continue querying in the next app session, after a restart. It will finally call the IStoreListener.ProcessPurchase
API if it finds a successful, unaccounted purchase.DuplicateTransaction
may still be reported while the retry is ongoing, until the user's product is repurchasable again. See below for new APIs to monitor the consumption flow.IStoreListener.ProcessPurchase
, then (2) the transaction is completed by returning ProcessPurchaseResult.Complete
from IStoreListener.ProcessPurchase
or by directly calling IStoreController.ConfirmPendingPurchase
[internally this always records the transaction identifier to the TransactionLog], and finally (3) an interruption (network or exit) aborts the transaction consumption. Only restarting the app or refunding the purchase would reliably resolve this case."isOwned" : <boolean>
sub-entry to the Product.receipt
's "Payload"
JSON entry in order to help developers understand this product's current ownership state.
true
if the product is owned by the user. And please note that true
may also indicate that Unity IAP is actively retrying consumption. Its boolean value will be false
if the product is available for repurchase, or if we do not yet know Google Play's current status for this product. To clarify the receipt structure, "isOwned"
is located in the Google Play-specific escaped-JSON sub-document. Sample Product.receipt
, abbreviated: {"Payload":"{\"json\": ..., \"signature\": ..., \"isOwned\":true}}"
. See the Google Play section of the Unity IAP Receipt receipt documentation for more on the receipt JSON structure.GooglePlay - Adds boolean IGooglePlayStoreExtensions.IsOwned(Product)
API to conveniently extract the new ownership state, above, from the Google Play JSON receipt.
Returns true
if the product is still owned by the user. Returns false
if the product is available for repurchase. Example:
.IsOwned(storeController.products.WithID("100.gold.coins"));```.
GooglePlay - Adds void IGooglePlayStoreExtensions.SetLogLevel(int level)
API to reduce logging.
level
defaults to the legacy value of 0
and configures the Google Play Java store integration to emit debug, info, warning, and error logs. Setting 1
will restrict logging to emit only warnings and errors. Example: extensionProvider.GetExtension<IGooglePlayStoreExtensions>().SetLogLevel(1)
.GooglePlay - After the purchasing dialog, "You already own this product" from Google Play is shown, the IStoreListener.OnPurchaseFailed
API is calls with an error of PurchaseFailureReason.DuplicateTransaction
.
IStoreListener.ProcessPurchase
. Note: This amends the related behavior introduced in 1.23.1.IStoreListener.OnPurchaseFailed
, reporting the interruption as a purchase failure. (4) The user restores the network, attempts to re-purchase, Google Play shows "You already own this product", and Unity IAP reports the message as an error, calling IStoreListener.OnPurchaseFailed
again. (4.1) Repeated re-purchase attempts fail, also potentially failing even after restarting the app.PurchaseFailureReason.DuplicateTransaction
for a ProductType.Consumable by rewarding the user with the product, and presuming that Unity IAP will automatically complete the transaction.UnityEngine.Store
, IUnityChannelExtensions
, IUnityChannelConfiguration
.developerPayload
has been encoded to base64 string and formatted to a JSON string with two other information of the product. When extract developerPayload
from the product receipt, firstly decode the json string and get the developerPayload
field base64 string, secondly decode the base64 string to the original developerPayload
.SubscriptionManager
- This new class allows developer to query the purchased subscription product's information. (available for AppleStore and GooglePlay store)
SubscriptionManager
to get its subscription information.StoreSpecificPurchaseErrorCode
enum. Currently contains values for all Apple and Google Play error codes that are returned directly from the store.ITransactionHistoryExtensions
extension. Developers can call GetLastPurchaseFailureDescription()
and GetLastStoreSpecificPurchaseErrorCode()
to get extended debugging/error information.Automatically initialize UnityPurchasing
checkbox to the IAP Catalog. Checking this box will cause IAP to automatically initialize on game start using the products contained in the catalog.proguard-user.txt.OPTIONAL.txt
into GooglePlay.aar, effectively.)IAppleExtensions.GetTransactionReceiptForProduct
method that returns the most recent iOS 6 style transaction receipt for a given product. This is used to validate Ask-to-buy purchases. Preliminary documentation is available.IAppleConfiguration.SetApplePromotionalPurchaseInterceptorCallback
, that intercepts Apple Promotional purchases in iOS and tvOS. Developers who implement the callback should call IAppleExtensions.ContinuePromotionalPurchases
to resume the purchase flow. Preliminary documentation is available.IAPListener
Component to extend Codeless IAP functionality. Normally with Codeless IAP, purchase events are dispatched to an IAPButton
UI Component that is associated with a particular product. The IAPListener
does not show any UI. It will receive purchase events that do not correspond to any active IAPButton
.
IAPListener
is a fallback—it will receive any successful or failed purchase events (calls to ProcessPurchase
or OnPurchaseFailed
) that are not handled by an active Codeless IAPButton
Component.IAPListener
, you should create it early in the lifecycle of your app, and not destroy it. By default, it will set its GameObject
to not be destroyed when a new scene is loaded, by calling DontDestroyOnLoad
. This behavior can be changed by setting the dontDestroyOnLoad
field in the Inspector.IAPListener
, it should be ready to handle purchase events at any time, for any product. Promo codes, interrupted purchases, and slow store behavior are only a few of the reasons why you might receive a purchase event when you are not showing a corresponding IAPButton
to handle the event.ProcessPurchase
the next time it is initialized—typically the next time the app is run. If your app creates an IAPListener
, the IAPListener
will be available to receive this ProcessPurchase
callback, even if you are not yet ready to create and show an IAPButton
in your UI.IMoolahConfiguration.notificationURL
. Removed deprecated IMoolahExtensions.Login
, IMoolahExtensions.FastRegister
, IMoolahExtensions.RequestPayOut
. Preliminary updated documentation is available.Packages/manifest.json
(new Package Manager).OnPurchaseFailed()
with an informative PurchaseFailureReason
UnityPurchasingEditor.TargetAndroidStore(AndroidStore store)
method or the Window > Unity IAP > Android > Xiaomi Mi Game Pay targeting menu.Apple platforms - Add support for controlling promoted items in the App Store through IAppleExtensions. This feature is available on iOS and tvOS 11. Set the order of promoted items in the App Store with IAppleExtensions.SetStorePromotionOrder, or control visiblility with IAppleExtensions.SetStorePromotionVisibility.
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
// Set the order of the promoted items
var appleExtensions = extensions.GetExtension<IAppleExtensions>();
appleExtensions.SetStorePromotionOrder(new List<Product>{
controller.products.WithID("sword"),
controller.products.WithID("subscription")
});
}
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
// Set the visibility of promoted items
var appleExtensions = extensions.GetExtension<IAppleExtensions>();
appleExtensions.SetStorePromotionVisibility(controller.products.WithID("subscription"), AppleStorePromotionVisibility.Hide);
appleExtensions.SetStorePromotionVisibility(controller.products.WithID("100.gold.coins"), AppleStorePromotionVisibility.Default);
}
ReflectionTypeLoadException[...]UnityEditor.Purchasing.UnityIAPInstaller.<k_Purchasing>
.Apple platforms - Added experimental support for setting "simulatesAskToBuyInSandbox". Please let us know how this impacts ask-to-buy testability for you.
extensions.GetExtension<IAppleExtensions>().simulateAskToBuy = true;
Apple platforms - Added support for setting "applicationUsername" field which will be added to every payment request to help the store detect fraud.
// Set the applicationUsername to help Apple detect fraud
extensions.GetExtension<IAppleExtensions>().SetApplicationUsername(hashedUsername);
android
Android SDK Manager tool manually and install "Android 7.0 (API 24)". Addresses build error messages: "Unable to merge android manifests." and "Main manifest has <uses-sdk android:targetSdkVersion='23'> but library uses targetSdkVersion='24'". Note the Minimum API Level support is unchanged; merely the installation of API 24 SDK is now required for Daydream VR.StandardPurchasingModule - Add bool useFakeStoreAlways
property to override native stores with the local debug FakeStore for rapid prototyping. Will not connect to any App Store when enabled.
// Enable the FakeStore for all IAP activity
var module = StandardPurchasingModule.Instance();
module.useFakeStoreAlways = true;
Editor Updater - Notify the developer when updates to Unity IAP are available with an actionable dialog. Periodically check the Asset Store version and prompt with an upgrade dialog capable of downloading the latest plugin.
Editor Installer - Simplify integration of Unity IAP into a Project, avoiding unexpected breakage of the scripting build environment after package installation. Detect and warn if Unity IAP Core Service is "Off" during installation.
IAPDemo.cs
Android - it is now possible to choose the store implementation to use at runtime.
Make a build containing all store implementations by choosing Window > Unity IAP > Android > "Select store at runtime"
// Pass the desired store to the module, e.g. Amazon Apps.
var module = StandardPurchasingModule.Instance(AndroidStore.AmazonAppStore);
IAndroidStoreSelection extended configuration for accessing the currently selected Android store.
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
Debug.Log(builder.Configure<IAndroidStoreSelection>().androidStore);
You should use this method if your App cannot fulfill an Amazon purchase and you need to call notifyFulfillment method with a FulfillmentResult of UNAVAILABLE.
Editor API call for toggling between Android billing platforms in build scripts; UnityPurchasingEditor.TargetAndroidStore(AndroidStore). See below for usage.
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEditor;
// A sample Editor script.
public class MyEditorScript {
void AnEditorMethod() {
// Set the store to Google Play.
UnityPurchasingEditor.TargetAndroidStore(AndroidStore.GooglePlay);
}
}
Apple Platforms - a method to check whether payment restrictions are in place; [SKPaymentQueue canMakePayments].
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
// Detect if IAPs are enabled in device settings on Apple platforms (iOS, Mac App Store & tvOS).
// On all other platforms this will always return 'true'.
bool canMakePayments = builder.Configure<IAppleConfiguration> ().canMakePayments;
Log
)IAppleConfiguration featuring an 'appReceipt' string property for reading the App Receipt from the device, if any;
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
// On iOS & Mac App Store, receipt will be a Base64 encoded App Receipt, or null
// if no receipt is present on the device.
// On other platforms, receipt will be a dummy placeholder string.
string receipt = builder.Configure<IAppleConfiguration> ().appReceipt;