diff --git a/node_modules/@capgo/native-purchases/android/src/main/java/ee/forgr/nativepurchases/NativePurchasesPlugin.java b/node_modules/@capgo/native-purchases/android/src/main/java/ee/forgr/nativepurchases/NativePurchasesPlugin.java index da1584f..053878a 100644 --- a/node_modules/@capgo/native-purchases/android/src/main/java/ee/forgr/nativepurchases/NativePurchasesPlugin.java +++ b/node_modules/@capgo/native-purchases/android/src/main/java/ee/forgr/nativepurchases/NativePurchasesPlugin.java @@ -693,12 +693,32 @@ public class NativePurchasesPlugin extends Plugin { ProductDetails.SubscriptionOfferDetails selectedOfferDetails = null; assert productDetailsItem.getSubscriptionOfferDetails() != null; Log.d(TAG, "Available offer details count: " + productDetailsItem.getSubscriptionOfferDetails().size()); - for (ProductDetails.SubscriptionOfferDetails offerDetails : productDetailsItem.getSubscriptionOfferDetails()) { - Log.d(TAG, "Checking offer: " + offerDetails.getBasePlanId()); - if (offerDetails.getBasePlanId().equals(planIdentifier)) { - selectedOfferDetails = offerDetails; - Log.d(TAG, "Found matching plan: " + planIdentifier); - break; + // Trazo: if the app chose a specific offer (free trial, milestone, base plan), use exactly that + // offer token. If it is not among the offers Play returns for this user, stop the purchase + // instead of falling back to another offer with different terms. + if (offerToken != null && !offerToken.isEmpty()) { + for (ProductDetails.SubscriptionOfferDetails offerDetails : productDetailsItem.getSubscriptionOfferDetails()) { + if (offerToken.equals(offerDetails.getOfferToken())) { + selectedOfferDetails = offerDetails; + break; + } + } + if (selectedOfferDetails == null) { + Log.d(TAG, "Subscription offer token not found for product: " + productIdentifier); + closeBillingClient(); + call.reject("Offer token not found for subscription: " + productIdentifier); + return; + } + } + // Without an explicit offer token, keep the original behaviour: match by base plan, else first. + if (selectedOfferDetails == null) { + for (ProductDetails.SubscriptionOfferDetails offerDetails : productDetailsItem.getSubscriptionOfferDetails()) { + Log.d(TAG, "Checking offer: " + offerDetails.getBasePlanId()); + if (offerDetails.getBasePlanId().equals(planIdentifier)) { + selectedOfferDetails = offerDetails; + Log.d(TAG, "Found matching plan: " + planIdentifier); + break; + } } } if (selectedOfferDetails == null) { diff --git a/node_modules/@capgo/native-purchases/android/src/main/java/ee/forgr/nativepurchases/ProductPayloadMapper.java b/node_modules/@capgo/native-purchases/android/src/main/java/ee/forgr/nativepurchases/ProductPayloadMapper.java index a7122d7..f2695cd 100644 --- a/node_modules/@capgo/native-purchases/android/src/main/java/ee/forgr/nativepurchases/ProductPayloadMapper.java +++ b/node_modules/@capgo/native-purchases/android/src/main/java/ee/forgr/nativepurchases/ProductPayloadMapper.java @@ -211,7 +211,12 @@ final class ProductPayloadMapper { List phases = offer.getPricingPhases() == null ? List.of() : offer.getPricingPhases().getPricingPhaseList(); - ProductDetails.PricingPhase phase = findStandardPricingPhase(phases); + // Trazo: the price of an offer is its first phase (what Google charges first: the free trial or the discounted + // first year), not the renewal phase, which is just the base plan price. + ProductDetails.PricingPhase phase = findIntroductoryPricingPhase(phases); + if (phase == null) { + phase = findStandardPricingPhase(phases); + } if (phase == null) { continue; }