iap-apple 1.3.3 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{src/constants/index.ts → constants/index.d.ts} +0 -0
- package/constants/index.js +18 -0
- package/constants/internal/index.d.ts +4 -0
- package/constants/internal/index.js +19 -0
- package/constants/shared/index.d.ts +14 -0
- package/constants/shared/index.js +31 -0
- package/{src/index.ts → index.d.ts} +0 -0
- package/index.js +19 -0
- package/lib/internal/index.d.ts +22 -0
- package/lib/internal/index.js +182 -0
- package/lib/shared/index.d.ts +6 -0
- package/lib/shared/index.js +187 -0
- package/package.json +7 -8
- package/{src/types/index.ts → types/index.d.ts} +0 -0
- package/types/index.js +18 -0
- package/types/internal/index.d.ts +41 -0
- package/types/internal/index.js +2 -0
- package/types/shared/index.d.ts +29 -0
- package/types/shared/index.js +2 -0
- package/LICENSE +0 -21
- package/README.md +0 -6
- package/src/__tests__/index.spec.ts +0 -286
- package/src/__tests__/receipts/apple +0 -46
- package/src/constants/internal/index.ts +0 -20
- package/src/constants/shared/index.ts +0 -27
- package/src/lib/internal/index.ts +0 -138
- package/src/lib/shared/index.ts +0 -132
- package/src/types/internal/index.ts +0 -201
- package/src/types/shared/index.ts +0 -45
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./internal"), exports);
|
|
18
|
+
__exportStar(require("./shared"), exports);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { RECEIPT_STATUS_ENUM } from '../shared';
|
|
2
|
+
export declare const STATUS_TO_MESSAGE_MAP: Record<RECEIPT_STATUS_ENUM, string>;
|
|
3
|
+
export declare const PROD_PATH = "https://buy.itunes.apple.com/verifyReceipt";
|
|
4
|
+
export declare const SANDBOX_PATH = "https://sandbox.itunes.apple.com/verifyReceipt";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SANDBOX_PATH = exports.PROD_PATH = exports.STATUS_TO_MESSAGE_MAP = void 0;
|
|
4
|
+
exports.STATUS_TO_MESSAGE_MAP = {
|
|
5
|
+
21000: 'The App Store could not read the JSON object you provided.',
|
|
6
|
+
21002: 'The data in the receipt-data property was malformed.',
|
|
7
|
+
21003: 'The receipt could not be authenticated.',
|
|
8
|
+
21004: 'The shared secret you provided does not match the shared secret on file for your account.',
|
|
9
|
+
21005: 'The receipt server is not currently available.',
|
|
10
|
+
21006: 'This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.',
|
|
11
|
+
21007: 'This receipt is a sandbox receipt, but it was sent to the production service for verification.',
|
|
12
|
+
21008: 'This receipt is a production receipt, but it was sent to the sandbox service for verification.',
|
|
13
|
+
21009: 'Internal data access error. Try again later',
|
|
14
|
+
21010: 'The user account cannot be found or has been deleted',
|
|
15
|
+
2: 'The receipt is valid, but purchased nothing.',
|
|
16
|
+
0: 'No error.',
|
|
17
|
+
};
|
|
18
|
+
exports.PROD_PATH = 'https://buy.itunes.apple.com/verifyReceipt';
|
|
19
|
+
exports.SANDBOX_PATH = 'https://sandbox.itunes.apple.com/verifyReceipt';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare enum RECEIPT_STATUS_ENUM {
|
|
2
|
+
SUCCESS = 0,
|
|
3
|
+
VALID_NO_PURCHASE = 2,
|
|
4
|
+
CANNOT_READ_JSON = 21000,
|
|
5
|
+
DATA_MALFORMED = 21002,
|
|
6
|
+
RECEIPT_NOT_AUTHENTICATED = 21003,
|
|
7
|
+
SHARED_SECRET_DOESNT_MATCH = 21004,
|
|
8
|
+
SERVER_NOT_AVAILABLE = 21005,
|
|
9
|
+
SUBSCRIPTION_EXPIRED = 21006,
|
|
10
|
+
TEST_ENV_RECEIPT_DETECTED = 21007,
|
|
11
|
+
PRODUCTION_ENV_RECEIPT_DETECTED = 21008,
|
|
12
|
+
INTERNAL_DATA_ACCESS_ERROR = 21009,
|
|
13
|
+
USER_ACCOUNT_DELETED = 21010
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RECEIPT_STATUS_ENUM = void 0;
|
|
4
|
+
var RECEIPT_STATUS_ENUM;
|
|
5
|
+
(function (RECEIPT_STATUS_ENUM) {
|
|
6
|
+
RECEIPT_STATUS_ENUM[RECEIPT_STATUS_ENUM["SUCCESS"] = 0] = "SUCCESS";
|
|
7
|
+
// 'The receipt is valid, but purchased nothing.'
|
|
8
|
+
RECEIPT_STATUS_ENUM[RECEIPT_STATUS_ENUM["VALID_NO_PURCHASE"] = 2] = "VALID_NO_PURCHASE";
|
|
9
|
+
// 'The App Store could not read the JSON object you provided.',
|
|
10
|
+
RECEIPT_STATUS_ENUM[RECEIPT_STATUS_ENUM["CANNOT_READ_JSON"] = 21000] = "CANNOT_READ_JSON";
|
|
11
|
+
// 'The data in the receipt-data property was malformed.',
|
|
12
|
+
RECEIPT_STATUS_ENUM[RECEIPT_STATUS_ENUM["DATA_MALFORMED"] = 21002] = "DATA_MALFORMED";
|
|
13
|
+
// 'The receipt could not be authenticated.',
|
|
14
|
+
RECEIPT_STATUS_ENUM[RECEIPT_STATUS_ENUM["RECEIPT_NOT_AUTHENTICATED"] = 21003] = "RECEIPT_NOT_AUTHENTICATED";
|
|
15
|
+
// The shared secret you provided does not match the shared secret on file for your account.
|
|
16
|
+
RECEIPT_STATUS_ENUM[RECEIPT_STATUS_ENUM["SHARED_SECRET_DOESNT_MATCH"] = 21004] = "SHARED_SECRET_DOESNT_MATCH";
|
|
17
|
+
// 'The receipt server is not currently available.',
|
|
18
|
+
RECEIPT_STATUS_ENUM[RECEIPT_STATUS_ENUM["SERVER_NOT_AVAILABLE"] = 21005] = "SERVER_NOT_AVAILABLE";
|
|
19
|
+
// 'This receipt is valid but the subscription has expired.
|
|
20
|
+
// When this status code is returned to your server, the receipt data is
|
|
21
|
+
// also decoded and returned as part of the response.',
|
|
22
|
+
RECEIPT_STATUS_ENUM[RECEIPT_STATUS_ENUM["SUBSCRIPTION_EXPIRED"] = 21006] = "SUBSCRIPTION_EXPIRED";
|
|
23
|
+
// 'This receipt is a sandbox receipt, but it was sent to the production service for verification.'
|
|
24
|
+
RECEIPT_STATUS_ENUM[RECEIPT_STATUS_ENUM["TEST_ENV_RECEIPT_DETECTED"] = 21007] = "TEST_ENV_RECEIPT_DETECTED";
|
|
25
|
+
// 'This receipt is a production receipt, but it was sent to the sandbox service for verification.'
|
|
26
|
+
RECEIPT_STATUS_ENUM[RECEIPT_STATUS_ENUM["PRODUCTION_ENV_RECEIPT_DETECTED"] = 21008] = "PRODUCTION_ENV_RECEIPT_DETECTED";
|
|
27
|
+
// 'Internal data access error. Try again later'
|
|
28
|
+
RECEIPT_STATUS_ENUM[RECEIPT_STATUS_ENUM["INTERNAL_DATA_ACCESS_ERROR"] = 21009] = "INTERNAL_DATA_ACCESS_ERROR";
|
|
29
|
+
// 'The user account cannot be found or has been deleted'
|
|
30
|
+
RECEIPT_STATUS_ENUM[RECEIPT_STATUS_ENUM["USER_ACCOUNT_DELETED"] = 21010] = "USER_ACCOUNT_DELETED";
|
|
31
|
+
})(RECEIPT_STATUS_ENUM = exports.RECEIPT_STATUS_ENUM || (exports.RECEIPT_STATUS_ENUM = {}));
|
|
File without changes
|
package/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./constants/shared"), exports);
|
|
18
|
+
__exportStar(require("./lib/shared"), exports);
|
|
19
|
+
__exportStar(require("./types/shared"), exports);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ILogger, IReceiptInAppItem, IReceiptValidationResponseBody } from '../../types';
|
|
2
|
+
export declare function isExpiredAppleResponse(responseData: IReceiptValidationResponseBody): boolean;
|
|
3
|
+
export declare function getPurchaseItem(item: IReceiptInAppItem, purchase: IReceiptValidationResponseBody): {
|
|
4
|
+
quantity: number;
|
|
5
|
+
productId: string;
|
|
6
|
+
transactionId: string;
|
|
7
|
+
originalTransactionId: string;
|
|
8
|
+
bundleId: string;
|
|
9
|
+
appItemId: string;
|
|
10
|
+
originalPurchaseDate: number;
|
|
11
|
+
purchaseDateMS: number;
|
|
12
|
+
cancellationDateMS: number | undefined;
|
|
13
|
+
isTrialPeriod: boolean;
|
|
14
|
+
expirationDateMS: number | undefined;
|
|
15
|
+
};
|
|
16
|
+
export declare const validateReceipt: ({ logger, validationEndpoint, receipt, password, excludeOldTransactions, }: {
|
|
17
|
+
logger?: ILogger | null | undefined;
|
|
18
|
+
validationEndpoint: string;
|
|
19
|
+
receipt: string;
|
|
20
|
+
password: string | undefined;
|
|
21
|
+
excludeOldTransactions: boolean;
|
|
22
|
+
}) => Promise<IReceiptValidationResponseBody | null>;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
24
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
+
function step(op) {
|
|
27
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
+
while (_) try {
|
|
29
|
+
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;
|
|
30
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
+
switch (op[0]) {
|
|
32
|
+
case 0: case 1: t = op; break;
|
|
33
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
+
default:
|
|
37
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
+
if (t[2]) _.ops.pop();
|
|
42
|
+
_.trys.pop(); continue;
|
|
43
|
+
}
|
|
44
|
+
op = body.call(thisArg, _);
|
|
45
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.validateReceipt = exports.getPurchaseItem = exports.isExpiredAppleResponse = void 0;
|
|
51
|
+
var constants_1 = require("../../constants");
|
|
52
|
+
var request = require("superagent");
|
|
53
|
+
function isExpiredAppleResponse(responseData) {
|
|
54
|
+
var date = Math.max.apply(Math, (responseData.latest_receipt_info || [])
|
|
55
|
+
.filter(function (lri) { return lri.expires_date_ms; })
|
|
56
|
+
.map(function (lri) { return parseInt(lri.expires_date_ms, 10); }));
|
|
57
|
+
if (date) {
|
|
58
|
+
return date > Date.now();
|
|
59
|
+
}
|
|
60
|
+
// old receipt
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
exports.isExpiredAppleResponse = isExpiredAppleResponse;
|
|
64
|
+
function sendRequest(url, content) {
|
|
65
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
66
|
+
return __generator(this, function (_a) {
|
|
67
|
+
return [2 /*return*/, new Promise(function (resolve, reject) {
|
|
68
|
+
try {
|
|
69
|
+
request
|
|
70
|
+
.post(url)
|
|
71
|
+
.set('Content-type', 'application/json')
|
|
72
|
+
.send(JSON.stringify(content))
|
|
73
|
+
.end(function (error, response) {
|
|
74
|
+
if (error || response.status !== 200) {
|
|
75
|
+
reject(error);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
resolve(response.body);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
reject(err);
|
|
83
|
+
}
|
|
84
|
+
})];
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function getPurchaseItem(item, purchase) {
|
|
89
|
+
return {
|
|
90
|
+
quantity: parseInt(item.quantity, 10),
|
|
91
|
+
productId: item.product_id,
|
|
92
|
+
transactionId: item.transaction_id,
|
|
93
|
+
originalTransactionId: item.original_transaction_id,
|
|
94
|
+
bundleId: purchase.receipt.bundle_id,
|
|
95
|
+
appItemId: item.app_item_id,
|
|
96
|
+
originalPurchaseDate: parseInt(item.original_purchase_date_ms, 10),
|
|
97
|
+
purchaseDateMS: parseInt(item.purchase_date_ms, 10),
|
|
98
|
+
cancellationDateMS: item.cancellation_date_ms ? parseInt(item.cancellation_date_ms, 10) : undefined,
|
|
99
|
+
isTrialPeriod: item.is_trial_period === 'true',
|
|
100
|
+
expirationDateMS: item.expires_date_ms ? parseInt(item.expires_date_ms, 10) : undefined,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
exports.getPurchaseItem = getPurchaseItem;
|
|
104
|
+
var validateReceipt = function (_a) {
|
|
105
|
+
var logger = _a.logger, validationEndpoint = _a.validationEndpoint, receipt = _a.receipt, password = _a.password, excludeOldTransactions = _a.excludeOldTransactions;
|
|
106
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
107
|
+
var _this = this;
|
|
108
|
+
return __generator(this, function (_b) {
|
|
109
|
+
return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
|
|
110
|
+
var content, data, error_1;
|
|
111
|
+
return __generator(this, function (_a) {
|
|
112
|
+
switch (_a.label) {
|
|
113
|
+
case 0:
|
|
114
|
+
content = {
|
|
115
|
+
'receipt-data': receipt,
|
|
116
|
+
password: password,
|
|
117
|
+
'exclude-old-transactions': excludeOldTransactions,
|
|
118
|
+
};
|
|
119
|
+
logger === null || logger === void 0 ? void 0 : logger.log("[iap-apple] Validating against: ".concat(validationEndpoint, " endpoint"));
|
|
120
|
+
logger === null || logger === void 0 ? void 0 : logger.log("[iap-apple] Validation data: ".concat(JSON.stringify(content, null, 2)));
|
|
121
|
+
_a.label = 1;
|
|
122
|
+
case 1:
|
|
123
|
+
_a.trys.push([1, 3, , 4]);
|
|
124
|
+
return [4 /*yield*/, sendRequest(validationEndpoint, content)];
|
|
125
|
+
case 2:
|
|
126
|
+
data = _a.sent();
|
|
127
|
+
logger === null || logger === void 0 ? void 0 : logger.log("[iap-apple] Endpoint ".concat(validationEndpoint, " response: ").concat(JSON.stringify(data, null, 2)));
|
|
128
|
+
// apple responded with error
|
|
129
|
+
if (data.status !== constants_1.RECEIPT_STATUS_ENUM.SUCCESS &&
|
|
130
|
+
data.status !== constants_1.RECEIPT_STATUS_ENUM.TEST_ENV_RECEIPT_DETECTED &&
|
|
131
|
+
data.status !== constants_1.RECEIPT_STATUS_ENUM.DATA_MALFORMED) {
|
|
132
|
+
if (data.status === constants_1.RECEIPT_STATUS_ENUM.SUBSCRIPTION_EXPIRED && !isExpiredAppleResponse(data)) {
|
|
133
|
+
/*
|
|
134
|
+
detected valid subscription receipt,
|
|
135
|
+
however it was cancelled, and it has not been expired
|
|
136
|
+
status code is 21006 for both expired receipt and cancelled receipt...
|
|
137
|
+
*/
|
|
138
|
+
logger === null || logger === void 0 ? void 0 : logger.log('[iap-apple] Valid receipt, but has been cancelled (not expired yet)');
|
|
139
|
+
// force status to be SUCCESS
|
|
140
|
+
resolve(__assign(__assign({}, data), { status: constants_1.RECEIPT_STATUS_ENUM.SUCCESS }));
|
|
141
|
+
return [2 /*return*/];
|
|
142
|
+
}
|
|
143
|
+
logger === null || logger === void 0 ? void 0 : logger.error("[iap-apple] Endpoint ".concat(validationEndpoint, " failed: ").concat(JSON.stringify(data, null, 2)));
|
|
144
|
+
reject({
|
|
145
|
+
rejectionMessage: constants_1.STATUS_TO_MESSAGE_MAP[data.status] || 'Unknown',
|
|
146
|
+
data: data,
|
|
147
|
+
});
|
|
148
|
+
return [2 /*return*/];
|
|
149
|
+
}
|
|
150
|
+
// try another environment...
|
|
151
|
+
if (data.status === constants_1.RECEIPT_STATUS_ENUM.TEST_ENV_RECEIPT_DETECTED) {
|
|
152
|
+
resolve(null);
|
|
153
|
+
return [2 /*return*/];
|
|
154
|
+
}
|
|
155
|
+
if (data.status === constants_1.RECEIPT_STATUS_ENUM.DATA_MALFORMED) {
|
|
156
|
+
reject({
|
|
157
|
+
rejectionMessage: constants_1.STATUS_TO_MESSAGE_MAP[data.status] || 'Unknown',
|
|
158
|
+
data: data,
|
|
159
|
+
});
|
|
160
|
+
return [2 /*return*/];
|
|
161
|
+
}
|
|
162
|
+
// receipt validated
|
|
163
|
+
logger === null || logger === void 0 ? void 0 : logger.log("[iap-apple] Validation successful: ".concat(JSON.stringify(data, null, 2)));
|
|
164
|
+
resolve(data);
|
|
165
|
+
return [3 /*break*/, 4];
|
|
166
|
+
case 3:
|
|
167
|
+
error_1 = _a.sent();
|
|
168
|
+
logger === null || logger === void 0 ? void 0 : logger.error("[iap-apple] Endpoint ".concat(validationEndpoint, " failed: ").concat(error_1));
|
|
169
|
+
reject({
|
|
170
|
+
rejectionMessage: error_1 === null || error_1 === void 0 ? void 0 : error_1.message,
|
|
171
|
+
data: null,
|
|
172
|
+
});
|
|
173
|
+
reject(error_1);
|
|
174
|
+
return [3 /*break*/, 4];
|
|
175
|
+
case 4: return [2 /*return*/];
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}); })];
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
exports.validateReceipt = validateReceipt;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { IIAPAppleConfig, IReceiptValidationResponseBody, PurchasedItem } from '../../types';
|
|
2
|
+
export declare function validate(receipt: string, config: IIAPAppleConfig): Promise<IReceiptValidationResponseBody>;
|
|
3
|
+
export declare const isValidated: (response: IReceiptValidationResponseBody) => boolean;
|
|
4
|
+
export declare const isExpired: (purchasedItem: PurchasedItem) => boolean;
|
|
5
|
+
export declare const isCanceled: (purchasedItem: PurchasedItem) => boolean;
|
|
6
|
+
export declare const getPurchaseData: (purchase?: IReceiptValidationResponseBody) => PurchasedItem[];
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (_) try {
|
|
18
|
+
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;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.getPurchaseData = exports.isCanceled = exports.isExpired = exports.isValidated = exports.validate = void 0;
|
|
40
|
+
var internal_1 = require("../internal");
|
|
41
|
+
var constants_1 = require("../../constants");
|
|
42
|
+
function validate(receipt, config) {
|
|
43
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
44
|
+
var logger;
|
|
45
|
+
var _this = this;
|
|
46
|
+
return __generator(this, function (_a) {
|
|
47
|
+
logger = config.logger;
|
|
48
|
+
return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
|
|
49
|
+
var validatedData, err_1;
|
|
50
|
+
var _a, _b, _c;
|
|
51
|
+
return __generator(this, function (_d) {
|
|
52
|
+
switch (_d.label) {
|
|
53
|
+
case 0:
|
|
54
|
+
validatedData = null;
|
|
55
|
+
_d.label = 1;
|
|
56
|
+
case 1:
|
|
57
|
+
_d.trys.push([1, 6, , 7]);
|
|
58
|
+
if (!!config.test) return [3 /*break*/, 3];
|
|
59
|
+
return [4 /*yield*/, (0, internal_1.validateReceipt)({
|
|
60
|
+
logger: logger,
|
|
61
|
+
validationEndpoint: constants_1.PROD_PATH,
|
|
62
|
+
receipt: receipt,
|
|
63
|
+
password: config.applePassword,
|
|
64
|
+
excludeOldTransactions: Boolean(config.appleExcludeOldTransactions),
|
|
65
|
+
})];
|
|
66
|
+
case 2:
|
|
67
|
+
validatedData = _d.sent();
|
|
68
|
+
_d.label = 3;
|
|
69
|
+
case 3:
|
|
70
|
+
if (!!validatedData) return [3 /*break*/, 5];
|
|
71
|
+
return [4 /*yield*/, (0, internal_1.validateReceipt)({
|
|
72
|
+
logger: logger,
|
|
73
|
+
validationEndpoint: constants_1.SANDBOX_PATH,
|
|
74
|
+
receipt: receipt,
|
|
75
|
+
password: config.applePassword,
|
|
76
|
+
excludeOldTransactions: Boolean(config.appleExcludeOldTransactions),
|
|
77
|
+
})];
|
|
78
|
+
case 4:
|
|
79
|
+
validatedData = _d.sent();
|
|
80
|
+
_d.label = 5;
|
|
81
|
+
case 5:
|
|
82
|
+
if (!validatedData) {
|
|
83
|
+
reject({
|
|
84
|
+
rejectionMessage: 'Unable to validate receipt using appstore endpoints.',
|
|
85
|
+
data: null,
|
|
86
|
+
});
|
|
87
|
+
return [2 /*return*/];
|
|
88
|
+
}
|
|
89
|
+
return [3 /*break*/, 7];
|
|
90
|
+
case 6:
|
|
91
|
+
err_1 = _d.sent();
|
|
92
|
+
reject(err_1);
|
|
93
|
+
return [2 /*return*/];
|
|
94
|
+
case 7:
|
|
95
|
+
if (validatedData.status === constants_1.RECEIPT_STATUS_ENUM.SUCCESS) {
|
|
96
|
+
if (((_a = validatedData.receipt) === null || _a === void 0 ? void 0 : _a.in_app) && ((_c = (_b = validatedData.receipt) === null || _b === void 0 ? void 0 : _b.in_app) === null || _c === void 0 ? void 0 : _c.length) === 0) {
|
|
97
|
+
/*
|
|
98
|
+
Detected valid receipt,
|
|
99
|
+
but the receipt bought nothing
|
|
100
|
+
probably hacked: https://forums.developer.apple.com/thread/8954
|
|
101
|
+
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_
|
|
102
|
+
*/
|
|
103
|
+
reject({
|
|
104
|
+
rejectionMessage: 'Detected valid receipt, however purchase list is empty',
|
|
105
|
+
data: validatedData,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// validated successfully
|
|
109
|
+
resolve(validatedData);
|
|
110
|
+
return [2 /*return*/];
|
|
111
|
+
}
|
|
112
|
+
// failed to validate reject with apple message
|
|
113
|
+
reject({
|
|
114
|
+
rejectionMessage: constants_1.STATUS_TO_MESSAGE_MAP[validatedData.status],
|
|
115
|
+
data: validatedData,
|
|
116
|
+
});
|
|
117
|
+
return [2 /*return*/];
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}); })];
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
exports.validate = validate;
|
|
125
|
+
var isValidated = function (response) {
|
|
126
|
+
return response && response.status === constants_1.RECEIPT_STATUS_ENUM.SUCCESS;
|
|
127
|
+
};
|
|
128
|
+
exports.isValidated = isValidated;
|
|
129
|
+
var isExpired = function (purchasedItem) {
|
|
130
|
+
if (!purchasedItem || !purchasedItem.transactionId) {
|
|
131
|
+
throw new Error('Detected invalid purchased item! Make sure object is defined and it has transaction id.');
|
|
132
|
+
}
|
|
133
|
+
// it has been cancelled
|
|
134
|
+
if (purchasedItem.cancellationDateMS) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
// there is no expiration date with this item
|
|
138
|
+
if (!purchasedItem.expirationDateMS) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
// has expired
|
|
142
|
+
if (Date.now() - purchasedItem.expirationDateMS >= 0) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
// has not expired yet
|
|
146
|
+
return false;
|
|
147
|
+
};
|
|
148
|
+
exports.isExpired = isExpired;
|
|
149
|
+
var isCanceled = function (purchasedItem) {
|
|
150
|
+
if (!purchasedItem || !purchasedItem.transactionId) {
|
|
151
|
+
throw new Error('Detected invalid purchased item! Make sure object is defined and it has transaction id.');
|
|
152
|
+
}
|
|
153
|
+
return Boolean(purchasedItem.cancellationDateMS);
|
|
154
|
+
};
|
|
155
|
+
exports.isCanceled = isCanceled;
|
|
156
|
+
var getPurchaseData = function (purchase) {
|
|
157
|
+
if (!purchase || !purchase.receipt) {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
var data = [];
|
|
161
|
+
var purchases = purchase.receipt.in_app || [];
|
|
162
|
+
var lri = purchase.latest_receipt_info || purchase.receipt.latest_receipt_info;
|
|
163
|
+
if (Array.isArray(lri)) {
|
|
164
|
+
purchases = purchases.concat(lri);
|
|
165
|
+
}
|
|
166
|
+
/*
|
|
167
|
+
we sort purchases by purchase_date_ms to make it easier
|
|
168
|
+
to weed out duplicates (items with the same original_transaction_id)
|
|
169
|
+
purchase_date_ms DESC
|
|
170
|
+
*/
|
|
171
|
+
purchases.sort(function (a, b) {
|
|
172
|
+
return parseInt(b.purchase_date_ms, 10) - parseInt(a.purchase_date_ms, 10);
|
|
173
|
+
});
|
|
174
|
+
var transactionIds = {};
|
|
175
|
+
for (var i = 0; i < purchases.length; i++) {
|
|
176
|
+
var item = purchases[i];
|
|
177
|
+
var tid = item.original_transaction_id;
|
|
178
|
+
// avoid duplicate
|
|
179
|
+
if (transactionIds[tid]) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
data.push((0, internal_1.getPurchaseItem)(item, purchase));
|
|
183
|
+
transactionIds[tid] = true;
|
|
184
|
+
}
|
|
185
|
+
return data;
|
|
186
|
+
};
|
|
187
|
+
exports.getPurchaseData = getPurchaseData;
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iap-apple",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"description": "Integration with Apples InAppPurchases in Typescript, available for NodeJS environments.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"prebuild": "yarn code-quality:check",
|
|
9
8
|
"build": "rm -rf dist && tsc --project tsconfig.build.json && cp package.json dist/package.json",
|
|
10
9
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
11
10
|
"test": "nyc --reporter=json-summary mocha --bail --slow 1 --timeout 60000 --require ts-node/register/transpile-only src/**/*.spec.ts",
|
|
@@ -74,11 +73,6 @@
|
|
|
74
73
|
"ts-node": "10.9.1",
|
|
75
74
|
"typescript": "4.8.4"
|
|
76
75
|
},
|
|
77
|
-
"files": [
|
|
78
|
-
"src/**/*",
|
|
79
|
-
"dist/**/*",
|
|
80
|
-
"README.md"
|
|
81
|
-
],
|
|
82
76
|
"dependencies": {
|
|
83
77
|
"superagent": "8.0.6"
|
|
84
78
|
},
|
|
@@ -150,7 +144,12 @@
|
|
|
150
144
|
}
|
|
151
145
|
}
|
|
152
146
|
],
|
|
153
|
-
|
|
147
|
+
[
|
|
148
|
+
"@semantic-release/npm",
|
|
149
|
+
{
|
|
150
|
+
"pkgRoot": "./dist"
|
|
151
|
+
}
|
|
152
|
+
],
|
|
154
153
|
"@semantic-release/release-notes-generator",
|
|
155
154
|
"@semantic-release/changelog",
|
|
156
155
|
[
|
|
File without changes
|
package/types/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./internal"), exports);
|
|
18
|
+
__exportStar(require("./shared"), exports);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { RECEIPT_STATUS_ENUM } from '../../constants';
|
|
2
|
+
interface IReceipt {
|
|
3
|
+
bundle_id: string;
|
|
4
|
+
application_version: string;
|
|
5
|
+
in_app: IReceiptInAppItem[];
|
|
6
|
+
latest_receipt_info: IReceiptInAppItem[];
|
|
7
|
+
original_application_version: string;
|
|
8
|
+
receipt_creation_date_ms: string;
|
|
9
|
+
expiration_date_ms: string;
|
|
10
|
+
original_purchase_date: string;
|
|
11
|
+
app_item_id: string;
|
|
12
|
+
version_external_identifier: string;
|
|
13
|
+
expires_date_ms?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface IReceiptInAppItem {
|
|
16
|
+
quantity: string;
|
|
17
|
+
product_id: string;
|
|
18
|
+
transaction_id: string;
|
|
19
|
+
original_transaction_id: string;
|
|
20
|
+
purchase_date: string;
|
|
21
|
+
purchase_date_ms: string;
|
|
22
|
+
original_purchase_date: string;
|
|
23
|
+
original_purchase_date_ms: string;
|
|
24
|
+
expires_date?: string;
|
|
25
|
+
expires_date_ms?: string;
|
|
26
|
+
expiration_intent?: '1' | '2' | '3' | '4' | '5';
|
|
27
|
+
is_trial_period: string;
|
|
28
|
+
cancellation_date?: string;
|
|
29
|
+
cancellation_date_ms?: string;
|
|
30
|
+
cancellation_reason?: '0' | '1';
|
|
31
|
+
app_item_id: string;
|
|
32
|
+
}
|
|
33
|
+
export interface IReceiptValidationResponseBody {
|
|
34
|
+
status: RECEIPT_STATUS_ENUM;
|
|
35
|
+
environment: 'Sandbox' | 'Production';
|
|
36
|
+
receipt: IReceipt;
|
|
37
|
+
latest_receipt: string;
|
|
38
|
+
latest_receipt_info: IReceiptInAppItem[];
|
|
39
|
+
'is-retryable': boolean;
|
|
40
|
+
}
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { IReceiptValidationResponseBody } from '../../types/internal';
|
|
2
|
+
export interface ILogger {
|
|
3
|
+
log: (message: string) => void;
|
|
4
|
+
warn: (message: string) => void;
|
|
5
|
+
error: (message: string) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface IIAPAppleConfig {
|
|
8
|
+
appleExcludeOldTransactions?: boolean | undefined;
|
|
9
|
+
applePassword?: string | undefined;
|
|
10
|
+
test?: boolean | undefined;
|
|
11
|
+
logger?: ILogger | null;
|
|
12
|
+
}
|
|
13
|
+
export interface PurchasedItem {
|
|
14
|
+
bundleId: string;
|
|
15
|
+
appItemId: string;
|
|
16
|
+
originalTransactionId?: string;
|
|
17
|
+
transactionId: string;
|
|
18
|
+
productId: string;
|
|
19
|
+
originalPurchaseDate?: number;
|
|
20
|
+
expirationDateMS?: number;
|
|
21
|
+
purchaseDateMS: number;
|
|
22
|
+
isTrialPeriod: boolean;
|
|
23
|
+
cancellationDateMS?: number;
|
|
24
|
+
quantity: number;
|
|
25
|
+
}
|
|
26
|
+
export interface IAPAppleError {
|
|
27
|
+
rejectionMessage: string;
|
|
28
|
+
data?: IReceiptValidationResponseBody | null;
|
|
29
|
+
}
|