diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md index 316a67b9ce99..824b432d5021 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.4+2 + +* Added price currency symbol to SkuDetailsWrapper. + ## 0.1.4+1 * Fixed typos. diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java index 079c18ab8b5c..7546fe7db58d 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java @@ -13,8 +13,10 @@ import com.android.billingclient.api.SkuDetails; import java.util.ArrayList; import java.util.Collections; +import java.util.Currency; import java.util.HashMap; import java.util.List; +import java.util.Locale; /** Handles serialization of {@link com.android.billingclient.api.BillingClient} related objects. */ /*package*/ class Translator { @@ -30,6 +32,7 @@ static HashMap fromSkuDetail(SkuDetails detail) { info.put("price", detail.getPrice()); info.put("priceAmountMicros", detail.getPriceAmountMicros()); info.put("priceCurrencyCode", detail.getPriceCurrencyCode()); + info.put("priceCurrencySymbol", currencySymbolFromCode(detail.getPriceCurrencyCode())); info.put("sku", detail.getSku()); info.put("type", detail.getType()); info.put("subscriptionPeriod", detail.getSubscriptionPeriod()); @@ -123,4 +126,21 @@ static HashMap fromBillingResult(BillingResult billingResult) { info.put("debugMessage", billingResult.getDebugMessage()); return info; } + + /** + * Gets the symbol of for the given currency code for the default {@link Locale.Category#DISPLAY + * DISPLAY} locale. For example, for the US Dollar, the symbol is "$" if the default locale is the + * US, while for other locales it may be "US$". If no symbol can be determined, the ISO 4217 + * currency code is returned. + * + * @param currencyCode the ISO 4217 code of the currency + * @return the symbol of this currency code for the default {@link Locale.Category#DISPLAY + * DISPLAY} locale + * @exception NullPointerException if currencyCode is null + * @exception IllegalArgumentException if currencyCode is not a supported ISO 4217 + * code. + */ + static String currencySymbolFromCode(String currencyCode) { + return Currency.getInstance(currencyCode).getSymbol(); + } } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java index e65afcf42467..2837dceea652 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java @@ -7,6 +7,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -22,8 +24,10 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import org.json.JSONException; +import org.junit.Before; import org.junit.Test; public class TranslatorTest { @@ -32,6 +36,12 @@ public class TranslatorTest { private static final String PURCHASE_EXAMPLE_JSON = "{\"orderId\":\"foo\",\"packageName\":\"bar\",\"productId\":\"consumable\",\"purchaseTime\":11111111,\"purchaseState\":0,\"purchaseToken\":\"baz\",\"developerPayload\":\"dummy payload\",\"isAcknowledged\":\"true\", \"obfuscatedAccountId\":\"Account101\", \"obfuscatedProfileId\": \"Profile105\"}"; + @Before + public void setup() { + Locale locale = new Locale("en", "us"); + Locale.setDefault(locale); + } + @Test public void fromSkuDetail() throws JSONException { final SkuDetails expected = new SkuDetails(SKU_DETAIL_EXAMPLE_JSON); @@ -182,6 +192,17 @@ public void fromBillingResult_debugMessageNull() throws JSONException { assertEquals(billingResultMap.get("debugMessage"), newBillingResult.getDebugMessage()); } + @Test + public void currencyCodeFromSymbol() { + assertEquals("$", Translator.currencySymbolFromCode("USD")); + try { + Translator.currencySymbolFromCode("EUROPACOIN"); + fail("Translator should throw an exception"); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + private void assertSerialized(SkuDetails expected, Map serialized) { assertEquals(expected.getDescription(), serialized.get("description")); assertEquals(expected.getFreeTrialPeriod(), serialized.get("freeTrialPeriod")); @@ -194,6 +215,7 @@ private void assertSerialized(SkuDetails expected, Map serialize assertEquals(expected.getPrice(), serialized.get("price")); assertEquals(expected.getPriceAmountMicros(), serialized.get("priceAmountMicros")); assertEquals(expected.getPriceCurrencyCode(), serialized.get("priceCurrencyCode")); + assertEquals("$", serialized.get("priceCurrencySymbol")); assertEquals(expected.getSku(), serialized.get("sku")); assertEquals(expected.getSubscriptionPeriod(), serialized.get("subscriptionPeriod")); assertEquals(expected.getTitle(), serialized.get("title")); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart index e3d13df2262a..da4d5c73d851 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart @@ -38,6 +38,7 @@ class SkuDetailsWrapper { required this.price, required this.priceAmountMicros, required this.priceCurrencyCode, + required this.priceCurrencySymbol, required this.sku, required this.subscriptionPeriod, required this.title, @@ -91,6 +92,12 @@ class SkuDetailsWrapper { @JsonKey(defaultValue: '') final String priceCurrencyCode; + /// [price] localized currency symbol + /// For example, for the US Dollar, the symbol is "$" if the locale + /// is the US, while for other locales it may be "US$". + @JsonKey(defaultValue: '') + final String priceCurrencySymbol; + /// The product ID in Google Play Console. @JsonKey(defaultValue: '') final String sku; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart index a14affdf9ed3..49e86087bc13 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart @@ -17,6 +17,7 @@ SkuDetailsWrapper _$SkuDetailsWrapperFromJson(Map json) { price: json['price'] as String? ?? '', priceAmountMicros: json['priceAmountMicros'] as int? ?? 0, priceCurrencyCode: json['priceCurrencyCode'] as String? ?? '', + priceCurrencySymbol: json['priceCurrencySymbol'] as String? ?? '', sku: json['sku'] as String? ?? '', subscriptionPeriod: json['subscriptionPeriod'] as String? ?? '', title: json['title'] as String? ?? '', @@ -37,6 +38,7 @@ Map _$SkuDetailsWrapperToJson(SkuDetailsWrapper instance) => 'price': instance.price, 'priceAmountMicros': instance.priceAmountMicros, 'priceCurrencyCode': instance.priceCurrencyCode, + 'priceCurrencySymbol': instance.priceCurrencySymbol, 'sku': instance.sku, 'subscriptionPeriod': instance.subscriptionPeriod, 'title': instance.title, diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_product_details.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_product_details.dart index 62589038804e..59d33fe26223 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_product_details.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_product_details.dart @@ -18,6 +18,7 @@ class GooglePlayProductDetails extends ProductDetails { required double rawPrice, required String currencyCode, required this.skuDetails, + required String currencySymbol, }) : super( id: id, title: title, @@ -25,6 +26,7 @@ class GooglePlayProductDetails extends ProductDetails { price: price, rawPrice: rawPrice, currencyCode: currencyCode, + currencySymbol: currencySymbol, ); /// Points back to the [SkuDetailsWrapper] object that was used to generate @@ -43,6 +45,7 @@ class GooglePlayProductDetails extends ProductDetails { price: skuDetails.price, rawPrice: ((skuDetails.priceAmountMicros) / 1000000.0).toDouble(), currencyCode: skuDetails.priceCurrencyCode, + currencySymbol: skuDetails.priceCurrencySymbol, skuDetails: skuDetails, ); } diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml index e0de3411e0ff..41136e7501f6 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_android description: An implementation for the Android platform of the Flutter `in_app_purchase` plugin. This uses the Android BillingClient APIs. repository: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase/in_app_purchase_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.1.4+1 +version: 0.1.4+2 environment: sdk: ">=2.12.0 <3.0.0" @@ -19,7 +19,7 @@ dependencies: collection: ^1.15.0 flutter: sdk: flutter - in_app_purchase_platform_interface: ^1.0.0 + in_app_purchase_platform_interface: ^1.1.0 json_annotation: ^4.0.1 meta: ^1.3.0 test: ^1.16.0 diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/sku_details_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/sku_details_wrapper_test.dart index ead6d26576f3..b8ba9d5cc854 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/sku_details_wrapper_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/sku_details_wrapper_test.dart @@ -17,6 +17,7 @@ final SkuDetailsWrapper dummySkuDetails = SkuDetailsWrapper( price: 'price', priceAmountMicros: 1000, priceCurrencyCode: 'priceCurrencyCode', + priceCurrencySymbol: r'$', sku: 'sku', subscriptionPeriod: 'subscriptionPeriod', title: 'title', @@ -139,6 +140,7 @@ Map buildSkuMap(SkuDetailsWrapper original) { 'price': original.price, 'priceAmountMicros': original.priceAmountMicros, 'priceCurrencyCode': original.priceCurrencyCode, + 'priceCurrencySymbol': original.priceCurrencySymbol, 'sku': original.sku, 'subscriptionPeriod': original.subscriptionPeriod, 'title': original.title, diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart index 01c73d6ed43e..52ec08bea07a 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart @@ -108,6 +108,7 @@ void main() { expect(response.productDetails.first.description, dummySkuDetails.description); expect(response.productDetails.first.price, dummySkuDetails.price); + expect(response.productDetails.first.currencySymbol, r'$'); }); test('should get the correct notFoundIDs', () async {