Last active
January 6, 2023 12:29
-
-
Save marcolabreu/f9eb3bbf0fc9cb84701e1ae8bc6eb76b to your computer and use it in GitHub Desktop.
Firebase function to validate Apple receipts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {https} from 'firebase-functions'; | |
import bent = require("bent"); | |
/* | |
* Docs | |
* https://firebase.google.com/docs/functions/typescript | |
* https://developer.apple.com/documentation/appstorereceipts/verifyreceipt | |
*/ | |
/* | |
Apple validation status codes | |
[0]: { message: 'Active', valid: true, error: false }, | |
[21000]: { message: 'App store could not read', valid: false, error: true }, | |
[21002]: { message: 'Data was malformed', valid: false, error: true }, | |
[21003]: { message: 'Receipt not authenticated', valid: false, error: true }, | |
[21004]: { message: 'Shared secret does not match', valid: false, error: true }, | |
[21005]: { message: 'Receipt server unavailable', valid: false, error: true }, | |
[21006]: { message: 'Receipt valid but sub expired', valid: true, error: false }, | |
[21007]: { message: 'Sandbox receipt sent to Production environment', valid: false, error: true, redirect: true }, | |
[21008]: { message: 'Production receipt sent to Sandbox environment', valid: false, error: true }, | |
*/ | |
const sendToApple = async (data: string) => { | |
const body = JSON.stringify({ 'receipt-data': data }) | |
const post = bent('POST', 'json', 200); | |
/* We try production following Apple recommendation */ | |
try { | |
const productionResponse: bent.Json & { status?: number } = await post('https://buy.itunes.apple.com/verifyReceipt', body) | |
/* If status is 21007, we try sandbox */ | |
if (productionResponse.status === 21007) { | |
return await post('https://sandbox.itunes.apple.com/verifyReceipt', body) | |
} | |
return productionResponse | |
} catch (e) { | |
console.error(e); | |
return { 'error': JSON.stringify(e) } | |
} | |
return productionResponse | |
} | |
/* | |
* Postman can be used to test the deployed function. | |
* Paste a receipt data encoded as base64, without quotes, to a POST request Body as raw Text. | |
* Check if Content-Type is set to text/plain in Header. | |
* | |
* * A sample receipt, working as of June 2020, can be obtained at | |
* https://stackoverflow.com/questions/33843281/apple-receipt-data-sample | |
*/ | |
export const validateReceipt = https.onRequest(async (req, res) => { | |
const appleResponse = await sendToApple(req.body) | |
if (appleResponse.status === 0) { | |
console.info(`Receipt for app ${appleResponse.receipt.bundle_id} decrypted by Apple ${appleResponse.environment} server.`) | |
} | |
res.header("Content-Type", "application/json") | |
res.send(JSON.stringify(appleResponse)) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "functions", | |
"scripts": { | |
"lint": "tslint --project tsconfig.json", | |
"build": "tsc", | |
"serve": "npm run build && firebase emulators:start --only functions", | |
"shell": "npm run build && firebase functions:shell", | |
"start": "npm run shell", | |
"deploy": "firebase deploy --only functions", | |
"logs": "firebase functions:log" | |
}, | |
"engines": { | |
"node": "10" | |
}, | |
"main": "lib/index.js", | |
"dependencies": { | |
"@types/bent": "~7.0.2", | |
"bent": "~7.3.2", | |
"firebase-admin": "~8.10.0", | |
"firebase-functions": "~3.6.1" | |
}, | |
"devDependencies": { | |
"tslint": "^5.12.0", | |
"typescript": "~3.8.0", | |
}, | |
"private": true | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment