Repository: hyochan/flutter_inapp_purchase

Issue:
Fail to requestProducts in Android
I am using real devices on android and fail to request products but it works in ios.

```
I/flutter (18994): running initialization
I/flutter (18994): [flutter_inapp_purchase] Setting up method call handler
I/flutter (18994): [flutter_inapp_purchase] Received method call: connection-updated
I/flutter (18994): [flutter_inapp_purchase] Arguments: {"connected":true}
I/flutter (18994): [flutter_inapp_purchase] requestProducts called with productIds: ["productId1", "productId2", "productId3"]
E/flutter (18994): [ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: [flutter_inapp_purchase]: PurchaseError: Failed to fetch products: PlatformException(error, null, null, java.lang.ClassCastException
E/flutter (18994):      at id.i.a(MethodCall.java:31)
E/flutter (18994):      at tc.b.c(AndroidInappPurchasePlugin.kt:3)
E/flutter (18994):      at tc.b.onMethodCall(AndroidInappPurchasePlugin.kt:369)
E/flutter (18994):      at id.k$a.a(MethodChannel.java:16)
E/flutter (18994):      at yc.b.run(R8$$SyntheticClass:153)
E/flutter (18994):      at android.os.Handler.handleCallback(Handler.java:789)
E/flutter (18994):      at android.os.Handler.dispatchMessage(Handler.java:98)
E/flutter (18994):      at android.os.Looper.loop(Looper.java:164)
E/flutter (18994):      at android.app.ActivityThread.main(ActivityThread.java:6944)
E/flutter (18994):      at java.lang.reflect.Method.invoke(Native Method)
E/flutter (18994):      at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
E/flutter (18994):      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
E/flutter (18994): )
E/flutter (18994): #0      FlutterInappPurchase.requestProducts (package:flutter_inapp_purchase/flutter_inapp_purchase.dart:235)
**Package version:**
```

- flutter_inapp_purchase: ^6.3.0

**Platform:**

- [ ] iOS
- [X] Android




Discussion: @tonytang0114 Sorry for this! I'm not sure why the `example` app worked fine when there was this kind of cast error! I'll fix this now so please stay tuned
@hyochan  Thanks!
Looks like the cause is here
`/lib/flutter_inapp_purchase.dart line 211`

```
rawResult = await _channel.invokeMethod(
          'getProducts',
          params.productIds, // bug here, should have been {'productIds': params.productIds}
        );
```

Similarly, I haven’t tested Subscriptions yet, but could the following part cause a bug?

```
rawResult = await _channel.invokeMethod(
          'getSubscriptions',
          params.productIds,
        );
      }
```
@quancr258 Can you try `6.3.1`? I've just released the fixes and I'll push the code later. It is in pub

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e1d21c34..749a656b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
 # CHANGELOG
 
+## 6.3.1
+
+### Bug Fixes
+
+- **Android Subscription Loading**: Fixed type casting error when loading subscriptions
+  - Fixed `_Map<String, dynamic>` type casting issue that prevented subscriptions from loading
+  - Improved handling of pricing phases data structure from Android
+  - Fixed parsing of subscription offer details from nested JSON structure
+- **Test Product Cleanup**: Removed deprecated `android.test.purchased` test product ID
+  - This test ID is no longer valid in Google Play Billing Library 6+
+- **Type Safety**: Improved type handling for Map conversions from native platforms
+  - Better handling of different Map implementations from iOS and Android
+
 ## 6.3.0
 
 ### Bug Fixes
diff --git a/CLAUDE.md b/CLAUDE.md
index 12af49f6..c0ae1595 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -15,12 +15,18 @@ Before committing any changes, run these commands in order and ensure ALL pass:
 1. **Format check**: `dart format --set-exit-if-changed .`
    - This will fail if any files need formatting (exit code 1)
    - If it fails, run `dart format .` to fix formatting, then retry
-2. **Test validation**: `flutter test`
+   - Always format code before committing to maintain consistent style
+2. **Lint check**: `flutter analyze`
+   - Fix any lint issues before committing
+   - Pay attention to type inference errors and explicitly specify type arguments when needed
+3. **Test validation**: `flutter test`
    - All tests must pass
-3. **Final verification**: Re-run `dart format --set-exit-if-changed .` to confirm no formatting issues
-4. Only commit if ALL checks succeed with exit code 0
+4. **Final verification**: Re-run `dart format --set-exit-if-changed .` to confirm no formatting issues
+5. Only commit if ALL checks succeed with exit code 0
 
-**Important**: Use `--set-exit-if-changed` flag to match CI behavior and catch formatting issues locally before they cause CI failures.
+**Important**: 
+- Use `--set-exit-if-changed` flag to match CI behavior and catch formatting issues locally before they cause CI failures
+- When using generic functions like `showModalBottomSheet`, always specify explicit type arguments (e.g., `showModalBottomSheet<void>`) to avoid type inference errors
 
 ### Platform-Specific Naming Conventions
 
diff --git a/README.md b/README.md
index a76497e5..a9ecfbeb 100644
--- a/README.md
+++ b/README.md
@@ -19,9 +19,17 @@
 
 ```yaml
 dependencies:
-  flutter_inapp_purchase: ^6.3.0
+  flutter_inapp_purchase: ^6.3.1
 ```
 
+## 🔄 What's New in 6.3.1
+
+### Bug Fixes
+
+- Fixed Android subscription loading type casting error
+- Removed deprecated test product IDs
+- Improved type safety for native platform data
+
 ## 🔄 What's New in 6.3.0
 
 **📝 [Read the full release blog post](docs/blog/2025-08-19-6.3.0.release.md)**
diff --git a/android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt b/android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt
index 01713692..0fa2ef03 100644
--- a/android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt
+++ b/android/src/main/kotlin/dev/hyo/flutterinapppurchase/AndroidInappPurchasePlugin.kt
@@ -500,31 +500,6 @@ class AndroidInappPurchasePlugin internal constructor() : MethodCallHandler,
                             }
                         }
                         item.put("subscriptionOfferDetails", subsOffers)
-                        
-                        // Keep backward compatibility with subscriptionOffers
-                        val subs = JSONArray()
-                        if (productDetails.subscriptionOfferDetails != null ) {
-                            for (offer in productDetails.subscriptionOfferDetails!!) {
-                                val offerItem = JSONObject()
-                                offerItem.put("offerId", offer.offerId)
-                                offerItem.put("basePlanId", offer.basePlanId)
-                                offerItem.put("offerToken", offer.offerToken)
-                                val pricingPhasesArray = JSONArray()
-                                for (pricing in offer.pricingPhases.pricingPhaseList) {
-                                    val pricingPhase = JSONObject()
-                                    pricingPhase.put("price", (pricing.priceAmountMicros / 1000000f).toString())
-                                    pricingPhase.put("formattedPrice", pricing.formattedPrice)
-                                    pricingPhase.put("billingPeriod", pricing.billingPeriod)
-                                    pricingPhase.put("currencyCode", pricing.priceCurrencyCode)
-                                    pricingPhase.put("recurrenceMode", pricing.recurrenceMode)
-                                    pricingPhase.put("billingCycleCount", pricing.billingCycleCount)
-                                    pricingPhasesArray.put(pricingPhase)
-                                }
-                                offerItem.put("pricingPhases", pricingPhasesArray)
-                                subs.put(offerItem)
-                            }
-                        }
-                        item.put("subscriptionOffers", subs)
                     }
 
                     items.put(item)
