apple / app-store-server-library-swift

MIT License
225 stars 32 forks source link

Inconsistency between iOS and macOS in App Store Receipt Handling within extractTransactionIdFromInAppReceipt(inAppReceiptContent:) #33

Closed ronaldmannak closed 8 months ago

ronaldmannak commented 8 months ago

I'm experiencing an issue where the handling of App Store Receipts seems to differ between iOS and macOS. This could be due to an error in my implementation, or it might be a bug in the AppStore Server Library.

I have an application that runs on both iOS and macOS. In the sandbox TestFlight environment, I fetch the App Store receipt and pass it to the server in the following way:

if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
           FileManager.default.fileExists(atPath: appStoreReceiptURL.path),
           let receiptData = try? Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped) {
            body = receiptData.base64EncodedString(options: [])
...

On the server side, extractTransactionId(appReceipt:) is used to extract the transactionId:

guard let transactionId = ReceiptUtility.extractTransactionId(appReceipt: body) else { 
...
}
// When macOS app calls the server, transactionId is the correct transactionId
// When iOS app calls the server, transactionId is a single digit (e.g. 0)

When the macOS app communicates with the server, extractTransactionId successfully retrieves the transaction id.

However, when the iOS app communicates with the server, the receipt parsing does not function properly. More specifically, at line 75 of ReceiptUtility.swift (result = String(utf8String)), a single digit is set during each loop, and this digit can vary from loop to loop. In contrast, when the macOS app uses this method, result is set to the full tx id during each loop.

alexanderjordanbaker commented 8 months ago

Is the iOS receipt generated from XCode or from the Sandbox environment?

ronaldmannak commented 8 months ago

@alexanderjordanbaker Sandbox. Note that I have a single codebase for iOS and macOS. Come to think of it, I did have an issue logging into the sandbox account last night on one device. Let me check if I'm logged in using the same account on both iOS and macOS.

ronaldmannak commented 8 months ago

I made sure I was logged in the same account and can confirm the server is not able to extract the transaction id from the iOS receipt, while it is able to extract it from the MacOS receipt.

FWIW, the receipts on iOS and Mac aren't the same size, the receipt on Mac is 151,248 bytes, while the iOS receipt is only 3,592 bytes (I measured the size on both on client and server just to be sure).

alexanderjordanbaker commented 8 months ago

@ronaldmannak This sounds a lot like the transaction id being generated isn't a "real" transaction id, that comes from Apple servers, but generated via the process described in https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode/. One ad-hoc way to check, does the much shorter iOS receipt end with a lot of AAAAs in the Base64? That is a common feature of how Xcode generates its receipts

ronaldmannak commented 8 months ago

@alexanderjordanbaker thanks, but I don't think that's the issue. 1) I haven't set up the StoreKit configuration in Xcode. 2) The issue occurs on TestFlight builds which don't use the local StoreKit configuration. (Correct me if I'm wrong but debug builds don't have App Store receipt files and only debug builds --not TestFlight-- use the local StoreKit configuration) 3) ReceiptUtility.extractTransactionId(appReceipt:) doesn't check if a transaction Id is valid, it just extracts the transaction Id from the receipt offline 4) ReceiptUtility.extractTransactionId(appReceipt:) doesn't return nil, instead it returns an invalid one-digit transactionId which is probably also an invalid format for a local configuration. 5) The Mac TestFlight version of the app works as expected. It's just the iOS TestFlight version that isn't working. Both the Mac and iOS version use the same code.

I experimented a bit more and found out that if the client app extracts the originalTransactionID and passes that to the server (instead of the App Store receipt), the App Store Server Library validates the transactions from both iOS and macOS correctly.

What remains is that the App Store Server Library doesn't parse the iOS receipt correctly. I'd be happy to email both receipt files if that helps.

alexanderjordanbaker commented 8 months ago

Could you please file a ticket in Feedback Assistant (feedbackassistant.apple.com) with the receipts and post the FB number here please

ronaldmannak commented 8 months ago

Will do!

ronaldmannak commented 8 months ago

@alexanderjordanbaker submitted as FB13626068. Thanks in advance!

alexanderjordanbaker commented 8 months ago

@ronaldmannak Could you please include the receipts in the ticket?

ronaldmannak commented 8 months ago

@alexanderjordanbaker sorry for the delay. I just added the receipt to the ticket.

alexanderjordanbaker commented 8 months ago

@ronaldmannak I've looked at the receipt and confirmed the shared receipt is an Xcode generated receipt signed by a local StoreKit CA that isn't using the Sandbox environment, but has environment Xcode

ronaldmannak commented 8 months ago

Thanks for confirming, I appreciate it.