How are IAP receipts supposed to work when an iOS app runs on an Arm Mac?

Does anyone know how IAPs and receipt validation is supposed to work when an iOS app is run on a new Arm Mac?

I have an iOS app which I'm pleased to discover runs well on my new Arm Mac mini. The app has non-consumable IAPs. At startup it parses the app receipt. A receipt is present, but my existing receipt validation code decides that it has the wrong hash. (This is with the app installed from the App Store, not a local build.)

I'm aware that receipt validation is different for native Mac apps, including catalyst apps (e.g. getting a device GUID from the ethernet MAC addresses etc.) But what is supposed to happen in the case of a regular iOS app running on macOS? Is this documented anywhere? Has anyone got IAPs working, with local receipt validation?
Hi autoliv, thanks for your reply.

Is there something particular in TPInAppReceipt that you think is relevant to iOS apps running on Arm Macs?
Have you run that code in an iOS app running on an Arm Mac and found that it validates the receipt correctly?

I have looked at its Validation.swift and it seems to have the same logic that I am currently using. I don't see anything that specifically handles an iOS app running on Arm macOS.

Hi endecotp, I'm the author of TPInAppReceipt. Unfortunately, I don't have access to the new arm macs yet and haven't tried to run ios app on macOS, but I'm 99.9% sure that it will work, like it works for Catalyst apps. I will be more than happy if you have a chance to try it out and prove my correctness.

Thank you,
P
Hi tikhop, thanks for replying.

I've opened a developer technical support request about this.

The initial reply was that I need to use the MAC address when an iOS app runs on macOS. They sent some code for getting the MAC address, which is a bit more involved than what you're using in TPInAppReceipt. Specifically, I think it is trying to get the correct MAC address on Macs that don't have wired ethernet, or where the wired ethernet is via a USB dongle. The logic is: try en0 if it's built-in, else try en1, else try en0, else fail.

Of course, this code isn't feasible because the calls to get the MAC address use the private IOKit so they would not pass app review. But I hacked it to run locally - and it doesn't work.

So I got back to developer technical support, and I think that they finally read my question properly, and they said: "I don't think it's clear whether a StoreKit app which implemets local receipt validation can work on an M1 system", and told me to "file a bug".

I have tried to understand where the identifierForVendor might be stored for iOS apps on macOS, but I've not found anything yet. I suspect there is a plist or a db or something somewhere. Perhaps I could somehow trace inside UIDevice.identifierForVendor and see what it reads?

Anyway, for the time being I've disabled my app from the Mac app store - which is unfortunate, because apart from this it works remarkably well. My guess is that it might start working in a macOS update. Or maybe an App Store change is needed to change the receipt.

I would be interested to hear if anyone else has tried to do this! Surely I can't be the only one?

Hi endecotp, I truly appreciate your efforts in trying to figure out what should we do in this case and kudos for reporting it to developer technical support.

Personally, I thought we can do the same what we did for Catalyst apps, basically, switch to macOS implementation which is close to what you've described:

Code Block
#elseif targetEnvironment(macCatalyst) || os(macOS)
    var masterPort = mach_port_t()
    var kernResult: kern_return_t = IOMasterPort(mach_port_t(MACH_PORT_NULL), &masterPort)
    if (kernResult != KERN_SUCCESS)
    {
        assertionFailure("Failed to initialize master port")
    }
    let matchingDict = IOBSDNameMatching(masterPort, 0, "en0")
    if (matchingDict == nil)
    {
assertionFailure("Failed to retrieve guid")
    }
    
    var iterator = io_iterator_t()
    kernResult = IOServiceGetMatchingServices(masterPort, matchingDict, &iterator)
    if (kernResult != KERN_SUCCESS)
    {
        assertionFailure("Failed to retrieve guid")
    }
    var guidData: Data?
    var service = IOIteratorNext(iterator)
    var parentService = io_object_t()
    defer
    {
        IOObjectRelease(iterator)
    }
    while(service != 0)
    {
        kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parentService)
        if (kernResult == KERN_SUCCESS)
        {
            guidData = IORegistryEntryCreateCFProperty(parentService, "IOMACAddress" as CFString, nil, 0).takeRetainedValue() as? Data
            IOObjectRelease(parentService)
        }
        IOObjectRelease(service)
        if  guidData != nil {
            break
        }else{
            service = IOIteratorNext(iterator)
        }
    }
    if guidData == nil
    {
        assertionFailure("Failed to retrieve guid")
    }
    return guidData!    
#endif


Anyway, I've created a ticket (https://github.com/tikhop/TPInAppReceipt/issues/73) — let's see maybe someone else knows how to deal with that.

Let me know if you have any questions or I can help somehow here. You can send me email if so (tikhop at gmail com)

Thank you.
I tried SwiftStoreKit & TPInAppReceipt in running iOS app to M1 mac book, it does not work.

I try to run my app in mac, finish the purchase process, and rerun it to see if it passes local receipt validation. But in this situation, my receipt file name is 'sandboxReceipt', however, the path 'Bundle.main.appStoreReceiptURL' returns 'receipt', hence the appStoreReceiptData returns by SwiftStoreKit is nil.

so I download my app by app store, finish the purchase process. My app version of App Store using SwiftStoreKit & OpenSSL to validate receipt, i reopen the download app, the local receipt validation fails, which is expected.