@@ -615,9 +590,18 @@ class AndroidInappPurchasePlugin internal constructor() : MethodCallHandler,
             }
 
             when (prorationMode) {
-                -1 -> {} //ignore
+                -1 -> {} //ignore - no proration mode specified
                 BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE -> {
-                    params.setOldPurchaseToken(purchaseToken ?: "")
+                    // For subscription replacement, purchaseToken is required
+                    if (purchaseToken.isNullOrEmpty()) {
+                        safeChannel.error(
+                            TAG,
+                            "buyItemByType",
+                            "Old SKU purchase information(token/id) or original external transaction id must be provided for subscription replacement."
+                        )
+                        return
+                    }
+                    params.setOldPurchaseToken(purchaseToken)
                         .setSubscriptionReplacementMode(BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE)
                     builder.setSubscriptionUpdateParams(params.build())
                     if (type != BillingClient.ProductType.SUBS) {
@@ -630,29 +614,64 @@ class AndroidInappPurchasePlugin internal constructor() : MethodCallHandler,
                     }
                 }
                 1 -> { // IMMEDIATE_WITHOUT_PRORATION
-                    params.setOldPurchaseToken(purchaseToken ?: "")
+                    if (purchaseToken.isNullOrEmpty()) {
+                        safeChannel.error(
+                            TAG,
+                            "buyItemByType",
+                            "Old SKU purchase information(token/id) or original external transaction id must be provided for subscription replacement."
+                        )
+                        return
+                    }
+                    params.setOldPurchaseToken(purchaseToken)
                         .setSubscriptionReplacementMode(BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITHOUT_PRORATION)
                     builder.setSubscriptionUpdateParams(params.build())
                 }
                 4 -> { // DEFERRED
-                    params.setOldPurchaseToken(purchaseToken ?: "")
+                    if (purchaseToken.isNullOrEmpty()) {
+                        safeChannel.error(
+                            TAG,
+                            "buyItemByType",
+                            "Old SKU purchase information(token/id) or original external transaction id must be provided for deferred subscription replacement."
+                        )
+                        return
+                    }
+                    params.setOldPurchaseToken(purchaseToken)
                         .setSubscriptionReplacementMode(BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.DEFERRED)
                     builder.setSubscriptionUpdateParams(params.build())
                 }
                 2 -> { // IMMEDIATE_WITH_TIME_PRORATION
-                    params.setOldPurchaseToken(purchaseToken ?: "")
+                    if (purchaseToken.isNullOrEmpty()) {
+                        safeChannel.error(
+                            TAG,
+                            "buyItemByType",
+                            "Old SKU purchase information(token/id) or original external transaction id must be provided for subscription replacement."
+                        )
+                        return
+                    }
+                    params.setOldPurchaseToken(purchaseToken)
                         .setSubscriptionReplacementMode(BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITH_TIME_PRORATION)
                     builder.setSubscriptionUpdateParams(params.build())
                 }
                 5 -> { // IMMEDIATE_AND_CHARGE_FULL_PRICE
-                    params.setOldPurchaseToken(purchaseToken ?: "")
+                    if (purchaseToken.isNullOrEmpty()) {
+                        safeChannel.error(
+                            TAG,
+                            "buyItemByType",
+                            "Old SKU purchase information(token/id) or original external transaction id must be provided for subscription replacement."
+                        )
+                        return
+                    }
+                    params.setOldPurchaseToken(purchaseToken)
                         .setSubscriptionReplacementMode(BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_FULL_PRICE)
                     builder.setSubscriptionUpdateParams(params.build())
                 }
                 else -> {
-                    params.setOldPurchaseToken(purchaseToken ?: "")
-                        .setSubscriptionReplacementMode(BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.UNKNOWN_REPLACEMENT_MODE)
-                    builder.setSubscriptionUpdateParams(params.build())
+                    // For any other proration mode, also require purchase token
+                    if (!purchaseToken.isNullOrEmpty()) {
+                        params.setOldPurchaseToken(purchaseToken)
+                            .setSubscriptionReplacementMode(BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.UNKNOWN_REPLACEMENT_MODE)
+                        builder.setSubscriptionUpdateParams(params.build())
+                    }
                 }
             }
             if (activity != null) {
diff --git a/docs/docs/guides/ios-purchase-state-detection.md b/docs/docs/guides/ios-purchase-state-detection.md
new file mode 100644
index 00000000..90c72688
--- /dev/null
+++ b/docs/docs/guides/ios-purchase-state-detection.md
@@ -0,0 +1,194 @@
+# iOS Purchase State Detection
+
+## Overview
+
+iOS purchase state detection can be complex due to the differences in how iOS App Store handles purchase confirmations compared to Android Google Play Store. This guide explains the common issues and solutions for proper iOS purchase state detection in flutter_inapp_purchase.
+
+## Common iOS Purchase Issues
+
+### 1. Processing State Stuck Issue
+
+**Problem**: iOS purchases succeed (valid tokens and transaction IDs are generated) but the UI remains stuck in "Processing..." state.
+
+**Root Cause**: The `transactionStateIOS` field often returns `null` even for successful purchases, while `purchaseToken` and `transactionId` contain valid data.
+
+**Solution**: Use multiple conditions to detect successful purchases:
+
+```dart
+bool isPurchased = false;
+
+if (Platform.isIOS) {
+  bool condition1 = purchase.transactionStateIOS == TransactionState.purchased;
+  bool condition2 = purchase.purchaseToken != null && purchase.purchaseToken!.isNotEmpty;
+  bool condition3 = purchase.transactionId != null && purchase.transactionId!.isNotEmpty;
+
+  // For iOS, receiving a purchase update usually means success
+  // especially if we have either a valid token OR transaction ID
+  isPurchased = condition1 || condition2 || condition3;
+}
+```
+
+### 2. Timeout Errors
+
+**Problem**: iOS purchases fail with timeout errors ("요청한 시간이 초과되었습니다").
+
+**Root Cause**: Apple App Store server issues or network connectivity problems.
+
+**Solutions**:
+- Wait a few minutes and retry
+- Check network connection (try switching between WiFi and cellular)
+- Test on real device instead of simulator
+- Restart the app or device
+- Check Apple system status
+
+## Implementation Best Practices
+
+### 1. Comprehensive Purchase State Detection
+
+```dart
+Future<void> _handlePurchaseUpdate(Purchase purchase) async {
+  print('🎯 Purchase update received: ${purchase.productId}');
+  print('  Platform: ${purchase.platform}');
+  print('  Transaction state iOS: ${purchase.transactionStateIOS}');
+  print('  Purchase token: ${purchase.purchaseToken}');
+  print('  Transaction ID: ${purchase.transactionId}');
+
+  bool isPurchased = false;
+
+  if (Platform.isIOS) {
+    // Enhanced iOS detection logic
+    bool hasValidState = purchase.transactionStateIOS == TransactionState.purchased;
+    bool hasValidToken = purchase.purchaseToken != null && purchase.purchaseToken!.isNotEmpty;
+    bool hasValidTransactionId = purchase.transactionId != null && purchase.transactionId!.isNotEmpty;
+
+    isPurchased = hasValidState || hasValidToken || hasValidTransactionId;
+    
+    print('  iOS condition checks:');
+    print('    transactionStateIOS == purchased: $hasValidState');
+    print('    has valid purchaseToken: $hasValidToken');
+    print('    has valid transactionId: $hasValidTransactionId');
+    print('  Final isPurchased: $isPurchased');
+  }
+
+  if (isPurchased) {
+    // Handle successful purchase
+    setState(() {
+      _isProcessing = false;
+      _purchaseResult = '✅ Purchase successful: ${purchase.productId}';
+    });
+
+    // Finish the transaction
+    await _iap.finishTransaction(purchase);
+  }
+}
+```
+
+### 2. Timeout Error Handling
+
+```dart
+void _handlePurchaseError(PurchaseError error) {
+  if (error.message.contains('요청한 시간이 초과되었습니다') || 
+      error.message.contains('timeout') ||
+      error.message.contains('timed out')) {
+    // Handle timeout specifically
+    setState(() {
+      _purchaseResult = '''
+⏱️ Request Timeout
+Code: ${error.code}
+Message: ${error.message}
+
+🔄 Suggested Actions:
+1. Check your internet connection
+2. Wait a few minutes and try again
+3. Restart the app
+4. Try on a different network (WiFi/Cellular)
+5. Restart your device
+6. Check Apple server status
+
+This is usually a temporary server issue.
+      ''';
+    });
+  }
+}
+```
+
+### 3. Duplicate Transaction Prevention
+
+```dart
+class _PurchaseScreenState extends State<PurchaseScreen> {
+  final Set<String> _processedTransactionIds = {};
+
+  Future<void> _handlePurchaseUpdate(Purchase purchase) async {
+    // Check for duplicate processing
+    final transactionId = purchase.transactionId ?? purchase.purchaseToken ?? '';
+    if (transactionId.isNotEmpty && _processedTransactionIds.contains(transactionId)) {
+      print('⚠️ Transaction already processed: $transactionId');
+      return;
+    }
+
+    // Process purchase...
+    
+    // Mark as processed
+    if (transactionId.isNotEmpty) {
+      _processedTransactionIds.add(transactionId);
+    }
+  }
+}
+```
+
+## Debugging iOS Purchase Issues
+
+### 1. Enable Detailed Logging
+
+Add comprehensive logging to understand what's happening:
+
+```dart
+print('🎯 Purchase updated: ${purchase.productId}');
+print('  Platform: ${purchase.platform}');
+print('  Purchase state: ${purchase.purchaseState}');
+print('  Transaction state iOS: ${purchase.transactionStateIOS}');
+print('  Transaction ID: ${purchase.transactionId}');
+print('  Purchase token: ${purchase.purchaseToken}');
+print('  Purchase token length: ${purchase.purchaseToken?.length ?? 0}');
+```
+
+### 2. Test on Real Device
+
+iOS Simulator can have different behavior than real devices. Always test critical purchase flows on physical iOS devices.
+
+### 3. Check StoreKit Configuration
+
+Ensure your StoreKit configuration file (if using StoreKit testing) has the correct product IDs and is properly configured.
+
+## Troubleshooting Checklist
+
+### For Processing State Issues:
+- [ ] Verify purchase update listener is properly set up
+- [ ] Check if `purchaseToken` or `transactionId` are present even when `transactionStateIOS` is null
+- [ ] Ensure UI state is updated in the purchase listener
+- [ ] Verify transaction is being finished with `finishTransaction()`
+
+### For Timeout Issues:
+- [ ] Test on different network connections
+- [ ] Try on real device vs simulator
+- [ ] Check Apple system status
+- [ ] Verify product IDs are correct
+- [ ] Ensure App Store Connect configuration is complete
+
+### For Double Purchase Issues:
+- [ ] Implement transaction ID tracking
+- [ ] Check for duplicate listeners
+- [ ] Verify `finishTransaction()` is called properly
+- [ ] Add proper error handling
+
+## Related Issues
+
+- [iOS purchases stuck in Processing state](https://github.com/hyochan/flutter_inapp_purchase/issues/XXX)
+- [Apple timeout errors](https://github.com/hyochan/flutter_inapp_purchase/issues/XXX)
+- [Transaction state detection improvements](https://github.com/hyochan/flutter_inapp_purchase/issues/XXX)
+
+## See Also
+
+- [Purchase Flow Implementation](../examples/basic-store.md)
+- [Error Handling Guide](./error-handling.md)
+- [Troubleshooting Guide](./troubleshooting.md)
\ No newline at end of file
diff --git a/docs/docs/guides/troubleshooting.md b/docs/docs/guides/troubleshooting.md
index 22f9e194..8f66953b 100644
--- a/docs/docs/guides/troubleshooting.md
+++ b/docs/docs/guides/troubleshooting.md
@@ -456,6 +456,80 @@ class ConnectionDiagnostics {
 }
 ```
 
+### iOS Purchase State Detection Issues
+
+**Problem**: iOS purchases succeed but UI remains stuck in "Processing..." state.
+
+**Symptoms:**
+- Purchase logs show valid `purchaseToken` and `transactionId`
+- `transactionStateIOS` returns `null`
+- UI doesn't update to success state
+- Transaction completes but user doesn't see confirmation
+
+**Root Cause**: iOS App Store sometimes returns `null` for `transactionStateIOS` even when purchases are successful.
+
+**Solution**: Use enhanced purchase state detection logic:
+
+```dart
+Future<void> _handlePurchaseUpdate(Purchase purchase) async {
+  bool isPurchased = false;
+
+  if (Platform.isIOS) {
+    // Enhanced iOS detection - check multiple conditions
+    bool condition1 = purchase.transactionStateIOS == TransactionState.purchased;
+    bool condition2 = purchase.purchaseToken != null && purchase.purchaseToken!.isNotEmpty;
+    bool condition3 = purchase.transactionId != null && purchase.transactionId!.isNotEmpty;
+
+    // For iOS, any valid token or transaction ID usually indicates success
+    isPurchased = condition1 || condition2 || condition3;
+    
+    print('iOS purchase state detection:');
+    print('  transactionStateIOS == purchased: $condition1');
+    print('  has valid purchaseToken: $condition2');
+    print('  has valid transactionId: $condition3');
+    print('  Final result: $isPurchased');
+  }
+
+  if (isPurchased) {
+    // Update UI immediately
+    setState(() {
+      _isProcessing = false;
+      _purchaseResult = '✅ Purchase successful';
+    });
+    
+    // Finish the transaction
+    await FlutterInappPurchase.instance.finishTransaction(purchase);
+  }
+}
+```
+
+**Timeout Error Handling**:
+
+```dart
+void _handlePurchaseError(PurchaseError error) {
+  if (error.message.contains('요청한 시간이 초과되었습니다') || 
+      error.message.contains('timeout')) {
+    // Apple server timeout - provide user guidance
+    setState(() {
+      _purchaseResult = '''
+⏱️ Request Timeout - Apple Server Issue
+
+Suggested actions:
+1. Check internet connection
+2. Wait 2-3 minutes and try again
+3. Restart the app
+4. Try on different network (WiFi/Cellular)
+5. Restart device if problem persists
+
+This is usually temporary.
+      ''';
+    });
+  }
+}
+```
+
+**See also**: [iOS Purchase State Detection Guide](./ios-purchase-state-detection.md)
+
 ### Platform-specific issues
 
 **iOS Specific:**
diff --git a/example/lib/src/screens/purchase_flow_screen.dart b/example/lib/src/screens/purchase_flow_screen.dart
index 81c8854d..35f17c61 100644
--- a/example/lib/src/screens/purchase_flow_screen.dart
+++ b/example/lib/src/screens/purchase_flow_screen.dart
@@ -3,6 +3,7 @@ import 'dart:io' show Platform;
 import 'package:flutter/material.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
+import '../widgets/product_detail_modal.dart';
 
 class PurchaseFlowScreen extends StatefulWidget {
   const PurchaseFlowScreen({Key? key}) : super(key: key);
@@ -21,6 +22,8 @@ class _PurchaseFlowScreenState extends State<PurchaseFlowScreen> {
   ];
 
   List<IAPItem> _products = [];
+  final Map<String, BaseProduct> _originalProducts =
+      {}; // Store original products for detail view
   bool _isProcessing = false;
   bool _connected = false;
   bool _loading = false;
@@ -106,6 +109,21 @@ class _PurchaseFlowScreenState extends State<PurchaseFlowScreen> {
   }
 
   Future<void> _handlePurchaseUpdate(Purchase purchase) async {
+    print('🎯 Purchase update received: ${purchase.productId}');
+    print('  Platform: ${purchase.platform}');
+    print('  Purchase state: ${purchase.purchaseState}');
+    print('  Purchase state Android: ${purchase.purchaseStateAndroid}');
+    print('  Transaction state iOS: ${purchase.transactionStateIOS}');
+    print('  Is acknowledged: ${purchase.isAcknowledgedAndroid}');
+    print('  Transaction ID: ${purchase.transactionId}');
+    print('  Purchase token: ${purchase.purchaseToken}');
+    print('  ID: ${purchase.id}');
+
+    if (!mounted) {
+      print('  ⚠️ Widget not mounted, ignoring update');
+      return;
+    }
+
     // Check if we've already processed this transaction
     final transactionId =
         purchase.id.isNotEmpty ? purchase.id : purchase.transactionId;
@@ -115,7 +133,61 @@ class _PurchaseFlowScreenState extends State<PurchaseFlowScreen> {
       return;
     }
 
-    debugPrint('Purchase successful: ${purchase.productId}');
+    // Determine if purchase is successful using same logic as subscription flow
+    bool isPurchased = false;
+
+    if (Platform.isAndroid) {
+      // For Android, check multiple conditions since fields can be null
+      bool condition1 = purchase.purchaseState == PurchaseState.purchased;
+      bool condition2 = (purchase.isAcknowledgedAndroid == false &&
+          purchase.purchaseToken != null &&
+          purchase.purchaseToken!.isNotEmpty);
+      bool condition3 = purchase.purchaseStateAndroid == 1;
+
+      print('  Android condition checks:');
+      print('    purchaseState == purchased: $condition1');
+      print('    unacknowledged with token: $condition2');
+      print('    purchaseStateAndroid == 1: $condition3');
+
+      isPurchased = condition1 || condition2 || condition3;
+      print('  Final isPurchased: $isPurchased');
+    } else {
+      // For iOS - same logic as subscription flow
+      bool condition1 =
+          purchase.transactionStateIOS == TransactionState.purchased;
+      bool condition2 =
+          purchase.purchaseToken != null && purchase.purchaseToken!.isNotEmpty;
+      bool condition3 =
+          purchase.transactionId != null && purchase.transactionId!.isNotEmpty;
+
+      print('  iOS condition checks:');
+      print('    transactionStateIOS == purchased: $condition1');
+      print('    has valid purchaseToken: $condition2');
+      print('    has valid transactionId: $condition3');
+
+      // For iOS, receiving a purchase update usually means success
+      isPurchased = condition1 || condition2 || condition3;
+      print('  Final isPurchased: $isPurchased');
+    }
+
+    if (!isPurchased) {
+      print('❓ Purchase not detected as successful');
+      setState(() {
+        _isProcessing = false;
+        _purchaseResult = '''
+⚠️ Purchase received but state unknown
+Platform: ${purchase.platform}
+Purchase state: ${purchase.purchaseState}
+iOS transaction state: ${purchase.transactionStateIOS}
+Android purchase state: ${purchase.purchaseStateAndroid}
+Has token: ${purchase.purchaseToken != null && purchase.purchaseToken!.isNotEmpty}
+        '''
+            .trim();
+      });
+      return;
+    }
+
+    debugPrint('✅ Purchase detected as successful: ${purchase.productId}');
     debugPrint('Purchase token: ${purchase.purchaseToken}');
     debugPrint('ID: ${purchase.id}'); // OpenIAP standard
     debugPrint('Transaction ID: ${purchase.transactionId}'); // Legacy field
@@ -178,6 +250,52 @@ Purchase Token: ${purchase.purchaseToken?.substring(0, 30)}...
       // Format error result like KMP-IAP
       if (error.code == ErrorCode.eUserCancelled) {
         _purchaseResult = '⚠️ Purchase cancelled by user';
+      } else if (error.message.contains('요청한 시간이 초과되었습니다') ||
+          error.message.contains('timeout') ||
+          error.message.contains('timed out')) {
+        // Apple/Google server timeout error
+        _purchaseResult = '''
+⏱️ Request Timeout
+Code: ${error.code}
+Message: ${error.message}
+
+🔄 Suggested Actions:
+1. Check your internet connection
+2. Wait a few minutes and try again
+3. Restart the app
+4. Try on a different network (WiFi/Cellular)
+5. Restart your device
+6. Check Apple/Google server status
+
+This is usually a temporary server issue.
+        '''
+            .trim();
+      } else if (error.message.contains('responseCode: 6')) {
+        // Server error - responseCode: 6 is BILLING_RESPONSE_RESULT_ERROR
+        _purchaseResult = '''
+❌ Google Play Server Error
+Code: ${error.code}
+Message: ${error.message}
+
+🔄 Suggested Actions:
+1. Wait a few minutes and try again
+2. Check your internet connection
+3. Clear Google Play Store cache
+4. Ensure Google Play Services is up to date
+5. Try testing with a different test account
+        '''
+            .trim();
+      } else if (error.message.contains('responseCode: 3')) {
+        // Service unavailable
+        _purchaseResult = '''
+❌ Service Unavailable
+Code: ${error.code}
+Message: ${error.message}
+
+The Google Play Store service is temporarily unavailable.
+Please try again in a few minutes.
+        '''
+            .trim();
       } else {
         _purchaseResult = '''
 ❌ Error: ${error.message}
@@ -193,32 +311,82 @@ Platform: ${error.platform}
     if (!_connected) return;
 
     try {
-      final products = await _iap.getProducts(productIds);
+      debugPrint('🔍 Loading products for IDs: ${productIds.join(", ")}');
+      // Use requestProducts instead of deprecated getProducts
+      final products = await _iap.requestProducts(
+        RequestProductsParams(
+          productIds: productIds,
+          type: PurchaseType.inapp,
+        ),
+      );
+
+      debugPrint(
+          '📦 Received ${products.length} products from requestProducts');
+
+      // Clear and store original products
+      _originalProducts.clear();
+
+      // Convert BaseProduct to IAPItem for compatibility
+      final items = products.map((product) {
+        // Store original product
+        _originalProducts[product.productId] = product;
+
+        // Cast to Product for more detailed info if available
+        if (product is Product) {
+          debugPrint('Product: ${product.productId} - ${product.title}');
+          debugPrint('  Price: ${product.price}');
+          debugPrint('  Currency: ${product.currency}');
+          debugPrint('  Localized Price: ${product.localizedPrice}');
+          debugPrint('  Description: ${product.description}');
+        }
+
+        return IAPItem.fromJSON({
+          'productId': product.productId,
+          'title': product.title,
+          'description': product.description,
+          'price': product.price,
+          'localizedPrice': product.localizedPrice,
+          'currency': product.currency,
+        });
+      }).toList();
+
       setState(() {
-        _products = products;
+        _products = items;
       });
     } catch (e) {
       debugPrint('Error loading products: $e');
     }
   }
 
-  Future<void> _handlePurchase(String productId) async {
-    debugPrint('🛒 Starting purchase for: $productId');
+  Future<void> _handlePurchase(String productId, {int retryCount = 0}) async {
+    debugPrint('🛒 Starting purchase for: $productId (retry: $retryCount)');
+    debugPrint('📱 Platform: ${Platform.operatingSystem}');
+    debugPrint('🔗 Connection status: $_connected');
+
     setState(() {
       _isProcessing = true;
+      _purchaseResult = null; // Clear previous results
     });
 
     try {
       debugPrint('Requesting purchase...');
-      await _iap.requestPurchase(
-        request: RequestPurchase(
-          ios: RequestPurchaseIOS(
-            sku: productId,
-          ),
-          android: RequestPurchaseAndroid(
-            skus: [productId],
-          ),
+      debugPrint('Product ID: $productId');
+
+      // Log the actual request being sent
+      final request = RequestPurchase(
+        ios: RequestPurchaseIOS(
+          sku: productId,
+        ),
+        android: RequestPurchaseAndroid(
+          skus: [productId],
         ),
+      );
+
+      debugPrint(
+          'Request details: Android SKUs: ${request.android?.skus}, iOS SKU: ${request.ios?.sku}');
+
+      await _iap.requestPurchase(
+        request: request,
         type: PurchaseType.inapp,
       );
       debugPrint('✅ Purchase request sent successfully');
@@ -229,13 +397,67 @@ Platform: ${error.platform}
       });
       debugPrint('❌ Purchase request error: $error');
 
+      // Check if it's a server error and we haven't exceeded retry limit
+      if (error.toString().contains('responseCode: 6') && retryCount < 2) {
+        // Show retry dialog for server errors
+        if (mounted) {
+          final shouldRetry = await showDialog<bool>(
+            context: context,
+            builder: (context) => AlertDialog(
+              title: const Text('Server Error'),
+              content: const Text(
+                'Google Play server error occurred.\n\n'
+                'This is usually temporary. Would you like to retry?',
+              ),
+              actions: [
+                TextButton(
+                  onPressed: () => Navigator.pop(context, false),
+                  child: const Text('Cancel'),
+                ),
+                TextButton(
+                  onPressed: () => Navigator.pop(context, true),
+                  child: const Text('Retry'),
+                ),
+              ],
+            ),
+          );
+
+          if (shouldRetry == true) {
+            // Wait a bit before retrying
+            await Future<void>.delayed(const Duration(seconds: 2));
+            await _handlePurchase(productId, retryCount: retryCount + 1);
+            return;
+          }
+        }
+      }
+
       // Show error to user
       if (mounted) {
         showDialog<void>(
           context: context,
           builder: (context) => AlertDialog(
             title: const Text('Purchase Failed'),
-            content: Text('Error: $error'),
+            content: SingleChildScrollView(
+              child: Column(
+                mainAxisSize: MainAxisSize.min,
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: [
+                  Text('Error: $error'),
+                  if (error.toString().contains('responseCode: 6')) ...[
+                    const SizedBox(height: 16),
+                    const Text(
+                      'This is a Google Play server error. Please try:',
+                      style: TextStyle(fontWeight: FontWeight.bold),
+                    ),
+                    const SizedBox(height: 8),
+                    const Text('• Wait a few minutes and try again'),
+                    const Text('• Clear Google Play Store cache'),
+                    const Text('• Check your internet connection'),
+                    const Text('• Restart your device'),
+                  ],
+                ],
+              ),
+            ),
             actions: [
               TextButton(
                 onPressed: () => Navigator.pop(context),
@@ -295,6 +517,115 @@ Platform: ${error.platform}
                 ),
                 const SizedBox(height: 16),
 
+                // Debug/Test Section
+                if (_connected) ...[
+                  Container(
+                    padding: const EdgeInsets.all(12),
+                    decoration: BoxDecoration(
+                      color: Colors.grey.shade50,
+                      borderRadius: BorderRadius.circular(8),
+                      border: Border.all(color: Colors.grey.shade300),
+                    ),
+                    child: Column(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: [
+                        const Text(
+                          'Debug Tools',
+                          style: TextStyle(
+                            fontSize: 14,
+                            fontWeight: FontWeight.bold,
+                          ),
+                        ),
+                        const SizedBox(height: 8),
+                        Wrap(
+                          spacing: 8,
+                          runSpacing: 8,
+                          children: [
+                            OutlinedButton.icon(
+                              onPressed: _isProcessing
+                                  ? null
+                                  : () async {
+                                      setState(() {
+                                        _isProcessing = true;
+                                        _purchaseResult = null;
+                                      });
+                                      try {
+                                        debugPrint(
+                                            'Checking available purchases...');
+                                        final purchases =
+                                            await _iap.getAvailablePurchases();
+                                        setState(() {
+                                          _purchaseResult = '''
+📊 Available Purchases: ${purchases.length}
+${purchases.map((p) => '- ${p.productId}: ${p.purchaseToken?.substring(0, 20)}...').join('\n')}
+                                          '''
+                                              .trim();
+                                          _isProcessing = false;
+                                        });
+                                      } catch (e) {
+                                        setState(() {
+                                          _purchaseResult =
+                                              '❌ Error checking purchases: $e';
+                                          _isProcessing = false;
+                                        });
+                                      }
+                                    },
+                              icon: const Icon(Icons.history, size: 16),
+                              label: const Text('Check Purchases'),
+                            ),
+                            OutlinedButton.icon(
+                              onPressed: _isProcessing
+                                  ? null
+                                  : () async {
+                                      setState(() {
+                                        _isProcessing = true;
+                                      });
+                                      await _loadProducts();
+                                      setState(() {
+                                        _isProcessing = false;
+                                      });
+                                    },
+                              icon: const Icon(Icons.refresh, size: 16),
+                              label: const Text('Reload Products'),
+                            ),
+                            OutlinedButton.icon(
+                              onPressed: _isProcessing
+                                  ? null
+                                  : () async {
+                                      setState(() {
+                                        _isProcessing = true;
+                                        _purchaseResult = null;
+                                      });
+                                      try {
+                                        // Re-initialize connection
+                                        await _iap.endConnection();
+                                        await Future<void>.delayed(
+                                            const Duration(seconds: 1));
+                                        await _initConnection();
+                                        setState(() {
+                                          _purchaseResult =
+                                              '✅ Connection reinitialized';
+                                          _isProcessing = false;
+                                        });
+                                      } catch (e) {
+                                        setState(() {
+                                          _purchaseResult = '❌ Error: $e';
+                                          _isProcessing = false;
+                                        });
+                                      }
+                                    },
+                              icon: const Icon(Icons.power_settings_new,
+                                  size: 16),
+                              label: const Text('Reinit Connection'),
+                            ),
+                          ],
+                        ),
+                      ],
+                    ),
+                  ),
+                  const SizedBox(height: 16),
+                ],
+
                 // Purchase Result Card (like KMP-IAP)
                 if (_purchaseResult != null) ...[
                   Card(
@@ -401,50 +732,66 @@ ${_currentPurchase!.purchaseToken}
                     ),
                   )
                 else
-                  ..._products.map((product) => Card(
-                        margin: const EdgeInsets.only(bottom: 12),
-                        child: Padding(
-                          padding: const EdgeInsets.all(16),
-                          child: Column(
-                            crossAxisAlignment: CrossAxisAlignment.start,
-                            children: [
-                              Text(
-                                product.title ?? product.productId ?? '',
-                                style: const TextStyle(
-                                  fontSize: 18,
-                                  fontWeight: FontWeight.bold,
+                  ..._products.map((product) => GestureDetector(
+                        onTap: () => ProductDetailModal.show(
+                          context: context,
+                          item: product,
+                          product: _originalProducts[product.productId],
+                        ),
+                        child: Card(
+                          margin: const EdgeInsets.only(bottom: 12),
+                          elevation: 0,
+                          shape: RoundedRectangleBorder(
+                            borderRadius: BorderRadius.circular(12),
+                            side: BorderSide(color: Colors.grey.shade200),
+                          ),
+                          child: Padding(
+                            padding: const EdgeInsets.all(16),
+                            child: Column(
+                              crossAxisAlignment: CrossAxisAlignment.start,
+                              children: [
+                                Text(
+                                  product.title ?? product.productId ?? '',
+                                  style: const TextStyle(
+                                    fontSize: 18,
+                                    fontWeight: FontWeight.bold,
+                                  ),
                                 ),
-                              ),
-                              const SizedBox(height: 8),
-                              Text(
-                                product.description ?? '',
-                                style: TextStyle(color: Colors.grey[600]),
-                              ),
-                              const SizedBox(height: 16),
-                              Row(
-                                mainAxisAlignment:
-                                    MainAxisAlignment.spaceBetween,
-                                children: [
-                                  Text(
-                                    product.localizedPrice ?? '',
-                                    style: const TextStyle(
-                                      fontSize: 20,
-                                      fontWeight: FontWeight.bold,
-                                      color: Color(0xFF007AFF),
+                                const SizedBox(height: 8),
+                                Text(
+                                  product.description ?? '',
+                                  style: TextStyle(color: Colors.grey[600]),
+                                ),
+                                const SizedBox(height: 16),
+                                Row(
+                                  mainAxisAlignment:
+                                      MainAxisAlignment.spaceBetween,
+                                  children: [
+                                    Text(
+                                      product.localizedPrice ?? '',
+                                      style: const TextStyle(
+                                        fontSize: 20,
+                                        fontWeight: FontWeight.bold,
+                                        color: Color(0xFF007AFF),
+                                      ),
                                     ),
-                                  ),
-                                  ElevatedButton(
-                                    onPressed: _isProcessing || !_connected
-                                        ? null
-                                        : () =>
-                                            _handlePurchase(product.productId!),
-                                    child: Text(_isProcessing
-                                        ? 'Processing...'
-                                        : 'Buy'),
-                                  ),
-                                ],
-                              ),
-                            ],
+                                    ElevatedButton(
+                                      onPressed: _isProcessing || !_connected
+                                          ? null
+                                          : () => _handlePurchase(
+                                              product.productId!),
+                                      style: ElevatedButton.styleFrom(
+                                        backgroundColor: Colors.blue,
+                                        foregroundColor: Colors.white,
+                                      ),
+                                      child: Text(_isProcessing
+                                          ? 'Processing...'
+                                          : 'Buy'),
+                                    ),
+                                  ],
+                                ),
+                              ],
+                            ),
                           ),
                         ),
                       )),
diff --git a/example/lib/src/screens/subscription_flow_screen.dart b/example/lib/src/screens/subscription_flow_screen.dart
index de98fa43..4752a25f 100644
--- a/example/lib/src/screens/subscription_flow_screen.dart
+++ b/example/lib/src/screens/subscription_flow_screen.dart
@@ -3,6 +3,7 @@ import 'dart:io' show Platform;
 import 'package:flutter/material.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
+import '../widgets/product_detail_modal.dart';
 
 class SubscriptionFlowScreen extends StatefulWidget {
   const SubscriptionFlowScreen({Key? key}) : super(key: key);
@@ -14,27 +15,47 @@ class SubscriptionFlowScreen extends StatefulWidget {
 class _SubscriptionFlowScreenState extends State<SubscriptionFlowScreen> {
   final FlutterInappPurchase _iap = FlutterInappPurchase.instance;
 
-  // Subscription IDs
+  // Multiple subscription tiers for testing upgrades/downgrades
+  // Replace these with your actual subscription IDs
   final List<String> subscriptionIds = [
-    'dev.hyo.martie.premium',
+    'dev.hyo.martie.basic', // Basic tier
+    'dev.hyo.martie.premium', // Premium tier
+    'dev.hyo.martie.pro', // Pro tier
   ];
 
   List<IAPItem> _subscriptions = [];
+  final Map<String, BaseProduct> _originalProducts = {};
   List<Purchase> _activeSubscriptions = [];
-  List<ActiveSubscription> _activeSubscriptionDetails = [];
+  Purchase? _currentSubscription;
   bool _hasActiveSubscription = false;
   bool _isProcessing = false;
   bool _connected = false;
   bool _isConnecting = true;
   bool _isLoadingProducts = false;
   String? _purchaseResult;
+
+  // Stream subscriptions
   StreamSubscription<Purchase>? _purchaseUpdatedSubscription;
   StreamSubscription<PurchaseError>? _purchaseErrorSubscription;
 
+  // Track processed transactions to avoid duplicates
+  final Set<String> _processedTransactionIds = {};
+
+  // Proration mode selection
+  int? _selectedProrationMode;
+  final Map<String, int> _prorationModes = {
+    'Immediate with Time Proration': 1,
+    'Immediate and Charge Prorated Price': 2,
+    'Immediate without Proration': 3,
+    'Deferred': 4,
+    'Immediate and Charge Full Price': 5,
+  };
+
   @override
   void initState() {
     super.initState();
     _initConnection();
+    _setupListeners();
   }
 
   @override
@@ -45,487 +66,746 @@ class _SubscriptionFlowScreenState extends State<SubscriptionFlowScreen> {
     super.dispose();
   }
 
-  Future<void> _initConnection() async {
-    if (mounted) {
-      setState(() {
-        _isConnecting = true;
-      });
-    }
+  void _setupListeners() {
+    // Listen to purchase updates
+    _purchaseUpdatedSubscription = _iap.purchaseUpdatedListener.listen(
+      (purchase) async {
+        print('🎯 Purchase updated: ${purchase.productId}');
+        print('  Platform: ${purchase.platform}');
+        print('  Purchase state: ${purchase.purchaseState}');
+        print('  Purchase state Android: ${purchase.purchaseStateAndroid}');
+        print('  Transaction state iOS: ${purchase.transactionStateIOS}');
+        print('  Is acknowledged: ${purchase.isAcknowledgedAndroid}');
+        print('  Transaction ID: ${purchase.transactionId}');
+        print('  Purchase token: ${purchase.purchaseToken}');
+        print('  Auto renewing: ${purchase.autoRenewingAndroid}');
+
+        if (!mounted) {
+          print('  ⚠️ Widget not mounted, ignoring update');
+          return;
+        }
 
-    try {
-      // Step 1: Initialize connection
-      final connectionResult = await _iap.initConnection();
-      if (mounted) {
-        setState(() {
-          _connected = connectionResult == true;
-        });
-      }
+        // Check for duplicate processing
+        final transactionId =
+            purchase.transactionId ?? purchase.purchaseToken ?? '';
+        if (transactionId.isNotEmpty &&
+            _processedTransactionIds.contains(transactionId)) {
+          print('  ⚠️ Transaction already processed: $transactionId');
+          return;
+        }
 
-      if (!_connected) {
-        debugPrint('Failed to connect to store');
-        return;
-      }
+        // Handle the purchase - check multiple conditions
+        // purchaseState.purchased or purchaseStateAndroid == 1 or isAcknowledgedAndroid == false (new purchase)
+        bool isPurchased = false;
+
+        if (Platform.isAndroid) {
+          // For Android, check multiple conditions since fields can be null
+          bool condition1 = purchase.purchaseState == PurchaseState.purchased;
+          bool condition2 = (purchase.isAcknowledgedAndroid == false &&
+              purchase.purchaseToken != null &&
+              purchase.purchaseToken!.isNotEmpty);
+          bool condition3 = purchase.purchaseStateAndroid == 1;
+
+          print('  Android condition checks:');
+          print('    purchaseState == purchased: $condition1');
+          print('    unacknowledged with token: $condition2');
+          print('    purchaseStateAndroid == 1: $condition3');
+
+          isPurchased = condition1 || condition2 || condition3;
+          print('  Final isPurchased: $isPurchased');
+        } else {
+          // For iOS - simpler logic like purchase_flow_screen.dart
+          // iOS purchase updates with valid tokens indicate successful purchases
+          bool condition1 =
+              purchase.transactionStateIOS == TransactionState.purchased;
+          bool condition2 = purchase.purchaseToken != null &&
+              purchase.purchaseToken!.isNotEmpty;
+          bool condition3 = purchase.transactionId != null &&
+              purchase.transactionId!.isNotEmpty;
+
+          print('  iOS condition checks:');
+          print('    transactionStateIOS == purchased: $condition1');
+          print('    has valid purchaseToken: $condition2');
+          print('    has valid transactionId: $condition3');
+
+          // For iOS, receiving a purchase update usually means success
+          // especially if we have either a valid token OR transaction ID
+          isPurchased = condition1 || condition2 || condition3;
+          print('  Final isPurchased: $isPurchased');
+        }
 
-      // Step 2: Connection successful, setup listeners and load products
-      _setupPurchaseListeners();
+        if (isPurchased) {
+          print('✅ Purchase detected as successful, updating UI...');
+          print('  _isProcessing before setState: $_isProcessing');
 
-      if (mounted) {
-        setState(() {
-          _isConnecting = false;
-          _isLoadingProducts = true;
-        });
-      }
+          // Mark as processed
+          if (transactionId.isNotEmpty) {
+            _processedTransactionIds.add(transactionId);
+          }
 
-      // Load active purchases and subscription products in parallel
-      await Future.wait([
-        _loadActiveSubscriptions(),
-        _loadSubscriptions(),
-      ]);
-    } catch (e) {
-      debugPrint('Failed to initialize IAP connection: $e');
-      if (mounted) {
-        setState(() {
-          _purchaseResult = 'Initialization error: $e';
-          _connected = false;
-        });
-      }
-    } finally {
-      if (mounted) {
-        setState(() {
-          _isConnecting = false;
-          _isLoadingProducts = false;
-        });
-      }
-    }
-  }
+          // Update UI immediately
+          if (mounted) {
+            setState(() {
+              _purchaseResult = '✅ Purchase successful: ${purchase.productId}';
+              _isProcessing = false;
+            });
+            print('  _isProcessing after setState: $_isProcessing');
+            print('  UI should be updated now');
+          } else {
+            print('  ⚠️ Widget not mounted, cannot update UI');
+          }
 
-  void _setupPurchaseListeners() {
-    debugPrint('Setting up subscription purchase listeners...');
+          // Acknowledge/finish the transaction
+          try {
+            print('Calling finishTransaction...');
+            await _iap.finishTransaction(purchase);
+            print('Transaction finished successfully');
+          } catch (e) {
+            print('Error finishing transaction: $e');
+          }
 
-    // Listen to purchase updates
-    _purchaseUpdatedSubscription = _iap.purchaseUpdatedListener.listen(
-      (purchase) {
-        debugPrint('🎉 Subscription update received!');
-        debugPrint('ProductId: ${purchase.productId}');
-        debugPrint('TransactionId: ${purchase.transactionId}');
-        _handlePurchaseUpdate(purchase);
+          // Refresh subscriptions after a short delay to ensure transaction is processed
+          await Future<void>.delayed(const Duration(milliseconds: 500));
+          print('Refreshing subscriptions...');
+          await _checkActiveSubscriptions();
+          print('Subscriptions refreshed');
+        } else if (purchase.purchaseState == PurchaseState.pending ||
+            purchase.purchaseStateAndroid == 0) {
+          // Pending
+          setState(() {
+            _purchaseResult = '⏳ Purchase pending: ${purchase.productId}';
+          });
+        } else {
+          // Unknown state - log for debugging
+          print('❓ Unknown purchase state');
+          print('  Purchase state: ${purchase.purchaseState}');
+          print('  Transaction state iOS: ${purchase.transactionStateIOS}');
+          print('  Purchase state Android: ${purchase.purchaseStateAndroid}');
+          print(
+              '  Has token: ${purchase.purchaseToken != null && purchase.purchaseToken!.isNotEmpty}');
+
+          setState(() {
+            _isProcessing = false;
+            _purchaseResult = '''
+⚠️ Purchase received but state unknown
+Platform: ${purchase.platform}
+Purchase state: ${purchase.purchaseState}
+iOS transaction state: ${purchase.transactionStateIOS}
+Android purchase state: ${purchase.purchaseStateAndroid}
+Has token: ${purchase.purchaseToken != null && purchase.purchaseToken!.isNotEmpty}
+            '''
+                .trim();
+          });
+        }
       },
       onError: (Object error) {
-        debugPrint('❌ Subscription stream error: $error');
+        print('Purchase stream error: $error');
+        setState(() {
+          _isProcessing = false;
+          _purchaseResult = '❌ Stream error: $error';
+        });
       },
     );
 
     // Listen to purchase errors
     _purchaseErrorSubscription = _iap.purchaseErrorListener.listen(
-      (purchaseError) {
-        debugPrint('❌ Subscription error received!');
-        debugPrint('Error code: ${purchaseError.code}');
-        debugPrint('Error message: ${purchaseError.message}');
-        _handlePurchaseError(purchaseError);
+      (error) {
+        print('Purchase error: ${error.code} - ${error.message}');
+
+        if (!mounted) return;
+
+        setState(() {
+          _isProcessing = false;
+          if (error.code == ErrorCode.eUserCancelled) {
+            _purchaseResult = '⚠️ Purchase cancelled';
+          } else {
+            _purchaseResult = '❌ Error: ${error.message}';
+          }
+        });
+      },
+      onError: (Object error) {
+        print('Error stream error: $error');
       },
     );
-
-    debugPrint('Subscription listeners setup complete');
   }
 
-  Future<void> _handlePurchaseUpdate(Purchase purchase) async {
-    debugPrint('Subscription successful: ${purchase.productId}');
-
-    // Debug purchase object structure
-    debugPrint('🔍 Purchase object debug:');
-    debugPrint('  productId: ${purchase.productId}');
-    debugPrint('  transactionId: ${purchase.transactionId}');
-    debugPrint('  purchaseToken: ${purchase.purchaseToken}');
-    debugPrint(
-        '  transactionReceipt: ${purchase.transactionReceipt?.substring(0, 50)}...');
-    debugPrint('  transactionDate: ${purchase.transactionDate}');
-    debugPrint('  isAcknowledgedAndroid: ${purchase.isAcknowledgedAndroid}');
-
-    if (!mounted) return;
+  Future<void> _initConnection() async {
+    try {
+      final result = await _iap.initConnection();
+      print('Connection initialized: $result');
 
-    setState(() {
-      _isProcessing = false;
-
-      // Format subscription result like KMP-IAP
-      _purchaseResult = '''
-✅ Subscription successful (${Platform.operatingSystem})
-Product: ${purchase.productId}
-Transaction ID: ${purchase.transactionId ?? "N/A"}
-Date: ${purchase.transactionDate ?? "N/A"}
-Receipt: ${purchase.transactionReceipt?.substring(0, purchase.transactionReceipt!.length > 50 ? 50 : purchase.transactionReceipt!.length)}...
-      '''
-          .trim();
-    });
+      if (!mounted) return;
 
-    // IMPORTANT: Server-side receipt validation should be performed here
-    // Send the receipt to your backend server for validation
-    // Example:
-    // final isValid = await validateReceiptOnServer(purchase.transactionReceipt);
-    // if (!isValid) {
-    //   setState(() {
-    //     _purchaseResult = '❌ Receipt validation failed';
-    //   });
-    //   return;
-    // }
-
-    // After successful server validation, finish the transaction
-    // For subscriptions, set isConsumable to false
-
-    // Skip transaction finishing for invalid transaction IDs
-    // Real products should still try, but handle errors gracefully
-    bool shouldSkipFinish = purchase.transactionId == null ||
-        purchase.transactionId!.isEmpty ||
-        // Skip obvious test transaction IDs
-        purchase.transactionId == '3' ||
-        // Skip very short transaction IDs that are clearly invalid
-        purchase.transactionId!.length < 3;
-
-    if (shouldSkipFinish) {
-      debugPrint('🔧 Skipping transaction finish in development environment');
-      debugPrint('Transaction ID: ${purchase.transactionId}');
-
-      // Still reload active subscriptions and update UI
-      await _loadActiveSubscriptions();
-
-      if (mounted) {
-        setState(() {
-          _purchaseResult =
-              '$_purchaseResult\n\n✅ Purchase successful (development mode - transaction finish skipped)';
+      setState(() {
+        _connected = result;
+        _isConnecting = false;
+      });
 
-          // Update active subscriptions list
-          if (subscriptionIds.contains(purchase.productId)) {
-            if (!_activeSubscriptions
-                .any((p) => p.productId == purchase.productId)) {
-              _activeSubscriptions.add(purchase);
-            }
-          }
-        });
+      if (_connected) {
+        await _loadSubscriptions();
+        await _checkActiveSubscriptions();
       }
-      return;
+    } catch (error) {
+      print('Failed to initialize connection: $error');
+      if (!mounted) return;
+      setState(() {
+        _connected = false;
+        _isConnecting = false;
+      });
     }
+  }
+
+  Future<void> _loadSubscriptions() async {
+    if (!_connected) return;
+
+    setState(() => _isLoadingProducts = true);
 
     try {
-      debugPrint(
-          'Attempting to finish transaction with ID: ${purchase.transactionId}');
-      debugPrint('Purchase token: ${purchase.purchaseToken}');
+      // Use the new requestProducts method
+      final params = RequestProductsParams(
+        productIds: subscriptionIds,
+        type: PurchaseType.subs,
+      );
 
-      await _iap.finishTransaction(purchase, isConsumable: false);
-      debugPrint('Subscription transaction finished successfully');
+      final products = await _iap.requestProducts(params);
 
-      // Reload active subscriptions with new API
-      await _loadActiveSubscriptions();
+      print('Loaded ${products.length} subscriptions');
 
-      if (mounted) {
-        setState(() {
-          _purchaseResult =
-              '$_purchaseResult\n\n✅ Transaction finished successfully';
+      if (!mounted) return;
 
-          // Update active subscriptions list
-          if (subscriptionIds.contains(purchase.productId)) {
-            if (!_activeSubscriptions
-                .any((p) => p.productId == purchase.productId)) {
-              _activeSubscriptions.add(purchase);
+      setState(() {
+        _subscriptions = products.map((baseProduct) {
+          // Store the original BaseProduct for detailed view
+          _originalProducts[baseProduct.productId] = baseProduct;
+
+          // Convert BaseProduct to IAPItem for compatibility
+          final itemMap = <String, dynamic>{
+            'productId': baseProduct.productId,
+            'price': baseProduct.price,
+            'currency': baseProduct.currency,
+            'localizedPrice': baseProduct.localizedPrice,
+            'title': baseProduct.title,
+            'description': baseProduct.description,
+          };
+
+          // Add subscription-specific fields if available
+          if (baseProduct is Subscription) {
+            itemMap['subscriptionPeriodUnitIOS'] =
+                baseProduct.subscriptionPeriodUnitIOS;
+            itemMap['subscriptionPeriodNumberIOS'] =
+                baseProduct.subscriptionPeriodNumberIOS;
+            itemMap['introductoryPricePaymentModeIOS'] =
+                baseProduct.introductoryPricePaymentModeIOS;
+            itemMap['subscriptionGroupIdIOS'] =
+                baseProduct.subscriptionGroupIdIOS;
+            if (baseProduct.discountsIOS != null) {
+              itemMap['discountsIOS'] = baseProduct.discountsIOS!
+                  .map((d) => {
+                        'identifier': d.identifier,
+                        'type': d.type,
+                        'price': d.price,
+                        'localizedPrice': d.localizedPrice,
+                        'numberOfPeriods': d.numberOfPeriods,
+                        'subscriptionPeriod': d.subscriptionPeriod,
+                        'paymentMode': d.paymentMode,
+                      })
+                  .toList();
             }
           }
-        });
+
+          return IAPItem.fromJSON(itemMap);
+        }).toList();
+        _isLoadingProducts = false;
+      });
+    } catch (error) {
+      print('Failed to load subscriptions: $error');
+      if (!mounted) return;
+      setState(() {
+        _isLoadingProducts = false;
+        _purchaseResult = '❌ Failed to load subscriptions: $error';
+      });
+    }
+  }
+
+  Future<void> _checkActiveSubscriptions() async {
+    if (!_connected) return;
+
+    try {
+      // Get all available purchases
+      final purchases = await _iap.getAvailablePurchases();
+
+      print('=== Checking Active Subscriptions ===');
+      print('Total purchases found: ${purchases.length}');
+      for (var p in purchases) {
+        print(
+            '  - ${p.productId}: token=${p.purchaseToken?.substring(0, 20)}...');
       }
-    } catch (e) {
-      debugPrint('Error finishing subscription transaction: $e');
 
-      // For development/testing, transaction finish errors are often not critical
-      // The purchase is still valid, just the cleanup failed
-      if (e.toString().contains('Transaction not found') ||
-          e.toString().contains('E_TRANSACTION_NOT_FOUND')) {
-        debugPrint(
-            '⚠️ Transaction finish failed (common in development), but purchase is valid');
+      // Filter for subscriptions
+      final activeSubs = purchases
+          .where((p) => subscriptionIds.contains(p.productId))
+          .toList();
 
-        // Still reload active subscriptions and update UI
-        await _loadActiveSubscriptions();
+      if (!mounted) return;
 
-        if (mounted) {
-          setState(() {
-            _purchaseResult =
-                '$_purchaseResult\n\n⚠️ Purchase successful but transaction cleanup failed (common in development)';
-
-            // Update active subscriptions list
-            if (subscriptionIds.contains(purchase.productId)) {
-              if (!_activeSubscriptions
-                  .any((p) => p.productId == purchase.productId)) {
-                _activeSubscriptions.add(purchase);
-              }
-            }
-          });
-        }
-      } else {
-        if (mounted) {
-          setState(() {
-            _purchaseResult =
-                '$_purchaseResult\n\n❌ Failed to finish transaction: $e';
-          });
+      setState(() {
+        _activeSubscriptions = activeSubs;
+        _hasActiveSubscription = activeSubs.isNotEmpty;
+        _currentSubscription = activeSubs.isNotEmpty ? activeSubs.first : null;
+
+        if (_currentSubscription != null) {
+          print('Current subscription: ${_currentSubscription!.productId}');
+          print('Purchase token: ${_currentSubscription!.purchaseToken}');
+          _purchaseResult =
+              'Active: ${_currentSubscription!.productId}\nToken: ${_currentSubscription!.purchaseToken?.substring(0, 30)}...';
+        } else {
+          print('No active subscription found in filtered list');
         }
-      }
+      });
+    } catch (error) {
+      print('Failed to check active subscriptions: $error');
+      setState(() {
+        _purchaseResult = '❌ Error checking subscriptions: $error';
+      });
     }
   }
 
-  void _handlePurchaseError(PurchaseError error) {
-    if (!mounted) return;
+  Future<void> _purchaseSubscription(IAPItem item,
+      {bool isUpgrade = false}) async {
+    if (_isProcessing) {
+      print('⚠️ Already processing a purchase, ignoring');
+      return;
+    }
 
-    setState(() {
-      _isProcessing = false;
+    print('🛒 Starting subscription purchase: ${item.productId}');
+    print('  isUpgrade: $isUpgrade');
+    print('  Current subscription: ${_currentSubscription?.productId}');
 
-      // Format error result like KMP-IAP
-      if (error.code == ErrorCode.eUserCancelled) {
-        _purchaseResult = '⚠️ Subscription cancelled by user';
-      } else {
-        _purchaseResult = '''
-❌ Error: ${error.message}
-Code: ${error.code}
-Platform: ${error.platform}
-        '''
-            .trim();
-      }
+    setState(() {
+      _isProcessing = true;
+      _purchaseResult = null;
     });
-  }
+    print('  Set _isProcessing to true');
 
-  Future<void> _loadActiveSubscriptions() async {
     try {
-      // Load purchases for restore/finish transaction purposes
-      final purchases = await _iap.getAvailablePurchases();
+      // Check for Android offers
+      SubscriptionOfferAndroid? selectedOffer;
+      if (Platform.isAndroid && item.subscriptionOffersAndroid != null) {
+        final offers = item.subscriptionOffersAndroid!;
+        if (offers.isNotEmpty) {
+          selectedOffer = offers.first;
+          print('Using offer token: ${selectedOffer.offerToken}');
+        }
+      }
 
-      // Use new APIs to get detailed subscription information
-      final activeSubscriptions = await _iap.getActiveSubscriptions(
-        subscriptionIds: subscriptionIds,
-      );
-      final hasActive = await _iap.hasActiveSubscriptions(
-        subscriptionIds: subscriptionIds,
-      );
+      // Request subscription using the new API
+      if (Platform.isAndroid) {
+        // Check if this is an upgrade/downgrade
+        if (isUpgrade &&
+            _currentSubscription != null &&
+            _selectedProrationMode != null) {
+          // This is an upgrade/downgrade with proration
+          print(
+              'Upgrading subscription with proration mode: $_selectedProrationMode');
+          print('Using purchase token: ${_currentSubscription!.purchaseToken}');
+
+          final request = RequestPurchase(
+            android: RequestSubscriptionAndroid(
+              skus: [item.productId!],
+              subscriptionOffers: selectedOffer != null ? [selectedOffer] : [],
+              purchaseTokenAndroid: _currentSubscription!.purchaseToken,
+              replacementModeAndroid: _selectedProrationMode,
+            ),
+          );
 
-      if (mounted) {
-        setState(() {
-          _activeSubscriptions = purchases
-              .where((p) => subscriptionIds.contains(p.productId))
-              .toList();
-          _activeSubscriptionDetails = activeSubscriptions;
-          _hasActiveSubscription = hasActive;
-        });
-      }
+          await _iap.requestPurchase(
+            request: request,
+            type: PurchaseType.subs,
+          );
+        } else {
+          // This is a new subscription purchase
+          print('Purchasing new subscription');
 
-      debugPrint('Loaded ${_activeSubscriptions.length} active subscriptions');
-      debugPrint('Has active subscription: $_hasActiveSubscription');
+          final request = RequestPurchase(
+            android: RequestSubscriptionAndroid(
+              skus: [item.productId!],
+              subscriptionOffers: selectedOffer != null ? [selectedOffer] : [],
+            ),
+          );
 
-      // Log detailed subscription info
-      for (final sub in _activeSubscriptionDetails) {
-        debugPrint('Active subscription: ${sub.productId}');
-        if (Platform.isIOS && sub.expirationDateIOS != null) {
-          debugPrint('  Expires: ${sub.expirationDateIOS}');
-          debugPrint('  Days until expiration: ${sub.daysUntilExpirationIOS}');
-          debugPrint('  Environment: ${sub.environmentIOS}');
-        }
-        if (Platform.isAndroid && sub.autoRenewingAndroid != null) {
-          debugPrint('  Auto-renewing: ${sub.autoRenewingAndroid}');
-        }
-        if (sub.willExpireSoon == true) {
-          debugPrint('  ⚠️ Will expire soon!');
+          await _iap.requestPurchase(
+            request: request,
+            type: PurchaseType.subs,
+          );
         }
+      } else {
+        // iOS
+        final request = RequestPurchase(
+          ios: RequestPurchaseIOS(
+            sku: item.productId!,
+          ),
+        );
+
+        await _iap.requestPurchase(
+          request: request,
+          type: PurchaseType.subs,
+        );
       }
-    } catch (e) {
-      debugPrint('Failed to load active subscriptions: $e');
+
+      // Result will be handled by the purchase stream listeners
+      print('Purchase request sent, waiting for response...');
+    } catch (error) {
+      print('Failed to request subscription: $error');
+      setState(() {
+        _isProcessing = false;
+        _purchaseResult = '❌ Failed to request: $error';
+      });
     }
   }
 
-  Future<void> _loadSubscriptions() async {
-    if (!_connected) return;
+  // Test with fake/invalid token (should fail on native side)
+  Future<void> _testWrongProrationUsage(IAPItem item) async {
+    if (_isProcessing) return;
+
+    setState(() {
+      _isProcessing = true;
+      _purchaseResult = null;
+    });
 
     try {
-      debugPrint(
-          'Loading subscriptions for IDs: ${subscriptionIds.join(", ")}');
-      // Use requestProducts instead of deprecated getSubscriptions
-      final products = await _iap.requestProducts(
-        RequestProductsParams(
-          productIds: subscriptionIds,
-          type: PurchaseType.subs,
+      print(
+          'Testing proration mode with FAKE purchaseToken (should fail on native side)');
+
+      // Use a fake/invalid token to test native validation
+      final fakeToken =
+          'fake_token_for_testing_${DateTime.now().millisecondsSinceEpoch}';
+      print('Using fake token: $fakeToken');
+
+      final request = RequestPurchase(
+        android: RequestSubscriptionAndroid(
+          skus: [item.productId!],
+          subscriptionOffers: item.subscriptionOffersAndroid ?? [],
+          purchaseTokenAndroid:
+              fakeToken, // Fake token that will fail on native side
+          replacementModeAndroid: AndroidProrationMode.deferred.value,
         ),
       );
 
-      // Convert BaseProduct to IAPItem for compatibility
-      final subscriptions = products.map((product) {
-        // Cast to Product or Subscription to access extended fields
-        if (product is Subscription) {
-          return IAPItem.fromJSON({
-            'productId': product.productId,
-            'title': product.title,
-            'description': product.description,
-            'price': product.price,
-            'localizedPrice': product.localizedPrice,
-            'currency': product.currency,
-            // iOS specific
-            'discountsIOS': product.discountsIOS,
-            'subscriptionGroupIdentifierIOS': product.subscriptionGroupIdIOS,
-            'isFamilyShareableIOS': product.isFamilyShareable,
-            'jsonRepresentationIOS': product.jsonRepresentation,
-            // Android specific
-            'subscriptionOffersAndroid': product.subscriptionOffersAndroid,
-            'subscriptionPeriodAndroid': product.subscriptionPeriodAndroid,
-          });
-        } else {
-          // Fallback for basic product
-          return IAPItem.fromJSON({
-            'productId': product.productId,
-            'title': product.title,
-            'description': product.description,
-            'price': product.price,
-            'localizedPrice': product.localizedPrice,
-            'currency': product.currency,
-          });
-        }
-      }).toList();
-
-      if (mounted) {
-        setState(() {
-          _subscriptions = subscriptions;
-        });
-      }
-
-      if (_subscriptions.isEmpty) {
-        if (mounted) {
-          setState(() {
-            _purchaseResult =
-                'No subscriptions found for IDs: ${subscriptionIds.join(", ")}';
-          });
-        }
-      } else {
-        debugPrint('Loaded ${_subscriptions.length} subscription products');
-      }
-    } catch (e) {
-      debugPrint('Error loading subscriptions: $e');
-      if (mounted) {
-        setState(() {
-          _purchaseResult = 'Failed to load subscriptions: $e';
-        });
-      }
-    }
-  }
+      await _iap.requestPurchase(
+        request: request,
+        type: PurchaseType.subs,
+      );
 
-  Future<void> _refreshSubscriptions() async {
-    if (mounted) {
+      // If we get here, the purchase was attempted
+      print('Purchase request sent with fake token');
+      // Result will come through purchaseUpdatedListener
+    } catch (error) {
+      print('Error with fake token: $error');
       setState(() {
-        _isLoadingProducts = true;
+        _isProcessing = false;
+        _purchaseResult = '❌ Error with fake token:\n$error';
       });
     }
+  }
+
+  // Test with empty purchaseToken (Issue #529)
+  Future<void> _testEmptyTokenProration(IAPItem item) async {
+    if (_isProcessing) return;
+
+    setState(() {
+      _isProcessing = true;
+      _purchaseResult = null;
+    });
 
     try {
-      await Future.wait([
-        _loadActiveSubscriptions(),
-        _loadSubscriptions(),
-      ]);
-    } catch (e) {
-      debugPrint('Failed to refresh subscriptions: $e');
-    } finally {
-      if (mounted) {
-        setState(() {
-          _isLoadingProducts = false;
-        });
-      }
-    }
-  }
+      print('Testing proration mode with EMPTY string purchaseToken');
+
+      // Use current subscription token if available, otherwise use a test token
+      final testToken = _currentSubscription?.purchaseToken ??
+          'test_empty_token_${DateTime.now().millisecondsSinceEpoch}';
+      print('Using test token: ${testToken.substring(0, 20)}...');
+
+      // Test with empty string - but pass validation by using a non-empty token
+      final request = RequestPurchase(
+        android: RequestSubscriptionAndroid(
+          skus: [item.productId!],
+          subscriptionOffers: item.subscriptionOffersAndroid ?? [],
+          purchaseTokenAndroid: testToken, // Use test token to pass validation
+          replacementModeAndroid: AndroidProrationMode.deferred.value,
+        ),
+      );
 
-  Future<void> _restorePurchases() async {
-    if (mounted) {
+      await _iap.requestPurchase(
+        request: request,
+        type: PurchaseType.subs,
+      );
+
+      print('Purchase request sent with test token');
+      // Result will come through purchaseUpdatedListener
+    } catch (error) {
+      print('Error with test token: $error');
       setState(() {
-        _isProcessing = true;
-        _purchaseResult = null;
+        _isProcessing = false;
+        _purchaseResult = '❌ Error with test token:\n$error';
       });
     }
+  }
 
-    try {
-      debugPrint('🔄 Restoring purchases...');
+  Future<void> _restorePurchases() async {
+    setState(() {
+      _isProcessing = true;
+      _purchaseResult = null;
+    });
 
-      // Get all available purchases (restored)
-      final restoredPurchases = await _iap.getAvailablePurchases();
+    try {
+      final purchases = await _iap.getAvailablePurchases();
+      print('Restored ${purchases.length} purchases');
 
-      // Load active subscriptions with new API
-      await _loadActiveSubscriptions();
+      if (!mounted) return;
 
-      if (_activeSubscriptionDetails.isNotEmpty) {
-        if (mounted) {
-          setState(() {
-            _purchaseResult = '''
-✅ Purchases restored successfully!
-Found ${_activeSubscriptionDetails.length} active subscription(s):
-${_activeSubscriptionDetails.map((sub) => '• ${sub.productId}').join('\n')}
-            '''
-                .trim();
-          });
-        }
-      } else if (restoredPurchases.isNotEmpty) {
-        if (mounted) {
-          setState(() {
-            _purchaseResult = '''
-✅ Restored ${restoredPurchases.length} purchase(s)
-${restoredPurchases.map((p) => '• ${p.productId}').join('\n')}
-            '''
-                .trim();
-          });
-        }
-      } else {
-        if (mounted) {
-          setState(() {
-            _purchaseResult = 'No purchases to restore';
-          });
-        }
-      }
+      setState(() {
+        _activeSubscriptions = purchases
+            .where((p) => subscriptionIds.contains(p.productId))
+            .toList();
+        _hasActiveSubscription = _activeSubscriptions.isNotEmpty;
+        _currentSubscription =
+            _activeSubscriptions.isNotEmpty ? _activeSubscriptions.first : null;
+        _isProcessing = false;
+        _purchaseResult =
+            '✅ Restored ${_activeSubscriptions.length} subscriptions';
+      });
 
-      debugPrint('✅ Restore completed');
-    } catch (e) {
-      debugPrint('❌ Restore failed: $e');
-      if (mounted) {
-        setState(() {
-          _purchaseResult = '❌ Failed to restore purchases: $e';
-        });
-      }
-    } finally {
-      if (mounted) {
-        setState(() {
-          _isProcessing = false;
-        });
+      // Verify each restored purchase
+      for (final purchase in _activeSubscriptions) {
+        print(
+            'Restored: ${purchase.productId}, Token: ${purchase.purchaseToken}');
       }
-    }
-  }
-
-  Future<void> _handleSubscribe(String productId) async {
-    debugPrint('🛒 Starting subscription for: $productId');
-    if (mounted) {
+    } catch (error) {
+      print('Failed to restore purchases: $error');
       setState(() {
-        _isProcessing = true;
-        _purchaseResult = null;
+        _isProcessing = false;
+        _purchaseResult = '❌ Failed to restore: $error';
       });
     }
+  }
 
-    try {
-      debugPrint('Requesting subscription purchase...');
-      await _iap.requestPurchase(
-        request: RequestPurchase(
-          ios: RequestPurchaseIOS(
-            sku: productId,
-          ),
-          android: RequestSubscriptionAndroid(
-            skus: [productId],
-            subscriptionOffers: [],
+  Widget _buildSubscriptionTier(IAPItem subscription) {
+    final isCurrentSubscription =
+        _currentSubscription?.productId == subscription.productId;
+    // Note: canUpgrade logic removed - now always show proration options for testing
+
+    return GestureDetector(
+      onTap: () => ProductDetailModal.show(
+        context: context,
+        item: subscription,
+        product: _originalProducts[subscription.productId ?? ''],
+      ),
+      child: Card(
+        margin: const EdgeInsets.only(bottom: 12),
+        color: isCurrentSubscription ? Colors.blue.shade50 : null,
+        child: Padding(
+          padding: const EdgeInsets.all(16),
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              Row(
+                children: [
+                  const Icon(Icons.star, color: Colors.amber),
+                  const SizedBox(width: 8),
+                  Expanded(
+                    child: Column(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: [
+                        Text(
+                          subscription.title ??
+                              subscription.productId ??
+                              'Subscription',
+                          style: const TextStyle(
+                            fontSize: 18,
+                            fontWeight: FontWeight.bold,
+                          ),
+                        ),
+                        if (isCurrentSubscription)
+                          Container(
+                            margin: const EdgeInsets.only(top: 4),
+                            padding: const EdgeInsets.symmetric(
+                                horizontal: 8, vertical: 2),
+                            decoration: BoxDecoration(
+                              color: Colors.blue,
+                              borderRadius: BorderRadius.circular(12),
+                            ),
+                            child: const Text(
+                              'CURRENT',
+                              style: TextStyle(
+                                color: Colors.white,
+                                fontSize: 10,
+                                fontWeight: FontWeight.bold,
+                              ),
+                            ),
+                          ),
+                      ],
+                    ),
+                  ),
+                  Text(
+                    subscription.localizedPrice ?? subscription.price ?? '',
+                    style: const TextStyle(
+                      fontSize: 18,
+                      fontWeight: FontWeight.bold,
+                      color: Colors.blue,
+                    ),
+                  ),
+                ],
+              ),
+              const SizedBox(height: 8),
+              Text(
+                subscription.description ?? 'Subscription tier',
+                style: TextStyle(color: Colors.grey[600]),
+              ),
+              const SizedBox(height: 12),
+
+              // Action buttons - Always show for testing
+              // Show current status if this is the current subscription
+              if (isCurrentSubscription) ...[
+                Container(
+                  width: double.infinity,
+                  padding:
+                      const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
+                  decoration: BoxDecoration(
+                    color: Colors.green.shade50,
+                    borderRadius: BorderRadius.circular(8),
+                  ),
+                  child: const Text(
+                    '✓ Currently Active',
+                    textAlign: TextAlign.center,
+                    style: TextStyle(
+                      color: Colors.green,
+                      fontSize: 12,
+                      fontWeight: FontWeight.bold,
+                    ),
+                  ),
+                ),
+                const SizedBox(height: 8),
+              ],
+
+              // Always show proration mode selector for testing
+              if (Platform.isAndroid) ...[
+                const Text(
+                  'Proration Mode (Test):',
+                  style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
+                ),
+                const SizedBox(height: 4),
+                SingleChildScrollView(
+                  scrollDirection: Axis.horizontal,
+                  child: Row(
+                    children: _prorationModes.entries.map((entry) {
+                      final isSelected = _selectedProrationMode == entry.value;
+                      return Padding(
+                        padding: const EdgeInsets.only(right: 8),
+                        child: ChoiceChip(
+                          label: Text(
+                            entry.key,
+                            style: const TextStyle(fontSize: 10),
+                          ),
+                          selected: isSelected,
+                          onSelected: (selected) {
+                            setState(() {
+                              _selectedProrationMode =
+                                  selected ? entry.value : null;
+                            });
+                          },
+                        ),
+                      );
+                    }).toList(),
+                  ),
+                ),
+                const SizedBox(height: 8),
+              ],
+
+              // Purchase/Upgrade buttons
+              Row(
+                children: [
+                  Expanded(
+                    child: ElevatedButton(
+                      onPressed: _isProcessing ||
+                              (_hasActiveSubscription &&
+                                  Platform.isAndroid &&
+                                  _selectedProrationMode == null)
+                          ? null
+                          : () => _purchaseSubscription(subscription,
+                              isUpgrade: _hasActiveSubscription),
+                      style: ElevatedButton.styleFrom(
+                        backgroundColor: _hasActiveSubscription
+                            ? Colors.orange.shade600
+                            : const Color(0xFF007AFF),
+                        foregroundColor: Colors.white,
+                        shape: RoundedRectangleBorder(
+                          borderRadius: BorderRadius.circular(8),
+                        ),
+                      ),
+                      child: Text(
+                        _isProcessing
+                            ? 'Processing...'
+                            : _hasActiveSubscription
+                                ? (isCurrentSubscription
+                                    ? 'Re-subscribe'
+                                    : 'Upgrade/Downgrade')
+                                : 'Subscribe',
+                      ),
+                    ),
+                  ),
+                  if (Platform.isAndroid) ...[
+                    const SizedBox(width: 4),
+                    // Test wrong usage button
+                    Expanded(
+                      child: OutlinedButton(
+                        onPressed: _isProcessing
+                            ? null
+                            : () => _testWrongProrationUsage(subscription),
+                        style: OutlinedButton.styleFrom(
+                          foregroundColor: Colors.red.shade600,
+                          side: BorderSide(color: Colors.red.shade300),
+                          padding: const EdgeInsets.symmetric(horizontal: 8),
+                          shape: RoundedRectangleBorder(
+                            borderRadius: BorderRadius.circular(8),
+                          ),
+                        ),
+                        child: const Tooltip(
+                          message: 'Test proration without token',
+                          child:
+                              Text('No Token', style: TextStyle(fontSize: 11)),
+                        ),
+                      ),
+                    ),
+                    const SizedBox(width: 4),
+                    // Test with empty token button
+                    Expanded(
+                      child: OutlinedButton(
+                        onPressed: _isProcessing
+                            ? null
+                            : () => _testEmptyTokenProration(subscription),
+                        style: OutlinedButton.styleFrom(
+                          foregroundColor: Colors.orange.shade600,
+                          side: BorderSide(color: Colors.orange.shade300),
+                          padding: const EdgeInsets.symmetric(horizontal: 8),
+                          shape: RoundedRectangleBorder(
+                            borderRadius: BorderRadius.circular(8),
+                          ),
+                        ),
+                        child: const Tooltip(
+                          message: 'Test proration with empty token',
+                          child: Text('Empty Token',
+                              style: TextStyle(fontSize: 11)),
+                        ),
+                      ),
+                    ),
+                  ],
+                ],
+              ),
+            ],
           ),
         ),
-        type: PurchaseType.subs,
-      );
-      debugPrint('✅ Subscription request sent successfully');
-      // Note: The actual subscription result will come through the purchaseUpdatedListener
-    } catch (error) {
-      if (mounted) {
-        setState(() {
-          _isProcessing = false;
-          _purchaseResult = 'Subscription failed: $error';
-        });
-      }
-      debugPrint('❌ Subscription request error: $error');
-    }
+      ),
+    );
   }
 
   @override
@@ -544,394 +824,222 @@ ${restoredPurchases.map((p) => '• ${p.productId}').join('\n')}
           style: TextStyle(color: Colors.black),
         ),
         actions: [
-          IconButton(
-            icon: _isLoadingProducts
-                ? const SizedBox(
-                    width: 20,
-                    height: 20,
-                    child: CircularProgressIndicator(strokeWidth: 2),
-                  )
-                : const Icon(Icons.refresh, color: Colors.black),
-            onPressed: _isLoadingProducts ? null : _refreshSubscriptions,
-          ),
-        ],
-      ),
-      body: ListView(
-        padding: const EdgeInsets.all(16),
-        children: [
-          // Status Card
-          Container(
-            padding: const EdgeInsets.all(12),
-            decoration: BoxDecoration(
-              color: _connected ? Colors.green[50] : Colors.grey[100],
-              borderRadius: BorderRadius.circular(8),
-            ),
-            child: Row(
-              children: [
-                if (_isConnecting)
-                  const SizedBox(
-                    width: 20,
-                    height: 20,
-                    child: CircularProgressIndicator(strokeWidth: 2),
-                  )
-                else
-                  Icon(
-                    _connected ? Icons.check_circle : Icons.error,
-                    color: _connected ? Colors.green : Colors.red,
-                  ),
-                const SizedBox(width: 8),
-                Text(
-                  _isConnecting
-                      ? 'Connecting...'
-                      : (_connected ? '✓ Connected to Store' : 'Not connected'),
-                  style: TextStyle(
-                    color: _connected ? Colors.green : Colors.grey[700],
-                    fontWeight: FontWeight.w600,
-                  ),
+          if (_currentSubscription != null)
+            Padding(
+              padding: const EdgeInsets.all(8.0),
+              child: Chip(
+                label: Text(
+                  'Token: ${_currentSubscription!.purchaseToken?.substring(0, 10)}...',
+                  style: const TextStyle(fontSize: 10),
                 ),
-              ],
-            ),
-          ),
-          const SizedBox(height: 20),
-
-          // Active Subscription Status
-          if (_hasActiveSubscription &&
-              _activeSubscriptionDetails.isNotEmpty) ...[
-            Container(
-              padding: const EdgeInsets.all(16),
-              decoration: BoxDecoration(
-                color: Colors.green[50],
-                borderRadius: BorderRadius.circular(12),
-                border: Border.all(color: Colors.green[200]!),
+                backgroundColor: Colors.green,
               ),
-              child: Column(
-                crossAxisAlignment: CrossAxisAlignment.start,
-                children: [
-                  Row(
+            ),
+        ],
+      ),
+      body: _isConnecting
+          ? const Center(child: CircularProgressIndicator())
+          : !_connected
+              ? Center(
+                  child: Column(
+                    mainAxisAlignment: MainAxisAlignment.center,
                     children: [
-                      Icon(Icons.check_circle,
-                          color: Colors.green[700], size: 24),
-                      const SizedBox(width: 8),
-                      Text(
-                        'Active Subscription',
-                        style: TextStyle(
-                          fontSize: 18,
-                          fontWeight: FontWeight.bold,
-                          color: Colors.green[800],
+                      const Icon(Icons.error, size: 64, color: Colors.red),
+                      const SizedBox(height: 16),
+                      const Text('Failed to connect to store'),
+                      const SizedBox(height: 16),
+                      ElevatedButton(
+                        onPressed: _initConnection,
+                        style: ElevatedButton.styleFrom(
+                          backgroundColor: const Color(0xFF007AFF),
+                          foregroundColor: Colors.white,
                         ),
+                        child: const Text('Retry'),
                       ),
                     ],
                   ),
-                  const SizedBox(height: 12),
-                  ..._activeSubscriptionDetails.map((sub) => Column(
-                        crossAxisAlignment: CrossAxisAlignment.start,
-                        children: [
-                          Text(
-                            'Product: ${sub.productId}',
-                            style: const TextStyle(fontWeight: FontWeight.w600),
+                )
+              : RefreshIndicator(
+                  onRefresh: () async {
+                    await _loadSubscriptions();
+                    await _checkActiveSubscriptions();
+                  },
+                  child: SingleChildScrollView(
+                    physics: const AlwaysScrollableScrollPhysics(),
+                    padding: const EdgeInsets.all(16),
+                    child: Column(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: [
+                        // Active Subscription Status Card
+                        Card(
+                          color: _hasActiveSubscription
+                              ? Colors.green.shade50
+                              : Colors.orange.shade50,
+                          child: ListTile(
+                            leading: Icon(
+                              _hasActiveSubscription
+                                  ? Icons.check_circle
+                                  : Icons.info,
+                              color: _hasActiveSubscription
+                                  ? Colors.green
+                                  : Colors.orange,
+                            ),
+                            title: Text(
+                              _hasActiveSubscription
+                                  ? 'Active Subscription: ${_currentSubscription?.productId}'
+                                  : 'No Active Subscription',
+                              style:
+                                  const TextStyle(fontWeight: FontWeight.bold),
+                            ),
+                            subtitle: Text(
+                              _hasActiveSubscription
+                                  ? 'You can upgrade/downgrade with proration mode'
+                                  : 'Subscribe to any tier to get started',
+                            ),
                           ),
-                          const SizedBox(height: 4),
-                          if (Platform.isIOS &&
-                              sub.expirationDateIOS != null) ...[
-                            Text(
-                                'Expires: ${sub.expirationDateIOS!.toLocal()}'),
-                            if (sub.daysUntilExpirationIOS != null)
-                              Text(
-                                  'Days until expiration: ${sub.daysUntilExpirationIOS}'),
-                            if (sub.environmentIOS != null)
-                              Text('Environment: ${sub.environmentIOS}'),
-                          ],
-                          if (Platform.isAndroid &&
-                              sub.autoRenewingAndroid != null)
-                            Text(
-                                'Auto-renewing: ${sub.autoRenewingAndroid! ? "Yes" : "No"}'),
-                          if (sub.willExpireSoon == true)
-                            Container(
-                              margin: const EdgeInsets.only(top: 4),
-                              padding: const EdgeInsets.symmetric(
-                                  horizontal: 8, vertical: 2),
-                              decoration: BoxDecoration(
-                                color: Colors.orange[100],
-                                borderRadius: BorderRadius.circular(4),
+                        ),
+
+                        const SizedBox(height: 24),
+
+                        // Available Subscriptions
+                        Row(
+                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                          children: [
+                            const Text(
+                              'Available Subscription Tiers',
+                              style: TextStyle(
+                                fontSize: 18,
+                                fontWeight: FontWeight.bold,
                               ),
-                              child: Text(
-                                '⚠️ Will expire soon',
-                                style: TextStyle(
-                                  fontSize: 12,
-                                  color: Colors.orange[800],
-                                  fontWeight: FontWeight.w600,
+                            ),
+                            if (_isLoadingProducts)
+                              const SizedBox(
+                                width: 20,
+                                height: 20,
+                                child:
+                                    CircularProgressIndicator(strokeWidth: 2),
+                              ),
+                          ],
+                        ),
+                        const SizedBox(height: 12),
+
+                        if (_subscriptions.isEmpty && !_isLoadingProducts)
+                          const Card(
+                            child: Padding(
+                              padding: EdgeInsets.all(32),
+                              child: Center(
+                                child: Text(
+                                  'No subscriptions available',
+                                  style: TextStyle(color: Colors.grey),
                                 ),
                               ),
                             ),
-                          const SizedBox(height: 8),
-                        ],
-                      )),
-                ],
-              ),
-            ),
-            const SizedBox(height: 20),
-          ],
-
-          // Subscriptions Section Title
-          const Text(
-            'Available Subscriptions',
-            style: TextStyle(
-              fontSize: 20,
-              fontWeight: FontWeight.bold,
-              color: Colors.black87,
-            ),
-          ),
-          const SizedBox(height: 12),
-
-          // Subscriptions
-          if (_isLoadingProducts)
-            Card(
-              child: Container(
-                padding: const EdgeInsets.all(24),
-                alignment: Alignment.center,
-                child: const CircularProgressIndicator(),
-              ),
-            )
-          else if (_subscriptions.isEmpty)
-            Card(
-              child: Container(
-                padding: const EdgeInsets.all(16),
-                child: Text(
-                  _connected
-                      ? 'No subscriptions available'
-                      : 'Connect to load subscriptions',
-                  style: TextStyle(color: Colors.grey[600]),
-                ),
-              ),
-            )
-          else
-            ..._subscriptions.map((subscription) {
-              final isSubscribed = _activeSubscriptions
-                  .any((p) => p.productId == subscription.productId);
-              final activeDetail = _activeSubscriptionDetails.firstWhere(
-                (sub) => sub.productId == subscription.productId,
-                orElse: () => ActiveSubscription(
-                  productId: '',
-                  isActive: false,
-                ),
-              );
+                          )
+                        else
+                          ..._subscriptions.map(_buildSubscriptionTier),
 
-              return Card(
-                margin: const EdgeInsets.only(bottom: 12),
-                child: Padding(
-                  padding: const EdgeInsets.all(16),
-                  child: Column(
-                    crossAxisAlignment: CrossAxisAlignment.start,
-                    children: [
-                      Row(
-                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                        crossAxisAlignment: CrossAxisAlignment.start,
-                        children: [
-                          Expanded(
-                            child: Column(
-                              crossAxisAlignment: CrossAxisAlignment.start,
-                              children: [
-                                Text(
-                                  subscription.title ??
-                                      subscription.productId ??
-                                      '',
-                                  style: const TextStyle(
-                                    fontSize: 18,
-                                    fontWeight: FontWeight.w600,
-                                  ),
-                                ),
-                                const SizedBox(height: 4),
-                                Text(
-                                  subscription.description ?? '',
-                                  style: TextStyle(
-                                    fontSize: 14,
-                                    color: Colors.grey[600],
-                                  ),
-                                ),
-                                if (Platform.isAndroid &&
-                                    subscription.subscriptionPeriodAndroid !=
-                                        null) ...[
-                                  const SizedBox(height: 4),
-                                  Text(
-                                    'Period: ${subscription.subscriptionPeriodAndroid}',
-                                    style: TextStyle(
-                                      fontSize: 12,
-                                      color: Colors.grey[600],
-                                    ),
-                                  ),
-                                ],
-                                const SizedBox(height: 4),
-                                Text(
-                                  'ID: ${subscription.productId}',
-                                  style: TextStyle(
-                                    fontSize: 12,
-                                    fontFamily: 'monospace',
-                                    color: Colors.grey[500],
-                                  ),
-                                ),
-                              ],
-                            ),
-                          ),
-                          Column(
-                            crossAxisAlignment: CrossAxisAlignment.end,
-                            children: [
-                              Text(
-                                subscription.localizedPrice ?? '',
-                                style: const TextStyle(
-                                  fontSize: 20,
-                                  fontWeight: FontWeight.bold,
-                                  color: Color(0xFF007AFF),
-                                ),
-                              ),
-                            ],
-                          ),
-                        ],
-                      ),
-                      const SizedBox(height: 12),
-                      // Show subscribed status badge if active
-                      if (isSubscribed)
+                        const SizedBox(height: 24),
+
+                        // Test Instructions
                         Container(
-                          width: double.infinity,
                           padding: const EdgeInsets.all(12),
                           decoration: BoxDecoration(
-                            color: Colors.green.shade50,
+                            color: Colors.blue.shade50,
                             borderRadius: BorderRadius.circular(8),
-                            border: Border.all(color: Colors.green.shade200),
+                            border: Border.all(color: Colors.blue.shade200),
                           ),
-                          child: Column(
+                          child: const Column(
+                            crossAxisAlignment: CrossAxisAlignment.start,
                             children: [
-                              const Text(
-                                '✓ Subscribed',
+                              Text(
+                                'How to Test Proration Mode:',
                                 style: TextStyle(
-                                  color: Colors.green,
                                   fontWeight: FontWeight.bold,
+                                  fontSize: 14,
+                                ),
+                              ),
+                              SizedBox(height: 8),
+                              Text('1. Subscribe to Basic tier first',
+                                  style: TextStyle(fontSize: 12)),
+                              Text('2. Wait for purchase to complete',
+                                  style: TextStyle(fontSize: 12)),
+                              Text(
+                                  '3. Tap "Restore Purchases" to load your subscription',
+                                  style: TextStyle(fontSize: 12)),
+                              Text(
+                                  '4. Select a proration mode (e.g., "Immediate with Time Proration")',
+                                  style: TextStyle(fontSize: 12)),
+                              Text('5. Upgrade to Premium or Pro tier',
+                                  style: TextStyle(fontSize: 12)),
+                              SizedBox(height: 8),
+                              Text(
+                                'Test Buttons: "No Token" = without token, "Empty Token" = with empty string',
+                                style: TextStyle(
+                                  fontSize: 11,
+                                  fontStyle: FontStyle.italic,
+                                  color: Colors.grey,
                                 ),
                               ),
-                              if (activeDetail.isActive &&
-                                  activeDetail.productId ==
-                                      subscription.productId) ...[
-                                const SizedBox(height: 8),
-                                if (Platform.isIOS &&
-                                    activeDetail.expirationDateIOS != null)
-                                  Text(
-                                    'Expires: ${activeDetail.expirationDateIOS!.toLocal().toString().split('.')[0]}',
-                                    style: TextStyle(
-                                      fontSize: 12,
-                                      color: Colors.grey[600],
-                                    ),
-                                  ),
-                                if (Platform.isAndroid &&
-                                    activeDetail.autoRenewingAndroid != null)
-                                  Text(
-                                    activeDetail.autoRenewingAndroid!
-                                        ? 'Auto-renewing'
-                                        : 'Not auto-renewing',
-                                    style: TextStyle(
-                                      fontSize: 12,
-                                      color: Colors.grey[600],
-                                    ),
-                                  ),
-                              ],
                             ],
                           ),
-                        )
-                      else
+                        ),
+
+                        const SizedBox(height: 16),
+
+                        // Restore Purchases Button
                         SizedBox(
                           width: double.infinity,
-                          child: ElevatedButton(
-                            onPressed: _isProcessing || !_connected
-                                ? null
-                                : () =>
-                                    _handleSubscribe(subscription.productId!),
-                            style: ElevatedButton.styleFrom(
-                              backgroundColor: const Color(0xFF007AFF),
-                              padding: const EdgeInsets.symmetric(vertical: 12),
-                            ),
-                            child: Text(
-                              _isProcessing ? 'Processing...' : 'Subscribe',
-                              style: const TextStyle(color: Colors.white),
-                            ),
+                          child: OutlinedButton.icon(
+                            onPressed: _isProcessing ? null : _restorePurchases,
+                            icon: const Icon(Icons.restore),
+                            label: const Text('Restore Purchases'),
                           ),
                         ),
-                    ],
-                  ),
-                ),
-              );
-            }),
-
-          // Restore Purchases Button
-          const SizedBox(height: 20),
-          OutlinedButton.icon(
-            onPressed: _isProcessing || !_connected ? null : _restorePurchases,
-            icon: const Icon(Icons.restore),
-            label: Text(_isProcessing ? 'Processing...' : 'Restore Purchases'),
-            style: OutlinedButton.styleFrom(
-              padding: const EdgeInsets.symmetric(vertical: 12),
-            ),
-          ),
 
-          // Purchase Result Card (like KMP-IAP)
-          if (_purchaseResult != null) ...[
-            const SizedBox(height: 20),
-            Card(
-              color: _purchaseResult!.contains('✅')
-                  ? Colors.green.shade50
-                  : _purchaseResult!.contains('❌')
-                      ? Colors.red.shade50
-                      : _purchaseResult!.contains('⚠️')
-                          ? Colors.orange.shade50
-                          : Colors.white,
-              child: Padding(
-                padding: const EdgeInsets.all(16),
-                child: Column(
-                  crossAxisAlignment: CrossAxisAlignment.start,
-                  children: [
-                    Row(
-                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                      children: [
-                        const Text(
-                          'Subscription Result',
-                          style: TextStyle(
-                            fontSize: 16,
-                            fontWeight: FontWeight.bold,
+                        // Purchase Result
+                        if (_purchaseResult != null) ...[
+                          const SizedBox(height: 16),
+                          Card(
+                            color: _purchaseResult!.contains('✅')
+                                ? Colors.green.shade50
+                                : _purchaseResult!.contains('❌')
+                                    ? Colors.red.shade50
+                                    : Colors.orange.shade50,
+                            child: Padding(
+                              padding: const EdgeInsets.all(12),
+                              child: Row(
+                                children: [
+                                  Expanded(
+                                    child: Text(
+                                      _purchaseResult!,
+                                      style: TextStyle(
+                                        color: _purchaseResult!.contains('✅')
+                                            ? Colors.green
+                                            : _purchaseResult!.contains('❌')
+                                                ? Colors.red
+                                                : Colors.orange,
+                                      ),
+                                    ),
+                                  ),
+                                  IconButton(
+                                    icon: const Icon(Icons.close, size: 18),
+                                    onPressed: () {
+                                      setState(() {
+                                        _purchaseResult = null;
+                                      });
+                                    },
+                                    padding: EdgeInsets.zero,
+                                    constraints: const BoxConstraints(),
+                                  ),
+                                ],
+                              ),
+                            ),
                           ),
-                        ),
-                        IconButton(
-                          icon: const Icon(Icons.close, size: 20),
-                          onPressed: () {
-                            if (mounted) {
-                              setState(() {
-                                _purchaseResult = null;
-                              });
-                            }
-                          },
-                        ),
+                        ],
                       ],
                     ),
-                    const SizedBox(height: 8),
-                    Container(
-                      width: double.infinity,
-                      padding: const EdgeInsets.all(12),
-                      decoration: BoxDecoration(
-                        color: Colors.grey.shade100,
-                        borderRadius: BorderRadius.circular(8),
-                        border: Border.all(color: Colors.grey.shade300),
-                      ),
-                      child: SelectableText(
-                        _purchaseResult!,
-                        style: const TextStyle(
-                          fontSize: 12,
-                          fontFamily: 'monospace',
-                        ),
-                      ),
-                    ),
-                  ],
+                  ),
                 ),
-              ),
-            ),
-          ],
-        ],
-      ),
     );
   }
 }
diff --git a/example/lib/src/widgets/product_detail_modal.dart b/example/lib/src/widgets/product_detail_modal.dart
new file mode 100644
index 00000000..8d7c715e
--- /dev/null
+++ b/example/lib/src/widgets/product_detail_modal.dart
@@ -0,0 +1,326 @@
+import 'dart:convert';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
+
+class ProductDetailModal extends StatelessWidget {
+  final IAPItem item;
+  final BaseProduct? product;
+
+  const ProductDetailModal({
+    Key? key,
+    required this.item,
+    this.product,
+  }) : super(key: key);
+
+  static void show({
+    required BuildContext context,
+    required IAPItem item,
+    BaseProduct? product,
+  }) {
+    showModalBottomSheet<void>(
+      context: context,
+      isScrollControlled: true,
+      backgroundColor: Colors.transparent,
+      builder: (context) => ProductDetailModal(
+        item: item,
+        product: product,
+      ),
+    );
+  }
+
+  Map<String, dynamic> _itemToMap(IAPItem item) {
+    final map = <String, dynamic>{
+      'productId': item.productId,
+      'price': item.price,
+      'currency': item.currency,
+      'localizedPrice': item.localizedPrice,
+      'title': item.title,
+      'description': item.description,
+      'introductoryPrice': item.introductoryPrice,
+      'subscriptionPeriodNumberIOS': item.subscriptionPeriodNumberIOS,
+      'subscriptionPeriodUnitIOS': item.subscriptionPeriodUnitIOS,
+      'introductoryPriceNumberOfPeriodsIOS':
+          item.introductoryPriceNumberOfPeriodsIOS,
+      'introductoryPriceSubscriptionPeriodIOS':
+          item.introductoryPriceSubscriptionPeriodIOS,
+      'discountsIOS': item.discountsIOS
+          ?.map((d) => {
+                'identifier': d.identifier,
+                'type': d.type,
+                'numberOfPeriods': d.numberOfPeriods,
+                'price': d.price,
+                'localizedPrice': d.localizedPrice,
+                'paymentMode': d.paymentMode,
+                'subscriptionPeriod': d.subscriptionPeriod,
+              })
+          .toList(),
+      'subscriptionPeriodAndroid': item.subscriptionPeriodAndroid,
+      'signatureAndroid': item.signatureAndroid,
+      'iconUrl': item.iconUrl,
+      'originalJson': item.originalJson,
+      'originalPrice': item.originalPrice,
+      'subscriptionOffersAndroid': item.subscriptionOffersAndroid
+          ?.map((o) => {
+                'sku': o.sku,
+                'offerToken': o.offerToken,
+              })
+          .toList(),
+    };
+
+    // Remove null values for cleaner display
+    map.removeWhere((key, value) => value == null);
+
+    return map;
+  }
+
+  Widget _buildSection(String title, Widget content) {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Padding(
+          padding: const EdgeInsets.symmetric(vertical: 8.0),
+          child: Text(
+            title,
+            style: const TextStyle(
+              fontSize: 16,
+              fontWeight: FontWeight.bold,
+            ),
+          ),
+        ),
+        content,
+        const SizedBox(height: 16),
+      ],
+    );
+  }
+
+  Widget _buildDetailRow(String label, String? value) {
+    if (value == null || value.isEmpty) return const SizedBox.shrink();
+
+    return Padding(
+      padding: const EdgeInsets.symmetric(vertical: 4.0),
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: [
+          SizedBox(
+            width: 140,
+            child: Text(
+              '$label:',
+              style: const TextStyle(
+                fontWeight: FontWeight.w500,
+                color: Colors.grey,
+              ),
+            ),
+          ),
+          Expanded(
+            child: Text(
+              value,
+              style: const TextStyle(fontWeight: FontWeight.w400),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final jsonString =
+        const JsonEncoder.withIndent('  ').convert(_itemToMap(item));
+
+    return DraggableScrollableSheet(
+      initialChildSize: 0.7,
+      minChildSize: 0.5,
+      maxChildSize: 0.95,
+      builder: (context, scrollController) => Container(
+        decoration: const BoxDecoration(
+          color: Colors.white,
+          borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
+        ),
+        child: Column(
+          children: [
+            Container(
+              margin: const EdgeInsets.symmetric(vertical: 8),
+              width: 40,
+              height: 4,
+              decoration: BoxDecoration(
+                color: Colors.grey[300],
+                borderRadius: BorderRadius.circular(2),
+              ),
+            ),
+            Padding(
+              padding: const EdgeInsets.all(16.0),
+              child: Row(
+                mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                children: [
+                  Expanded(
+                    child: Text(
+                      item.title ?? item.productId ?? 'Product Details',
+                      style: const TextStyle(
+                        fontSize: 20,
+                        fontWeight: FontWeight.bold,
+                      ),
+                      maxLines: 2,
+                      overflow: TextOverflow.ellipsis,
+                    ),
+                  ),
+                  IconButton(
+                    icon: const Icon(Icons.close),
+                    onPressed: () => Navigator.of(context).pop(),
+                  ),
+                ],
+              ),
+            ),
+            const Divider(height: 1),
+            Expanded(
+              child: ListView(
+                controller: scrollController,
+                padding: const EdgeInsets.all(16),
+                children: [
+                  // Basic Information
+                  _buildSection(
+                    'Basic Information',
+                    Column(
+                      children: [
+                        _buildDetailRow('Product ID', item.productId),
+                        _buildDetailRow('Price', item.localizedPrice),
+                        _buildDetailRow('Currency', item.currency),
+                        if (item.originalPrice != null)
+                          _buildDetailRow(
+                              'Original Price', item.originalPrice.toString()),
+                        _buildDetailRow('Description', item.description),
+                      ],
+                    ),
+                  ),
+
+                  // Subscription Information (if applicable)
+                  if (item.subscriptionPeriodAndroid != null ||
+                      item.subscriptionPeriodUnitIOS != null)
+                    _buildSection(
+                      'Subscription Details',
+                      Column(
+                        children: [
+                          if (item.subscriptionPeriodAndroid != null)
+                            _buildDetailRow('Period (Android)',
+                                item.subscriptionPeriodAndroid),
+                          if (item.subscriptionPeriodUnitIOS != null)
+                            _buildDetailRow(
+                              'Period (iOS)',
+                              '${item.subscriptionPeriodNumberIOS ?? ''} ${item.subscriptionPeriodUnitIOS}',
+                            ),
+                          if (item.introductoryPrice != null)
+                            _buildDetailRow(
+                                'Introductory Price', item.introductoryPrice),
+                        ],
+                      ),
+                    ),
+
+                  // Android Offers
+                  if (item.subscriptionOffersAndroid?.isNotEmpty ?? false)
+                    _buildSection(
+                      'Android Subscription Offers',
+                      Column(
+                        children: item.subscriptionOffersAndroid!
+                            .map((offer) => Card(
+                                  margin:
+                                      const EdgeInsets.symmetric(vertical: 4),
+                                  child: Padding(
+                                    padding: const EdgeInsets.all(12),
+                                    child: Column(
+                                      crossAxisAlignment:
+                                          CrossAxisAlignment.start,
+                                      children: [
+                                        _buildDetailRow('SKU', offer.sku),
+                                        _buildDetailRow(
+                                          'Token',
+                                          offer.offerToken.length > 20
+                                              ? '${offer.offerToken.substring(0, 20)}...'
+                                              : offer.offerToken,
+                                        ),
+                                      ],
+                                    ),
+                                  ),
+                                ))
+                            .toList(),
+                      ),
+                    ),
+
+                  // iOS Discounts
+                  if (item.discountsIOS?.isNotEmpty ?? false)
+                    _buildSection(
+                      'iOS Discounts',
+                      Column(
+                        children: item.discountsIOS!
+                            .map((discount) => Card(
+                                  margin:
+                                      const EdgeInsets.symmetric(vertical: 4),
+                                  child: Padding(
+                                    padding: const EdgeInsets.all(12),
+                                    child: Column(
+                                      crossAxisAlignment:
+                                          CrossAxisAlignment.start,
+                                      children: [
+                                        _buildDetailRow(
+                                            'Identifier', discount.identifier),
+                                        _buildDetailRow(
+                                            'Price', discount.localizedPrice),
+                                        _buildDetailRow('Type', discount.type),
+                                        _buildDetailRow('Payment Mode',
+                                            discount.paymentMode),
+                                      ],
+                                    ),
+                                  ),
+                                ))
+                            .toList(),
+                      ),
+                    ),
+
+                  // Raw JSON Data
+                  _buildSection(
+                    'Raw Data (JSON)',
+                    Container(
+                      padding: const EdgeInsets.all(12),
+                      decoration: BoxDecoration(
+                        color: Colors.grey[100],
+                        borderRadius: BorderRadius.circular(8),
+                        border: Border.all(color: Colors.grey[300]!),
+                      ),
+                      child: SingleChildScrollView(
+                        scrollDirection: Axis.horizontal,
+                        child: SelectableText(
+                          jsonString,
+                          style: const TextStyle(
+                            fontFamily: 'monospace',
+                            fontSize: 12,
+                          ),
+                        ),
+                      ),
+                    ),
+                  ),
+
+                  // Original Product Data (if available)
+                  if (product != null)
+                    _buildSection(
+                      'Original Product Object',
+                      Container(
+                        padding: const EdgeInsets.all(12),
+                        decoration: BoxDecoration(
+                          color: Colors.blue[50],
+                          borderRadius: BorderRadius.circular(8),
+                          border: Border.all(color: Colors.blue[200]!),
+                        ),
+                        child: Text(
+                          product.toString(),
+                          style: const TextStyle(fontSize: 12),
+                        ),
+                      ),
+                    ),
+                ],
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/enums.dart b/lib/enums.dart
index 1f75e1ce..def17cf6 100644
--- a/lib/enums.dart
+++ b/lib/enums.dart
@@ -135,6 +135,32 @@ enum ResponseCodeAndroid {
 enum PurchaseState { pending, purchased, unspecified }
 
 /// Android Proration Mode
+///
+/// IMPORTANT: Proration modes are ONLY for upgrading/downgrading EXISTING subscriptions.
+/// For NEW subscriptions, do NOT use any proration mode.
+///
+/// To use proration mode:
+/// 1. User must have an active subscription
+/// 2. You must provide the purchaseToken from the existing subscription
+/// 3. Get the token using getAvailablePurchases()
+///
+/// Example:
+/// ```dart
+/// // First, check for existing subscription
+/// final purchases = await FlutterInappPurchase.instance.getAvailablePurchases();
+/// if (purchases.isEmpty) {
+///   // User has no subscription - purchase new one WITHOUT proration mode
+///   await FlutterInappPurchase.instance.requestSubscription('premium_monthly');
+/// } else {
+///   // User has subscription - can upgrade/downgrade WITH proration mode
+///   final existingSub = purchases.first;
+///   await FlutterInappPurchase.instance.requestSubscription(
+///     'premium_yearly',
+///     prorationModeAndroid: AndroidProrationMode.immediateWithTimeProration.value,
+///     purchaseTokenAndroid: existingSub.purchaseToken,
+///   );
+/// }
+/// ```
 enum AndroidProrationMode {
   unknownSubscriptionUpgradeDowngradePolicy(0),
   immediateWithTimeProration(1),
diff --git a/lib/flutter_inapp_purchase.dart b/lib/flutter_inapp_purchase.dart
index e31b5ffd..bee51d5d 100644
--- a/lib/flutter_inapp_purchase.dart
+++ b/lib/flutter_inapp_purchase.dart
@@ -207,30 +207,44 @@ class FlutterInappPurchase
       } else if (params.type == iap_types.PurchaseType.inapp) {
         rawResult = await _channel.invokeMethod(
           'getProducts',
-          params.productIds,
+          {'productIds': params.productIds},
         );
       } else {
         rawResult = await _channel.invokeMethod(
           'getSubscriptions',
-          params.productIds,
+          {'productIds': params.productIds},
         );
       }
 
-      final List<dynamic> result = rawResult as List<dynamic>? ?? [];
+      // Android returns JSON string, iOS returns List
+      final List<dynamic> result;
+      if (rawResult is String) {
+        // Parse JSON string from Android
+        result = jsonDecode(rawResult) as List<dynamic>? ?? [];
+      } else {
+        result = rawResult as List<dynamic>? ?? [];
+      }
 
       print(
         '[flutter_inapp_purchase] Received ${result.length} items from native',
       );
 
       // Convert directly to Product/Subscription without intermediate IAPItem
-      return result
-          .map(
-            (item) => _parseProductFromNative(
-              Map<String, dynamic>.from(item as Map),
-              params.type,
-            ),
-          )
-          .toList();
+      return result.map(
+        (item) {
+          // Handle different Map types from iOS and Android
+          final Map<String, dynamic> itemMap;
+          if (item is Map<String, dynamic>) {
+            itemMap = item;
+          } else if (item is Map) {
+            // Convert Map<Object?, Object?> to Map<String, dynamic>
+            itemMap = Map<String, dynamic>.from(item);
+          } else {
+            throw Exception('Unexpected item type: ${item.runtimeType}');
+          }
+          return _parseProductFromNative(itemMap, params.type);
+        },
+      ).toList();
     } catch (e) {
       throw iap_types.PurchaseError(
         code: iap_types.ErrorCode.eServiceError,
@@ -315,6 +329,20 @@ class FlutterInappPurchase
         if (type == iap_types.PurchaseType.subs) {
           // Check if this is a RequestSubscriptionAndroid
           if (androidRequest is iap_types.RequestSubscriptionAndroid) {
+            // Validate proration mode requirements before calling requestSubscription
+            if (androidRequest.replacementModeAndroid != null &&
+                androidRequest.replacementModeAndroid != -1 &&
+                (androidRequest.purchaseTokenAndroid == null ||
+                    androidRequest.purchaseTokenAndroid!.isEmpty)) {
+              throw iap_types.PurchaseError(
+                code: iap_types.ErrorCode.eDeveloperError,
+                message:
+                    'purchaseTokenAndroid is required when using replacementModeAndroid (proration mode). '
+                    'You need the purchase token from the existing subscription to upgrade/downgrade.',
+                platform: iap_types.IapPlatform.android,
+              );
+            }
+
             await requestSubscription(
               sku,
               obfuscatedAccountIdAndroid:
@@ -650,7 +678,7 @@ class FlutterInappPurchase
         discountsIOS: _parseDiscountsIOS(json['discounts']),
         subscription: json['subscription'] != null
             ? iap_types.SubscriptionInfo.fromJson(
-                json['subscription'] as Map<String, dynamic>)
+                Map<String, dynamic>.from(json['subscription'] as Map))
             : json['subscriptionGroupId'] != null
                 ? iap_types.SubscriptionInfo(
                     subscriptionGroupId: json['subscriptionGroupId'] as String?,
@@ -699,13 +727,32 @@ class FlutterInappPurchase
     if (json == null) return null;
     final list = json as List<dynamic>;
     return list
-        .map((e) => iap_types.DiscountIOS.fromJson(e as Map<String, dynamic>))
+        .map((e) => iap_types.DiscountIOS.fromJson(e is Map<String, dynamic>
+            ? e
+            : Map<String, dynamic>.from(e as Map)))
         .toList();
   }
 
   List<iap_types.OfferDetail>? _parseOfferDetails(dynamic json) {
     if (json == null) return null;
-    final list = json as List<dynamic>;
+
+    // Handle both List and String (JSON string from Android)
+    List<dynamic> list;
+    if (json is String) {
+      // Parse JSON string from Android
+      try {
+        final parsed = jsonDecode(json);
+        if (parsed is! List) return null;
+        list = parsed;
+      } catch (e) {
+        return null;
+      }
+    } else if (json is List) {
+      list = json;
+    } else {
+      return null;
+    }
+
     return list
         .map(
           (e) => iap_types.OfferDetail(
@@ -721,18 +768,60 @@ class FlutterInappPurchase
 
   List<iap_types.PricingPhase>? _parsePricingPhases(dynamic json) {
     if (json == null) return null;
-    final list = json as List<dynamic>;
-    return list
-        .map(
-          (e) => iap_types.PricingPhase(
-            priceAmount: (e['priceAmountMicros'] as num?)?.toDouble() ?? 0.0,
-            price: e['formattedPrice'] as String? ?? '0',
-            currency: e['priceCurrencyCode'] as String? ?? 'USD',
-            billingPeriod: e['billingPeriod'] as String?,
-            billingCycleCount: e['billingCycleCount'] as int?,
-          ),
-        )
-        .toList();
+
+    // Handle nested structure from Android
+    List<dynamic>? list;
+    if (json is Map && json['pricingPhaseList'] != null) {
+      list = json['pricingPhaseList'] as List<dynamic>?;
+    } else if (json is List) {
+      list = json;
+    } else {
+      return null;
+    }
+
+    if (list == null) return null;
+
+    return list.map(
+      (e) {
+        // Handle priceAmountMicros as either String or num and scale to currency units
+        final priceAmountMicros = e['priceAmountMicros'];
+        double priceAmount = 0.0;
+        if (priceAmountMicros != null) {
+          final double micros = priceAmountMicros is num
+              ? priceAmountMicros.toDouble()
+              : (priceAmountMicros is String
+                  ? double.tryParse(priceAmountMicros) ?? 0.0
+                  : 0.0);
+          priceAmount = micros / 1000000.0; // Convert micros to currency units
+        }
+
+        // Map recurrenceMode if present (BillingClient: 1=infinite, 2=finite, 3=non-recurring)
+        iap_types.RecurrenceMode? recurrenceMode;
+        final rm = e['recurrenceMode'];
+        if (rm is int) {
+          switch (rm) {
+            case 1:
+              recurrenceMode = iap_types.RecurrenceMode.infiniteRecurring;
+              break;
+            case 2:
+              recurrenceMode = iap_types.RecurrenceMode.finiteRecurring;
+              break;
+            case 3:
+              recurrenceMode = iap_types.RecurrenceMode.nonRecurring;
+              break;
+          }
+        }
+
+        return iap_types.PricingPhase(
+          priceAmount: priceAmount,
+          price: e['formattedPrice'] as String? ?? '0',
+          currency: e['priceCurrencyCode'] as String? ?? 'USD',
+          billingPeriod: e['billingPeriod'] as String?,
+          billingCycleCount: e['billingCycleCount'] as int?,
+          recurrenceMode: recurrenceMode,
+        );
+      },
+    ).toList();
   }
 
   iap_types.PurchaseState _mapAndroidPurchaseState(int state) {
@@ -990,6 +1079,31 @@ class FlutterInappPurchase
   }
 
   /// Request a subscription
+  ///
+  /// For NEW subscriptions:
+  /// - Simply call with productId
+  /// - Do NOT set prorationModeAndroid (or set it to -1)
+  ///
+  /// For UPGRADING/DOWNGRADING existing subscriptions (Android only):
+  /// - Set prorationModeAndroid to desired mode (1-5)
+  /// - MUST provide purchaseTokenAndroid from the existing subscription
+  /// - Get the token using getAvailablePurchases()
+  ///
+  /// Example for new subscription:
+  /// ```dart
+  /// await requestSubscription('premium_monthly');
+  /// ```
+  ///
+  /// Example for upgrade with proration:
+  /// ```dart
+  /// final purchases = await getAvailablePurchases();
+  /// final existingSub = purchases.firstWhere((p) => p.productId == 'basic_monthly');
+  /// await requestSubscription(
+  ///   'premium_monthly',
+  ///   prorationModeAndroid: AndroidProrationMode.immediateWithTimeProration.value,
+  ///   purchaseTokenAndroid: existingSub.purchaseToken,
+  /// );
+  /// ```
   Future<dynamic> requestSubscription(
     String productId, {
     int? prorationModeAndroid,
@@ -999,6 +1113,22 @@ class FlutterInappPurchase
     int? offerTokenIndex,
   }) async {
     if (_platform.isAndroid) {
+      // Validate that purchaseToken is provided when using proration mode
+      // Proration mode -1 means no proration (new subscription)
+      if (prorationModeAndroid != null &&
+          prorationModeAndroid != -1 &&
+          (purchaseTokenAndroid == null || purchaseTokenAndroid.isEmpty)) {
+        throw iap_types.PurchaseError(
+          code: iap_types.ErrorCode.eDeveloperError,
+          message:
+              'purchaseTokenAndroid is required when using proration mode (prorationModeAndroid: $prorationModeAndroid). '
+              'Proration modes are only for upgrading/downgrading EXISTING subscriptions. '
+              'For NEW subscriptions, do not set prorationModeAndroid or set it to -1. '
+              'To upgrade/downgrade, provide the purchaseToken from getAvailablePurchases().',
+          platform: iap_types.IapPlatform.android,
+        );
+      }
+
       return await _channel.invokeMethod('buyItemByType', <String, dynamic>{
         'type': TypeInApp.subs.name,
         'productId': productId,
@@ -1086,12 +1216,18 @@ class FlutterInappPurchase
         return;
       } else {
         if (purchase.isAcknowledgedAndroid == true) {
-          print(
-              '[FlutterInappPurchase] Android: Purchase already acknowledged');
+          if (kDebugMode) {
+            debugPrint(
+                '[FlutterInappPurchase] Android: Purchase already acknowledged');
+          }
           return;
         } else {
-          print(
-              '[FlutterInappPurchase] Android: Acknowledging purchase with token: ${purchase.purchaseToken}');
+          if (kDebugMode) {
+            final maskedToken = (purchase.purchaseToken ?? '')
+                .replaceAllMapped(RegExp(r'.(?=.{4})'), (m) => '*');
+            debugPrint(
+                '[FlutterInappPurchase] Android: Acknowledging purchase with token: $maskedToken');
+          }
           await _channel.invokeMethod('acknowledgePurchase', <String, dynamic>{
             'purchaseToken': purchase.purchaseToken,
           });
diff --git a/lib/types.dart b/lib/types.dart
index bfddf56f..bb153934 100644
--- a/lib/types.dart
+++ b/lib/types.dart
@@ -179,7 +179,7 @@ class Product extends BaseProduct {
           : null,
       subscription: json['subscription'] != null
           ? SubscriptionInfo.fromJson(
-              json['subscription'] as Map<String, dynamic>,
+              Map<String, dynamic>.from(json['subscription'] as Map),
             )
           : null,
       introductoryPriceNumberOfPeriodsIOS:
@@ -411,7 +411,7 @@ class Subscription extends BaseProduct {
           : null,
       subscription: json['subscription'] != null
           ? SubscriptionInfo.fromJson(
-              json['subscription'] as Map<String, dynamic>,
+              Map<String, dynamic>.from(json['subscription'] as Map),
             )
           : null,
       introductoryPriceNumberOfPeriodsIOS:
@@ -645,8 +645,12 @@ class SubscriptionInfo {
   factory SubscriptionInfo.fromJson(Map<String, dynamic> json) {
     return SubscriptionInfo(
       subscriptionGroupId: json['subscriptionGroupId'] as String?,
-      subscriptionPeriod: json['subscriptionPeriod'] as Map<String, dynamic>?,
-      introductoryOffer: json['introductoryOffer'] as Map<String, dynamic>?,
+      subscriptionPeriod: json['subscriptionPeriod'] != null
+          ? Map<String, dynamic>.from(json['subscriptionPeriod'] as Map)
+          : null,
+      introductoryOffer: json['introductoryOffer'] != null
+          ? Map<String, dynamic>.from(json['introductoryOffer'] as Map)
+          : null,
       promotionalOffers: json['promotionalOffers'] as List<dynamic>?,
       introductoryPrice: json['introductoryPrice'] as String?,
     );
@@ -997,7 +1001,9 @@ class Purchase {
       ownershipTypeIOS: json['ownershipTypeIOS'] as String?,
       transactionReasonIOS: json['transactionReasonIOS'] as String?,
       reasonIOS: json['reasonIOS'] as String?,
-      offerIOS: json['offerIOS'] as Map<String, dynamic>?,
+      offerIOS: json['offerIOS'] != null
+          ? Map<String, dynamic>.from(json['offerIOS'] as Map)
+          : null,
       jwsRepresentationIOS: json['jwsRepresentationIOS'] as String?,
       // Android specific per OpenIAP spec
       signatureAndroid: json['signatureAndroid'] as String?,
@@ -1308,9 +1314,39 @@ class RequestPurchaseAndroid {
 }
 
 /// Android specific subscription request (OpenIAP compliant)
+///
+/// When upgrading/downgrading a subscription (using replacementModeAndroid),
+/// you MUST provide the purchaseTokenAndroid from the existing subscription.
+///
+/// Example:
+/// ```dart
+/// // Get existing subscription's purchase token
+/// final purchases = await FlutterInappPurchase.instance.getAvailablePurchases();
+/// final existingSubscription = purchases.firstWhere((p) => p.productId == 'current_subscription');
+///
+/// // Upgrade/downgrade with proration mode
+/// await FlutterInappPurchase.instance.requestPurchase(
+///   request: RequestPurchase(
+///     android: RequestSubscriptionAndroid(
+///       skus: ['new_subscription_id'],
+///       purchaseTokenAndroid: existingSubscription.purchaseToken, // Required!
+///       replacementModeAndroid: AndroidProrationMode.deferred.value,
+///       subscriptionOffers: [...],
+///     ),
+///   ),
+///   type: PurchaseType.subs,
+/// );
+/// ```
 class RequestSubscriptionAndroid extends RequestPurchaseAndroid {
+  /// The purchase token from the existing subscription that is being replaced.
+  /// REQUIRED when using replacementModeAndroid (proration mode).
   final String? purchaseTokenAndroid;
+
+  /// The proration mode for subscription replacement.
+  /// When set, purchaseTokenAndroid MUST be provided.
+  /// Use values from AndroidProrationMode class.
   final int? replacementModeAndroid;
+
   final List<SubscriptionOfferAndroid> subscriptionOffers;
 
   RequestSubscriptionAndroid({
@@ -1326,7 +1362,15 @@ class RequestSubscriptionAndroid extends RequestPurchaseAndroid {
           obfuscatedAccountIdAndroid: obfuscatedAccountIdAndroid,
           obfuscatedProfileIdAndroid: obfuscatedProfileIdAndroid,
           isOfferPersonalized: isOfferPersonalized,
-        );
+        ) {
+    // Add assertion for development time validation
+    assert(
+      replacementModeAndroid == null ||
+          replacementModeAndroid == -1 ||
+          (purchaseTokenAndroid != null && purchaseTokenAndroid!.isNotEmpty),
+      'purchaseTokenAndroid is required when using replacementModeAndroid (proration mode)',
+    );
+  }
 }
 
 /// Subscription offer for Android
@@ -1642,8 +1686,12 @@ class ValidationResult {
     return ValidationResult(
       isValid: json['isValid'] as bool? ?? false,
       errorMessage: json['errorMessage'] as String?,
-      receipt: json['receipt'] as Map<String, dynamic>?,
-      parsedReceipt: json['parsedReceipt'] as Map<String, dynamic>?,
+      receipt: json['receipt'] != null
+          ? Map<String, dynamic>.from(json['receipt'] as Map)
+          : null,
+      parsedReceipt: json['parsedReceipt'] != null
+          ? Map<String, dynamic>.from(json['parsedReceipt'] as Map)
+          : null,
       originalResponse: json['originalResponse'] as String?,
     );
   }
diff --git a/pubspec.yaml b/pubspec.yaml
index a266fba1..7a023cbe 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -2,7 +2,7 @@ name: flutter_inapp_purchase
 description: In App Purchase plugin for flutter. This project has been forked by
   react-native-iap and we are willing to share same experience with that on
   react-native.
-version: 6.3.0
+version: 6.3.1
 homepage: https://github.com/hyochan/flutter_inapp_purchase/blob/main/pubspec.yaml
 environment:
   sdk: ">=3.0.0 <4.0.0"


Test patch:
diff --git a/test/proration_mode_validation_test.dart b/test/proration_mode_validation_test.dart
new file mode 100644
index 00000000..b0e16ddd
--- /dev/null
+++ b/test/proration_mode_validation_test.dart
@@ -0,0 +1,110 @@
+import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  group('Proration Mode Validation Tests', () {
+    test(
+        'requestSubscription throws error when using deferred proration without purchaseToken',
+        () {
+      // Test that using deferred proration mode without purchaseToken throws an error
+      expect(
+        () => RequestSubscriptionAndroid(
+          skus: ['test_subscription'],
+          replacementModeAndroid: 4, // DEFERRED mode
+          subscriptionOffers: [],
+          // Missing purchaseTokenAndroid - should trigger assert in debug mode
+        ),
+        throwsAssertionError,
+      );
+    });
+
+    test('requestSubscription allows deferred proration with purchaseToken',
+        () {
+      // Test that using deferred proration mode with purchaseToken works
+      final request = RequestSubscriptionAndroid(
+        skus: ['test_subscription'],
+        replacementModeAndroid: 4, // DEFERRED mode
+        purchaseTokenAndroid: 'valid_purchase_token',
+        subscriptionOffers: [],
+      );
+
+      expect(request.skus, ['test_subscription']);
+      expect(request.replacementModeAndroid, 4);
+      expect(request.purchaseTokenAndroid, 'valid_purchase_token');
+    });
+
+    test('requestSubscription works without proration mode', () {
+      // Test that normal subscription request without proration works
+      final request = RequestSubscriptionAndroid(
+        skus: ['test_subscription'],
+        subscriptionOffers: [],
+      );
+
+      expect(request.skus, ['test_subscription']);
+      expect(request.replacementModeAndroid, null);
+      expect(request.purchaseTokenAndroid, null);
+    });
+
+    test('requestSubscription works with proration mode -1 (default)', () {
+      // Test that using default value (-1) doesn't require purchaseToken
+      final request = RequestSubscriptionAndroid(
+        skus: ['test_subscription'],
+        replacementModeAndroid: -1,
+        subscriptionOffers: [],
+      );
+
+      expect(request.skus, ['test_subscription']);
+      expect(request.replacementModeAndroid, -1);
+    });
+
+    test(
+        'requestSubscription throws for all proration modes without purchaseToken',
+        () {
+      // Test all Android proration modes that require purchaseToken
+      final prorationModes = [
+        1, // IMMEDIATE_WITH_TIME_PRORATION
+        2, // IMMEDIATE_WITHOUT_PRORATION
+        3, // IMMEDIATE_AND_CHARGE_PRORATED_PRICE
+        4, // DEFERRED
+        5, // IMMEDIATE_AND_CHARGE_FULL_PRICE
+      ];
+
+      for (final mode in prorationModes) {
+        expect(
+          () => RequestSubscriptionAndroid(
+            skus: ['test_subscription'],
+            replacementModeAndroid: mode,
+            subscriptionOffers: [],
+            // Missing purchaseTokenAndroid
+          ),
+          throwsAssertionError,
+          reason: 'Proration mode $mode should require purchaseToken',
+        );
+      }
+    });
+
+    test('requestSubscription works for all proration modes with purchaseToken',
+        () {
+      // Test all Android proration modes work with purchaseToken
+      final prorationModes = [
+        1, // IMMEDIATE_WITH_TIME_PRORATION
+        2, // IMMEDIATE_WITHOUT_PRORATION
+        3, // IMMEDIATE_AND_CHARGE_PRORATED_PRICE
+        4, // DEFERRED
+        5, // IMMEDIATE_AND_CHARGE_FULL_PRICE
+      ];
+
+      for (final mode in prorationModes) {
+        final request = RequestSubscriptionAndroid(
+          skus: ['test_subscription'],
+          replacementModeAndroid: mode,
+          purchaseTokenAndroid: 'valid_token',
+          subscriptionOffers: [],
+        );
+
+        expect(request.replacementModeAndroid, mode);
+        expect(request.purchaseTokenAndroid, 'valid_token');
+      }
+    });
+  });
+}
diff --git a/test/purchase_flow_test.dart b/test/purchase_flow_test.dart
index 062a0ad0..ee0b2c05 100644
--- a/test/purchase_flow_test.dart
+++ b/test/purchase_flow_test.dart
@@ -25,7 +25,13 @@ void main() {
             case 'getProducts':
             case 'getSubscriptions':
               final args = methodCall.arguments;
-              final productIds = args is List ? args.cast<String>() : null;
+              final productIds = args is Map
+                  ? (args['productIds'] as Iterable?)
+                      ?.map((e) => e.toString())
+                      .toList()
+                  : args is Iterable
+                      ? args.map((e) => e.toString()).toList()
+                      : null;
               final allProducts = _getMockProducts();
               final filteredProducts = productIds != null
                   ? allProducts
diff --git a/test/subscription_flow_test.dart b/test/subscription_flow_test.dart
index 7ea12e42..0e01ab5a 100644
--- a/test/subscription_flow_test.dart
+++ b/test/subscription_flow_test.dart
@@ -24,7 +24,13 @@ void main() {
               return true;
             case 'getSubscriptions':
               final args = methodCall.arguments;
-              final productIds = args is List ? args.cast<String>() : null;
+              final productIds = args is Map
+                  ? (args['productIds'] as Iterable?)
+                      ?.map((e) => e.toString())
+                      .toList()
+                  : args is Iterable
+                      ? args.map((e) => e.toString()).toList()
+                      : null;
               final allSubs = _getMockSubscriptions();
               final filteredSubs = productIds != null
                   ? allSubs