Skip to content

Instantly share code, notes, and snippets.

@pkcpkc
Last active February 12, 2024 09:59
Show Gist options
  • Save pkcpkc/53994b1c18662c51651dda0de9b7f87b to your computer and use it in GitHub Desktop.
Save pkcpkc/53994b1c18662c51651dda0de9b7f87b to your computer and use it in GitHub Desktop.
decode-ios-receipt
// see https://www.npmjs.com/package/node-apple-receipt-verify#configoptions-object
// https://medium.com/axel-springer-tech/debugging-and-reading-apple-receipts-2e47f9793f74
const params = {
"-p": {
"name": "environment",
"default": ['sandbox'],
"set": ['production']
},
"-ie": {
"name": "ignoreExpired",
"default": false,
"set": true
},
"-e": {
"name": "extended",
"default": false,
"set": true
},
"-v": {
"name": "verbose",
"default": false,
"set": true
},
"-iee": {
"name": "ignoreExpiredError",
"default": false,
"set": true
},
"-eot": {
"name": "excludeOldTransactions",
"default": false,
"set": true
}
};
if (process.argv.length < 4) {
console.log('Usage: node index.js <keychain password name> <receipt file path>{ <optionalFlag>}\n');
console.log('Optional flags (see https://www.npmjs.com/package/node-apple-receipt-verify#configoptions-object):');
for (const [key, value] of Object.entries(params)) {
console.log(` ${key}, ${value.name}, default=${value.default}, set=${value.set}`);
}
console.log('\nPrecondition: Add App Store Connect Shared Secret in KeyChain:');
console.log('security add-generic-password -a "$USER" -s \'<keychain password name>\' -w \'<shared secret>\'\n');
process.exit(1);
}
const appleReceiptVerify = require('node-apple-receipt-verify');
const fs = require('fs');
const { execSync } = require('child_process');
const keychainPasswordName = process.argv[2];
const filePath = process.argv[3];
const command = `security find-generic-password -a "$USER" -s "${keychainPasswordName}" -w`;
const sharedSecret = execSync(command).toString().trim();
// TODO accept also non-json plain-text receipt data
const receiptData = JSON.parse(fs.readFileSync(filePath, 'utf8')).receiptData;
var config = { secret: sharedSecret, environment: ['sandbox'], ignoreExpired: false, extended: false, verbose: true };
for (var i = 4; i < process.argv.length; i++) {
const arg = process.argv[i];
if (params[arg]) {
config[params[arg].name] = params[arg].set;
}
}
appleReceiptVerify.config(config);
var expired = [];
var valid = [];
receiptData.forEach((receipt) => {
appleReceiptVerify.validate({ 'receipt': receipt.toString() })
.then((validatedData) => {
validatedData.forEach((data) => {
let expirationMs = parseInt(data.expirationDate);
data.expirationDateString = new Date(expirationMs).toLocaleString();
let purchaseMs = parseInt(data.purchaseDate);
data.purchaseDateString = new Date(purchaseMs).toLocaleString();
});
console.log(validatedData);
})
.catch((error) => {
console.error(error);
});
});
@pkcpkc
Copy link
Author

pkcpkc commented Feb 12, 2024

Read about the details here: Decoding and Debugging Apple Receipts

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