now i got both the sandboxReceipt & receipt, so i run my app with Xcode in mac again. Now i got the receipt data, and enter the validation process in TPInAppReceipt, and the validation failed.

I notice in the 'guid' method, it actually runs the same block as in iOS device. I guess the expected result is to run the last block.

Code Block swift
fileprivate func guid() -> Data
{
   
#if os(watchOS)
  var uuidBytes = WKInterfaceDevice.current().identifierForVendor!.uuid
  return Data(bytes: &uuidBytes, count: MemoryLayout.size(ofValue: uuidBytes))
#elseif !targetEnvironment(macCatalyst) && (os(iOS) os(tvOS))
  var uuidBytes = UIDevice.current.identifierForVendor!.uuid <---------------------- code runs here!
  return Data(bytes: &uuidBytes, count: MemoryLayout.size(ofValue: uuidBytes))
#elseif targetEnvironment(macCatalyst) os(macOS)
   
  var masterPort = mach_port_t()
  var kernResult: kern_return_t = IOMasterPort(mach_port_t(MACH_PORT_NULL), &masterPort)
...
#endif
}


Here is my debug information:
Code Block swift
let device = UIDevice.current
log.info([
"=== Device Information ===",
"Name: \(device.name)",
"System Name: \(device.systemName)",
"System Version: \(device.systemVersion)",
"Model: \(device.model)",
"Localized Model: \(device.localizedModel)",
].joined(separator: "\n"))
/* output:
=== Device Information ===
Name: Alen’s MacBook Pro 13
System Name: iOS
System Version: 14.2
Model: iPad
Localized Model: iPad
*/




Alen, thanks for your post.

The receipt / sandboxReceipt difference is interesting. I've not tried to understand how the test user sandbox things work in this case. It's one more thing that can go wrong!

Regarding whether it should be running the identifierForVendor or the MAC address code - we simply don't know what Apple expect us to do, but the MAC address code requires private frameworks on iOS, and neither correctly validates the receipt in my experience. Alen, can you hack your code to use the known MAC address of your Mac, and see if it validates? (I predict not.)

My bug report got a response from Apple; it's the usual "do you have a sample project that demonstrates the problem?" reply. Do any of you have something simple we could send?

Thanks.


tikhop, I've emailed you the Mac MAC-address code that Apple Developer Support sent me.

You might like to incorporate it into TPInAppReceipt.
Hi, endecotp.

The receipt/sandboxReceipt I guess it's a bug that App Store should fix.

I tried the develop version of TPInAppReceipt, it does not work because when compute macos_guid, many code can't compiler when target is My Mac (Designed for iPad). But I have another project, that's designed for Mac, so it should be able to get it's guid data, since the guid data is unique per mac (i guess), so i can use the guid data returns by the mac project, to see if it can validates my iOS receipt? I'll give it a try.

You can follow our discuss in TPInAppReceipt here: https://github.com/tikhop/TPInAppReceipt/issues/73



Update: I try to get the macos_guid from another project that build for mac, and use that guid data to verify my iOS project runs on mac, and it fails because the computed hash is not equal to receipt hash. maybe the guid data is not the same for different projects?
By the way, guess we can add Apple Silicon tag for this issue.
Alen, you are seeing the same behaviour as me; neither the identiferForVendor nor the MAC address validates the receipt.

I think the whole MAC address thing is a distraction. People only mention it because they don't understand the question. People read the question and think I'm asking how to make my iOS receipt validation code work when recompiled for macOS, to which the answer is "you need to use the MAC address instead of identifierForVendor". But what I'm actually asking is how to make my iOS receipt validation work when the iOS app is run on an Arm Mac.

identifierForVendor does return a plausible-looking random value when my iOS app runs on my Mac - it just fails to validate the receipt. I think it's quite likely that this is just a bug and it will just start working after a macOS update - is anyone here running the macOS betas?

Maybe I should try to work out where the identifierForVendor comes from. If they are distinct per-app (I've not checked), there may be a database or plist or something where there are stored.

Of course the people who best understand how this works are the people trying to crack our apps.....
Yes, when my iOS app running on Mac, it feels it's running on iPad, so the verify logic should be the same process on iOS. I think the receipt provided by Mac App Store should make some changes, let's wait for the next release of macOS.

Still experiencing the "incorrectHash" issue today. Hope there is a fix (or maybe I overlooked if a fix that had been published). Thanks!

Hi @webcompanist,

Still experiencing the "incorrectHash" issue today.

I posted this thread when receipt validation was not working on macOS 11.0. At some point it got fixed by Apple, either in macOS 11.1 or in a server-side change around that time.

Now, more than a year later, I believe that it may have stopped working again. Perhaps due to a macOS update. )Or maybe I have managed to ***** up my code somehow.) In my development environment it seems to not work on macOS 12.2.1. Looking at my diagnostics, it may be not working for customers on macOS 12.2 and macOS 11.5 - but it's also possible that those diagnostics are coming from people with cracked apps.

Can anyone else report whether they have app receipt validation working with iOS-on-macOS ("Designed for iPad") with newish versions of macOS?

Having updated my Mac, it's also failing on 12.3.

Edit: or maybe it isn't failing anymore. Hmm. Anyone else have anything to share?

How are IAP receipts supposed to work when an iOS app runs on an Arm Mac?
 
 
Q