The primary purpose of Receigen is to provide the required code to validate AppStore receipts. If you distribute your application on and off the AppStore, the receipt file can be used to validate an application purchased directly. By doing so, you can provide your users a version of the application that does not fit in the AppStore rules or an emergency fix.

This blog entry described the whole process about the solution.

Generation

NOTE: Currently, the generation can only be done on the command line.

The following command generates the receipt validation code in a file named receipt.h that:

$> /Applications/Receigen.app/Contents/MacOS/Receigen --identifier com.example.MyApplication --version 1.3.0 --os osx --success callblock --failure callblock --receipt-path "~/Library/Application Support/MyApplication/MASReceipt/receipt" > receipt.h
Receigen, a smart code generator for Mac App Store receipt validation.
----------------------------------------------------------------------
Generating receipt validation code for CFBundleIdentifier='com.example.MyApplication' and CFBundleShortVersionString='1.3.0'...
Generation done.

Integration

The generated code integration follows the general guides.

In order to enable the external receipt validation, you have to define the pre-processor constant RECEIGEN_EXTERNAL_RECEIPT.

Bundle Identifier Check

In the direct-sale scenario, receipt and bundle identifiers may probably not match. This is the case when you provide a beta version when a production version is available on the Mac App Store.

To solve that case, you have to define the RECEIGEN_LOOSE_IDENTIFIER_CHECK preprocessor value; it will silently ignore the identifier mismatch in the receipt while performing all the remaining tests.

Note: There is no partial match on the bundle identifier. Even if the bundle identifier defined in the code is totally different from the bundle identifier encoded in the receipt, the check will pass.

Bundle Version Check

In the direct-sale scenario, receipt and bundle versions may probably not match. This is the case when you provide a version 1.2.1 while the 1.2 is available on the Mac App Store.

To solve that case, you have to define the RECEIGEN_LOOSE_VERSION_CHECK preprocessor value; it will silently ignore the version mismatch in the receipt while performing all the remaining tests.

Note: There is no partial match on the bundle version. Even if the bundle version defined in the code is totally different from the bundle version encoded in the receipt, the check will pass.

Entitlements

In the direct-sale scenario, application's entitlements are not required. If you need to skip the entitlements check, you have to define the RECEIGEN_CHECK_ENTITLEMENTS preprocessor value; it will skip the entitlements check while performing all the remaining tests.

Pitfalls

The path to the receipt is mostly fixed at code generation time, since it is hard to obfuscate variable arguments at runtime.

If you store multiple receipts named by GUID, as recommended in the blog above, you will need to copy the receipt file you wish to validate to a fixed location (specified at code generation time) before calling Receigen.

Although the path is "fixed", it is modified in one specific way at runtime: if the path contains the string ~/, that reference will be expanded (or globbed). The exact expansion varies depending upon whether the app sandboxed or not.

In a non-sandboxed app, the string ~/ will be replaced with the user's home folder. For example, ~/Library/Application Support will be resolved to /Users/<username>/Library/Application Support.

In a sandboxed app, the string ~/ will be replaced with a reference into the application sandbox. For example, ~/Library/Application Support will be resolved to /Users/<username>/Library/Containers/<bundle ID>/Data/Library/Application Support.

Note that the expansion (or globbing) mechanism uses the glob API, and so is unaware of Cocoa special cases. For example, a reference to ~/Library/Group Containers at generate time will end up referring to /Users/<username>/Library/Containers/<bundle ID>/Data/Library/Group Containers/ at runtime (which probably isn't what you want in this case).

To assist with diagnosis here, by default, Receigen logs messages like the below:

Loading external receipt at '~/Library/Application Support/MyApp/MASReceipts/temp'
Receipt path resolved to '/Users/<username>/Library/Containers/<bundle ID>/Data/Library/Application Support/MyApp/MASReceipts/temp'