Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promotional Offers not Working - Unable to Purchase #2715

Open
jlafosse opened this issue Mar 28, 2024 · 2 comments
Open

Promotional Offers not Working - Unable to Purchase #2715

jlafosse opened this issue Mar 28, 2024 · 2 comments

Comments

@jlafosse
Copy link

jlafosse commented Mar 28, 2024

Description

When attempting to use apple promotional offers for subscriptions I consistently receive the popup that says "Unable to Purchase - Contact the developer for more information.". I have spent the past couple of days trying to figure this out, but I have hit a dead end. Here is the process that I go through in order to receive the error:

  1. I create a brand new sandbox user on AppStoreConnect.
  2. I subscribe to a 1 month subscription that has an introductory offer sku premium_1m_intro_a
  3. I let the subscription renewals run their course - 3 mins each - 12 times typically. The final server notification type is EXPIRED
  4. I then verify that the sandbox subscription is expired on my phone in Settings > App Store > Sandbox Account > Subscriptions
  5. I then verify that the user is no longer eligible for intro offers using: IapIosSk2.isEligibleForIntroOffer(MY_GROUP_ID)
  6. Now when I attempt to subscribe to the (non-intro) subscription sku premium_1m using the premium_1m_20 promotional offer id, I always get the "Unable to Purchase" error.
  7. I have tried restarting my phone & signing out/in of my sandbox user... to no avail.

App Store Connect Subscriptions Setup

Subscription Group: premium
Subscriptions:
    - premium_1m
        - premium_1m_20 (pay-up-front promotional offer)
        - premium_1m_25 (pay-up-front promotional offer)
    - premium_1m_intro_a
    - premium_1m_intro_b
    - premium_6m
        - premium_6m_30 (pay-up-front promotional offer)
        - premium_6m_40 (pay-up-front promotional offer)
    - premium_6m_intro_a
    - premium_6m_intro_b
    - premium_1y

Additional Notes

  • I can purchase non-offer subscriptions (1 month, 6 month and 12 month) without issue.
  • I can purchase subscriptions with introductory offers without issue.
  • Subscriptions and Intro offers have been running on my production app for sometime without issue.
  • I have verified that my promotional offer premium_1m_20 is an active/correct identifier and a child of the premium_1m subscription identifier.
  • I have verified that my In-App-Purchase key has been created, active and correct under AppStoreConnect > Users/Access > Integrations > In-App-Purchases
  • I have verified the private p8 key by running openssl ec -in iap-offers-key.p8 -pubout -out public_key.pem on my server, which outputs a valid public_key.pem.
  • My server-side offer signature creation is done in php. I have triple checked this to ensure that it follows all of the requirements listed here: Generating a signature for promotional offers
  • In the event that there could possibly be something wrong with my server-side php signature creation, I have downloaded and installed the Apple sample node script Generating a Promotional Offer Signature on the Server. I then added my private key and public key id to my env, and ran Apple's script. I took the output of their signature and hard-coded the output directly into my RNIAP.requestSubscription() and received the same error.
  • I have added a try/catch block around the RNIAP.requestSubscription() and get a similar error as the popup - [Error: Purchased failed for sku:premium_1m: Unable to Complete Purchase]

Code Sample

setup({ storekitMode: 'STOREKIT2_MODE' });
const result = await initConnection();
...
...

const productId = 'premium_1m';
const offerId = 'premium_1m_20';
const userUuid = '268482a9-312d-4f29-97fe-e659b400f504';

const signedOffer = await getSignedOfferFromServer(productId, offerId, userUuid);

/* signedOffer output:
{
    "identifier": "premium_1m_20", 
    "keyIdentifier": "APPLEKEYID", 
    "nonce": "6ffb4558-d5f1-424d-b355-ab2ff11c20fc", 
    "signature": "MEQXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 
    "timestamp": 1711569543000
}
*/

try {
    await requestSubscription({
        'appAccountToken': userUuid, 
        'sku': productId, 
        'withOffer': signedOffer
    });
} catch(err) {
    console.log(err);
    
    /* Error output:
        [Error: Purchased failed for sku:premium_1m: Unable to Complete Purchase]
    */
}

Environment:

react-native-iap: 12.12.2
react-native: 0.72.6
expo: 49.0.0
Platforms: iOS v17.3.1

Screenshots

@jlafosse
Copy link
Author

jlafosse commented Apr 1, 2024

Upon further testing, I am able to successfully use promotional offers in STOREKIT1_MODE, so this looks like there may be an issue with STOREKIT2_MODE. I have verified that the minimum IOS build version is 15.0 in my IOS/Podfile. I also verified that I am indeed running in SK2 mode by calling isIosStorekit2() - which returns true and using IapIosSk2.isEligibleForIntroOffer() - which does in fact return the correct eligibility based upon whether I have previously used an intro offer or not.

Perhaps someone else who has posted a similar issue with promotional offers could confirm as to whether or not they were using SK1 or SK2 mode. - #2628

@jlafosse
Copy link
Author

jlafosse commented Apr 1, 2024

I think I figured out what is going on. My server-side script that is generating the signature is returning it as base64 encoded. I am not overly familiar with swift but on line 706 of RNIapIosSk2.swift it looks like the signature is expected to be type data which is then converted to utf8...?

Changing the following on line 706 of RNIapIosSk2.swift fixes the issue:

let signature = signature.data(using: .utf8)

Change To:

let signature = Data(base64Encoded: signature)

Is it a correct assumption that the back-end server script should return a base64 encoded signature? The apple docs and demo script seem to imply so...

https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/subscriptions_and_offers/generating_a_signature_for_promotional_offers#3342577

https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/subscriptions_and_offers/generating_a_promotional_offer_signature_on_the_server

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant