hi ! I have a problem with currency in IOs
the same email account on different devices in the same region show different currencies
How is this possible? is there any solution?
im using Purchasing api
Thanks!
Can you share your debug output? Or some screenshots? What specific devices? This should not be possible. Please Debug.Log the values and provide the XCode logs as an attachment here, specify what I would be looking for in the logs (the syntax of your Debug.Log statement for example) How To - Capturing Device Logs on iOS . Also please attach your purchasing script.
I am Japanese.
Please excuse my bad English.
It seems to be a similar problem, so I will post it here.
I’m currently testing on iOS13.7 and iPadOS14.3.
In some cases, USD is returned for the currency code obtained by using the IAP plugin function on iOS devices.
The value returned here is expected to be JPY.
Tap the icon in the upper right corner of the App Store to open the account dialog.
User Name (top item) > Country or Region Name > Change Country or Region > Japan
Make the above settings on each device.
On iOS13.7, the expected value of JPY will be returned.
On iPad OS 14.3, the wrong value of USD is returned.
Is this difference in the OS version?
Or is there a problem with the device specific settings?
It is difficult to provide the source code due to the public nature of the site.
Thank you.
7307875–885592–iOS13.7_No_problem_log.txt (5.01 KB)
7307875–885595–iPadOS14.3_Problem_log.txt (5.35 KB)
What version of IAP? It sounds like a possible issue with the iOS version, we will need to check. If we find the cause and are able to address it, we would include the update in a future release.
The version of IAP used in the delivered app is “com.unity.purchasing”: “2.0.6”.
2.x.x latest 2.2.2
3.x.x latest 3.2.2
I have tried to check the above versions up in order, but the result is the same and does not return the expected value in iPadOS14.3.
You will want to upgrade IAP and release a new version of your app. The latest version is 3.2.3. How are you checking the version of IAP on the released app?
I was on vacation last week, so it took me a while to check and respond.
IAP version information was checked in “. /Packages/manifest.json” for the IAP version information.
In the released application “. /Packages/manifest.json” has the following.
"com.unity.purchasing": "2.0.6",
However, the StandardPurchasingModule class was defined as 1.20.0.
public const string k_PackageVersion = "1.20.0";
I upgraded the IAP plugin to 3.2.3 today and checked it out.
However, when I ran it on iPadOS 14.3, it did not return the expected values.
In iOS13.7, I have not checked all countries, but it returns values like Australia “AUD”, Canada “CAD”, and so on.
In iPad OS 14.3, the value “USD” is always returned when selecting the same as in iOS 13.7.
In this case, the following variables are being logged to the console.
StandardPurchasingModule.Instance().Version
It appears that IAP version 3.2.3 is being used, but the situation is that the expected value is not being returned.
I’m checking this by building an app that just adds logs to the released app.
Is there anything else I should check?
The string that cannot be published in the attachment is set to “***”.
7362011–896855–iPadOS14.3_Problem_log_IAP_v3.2.3.txt (2.28 KB)
So you are building directly to your iPad from XCode, correct? Can you capture the logs that confirms the IAP version? How To - Capturing Device Logs on iOS We will test the localized price
To be precise, I’m building it on a build server machine.
I’m outputting the XCode project in Unity, building it with the “Provisioning Profile” for Distribution, and uploading the resulting “.ipa” file to DeployGate.
I don’t think I can check “Debug.Log” from XCode because I install and check it from DeployGate.
Distribution cannot be built on the development terminal provided by the company to the individual.
As far as I know, “Provisioning Profile” must be built in Distribution.
The “Provisioning Profile” for Development fails in the initialization process of the billing plugin.
For this reason, we do not build directly from XCode to iPad.
Currently, it’s difficult to check the build from XCode from the console.
For Unity’s build options, I have it set to development build and set it as follows.
BuildPlayerOptions options = new BuildPlayerOptions();
options.locationPathName = "bin/ios";
options.scenes = GetScenes();
options.target = BuildTarget.iOS;
options.targetGroup = BuildTargetGroup.iOS;
options.options = BuildOptions.SymlinkLibraries | BuildOptions.Development | BuildOptions.ConnectWithProfiler;
BuildReport buildReport = BuildPipeline.BuildPlayer(options);
How do you currently check it?
By using NSLog in Objective-C, I can see the contents output by “Debug.Log” in the Mac console.
The output log is filtered by [Purchase].
In the attached file, 3.2.3 at the end of the third line is the IAP version value, but is it different from what you want me to check?
iPadOS14.3_Problem_log_IAP_v3.2.3.txt
Here is the code used, to the best of my ability.
BaseUnityIAPService.cs
using UnityEngine;
using UnityEngine.Purchasing;
public class BaseUnityIAPService : MonoBehaviour, IPurchaseService, IStoreListener {
public void OnInitialized(IStoreController controller, IExtensionProvider extensions) {
Debug.Log($"[Purchase]BaseUnityIAPService.OnInitialized Start IAP version: {UnityEngine.Purchasing.StandardPurchasingModule.Instance().Version}");
storeController = controller;
storeExtensionProvider = extensions;
#if UNITY_IOS
var products = storeController.products.all;
if (products != null && products.Length > 0 && products [0].metadata.isoCurrencyCode != null) {
this.manager.CountryCode = products [0].metadata.isoCurrencyCode;
}
Debug.Log(string.Concat("[Purchase]", $"*** CountryCode: {this.manager.CountryCode} {((products == null) ? "products is null" : "products != null")}"));
Debug.Log(string.Concat("[Purchase]", $"*** localizedPriceString: {products [0].metadata.localizedPriceString}"));
if (products != null) {
Debug.Log(string.Concat("[Purchase]", $"products.Length: {products.Length}"));
if (products.Length > 0) {
for(var i = 0; i < products.Length; ++i) {
var outputStr = $"{((products[i].metadata.isoCurrencyCode == null) ? $"products[i].metadata.isoCurrencyCode is null" : $"prfoducts[i].metadata.isoCurrencyCode != null")}\n";
outputStr += string.Concat($"products[{i}].metadata.localizedTitle: " + products[i].metadata.localizedTitle, "\n");
outputStr += string.Concat($"products[{i}].metadata.localizedDescription: " + products[i].metadata.localizedDescription, "\n");
outputStr += string.Concat($"products[{i}].metadata.localizedPriceString: " + products[i].metadata.localizedPriceString, "\n");
Debug.Log(string.Concat("[Purchase]", outputStr));
}
}
}
#endif
this.manager.ReceivedtPurchaseProductListForUnityIAP ();
Debug.Log("[Purchase]Purchasing***");
}
}
Debug.cs
#if !UNITY_EDITOR
#define DEBUG_LOG_OVERWRAP
#endif
using System;
using System.Runtime.InteropServices;
using UnityEngine;
#if DEBUG_LOG_OVERWRAP
public static class Debug
{
static public void Log( object message ){
if( IsEnable() ){
UnityEngine.Debug.Log( message );
#if UNITY_IOS
UnityNSLog( message.ToString() );
#endif
}
}
static bool IsEnable(){ return UnityEngine.Debug.isDebugBuild; }
#if UNITY_IOS
[DllImport("__Internal")]
private static extern void UnityNSLog(string message);
#endif
}
#endif
MyAppController.mm
#import "UnityAppController.h"
#import <Foundation/Foundation.h>
#include "Adjust.h"
@interface MyAppController : UnityAppController
+ (void)RemoveUrlUserDefaults;
@end
extern "C"{
void UnityNSLog(const char* message) {
NSLog (@"%s", message);
}
}
Because Japanese is included in the prohibited terms, the corresponding parts are marked with “***”.
@akiba_unity427 Thank for the info, we will check. If we can confirm the issue, we will likely include an update in an upcoming release.
Hi. Is there any update on this as I see a similar issue.
Using Unity IAP 4.0.3, Unity 2020.3.2f1:
- on iPad OS 14.7.1 prices are returned in USD with USD values
- on iPhone OS 14.2 prices are returned in GBP with USD values
Afterthought: my IAP products “Ready to Submit”. Is that likely to cause issues?
“Ready to Submit” is the proper status. This looks to be an Apple issue, we have contacted them. Can you share the code you are using and steps to reproduce, to confirm?
@JeffDUnity3D Here’s the relevant code. This works fine on Android so I think you’re correct that this is an Apple issue. This code is used in conjunction with Playfab so the ItemIds are set through that system. I’ve confirmed that the names are identical. I submitted a beta build to Apple thinking that the “Ready to Submit” was an indicator that the products needed to be in place for them to work. It was rejected however under Section 2.1:
We found that your in-app purchase products exhibited one or more bugs when reviewed on iPad running iOS 14.7.1 on Wi-Fi.
Specifically, we were unable to make a purchase due to an error.
namespace gg.core.server
{
public abstract class ServerInterfacePlayFab : ServerInterface
#if (UNITY_ANDROID && GOOGLEPLAY) || ((UNITY_IOS || UNITY_STANDALONE_OSX) && APPSTORE)
, IStoreListener
#endif
{
#if (UNITY_ANDROID && GOOGLEPLAY) || ((UNITY_IOS || UNITY_STANDALONE_OSX) && APPSTORE)
private IStoreController storeController;
private IExtensionProvider extensions;
private bool initialisingStoreController;
protected Action<IAPResponse> onPurchase;
#endif
public ServerInterfacePlayFab()
{
#if (UNITY_ANDROID && GOOGLEPLAY) || ((UNITY_IOS || UNITY_STANDALONE_OSX) && APPSTORE)
initialisingStoreController = false;
onPurchase = null;
#endif
}
public override void RefreshIAP(string[] storeIds, Action<Response, IAPStore[]> onRefresh)
{
IAPStore[] stores = new IAPStore[storeIds.Length];
IEnumerator GetStores()
{
#if (UNITY_ANDROID && GOOGLEPLAY) || ((UNITY_IOS || UNITY_STANDALONE_OSX) && APPSTORE)
ConfigurationBuilder builder = null;
if (!initialisingStoreController && storeController == null)
{
initialisingStoreController = true;
builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
}
#endif
Dictionary<string, ItemInfo> itemInfos = new Dictionary<string, ItemInfo>();
bool gettingCatalogItems = true;
GetCatalogItemsRequest getCatalogRequest = new GetCatalogItemsRequest()
{
CatalogVersion = "default"
};
Action<GetCatalogItemsResult> getCatalogSuccess = (GetCatalogItemsResult result) =>
{
for (int i = 0; i < result.Catalog.Count; ++i)
{
ItemInfo itemInfo = new ItemInfo(result.Catalog[i]);
//TODO subscriptions
itemInfos.Add(result.Catalog[i].ItemId, itemInfo);
}
gettingCatalogItems = false;
};
Action<PlayFabError> getCatalogError = (PlayFabError error) =>
{
gettingCatalogItems = false;
};
PlayFabClientAPI.GetCatalogItems(getCatalogRequest, getCatalogSuccess, getCatalogError);
yield return new WaitUntil(() => !gettingCatalogItems);
Response getStoreResponse = Response.None;
for (int i = 0; i < storeIds.Length; ++i)
{
bool gettingStore = true;
getStoreResponse = Response.None;
GetStoreItemsRequest request = new GetStoreItemsRequest()
{
StoreId = storeIds[i],
CatalogVersion = "default"
};
Action<GetStoreItemsResult> onGetItemsSuccess = (GetStoreItemsResult result) =>
{
IAPStore store = new IAPStore(storeIds[i]);
for (int j = 0; j < result.Store.Count; ++j)
{
StoreItem storeItem = result.Store[j];
string costItemId = null;
uint costAmount = 0;
float realCurrencyPrice = -1;
ItemInfo itemInfo;
itemInfos.TryGetValue(storeItem.ItemId, out itemInfo);
if (storeItem.VirtualCurrencyPrices.Count > 0)
{
Dictionary<string, uint>.Enumerator it = storeItem.VirtualCurrencyPrices.GetEnumerator();
while (it.MoveNext())
{
costItemId = it.Current.Key;
costAmount = it.Current.Value;
break;
}
}
else
{
continue;
}
IAPItem item = new IAPItem(storeIds[i], storeItem.ItemId, new IAPCost(costItemId, costAmount, realCurrencyPrice), itemInfo?.consumeImmediately ?? false);
store.AddItem(item);
#if (UNITY_ANDROID && GOOGLEPLAY) || ((UNITY_IOS || UNITY_STANDALONE_OSX) && APPSTORE)
if (builder != null && !item.Cost.IsVirtualCurrency())
{
builder.AddProduct(item.ItemId, itemInfo?.productType ?? ProductType.Consumable, new IDs {
#if UNITY_ANDROID && GOOGLEPLAY
{ item.ItemId, GooglePlay.Name }
#elif APPSTORE
#if UNITY_IOS
{ item.ItemId, AppleAppStore.Name }
#else
{ item.ItemId, MacAppStore.Name }
#endif
#endif
});
}
#endif
}
stores[i] = store;
getStoreResponse = Response.Success;
gettingStore = false;
};
Action<PlayFabError> onGetItemsFail = (PlayFabError error) =>
{
if (error.Error != PlayFabErrorCode.StoreNotFound)
{
getStoreResponse = ToResponse(error);
onRefresh(getStoreResponse, null);
}
gettingStore = false;
};
PlayFabClientAPI.GetStoreItems(request, onGetItemsSuccess, onGetItemsFail);
yield return new WaitUntil(() => !gettingStore);
if (!getStoreResponse)
{
break;
}
}
#if (UNITY_ANDROID && GOOGLEPLAY) || ((UNITY_IOS || UNITY_STANDALONE_OSX) && APPSTORE)
if (builder != null)
{
UnityPurchasing.Initialize(this, builder);
}
#endif
onRefresh(Response.Success, stores);
}
App.Instance.StartCoroutine(GetStores());
}
#if (UNITY_ANDROID && GOOGLEPLAY) || ((UNITY_IOS || UNITY_STANDALONE_OSX) && APPSTORE)
public override bool IAPInitializing
{
get
{
return initialisingStoreController;
}
}
public override bool IAPInitialized
{
get
{
return !initialisingStoreController && storeController != null;
}
}
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
storeController = controller;
this.extensions = extensions;
initialisingStoreController = false;
}
public void OnInitializeFailed(InitializationFailureReason error)
{
initialisingStoreController = false;
}
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent)
{
if (storeController == null
|| purchaseEvent.purchasedProduct == null
|| string.IsNullOrEmpty(purchaseEvent.purchasedProduct.receipt))
{
onPurchase(new IAPResponse(Error.Unknown, "IAP Not Initialized"));
return PurchaseProcessingResult.Complete;
}
ValidatePurchase(purchaseEvent.purchasedProduct);
return PurchaseProcessingResult.Complete;
}
protected abstract void ValidatePurchase(Product product);
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
onPurchase(new IAPResponse(ToErrorCode(failureReason), failureReason.ToString()));
}
public override string GetLocalizedPriceString(string itemId)
{
if (!IAPInitialized)
{
return null;
}
return storeController.products.WithStoreSpecificID(itemId)?.metadata.localizedPriceString ?? null;
}
#endif
}
}
@GG_JamesBattersby You are raising an exception in ProcessPurchase yet returning Complete? I might suggest instead to use IAPManager.cs in the Sample IAP Project v2. Also, is Unity IAP on iOS working for you in TestFlight? If not, then don’t publish. I would recommend to get documented scripts working first, before adding your customizations like you have. If the basics don’t work, something more complex won’t either.
@JeffDUnity3D Last I checked it had been working, at least within a Sandbox environment. I’ll check in TestFlight tomorrow now that I’ve cleared the current workload.
As for returning Complete, the only alternative is Pending. My interpretation was that the purchase process was “complete”, regardless of whether it was successful or not.
Perhaps I don’t follow. You’re in ProcessPurchase, a success callback that can only happen when you have a valid connection with the store.
@JeffDUnity3D I can confirm that the purchases go through on TestFlight - TLDR it’s functioning but not as expected.
The reason the prices were appearing as USD whilst in the UK was because the testing account wasn’t signed into their Apple ID so it defaults to USD. Once signed in it fetches the prices in GBP, presumably Apple indicates that’s the region it should be associated with. Presumably there isn’t a way to fetch localised prices based on the device region if an Apple account isn’t available?
As for the strange prices there seems to be some inconsistencies but it is at least consistent between the iOS platforms.
-
Some prices appear to be set on the Alternate Tiers in Apple’s pricing system.
-
Tiers 1, 2, 3 and 5 are returning as Alternate Tier 1, 2, 3 and 5 respectively
-
Some prices appear to be returning different tier prices
-
Tier 12 is returning as Tier 14, 30 as 33, 54 as 55, 60 as 62
The interesting thing is that the decimal values of these prices match the USD value of the tier that I have selected for these products. This is also probably the cause of some strange behaviour when validating the receipts on Playfab but that’s a separate issue.
@arivasaim and @akiba_unity427 , the problem may have been with your Sandbox Apple ID. When testing development builds on iOS, the device’s region setting is not used for in-app purchases. Instead, the Sandbox Apple ID’s region is used. You can set your Sandbox Apple ID’s region in App Store Connect.
See Apple’s documentation:
To test in-app purchases in multiple regions using the same Sandbox Apple ID, change the Sandbox Apple ID’s App Store Country or Region setting in App Store Connect.
this helps me too
This thread is now closed. Feel free to reach out via a new thread if you encounter further issues.
Thanks!