Skip to content

Fixed purchasing with PromotionalOffers using StoreKit 2#2020

Merged
NachoSoto merged 2 commits into
mainfrom
sk2-promo-purchases
Dec 9, 2022
Merged

Fixed purchasing with PromotionalOffers using StoreKit 2#2020
NachoSoto merged 2 commits into
mainfrom
sk2-promo-purchases

Conversation

@NachoSoto

@NachoSoto NachoSoto commented Nov 2, 2022

Copy link
Copy Markdown
Contributor

Fixes SDKONCALL-160, SDKONCALL-182, #2114, and RevenueCat/react-native-purchases#455.
Depends on #2118 and https://github.com/RevenueCat/khepri/pull/4843 / https://github.com/RevenueCat/khepri/pull/4852.

Changes:

[Purchases] - ERROR: 😿‼️ The information associated with this PromotionalOffer is not valid.
See https://rev.cat/ios-subscription-offers for more info. The signature generated by RevenueCat could not be decoded: signature 914

@NachoSoto NachoSoto added the pr:fix A bug fix label Nov 2, 2022
@NachoSoto NachoSoto requested a review from a team November 2, 2022 18:54
@NachoSoto NachoSoto force-pushed the sk2-promo-purchases branch from f40b7bb to a2d3047 Compare November 2, 2022 18:56
var sk2PurchaseOption: Product.PurchaseOption {
let signature: Data

if let decoded = Data(base64Encoded: self.signature) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping this would fix it, but the test is still failing :/

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm out of ideas, I'll probably have to file a Radar and pray? 🙏🏻

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️

Looks like one of our customers tried to do this without RC, using StoreKit directly, and he gets the same result, so that confirms the fact that this is an Apple bug.
He's opened a TSI with Apple, so hopefully they can address this soon.

In the mean time, I'll leave this PR open. I believe the fix here will still be required (doing the base64 decode). But once Apple fixes it on their end we can use this PR to confirm, as well as the now enabled test.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have the ticket # with Apple?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the original, but we reported it today as well under: FB11764024.

if let decoded = Data(base64Encoded: self.signature) {
signature = decoded
} else {
// TODO: log warning

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, definitely worth logging something loud here.
Stuff logged from here would also show up in the diagnostics API call, right? since we get offerings and parse products?

@NachoSoto NachoSoto Nov 3, 2022

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, definitely worth logging something loud here.

👍🏻

Stuff logged from here would also show up in the diagnostics API call, right? since we get offerings and parse products?

This property is only used when purchasing though.

NachoSoto added a commit that referenced this pull request Nov 4, 2022
…ons`

This changes the default back to `StoreKit 1`. We decided to do this for the following reasons:
- Purchasing with `PromotionalOffer`s does not work with StoreKit 2 due to an Apple bug (see #2020 (comment))
- `checkTrialOrIntroDiscountEligibility` is significantly slower with StoreKit 2 (#1893). We're adding optimizations to help with that (#2007), but the underlying logic will still be slow.
- A rare race-condition where `StoreKit 2` does not have transactions after a purchase ([TRIAGE-82]). We have some workarounds (#1945), but it's still being investigated.

_ Note: This effectively reverts 0ee540a. That commit made it easier to only change the default in one place which is why this PR is basically just one line._
NachoSoto added a commit that referenced this pull request Nov 4, 2022
…ons` (#2022)

This changes the default back to `StoreKit 1`. We decided to do this for
the following reasons:
- Purchasing with `PromotionalOffer`s does not work with StoreKit 2 due
to an Apple bug (see
#2020 (comment))
- `checkTrialOrIntroDiscountEligibility` is significantly slower with
`StoreKit 2` (#1893). We're adding optimizations to help with that
(#2007), but the underlying logic will still be slow.
- A rare race-condition where `StoreKit 2` does not have transactions
after a purchase ([TRIAGE-82]). We have some workarounds (#1945), but
it's still being investigated.

_Note: This effectively reverts 0ee540a. That commit made it easier to
only change the default in one place which is why this PR is basically
just one line._

[TRIAGE-82]:
https://revenuecats.atlassian.net/browse/TRIAGE-82?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
@NachoSoto

Copy link
Copy Markdown
Contributor Author

I just realized this cursed PR is # 2020 🙈

@aboedo

aboedo commented Nov 9, 2022

Copy link
Copy Markdown
Member

I just realized this cursed PR is # 2020 🙈

#destiny

@mamouneyya

Copy link
Copy Markdown

Update: We noticed today that this started to work actually. Also, I can confirm that the decoding logic in this PR is important. When not decoding the signature, I was getting the invalidOfferSignature error code every time.

Something I'm a bit confused about is the fact that this is now working even when using a simulator and a StoreKit testing store file. I thought that in that scenario, Xcode won't make any remote request, so I cannot understand how this was fixed from the App Store server side. Glad it is though.

@NachoSoto

Copy link
Copy Markdown
Contributor Author

Interesting, our integration test is still failing. I'll dig more next week though.

@NachoSoto

Copy link
Copy Markdown
Contributor Author

Update: We noticed today that this started to work actually. Also, I can confirm that the decoding logic in this PR is important. When not decoding the signature, I was getting the invalidOfferSignature error code every time.

Weird, I can't get this to work even with this fix. Are you doing something else differently? Is it running on iOS 16.1.2 only or something?

@NachoSoto NachoSoto requested a review from a team December 5, 2022 18:43
@NachoSoto NachoSoto force-pushed the sk2-promo-purchases branch from 2c82f1f to 22beb89 Compare December 5, 2022 18:43
@NachoSoto NachoSoto changed the title StoreKit2: fix purchasing with PromotionalOffers StoreKit 2: fix purchasing with PromotionalOffers Dec 5, 2022
@mamouneyya

mamouneyya commented Dec 5, 2022

Copy link
Copy Markdown

Weird, I can't get this to work even with this fix. Are you doing something else differently? Is it running on iOS 16.1.2 only or something?

My personal device is still at 15.7. I could get it working on it, plus when using Xcode 14.1 with a simulator and a StoreKit test store file. Another colleague confirmed he could get it working on 15.7 as well.

Not sure this is relevant to you, but I noticed that when using the sample server app Apple attached here to generate the signature, you shouldn't omit the applicationUsername param but instead you should pass an empty string when you don't want to include a appAccountToken(_:) in the options. Otherwise, the token will be included in the signature as a string value of "undefined".

@mamouneyya

Copy link
Copy Markdown

The test cases we validated included when you're an active member or a former member. Meaning, purchasing a subscription with a promo offer when you have an active subscription already, and purchasing a subscription with a promo offer after cancelling a previous subscription.

@NachoSoto

Copy link
Copy Markdown
Contributor Author

Thanks, I just ran it on iOS 15.x and it's still failing. I verified in our backend that the username is never empty.

@NachoSoto

Copy link
Copy Markdown
Contributor Author

I love how the docs aren't even updated for SK2?

@mamouneyya

mamouneyya commented Dec 5, 2022

Copy link
Copy Markdown

Thanks, I just ran it on iOS 15.x and it's still failing. I verified in our backend that the username is never empty.

By never empty, do you mean that you're actually using a value when generating the signature? If that's the case, then you'll have to pass an .appAccountToken(_:) purchase option with the exact same value.

@NachoSoto

Copy link
Copy Markdown
Contributor Author

By never empty, do you mean that you're actually using a value when generating the signature? If that's the case, then you'll have to pass an .appAccountToken(_:) purchase option with the exact same value.

Whoa we're definitely not doing that.

This is still not working:

options.insert(signedData.sk2PurchaseOption)
options.insert(.appAccountToken(UUID(uuidString: self.appUserID)!))

But I feel like we're on the right track. I missed this:

If you're providing an appAccountToken in the purchase options, you must include that value when you generate the signature. Use lowercase for the UUID string representations of the app account token and the nonce in the siganture.

@NachoSoto

Copy link
Copy Markdown
Contributor Author

(Testing now calling lowercased())

@NachoSoto

Copy link
Copy Markdown
Contributor Author

applicationUsername or appAccountToken
An optional string value that you define; may be an empty string. If your app uses applicationUsername, provide applicationUsername. If your app uses appAccountToken, provide appAccountToken. Use lowercase for the string representation of appAccountToken.

Our backend isn't lowercasing the user ID for SK2, so that's likely the issue.

@NachoSoto

Copy link
Copy Markdown
Contributor Author

CONFIRMED!
Using a lowercase user ID (and passing the missing .appAccountToken) makes the test pass.

Thanks a lot for that pointer @mamouneyya! We'll get this fixed.

@mamouneyya

Copy link
Copy Markdown

CONFIRMED! Using a lowercase user ID (and passing the missing .appAccountToken) makes the test pass.

Thanks a lot for that pointer @mamouneyya! We'll get this fixed.

Wohoo! Glad you got it working! 🎉

NachoSoto added a commit that referenced this pull request Dec 5, 2022
@NachoSoto NachoSoto marked this pull request as ready for review December 5, 2022 23:49
@NachoSoto NachoSoto force-pushed the sk2-promo-purchases branch from 22beb89 to 5038cef Compare December 5, 2022 23:49
@NachoSoto NachoSoto removed the blocked label Dec 5, 2022
@NachoSoto NachoSoto changed the title StoreKit 2: fix purchasing with PromotionalOffers Fixed purchasing with PromotionalOffers using StoreKit 2 Dec 5, 2022
if let decoded = Data(base64Encoded: self.signature) {
signature = decoded
} else {
throw Error.failedToDecodeSignature(self.signature)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I improved error handling here (the last TODO) and added a few tests.

@available(iOS 15.2, tvOS 15.2, macOS 12.1, watchOS 8.3, *)
func testPurchaseWithPromotionalOffer() async throws {
try AvailabilityChecks.iOS15APIAvailableOrSkipTest()
try XCTSkipIf(Self.storeKit2Setting == .enabledForCompatibleDevices,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@aboedo

aboedo commented Dec 7, 2022

Copy link
Copy Markdown
Member

thinking more about this:
if, for affected versions, the backend sent the signature as utf 8 instead of base64 encoded, wouldn't that fix them?

@NachoSoto

Copy link
Copy Markdown
Contributor Author

thinking more about this:
if, for affected versions, the backend sent the signature as utf 8 instead of base64 encoded, wouldn't that fix them?

Here's an example base64 encoded signature:

MEQCIEQlmZRNfYzKBSE8QnhLTIHZZZWCFgZpRqRxHss65KoFAiAJgJKjdrWdkLUOCCjuEx2RmFS7daRzSVZRVZ8RyMyUXg

And decoded:

0D� D%M}�!<BxKLe��iFq�:�� v��(��TusIVQU�̔^

I suppose if the backend sent it without encoding, and we create the Data with the existing implementation (Data(self.utf8)) it might work.
We should test this locally. Who can help with that?

NachoSoto added a commit that referenced this pull request Dec 9, 2022
@NachoSoto NachoSoto force-pushed the sk2-promo-purchases branch from 5038cef to 4f4e0f8 Compare December 9, 2022 18:08
@NachoSoto NachoSoto requested a review from aboedo December 9, 2022 18:08
NachoSoto added a commit that referenced this pull request Dec 9, 2022
…d` (#2118)

This will be used for https://github.com/RevenueCat/khepri/pull/4843, to
fix [SDKONCALL-160] and #2020.

`X-StoreKit2-Setting` only told us if the apps had that setting enabled.
However, when running on an older device, `SK2` would still be disabled.

[SDKONCALL-160]:
https://revenuecats.atlassian.net/browse/SDKONCALL-160?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
@NachoSoto NachoSoto force-pushed the sk2-promo-purchases branch from 4f4e0f8 to 24b938e Compare December 9, 2022 18:39
Comment on lines 482 to 483

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not base64 the result of this so it looks realistic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is

testPurchaseSK2PackageWithInvalidPromotionalOfferSignatureThrowsError

So I'm testing that if it's not base64 (so it's just a random string), it produces the correct error instead of crashing or something.

@NachoSoto NachoSoto force-pushed the sk2-promo-purchases branch from 24b938e to a36c1b6 Compare December 9, 2022 21:50
@NachoSoto

Copy link
Copy Markdown
Contributor Author

Merging 🎉

@NachoSoto NachoSoto merged commit 8e32400 into main Dec 9, 2022
@NachoSoto NachoSoto deleted the sk2-promo-purchases branch December 9, 2022 23:04
@KrisJulioRSVP

KrisJulioRSVP commented Mar 20, 2023

Copy link
Copy Markdown

This solves my problem, Im just passing empty string in applicationUsername and it works. Thank you guys! Apple documentation is pain in the a$$

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StoreKit 2 - Promotional Offers - .invalidOfferSignature error. Can't purchase any offer.

5 participants