Fix: Wrong USD Price in TestFlight Instead of Local Currency

Fix: Wrong USD Price in TestFlight Instead of Local Currency
Quick answer: This is expected TestFlight behavior: StoreKit product metadata can return USD prices in TestFlight even when the tester is on another storefront. The actual charge uses the correct local price shown on Apple’s purchase sheet. Log Storefront.current to confirm it is StoreKit, not your app code.

If your app shows USD 5.99 in TestFlight but EUR 6.99 in a development build on the same iPhone — while Apple’s purchase sheet shows EUR 6.99 — your code is almost certainly correct. This is a StoreKit metadata localization difference between development-signed builds and TestFlight builds.

It is confusing because everything else looks right: you are not hardcoding prices, you are using the localized price from StoreKit, and App Store Connect has the right tiers for Bulgaria and the US. Here is how to prove it and what to tell testers.

Why this happens

Development builds and TestFlight builds do not resolve the storefront the same way.

A development-signed build running from Xcode uses the Sandbox Apple Account signed in on the device under Settings > App Store > Sandbox Account. If that sandbox tester is set to Bulgaria, Product.price and priceFormatStyle come back localized for Bulgaria. That is why both a bare StoreKit 2 probe and the full Flutter app show EUR 6.99 when sideloaded.

A TestFlight build runs in the sandbox purchase environment, but it is tied to the tester’s real Apple ID, not the separate Sandbox Account. StoreKit product metadata in TestFlight can fall back to USD / US storefront data even when the device, App Store region, and purchase sheet are Bulgarian. The purchase itself still goes through at the correct local price, which is why tapping Buy shows EUR 6.99 on Apple’s native confirmation sheet.

In short: your Flutter in_app_purchase display layer is just rendering what StoreKit gave it. When TestFlight gives it USD metadata, the app shows USD. When the system purchase sheet appears, iOS re-resolves the correct storefront and shows EUR.

How to fix it

There is no code fix that forces TestFlight to return the Bulgarian price, because the wrong value is coming from StoreKit before your app ever formats it. The goal is to verify that, rule out your app, and avoid shipping a workaround that breaks production.

1. Prove it is StoreKit, not Flutter, with a TestFlight probe

The most important diagnostic is to get the same logging build into TestFlight. Developers often test only a local StoreKit probe via Xcode and conclude the Flutter plugin is broken when TestFlight differs. Upload that probe to TestFlight too.

Create a minimal native probe or add temporary logging to your app and run it from TestFlight on the same Bulgarian iPhone:

import StoreKit

func logStoreKitLocalization(for productID: String) async {
    do {
        let storefront = try await Storefront.current
        print("Storefront countryCode: \(storefront.countryCode)")
        print("Storefront id: \(storefront.id)")

        let products = try await Product.products(for: [productID])
        guard let product = products.first else {
            print("Product not found: \(productID)")
            return
        }

        print("displayPrice: \(product.displayPrice)")
        print("price: \(product.price)")
        print("currencyCode: \(product.priceFormatStyle.currencyCode)")
    } catch {
        print("StoreKit lookup failed: \(error)")
    }
}

Steps:

  1. Replace productID with your non-consumable product ID, for example com.example.app.pro.
  2. Archive and upload build 23 or later to App Store Connect and release it to your internal TestFlight group.
  3. Install that TestFlight build on the Bulgarian iPhone, launch it, and collect the console output via Xcode > Window > Devices and Simulators > View Device Logs, or send the strings to your own debug screen.
  4. Compare to the same build installed development-signed from Xcode.

If the TestFlight log shows countryCode: USA and currencyCode: USD while the dev-signed log shows countryCode: BGR and currencyCode: EUR, you have confirmed TestFlight storefront metadata behavior. Do not change your price formatting code.

2. Check that you are displaying StoreKit prices correctly

Make sure you are not accidentally caching an old product or converting currencies yourself. This will not fix TestFlight returning USD, but it rules out a second bug.

For StoreKit 2, display the price exactly as provided:

import StoreKit
import SwiftUI

struct PriceView: View {
    let product: Product

    var body: some View {
        Text(product.displayPrice)
    }
}

For Flutter in_app_purchase, use the price string from ProductDetails directly. Do not do this:

  • Do not store price in SharedPreferences / UserDefaults and reuse it across builds.
  • Do not parse the numeric amount and reformat with NumberFormatter set to another locale.
  • Do not map productID to a hardcoded "$5.99" fallback shown when lookup is slow.

Also call queryProductDetails fresh on each TestFlight launch during testing. If you query once on first launch in the US and cache it, later Bulgarian testers will keep seeing USD.

Verify in App Store Connect:

  1. Go to Apps > your app > Monetization > In-App Purchases > your non-consumable.
  2. Open Pricing and confirm Bulgaria is EUR 6.99 and United States is USD 5.99.
  3. Confirm the product status is Approved or Ready to Submit and attached to the TestFlight build’s version.

If App Store Connect is correct and Apple’s purchase sheet shows EUR 6.99, your catalog is correct.

3. Validate the real charge and document it for QA

Since you cannot change TestFlight metadata localization, treat the purchase sheet as source of truth during testing.

  1. In the TestFlight build, note the in-app list price (likely USD 5.99).
  2. Tap Buy and screenshot Apple’s system sheet (should be EUR 6.99).
  3. Complete the sandbox purchase with a sandbox tester and confirm no error.
  4. Add a TestFlight tester note: “Known TestFlight issue: list price may show USD, checkout charges correct local price.”

Do not ship a workaround like forcing EUR based on Locale.current or device region. In production, Product.displayPrice is correct for the customer’s actual App Store storefront. Overriding it with device locale will break travelers, users with a US account living in Bulgaria, and price-increase consent flows.

If you need automated testing, test pricing logic with Xcode StoreKit Configuration files (.storekit) where you control the storefront, and test real localization in production with a phased release or promo codes rather than relying on TestFlight screenshots.

How to prevent it

  • Always render displayPrice / ProductDetails.price verbatim — never reformat or hardcode currency symbols.
  • Never cache Product responses across installs; query on launch and after AppStore.sync() if needed.
  • Keep a native StoreKit 2 logging build you can push to TestFlight to check Storefront.current.countryCode when testers report pricing bugs.
  • Document for QA that TestFlight list prices can show USD while the Apple purchase sheet shows the correct local total.
  • Verify final pricing in App Store Connect and with the production purchase sheet, not with TestFlight list UI screenshots.

FAQ

Does this mean customers will be charged USD 5.99 instead of EUR 6.99?

No. The charge follows the storefront resolved at purchase time, which is what Apple’s system sheet shows. If the sheet says EUR 6.99, that is the correct sandbox and production behavior for that Bulgarian account.

Why does the same iPhone show EUR in dev builds and USD in TestFlight?

Because dev builds use the Sandbox Account region set in Settings, while TestFlight uses the real Apple ID in a sandbox purchase context. That account linkage can cause StoreKit to return US metadata for the product list in TestFlight only.

Should I file a bug or change my Flutter plugin version?

Updating the plugin will not help if a native StoreKit 2 Product.products(for:) call also returns USD in TestFlight. Log Storefront.current and priceFormatStyle.currencyCode from TestFlight first. If native StoreKit shows the same USD result, it is platform behavior to work around with tester communication, not app code.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.