purchases-flutter icon indicating copy to clipboard operation
purchases-flutter copied to clipboard

ERROR: 🍎‼️ There was a problem with the App Store in both Production and Sandbox

Open AnAlpaca opened this issue 2 years ago • 16 comments

Describe the bug I have a issue at the moment where a user can not make a purchase on the Apple App Store in BOTH Production and Testing, which rules out Sandbox accounts etc (have also dived down said route). The same code is working perfectly for Android.

  1. Environment
    1. Output of flutter doctor
    Doctor summary (to see all details, run flutter doctor -v):
    [✓] Flutter (Channel stable, 2.8.1, on macOS 11.4 20F71 darwin-x64, locale
        en-ZA)
    [✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
    [✓] Chrome - develop for the web
    [✓] Android Studio (version 2021.1)
    [✓] IntelliJ IDEA Ultimate Edition (version 2020.3.2)
    [✓] VS Code (version 1.63.2)
    [✓] VS Code (version 1.43.0)
    [✓] VS Code (version 1.43.0)
    [✓] Connected device (1 available)
  1. How widespread is the issue. Percentage of devices affected. Affects devices only

  2. Debug logs that reproduce the issue

flutter: \^[[38;5;12m│ 💡 now trying to purchase<…>

[Purchases] - DEBUG: ℹ️ Vending Offerings from cache
[Purchases] - DEBUG: ℹ️ makePurchase
[Purchases] - DEBUG: 💰 Purchasing product from package  - finMonitor_1500_1y_1w0 in Offering App Access
[Purchases] - DEBUG: ℹ️ PaymentQueue updatedTransaction: finMonitor_1500_1y_1w0 (null) ((null)) (null) - 0
<SKPaymentQueue: 0x2824dfa00>: Payment completed with error: Error Domain=ASDErrorDomain Code=500 "Unhandled exception" UserInfo={NSUnderlyingError=0x282863960 {Error Domain=AMSErrorDomain Code=301 "Invalid Status Code" UserInfo={NSLocalizedDescription=Invalid Status Code, NSLocalizedFailureReason=The response has an invalid status code}}, NSLocalizedFailureReason=An unknown error occurred, NSLocalizedDescription=Unhandled exception}
[Purchases] - DEBUG: ℹ️ PaymentQueue updatedTransaction: finMonitor_1500_1y_1w0 (null) (Error Domain=SKErrorDomain Code=0 "An unknown error occurred" UserInfo={NSLocalizedDescription=An unknown error occurred, NSUnderlyingError=0x2828602a0 {Error Domain=ASDErrorDomain Code=500 "Unhandled exception" UserInfo={NSUnderlyingError=0x282863960 {Error Domain=AMSErrorDomain Code=301 "Invalid Status Code" UserInfo={NSLocalizedDescription=Invalid Status Code, NSLocalizedFailureReason=The response has an invalid status code}}, NSLocalizedFailureReason=An unknown error occurred, NSLocalizedDescription=Unhandled exception}}}) (null) - 2
[Purchases] - ERROR: 🍎‼️ There was a problem with the App Store.
[Purchases] - DEBUG: 💰 Finishing transaction finMonitor_1500_1y_1w0 (null) ((null))
[Purchases] - DEBUG: ℹ️ PaymentQueue removedTransaction: finMonitor_1500_1y_1w0 (null) ((null) Error Domain=SKErrorDomain Code=0 "An unknown error occurred" UserInfo={NSLocalizedDescription=An unknown error occurred, NSUnderlyingError=0x2828602a0 {Error Domain=ASDErrorDomain Code=500 "Unhandled exception" UserInfo={NSUnderlyingError=0x282863960 {Error Domain=AMSErrorDomain Code=301 "Invalid Status Code" UserInfo={NSLocalizedDescription=Invalid Status Code, NSLocalizedFailureReason=The response has an invalid status code}}, NSLocalizedFailureReason=An unknown error occurred, NSLocalizedDescription=Unhandled exception}}}) {
    NSLocalizedDescription = "An unknown error occurred";
    NSUnderlyingError = "Error Domain=ASDErrorDomain Code=500 \"Unhandled exception\" UserInfo={NSUnderlyingError=0x282863960 {Error Domain=AMSErrorDomain Code=301 \"Invalid Status Code\" UserInfo={NSLocalizedDescription=Invalid Status Code, NSLocalizedFailureReason=The response has an invalid status code}}, NSLocalizedFailureRea

flutter: \^[[38;5;196m│ ⛔ Paywall Upgrade - Exception caught: Error code PurchasesErrorCode.storeProblemError<…>
  1. Steps to reproduce, with a description of expected vs. actual behavior

Here is the code I am using to make the purchase:

onPressed: () async {  
showLoadingBanner(context, 'Purchase is progress...');  
try {    
loggerInfo(logMessage: 'now trying to purchase');    
_purchaserInfo = await Purchases.purchasePackage(widget.package);    

loggerInfo(logMessage: 'purchase completed');    
paywallData.isPro = _purchaserInfo.entitlements.all["App Access"]!.isActive;    

loggerDebug(logMessage: 'is user pro? ${paywallData.isPro}');

The code continues for some dialog handling etc, but it never reaches the loggerInfo(logMessage: 'purchase completed');

The widget.package is passed from the widget above, and is fetched as follows:

return FutureBuilder(
        future: _fetchOfferings(),
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          if (snapshot.connectionState == ConnectionState.done && snapshot.data != null) {
            Offerings offerings = snapshot.data;
            final offering = offerings.current;
            if (offering != null) {
              final annual = offering.annual;
              if (annual != null) {
                return TopBarAgnosticNoIcon(
                  text: "Welcome!",
                  style: kSendButtonTextStyle(context),
                  uniqueHeroTag: 'purchase_screen',
                  child: Scaffold(
                      backgroundColor: Theme.of(context).backgroundColor,
                      body: Stack(children: [
                        Center(
                          child: SingleChildScrollView(
                              child: Column(
                            mainAxisSize: MainAxisSize.min,
                            mainAxisAlignment: MainAxisAlignment.start,
                            crossAxisAlignment: CrossAxisAlignment.center,
                            children: <Widget>[
                              Padding(
                                padding: const EdgeInsets.all(18.0),
                                child: Image.asset("assets/logos/finMonitor_Transparent.png"),
                              ),
                              Text(
                                'Select the subscription plan to get started.',
                                textAlign: TextAlign.center,
                                style: kSendButtonTextStyle(context),
                              ),
                              Padding(
                                padding: const EdgeInsets.all(8.0),
                                child: PurchaseButton(package: annual),

The code used for the _fetchOfferings future function is:

Future<Offerings?> _fetchOfferings() async {
    Offerings? offerings;
    try {
      offerings = await Purchases.getOfferings();
    } on PlatformException catch (e) {
      loggerError(logMessage: 'Paywall Upgrade - _fetchOfferings: Exception caught $e');
      if (Platform.isAndroid) {
        _showSnackBar(context, 'Issue fetching information for purchase, please check you are signed into the play store');
      } else {
        _showSnackBar(context, 'Issue fetching information for purchase, please check you are signed into the app store');
      }
    }
    return offerings;
  }

Lastly the the SDK is configured as shown in the documentation, with a few conditional tweaks:

    await Purchases.setDebugLogsEnabled(true)
        .whenComplete(() => loggerInfo(logMessage: 'setDebugLogsEnabled completed'));
    if (Platform.isAndroid){
      if ((username != null && username != '') && (password != null && password != '') && (custKey != null && custKey != '')) {
        await Purchases.setup(apiAndroidRevenueKey, appUserId: '$username-$custKey');
      }else{
        await Purchases.setup(apiAndroidRevenueKey);
      }

    }
    else if (Platform.isIOS){
      if ((username != null && username != '') && (password != null && password != '') && (custKey != null && custKey != '')) {
        await Purchases.setup(apiIosRevenueKey, appUserId: '$username-$custKey');
      }else{
        await Purchases.setup(apiIosRevenueKey);
      }

    loggerInfo(logMessage: 'After purchases.setup');
  }
  1. Other information (e.g. stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, etc.)

Additional context Things I have explored/tried:

Deleting and creating sandbox accounts for Apple. Checked I have no outstanding agreements with Apple. Made sure in-app purchases in enabled. Tried configuring the purchases_flutter package with both legacy and new api key. I am sure a ton more, that I don’t remember. The error looks as follows:

Any help will be hugely appreciated.

AnAlpaca avatar Feb 10 '22 07:02 AnAlpaca

Hello! It doesn't seem like we have quite enough information to send this to a human yet to help out. We would love if you could provide more details about your issue by following the template without modifying any of the pre-filled text. If you're looking for support, head over to our Community.

ghost avatar Feb 10 '22 07:02 ghost

I have edited my post above to fit the template

AnAlpaca avatar Feb 10 '22 07:02 AnAlpaca

Thanks for the thorough report. This is a very weird error. I've never seen anything like this before. Looking at the logs, it looks like it's coming directly from Apple, so I am curious about one thing. You say "where a user can not make a purchase on the Apple App Store in BOTH Production and Testing", do you mean this happens to all users, or to a specific user? I wonder if there's something wrong with that particular account or the device. Have you tried in other device? Other network? Are you using a VPN or something that could be interfering with your network requests?

Thanks! Looking forward to your answers

vegaro avatar Feb 10 '22 13:02 vegaro

Hi Vegaro, so the app is on the App Store (not sure how it got through), but luckily we have not started marketing hence, we have no "real" customers yet.

I have used a few iPhones (7, 8+, 11) with unrelated account information to our Apple Dev Account (as I just assumed in testing that somehow I have messed up the sandbox accounts etc), and all of which have given the error.

The testing I have done on have been on different networks, we are based in South Africa if that means anything, but no VPNs are being used by said users, and I have tried over wifi and on cellular networks. Our servers do block traffic trying to perform a login from outside South Africa, but the app itself can communicate freely, for example we receive push notification from firebase etc. Do you think the network request is maybe being rerouted or something along those lines? Apologies for my poor networking knowledge.

AnAlpaca avatar Feb 10 '22 13:02 AnAlpaca

Hey @AnAlpaca! Jumping in to try and help here. Since it's coming from Apple, we don't have a lot of insight into why they would be returning this error- usually this is only seen in sandbox, and we don't have any indication currently that Apple's production API is down. There's a few other things we can check though:

  • Are you certain it's this same error you're encountering in production?
  • How are you collecting these logs in production?
  • Are you certain you are using the App Store version of the app? (you'd have to delete the testing version, and re-download the production version from the App Store)
  • If so, are you certain that your in-app products are in the Approved state and your app is Ready for Sale?
  • How long has your app been approved on the App Store?

codykerns avatar Feb 10 '22 15:02 codykerns

Hi @codykerns Really appreciate all the assistance. I am currently at home at the moment as its 6.30pm here currently, but as soon as Im back in the office at 8am (gmt +2) tomorrow ill respond in full. What I can potentially do (just an idea) is generate a promo code for you guys to get past the first purchase on the actual store and then from there, you should be able to try to subscribe. I have had a the app on the store before and everything worked fine, including the subscription, ill include more details tomorrow but yes the in app product are definitely approved and ready for sale. The production version was 100% downloaded on at least 3 of the devices as they were installed from the store on a few friends devices and have never made contact with my pc (ie dev version) and arent part of the testflight accounts.

AnAlpaca avatar Feb 10 '22 16:02 AnAlpaca

Hi @codykerns @vegaro.

So I really am just hoping with all of this that I have been stupid somewhere along the way, because obviously that would be the easiest outcome.

But to answer with a little more detail:

Are you certain it's this same error you're encountering in production?

  • I can only be about 70% sure, however the dialog handling in the function places the exactly ErrorCode and description onto a dialog for the user, so I can see that it is saying "Error code PurchasesErrorCode.storeProblemError". But yes there is debug there I can't see, as its in production, I assumed (potentially wrongly) that it would be the same cause as its the same error, on multiple devices.

How are you collecting these logs in production?

  • To be honest I have yet to find an efficient way to do this on IOS, so no logs, only as said above. Apologies for this, if you have any suggestions on how to do this I will gladly implement it and run some checks.

Are you certain you are using the App Store version of the app? (you'd have to delete the testing version, and re-download the production version from the App Store)

  • As said above the production version was 100% downloaded on at least 3 of the devices as they were installed from the store on a few friends devices and have never made contact with my pc (ie. dev version) and aren't part of the test-flight accounts.

If so, are you certain that your in-app products are in the Approved state and your app is Ready for Sale? Screenshot_20220211_100737

How long has your app been approved on the App Store? Screenshot_20220211_101217

Some extra context and added information:

  • The has been released on the Apple App Store before and the product and subscriptions were working fine on both Sandbox and Production, this was using Flutter 2.5, and package version was 3.4.3. However the subscription logic has never changed (besides for some tidying and logging). This was between the 7th of October to 27th January.

Let me know if more testing/debugging is required and what I can do to explore this issue further. I understand that I may be a complete idiot, so feel to let me know ;)

AnAlpaca avatar Feb 11 '22 08:02 AnAlpaca

Hey @AnAlpaca you're certainly not an idiot! This is a really confusing and frustrating error... I'm going to continue digging on our side, but one question:

I see the log ⛔ Paywall Upgrade - Exception caught: on the failed purchase. You mention that the exact error code and description are tied to a dialog, can you post the code for that? I'm only seeing the 'Paywall Upgrade - _fetchOfferings: log prefix in the code you posted. I'm wondering if that might help us pinpoint what's going on. Let me know if I somehow missed that particular code in your post...

beylmk avatar Feb 15 '22 01:02 beylmk

Hi @beylmk,

The actual error handing was not shown above, but the error that displays is the same as that of the logs 'PurchasesErrorCode.storeProblemError<…>'

But see below, it is the code for the button the user presses, excuse the untidiness, but you will see that it calls the 'on PlatformException' and the last else statement.

class PurchaseButton extends StatefulWidget {
  final Package package;

  PurchaseButton({required this.package});

  @override
  _PurchaseButtonState createState() => _PurchaseButtonState();
}

class _PurchaseButtonState extends State<PurchaseButton> {
  late PurchaserInfo _purchaserInfo;

  void _successfulPurchase() {
    BlocProvider.of<SessionCubit>(context).showSession();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(top: 18.0),
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(elevation: 4, primary: Colors.teal, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))),
        child: Container(
          width: MediaQuery.of(context).size.width / 1.5,
          padding: const EdgeInsets.all(10.0),
          child: Column(
            children: [
              Padding(
                padding: const EdgeInsets.only(bottom: 5),
                child: Text(
                  'Purchase',
                  style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold, color: Theme.of(context).textTheme.bodyText1!.color),
                  textAlign: TextAlign.center,
                ),
              ),
              Text(
                '${Platform.isAndroid ? widget.package.product.copyWith(description: 'finMonitor Access').description : widget.package.product.title} - Yearly Subscription\n${widget.package.product.priceString}',
                style: TextStyle(fontSize: 15, color: Theme.of(context).textTheme.bodyText1!.color),
                textAlign: TextAlign.center,
              ),
            ],
          ),
        ),
        onPressed: () async {
          showLoadingBanner(context, 'Purchase is progress...');
          try {
            loggerInfo(logMessage: 'now trying to purchase');
            _purchaserInfo = await Purchases.purchasePackage(widget.package);
            loggerInfo(logMessage: 'purchase completed');

            paywallData.isPro = _purchaserInfo.entitlements.all["App Access"]!.isActive;

            loggerDebug(logMessage: 'is user pro? ${paywallData.isPro}');
            if (paywallData.isPro) {
              clearBanner(context);
              showModalBottomSheet<void>(
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)),
                ),
                backgroundColor: Theme.of(context).backgroundColor,
                isScrollControlled: true,
                enableDrag: true,
                isDismissible: false,
                context: context,
                builder: (BuildContext context) => PurchaseSheet(
                  title: 'Successful!',
                  buttonTitle: 'Proceed',
                  icon: Icon(
                    Icons.task_alt_outlined,
                    color: Colors.green,
                    size: 40,
                  ),
                  onPressed: () {
                    Navigator.of(context).pop();
                    _successfulPurchase();
                  },
                ),
              );
            } else {
              clearBanner(context);
              showModalBottomSheet<void>(
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)),
                ),
                backgroundColor: Theme.of(context).backgroundColor,
                isScrollControlled: true,
                isDismissible: false,
                enableDrag: true,
                context: context,
                builder: (BuildContext context) => PurchaseSheet(
                  title: 'Unsuccessful!',
                  buttonTitle: 'Dismiss',
                  message: 'Purchase was unsuccessful, please close the app and try again later.',
                  icon: Icon(
                    Icons.sentiment_dissatisfied_outlined,
                    color: Colors.red,
                    size: 40,
                  ),
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                ),
              );
            }
          } on PlatformException catch (e) {
            clearBanner(context);
            var errorCode = PurchasesErrorHelper.getErrorCode(e);
            loggerError(logMessage: 'Paywall Upgrade - Exception caught: Error code $errorCode');
            if (errorCode == PurchasesErrorCode.productAlreadyPurchasedError) {
              loggerInfo(logMessage: "User has active subscription");
              showModalBottomSheet<void>(
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)),
                ),
                backgroundColor: Theme.of(context).backgroundColor,
                isScrollControlled: true,
                enableDrag: true,
                isDismissible: false,
                context: context,
                builder: (BuildContext context) => PurchaseSheet(
                  title: 'Already Purchased',
                  buttonTitle: 'Proceed',
                  onPressed: () {
                    Navigator.of(context).pop();
                    _successfulPurchase();
                  },
                  icon: Icon(
                    Icons.task_alt_outlined,
                    size: 40,
                    color: Colors.green,
                  ),
                  message: 'Looks like you already have a subscription, press Proceed to continue.',
                ),
              );
            } else if (errorCode == PurchasesErrorCode.purchaseCancelledError) {
              showModalBottomSheet<void>(
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)),
                ),
                backgroundColor: Theme.of(context).backgroundColor,
                isScrollControlled: true,
                enableDrag: true,
                context: context,
                builder: (BuildContext context) {
                  double paddingBottom = MediaQuery.of(context).viewInsets.bottom;
                  return Padding(
                    padding: EdgeInsets.only(bottom: paddingBottom),
                    child: PurchaseSheet(
                      title: 'Cancelled',
                      buttonTitle: 'Dismiss',
                      onPressed: () {
                        Navigator.of(context).pop();
                      },
                      icon: Icon(
                        Icons.cancel_outlined,
                        size: 40,
                        color: Colors.blue,
                      ),
                      message: 'Purchase has been cancelled.',
                    ),
                  );
                },
              );
            } else {
              showModalBottomSheet<void>(
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)),
                ),
                backgroundColor: Theme.of(context).backgroundColor,
                isScrollControlled: true,
                enableDrag: true,
                context: context,
                builder: (BuildContext context) => PurchaseSheet(
                  title: 'Purchase Error',
                  buttonTitle: 'Dismiss',
                  onPressed: () {
                    MiStorageService.instance.clearCredentialsUsernamePassword();
                    Navigator.of(context).pop();
                  },
                  icon: Icon(
                    Icons.sentiment_dissatisfied_outlined,
                    size: 40,
                    color: Colors.red,
                  ),
                  message: 'An error was encountered, please close the app and try again later.\n $errorCode',
                ),
              );
            }
          }
        },
      ),
    );
  }
}
`

AnAlpaca avatar Feb 15 '22 09:02 AnAlpaca

Hey @AnAlpaca, got it, thank you! So we know it's a storeProblemError but we don't necessary know the error details/message in prod.

This is tricky. Here's my thought: Can you try running our sample app with your API key and bundle ID, and see if you're able to make a purchase in sandbox? That could help us narrow down what's going on.

We tried downloading your app but since it's restricted by region, we can't (we tried in US). One thing we did notice is that no API calls have come from your app since February 11th, which leads us to think something bigger is going on here...perhaps both our APIs and Apple are being blocked or something. Have you tried running the app since then?

beylmk avatar Feb 15 '22 20:02 beylmk

Hi @beylmk, it is very possible that there has been no activity as I have been doing some back-end work on another project for the past week. But I have did do a test subscription last night on Android, so it that doesn't show up then there is something not quite right (or were you meaning IOS specifically). I will try run the app on IOS today and post on here when i have done so.

I like your plan, as it rules out any general account issues, I will download and give that a go at the same time as said above and let you know what I find.

Just wanted to say that i am really impressed by all your help on this matter, and really appreciate it!

AnAlpaca avatar Feb 16 '22 07:02 AnAlpaca

Happy to help!! I'm really hoping we can figure this out...

Just a quick update -- I do see Android logs coming through to our backend in the past couple days but still no iOS ones. Let me know when/if you're able to try iOS and we can check again.

beylmk avatar Feb 17 '22 17:02 beylmk

This issue has been automatically marked as stale due to inactivity. It will be closed if no further activity occurs. Please reach out if you have additional information to help us investigate further!

stale[bot] avatar Feb 24 '22 21:02 stale[bot]

any news? i am having the same error still

Lazizbek97 avatar Dec 05 '23 08:12 Lazizbek97

Can you provide more context on what other information you get with the error?

NachoSoto avatar Dec 06 '23 19:12 NachoSoto

Sorry for taking far to long to reply to this, and sadly im going to be a little frustrating because I can't give an exact solution. I ended up moving away from the problem for about two months due to time contraints, I then came back to it and re looked at it with some fresher eyes, and since then there had been a few updates including I think store kit 2, which I enabled.

But more importantly that I was getting really frustrated that some other apps on the device, scuh as the appstore, sometimes it would connect but then I couldnt download anything, and everything else in the app was perfect. I realized that it was on our office wifi, when things would work but be flaky and on mobile data things would be stable.

So I checked the firewall (fortigate) and everything looked fine, I had the correct rules in place. I then made an exception in the web filter ( that would live check the packets of data), and I think that is what ended up solving the issue for me, no one external testers have experienced this issue in the wild either.

Another piece of information, is that I also had deleted my apple sandbox account, left it for that period, and then made new one as I was worried that I was doing something incorrectly and they were potentialed blacklisting the account.

AnAlpaca avatar Jan 08 '24 20:01 AnAlpaca