UNPKG

iap-apple

Version:

Integration with Apples InAppPurchases in Typescript, available for NodeJS environments.

217 lines (216 loc) 11.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPurchasedItems = exports.isPurchasedItemCanceled = exports.isPurchasedItemExpired = exports.isVerifiedReceipt = exports.verify = void 0; var internal_1 = require("../internal"); var constants_1 = require("../../constants"); /** * It takes a receipt data and a config object, and returns a promise that resolves to a validated receipt object or * rejects with an error * @param {string} receipt - The receipt data. * @param {IIAPAppleConfig} config - IIAPAppleConfig * @returns a promise. */ function verify(receipt, config) { return __awaiter(this, void 0, void 0, function () { var appleExcludeOldTransactions, logger, test, appSharedSecret; var _this = this; return __generator(this, function (_a) { appleExcludeOldTransactions = config.appleExcludeOldTransactions, logger = config.logger, test = config.test, appSharedSecret = config.appSharedSecret; return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { var verifyReceiptResponse, err_1; var _a, _b, _c; return __generator(this, function (_d) { switch (_d.label) { case 0: verifyReceiptResponse = null; _d.label = 1; case 1: _d.trys.push([1, 6, , 7]); if (!!test) return [3 /*break*/, 3]; return [4 /*yield*/, (0, internal_1.verifyReceipt)({ logger: logger, validationEndpoint: constants_1.PROD_PATH, receiptData: receipt, appSharedSecret: appSharedSecret, excludeOldTransactions: Boolean(appleExcludeOldTransactions), })]; case 2: verifyReceiptResponse = _d.sent(); _d.label = 3; case 3: if (!!verifyReceiptResponse) return [3 /*break*/, 5]; return [4 /*yield*/, (0, internal_1.verifyReceipt)({ logger: logger, validationEndpoint: constants_1.SANDBOX_PATH, receiptData: receipt, appSharedSecret: appSharedSecret, excludeOldTransactions: Boolean(appleExcludeOldTransactions), })]; case 4: verifyReceiptResponse = _d.sent(); _d.label = 5; case 5: if (!verifyReceiptResponse) { reject({ rejectionMessage: 'Unable to validate receipt using appstore endpoints.', data: null, }); return [2 /*return*/]; } return [3 /*break*/, 7]; case 6: err_1 = _d.sent(); reject(err_1); return [2 /*return*/]; case 7: if (verifyReceiptResponse.status === constants_1.RECEIPT_STATUS_ENUM.SUCCESS) { if (((_a = verifyReceiptResponse.receipt) === null || _a === void 0 ? void 0 : _a.in_app) && ((_c = (_b = verifyReceiptResponse.receipt) === null || _b === void 0 ? void 0 : _b.in_app) === null || _c === void 0 ? void 0 : _c.length) === 0) { /* Detected valid receipt, but the receipt bought nothing probably hacked: https://forums.developer.apple.com/thread/8954 https://developer.apple.com/library/mac/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-RECEIPT-HOW_DO_I_USE_THE_CANCELLATION_DATE_FIELD_ */ reject({ rejectionMessage: 'Detected valid receipt, however purchase list is empty', data: verifyReceiptResponse, }); } // validated successfully resolve(verifyReceiptResponse); return [2 /*return*/]; } // failed to validate reject with apple message reject({ rejectionMessage: constants_1.STATUS_TO_MESSAGE_MAP[verifyReceiptResponse.status], data: verifyReceiptResponse, }); return [2 /*return*/]; } }); }); })]; }); }); } exports.verify = verify; /** * It checks if the receipt is valid. * @param {IVerifyReceiptResponseBody | null} verifyReceiptResponse - IVerifyReceiptResponseBody | null * @returns A boolean */ var isVerifiedReceipt = function (verifyReceiptResponse) { return (verifyReceiptResponse === null || verifyReceiptResponse === void 0 ? void 0 : verifyReceiptResponse.status) === constants_1.RECEIPT_STATUS_ENUM.SUCCESS; }; exports.isVerifiedReceipt = isVerifiedReceipt; /** * If the purchased item has been cancelled or if the expiration date has passed, then it has expired * @param {PurchasedItem | null} purchasedItem - PurchasedItem | null * @returns A boolean */ var isPurchasedItemExpired = function (purchasedItem) { if (!(purchasedItem === null || purchasedItem === void 0 ? void 0 : purchasedItem.transactionId)) { throw new Error('Detected invalid purchased item! Make sure object is defined and it has transaction id.'); } // it has been cancelled if (purchasedItem.cancellationDateMS) { return true; } // there is no expiration date with this item if (!purchasedItem.expirationDateMS) { return false; } // has expired if (Date.now().valueOf() - purchasedItem.expirationDateMS >= 0) { return true; } // has not expired yet return false; }; exports.isPurchasedItemExpired = isPurchasedItemExpired; /** * If the purchased item has a cancellation date, then it's canceled. * @param {PurchasedItem} purchasedItem - PurchasedItem - this is the purchased item object that you get from the * getPurchasedItems() method. * @returns A boolean value. */ var isPurchasedItemCanceled = function (purchasedItem) { if (!(purchasedItem === null || purchasedItem === void 0 ? void 0 : purchasedItem.transactionId)) { throw new Error('Detected invalid purchased item! Make sure object is defined and it has transaction id.'); } return Boolean(purchasedItem.cancellationDateMS); }; exports.isPurchasedItemCanceled = isPurchasedItemCanceled; /** * It takes a response from the Apple App Store and returns an array of PurchasedItem objects sorted by their purchase date in descending order, * the latest purchase comes first. * @param {IVerifyReceiptResponseBody | null} verifyReceiptResponse - IVerifyReceiptResponseBody | null * @returns An array of PurchasedItem objects. */ var getPurchasedItems = function (verifyReceiptResponse) { if (!(verifyReceiptResponse === null || verifyReceiptResponse === void 0 ? void 0 : verifyReceiptResponse.receipt)) { return []; } var data = []; var purchases = verifyReceiptResponse.receipt.in_app || []; var lri = verifyReceiptResponse.latest_receipt_info || verifyReceiptResponse.receipt.latest_receipt_info; if (Array.isArray(lri)) { purchases = purchases.concat(lri); } /* we sort purchases by purchase_date_ms to make it easier to weed out duplicates (items with the same original_transaction_id) purchase_date_ms DESC */ purchases.sort(function (a, b) { return parseInt(b.purchase_date_ms, 10) - parseInt(a.purchase_date_ms, 10); }); var transactionIds = {}; for (var i = 0; i < purchases.length; i++) { var item = purchases[i]; var tid = item.original_transaction_id; // avoid duplicate if (transactionIds[tid]) { continue; } data.push((0, internal_1.getPurchaseItem)(item, verifyReceiptResponse)); transactionIds[tid] = true; } return data; }; exports.getPurchasedItems = getPurchasedItems;