iap-apple 1.3.7 → 1.3.9
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/CHANGELOG.md +9 -0
- package/README.md +171 -2
- package/lib/internal/index.d.ts +7 -19
- package/lib/internal/index.js +27 -24
- package/lib/shared/index.d.ts +35 -6
- package/lib/shared/index.js +80 -51
- package/package.json +1 -1
- package/types/shared/index.d.ts +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## [1.3.8](https://github.com/ssbarbee/iap-apple/compare/v1.3.7...v1.3.8) (2022-12-22)
|
|
2
|
+
|
|
3
|
+
## [1.3.7](https://github.com/ssbarbee/iap-apple/compare/v1.3.6...v1.3.7) (2022-12-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* add README, CHANGELOG and LICENCE to dist ([deb9be6](https://github.com/ssbarbee/iap-apple/commit/deb9be6090ffba51599ea5424f9fd84ce967a87a))
|
|
9
|
+
|
|
1
10
|
## [1.3.6](https://github.com/ssbarbee/iap-apple/compare/v1.3.5...v1.3.6) (2022-12-22)
|
|
2
11
|
|
|
3
12
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,175 @@
|
|
|
1
1
|
| Statements | Branches | Functions | Lines |
|
|
2
2
|
| --------------------------- | ----------------------- | ------------------------- | ----------------- |
|
|
3
|
-
|  |  |  |  |
|
|
4
4
|
|
|
5
5
|
# iap-apple
|
|
6
|
-
Integration
|
|
6
|
+
📦🚀 Integration of Apples **validation service** for App Store Receipts, written in Typescript, available for NodeJS environments.
|
|
7
|
+
|
|
8
|
+
A NodeJS module for in-app purchase (in-app billing) and subscription for Apple.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
Create a Typescript package for validation of [App Store Receipts](https://developer.apple.com/documentation/appstorereceipts).
|
|
13
|
+
This package is meant to be used server side to validate receipts from the App Store by talking to Apples servers.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### npm
|
|
18
|
+
|
|
19
|
+
```npm install iap-apple```
|
|
20
|
+
|
|
21
|
+
### yarn
|
|
22
|
+
|
|
23
|
+
```yarn add iap-apple```
|
|
24
|
+
|
|
25
|
+
## API documentation
|
|
26
|
+
|
|
27
|
+
### verify
|
|
28
|
+
|
|
29
|
+
API used to verify receipt data received during an App Store purchase.
|
|
30
|
+
Requires **appSharedSecret** to be passed as part of the configuration.
|
|
31
|
+
See example for more details.
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
|
|
35
|
+
import { verify, IAPAppleError, IVerifyReceiptResponseBody } from 'iap-apple';
|
|
36
|
+
|
|
37
|
+
async function verifyAppleReceipt(receipt: string) {
|
|
38
|
+
try {
|
|
39
|
+
const verifyReceiptResponse = await verify(receipt, {
|
|
40
|
+
/*
|
|
41
|
+
Your app's shared secret, which is a hexadecimal string. For more information about the shared secret.
|
|
42
|
+
https://help.apple.com/app-store-connect/#/devf341c0f01
|
|
43
|
+
*/
|
|
44
|
+
appSharedSecret,
|
|
45
|
+
/*
|
|
46
|
+
To exclude old transaction, set this to true.
|
|
47
|
+
Default is false.
|
|
48
|
+
*/
|
|
49
|
+
excludeOldTransactions: false,
|
|
50
|
+
/*
|
|
51
|
+
Force validation against Apple Sandbox only.
|
|
52
|
+
In effect this means that the validation against Apple Production endpoint won't be used.
|
|
53
|
+
Default is false.
|
|
54
|
+
*/
|
|
55
|
+
test: false,
|
|
56
|
+
/*
|
|
57
|
+
Optional can be omitted, pass logger object if you want to debug.
|
|
58
|
+
Default is null object.
|
|
59
|
+
*/
|
|
60
|
+
logger: console,
|
|
61
|
+
});
|
|
62
|
+
console.log('verifyReceiptResponse', verifyReceiptResponse);
|
|
63
|
+
} catch(error) {
|
|
64
|
+
const iapAppleError = error as IAPAppleError;
|
|
65
|
+
const rejectionMessage: string = error.rejectionMessage;
|
|
66
|
+
const errorData: IVerifyReceiptResponseBody | null = error.data;
|
|
67
|
+
console.error('Error happened', rejectionMessage);
|
|
68
|
+
console.error('Details', errorData);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### isVerifiedReceipt
|
|
74
|
+
|
|
75
|
+
API used to verify if response returned by `verify` is verified.
|
|
76
|
+
Requires the output of `verify` to be passed.
|
|
77
|
+
See example for more details.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
|
|
81
|
+
import { verify, isVerifiedReceipt, IIAPAppleConfig } from 'iap-apple';
|
|
82
|
+
|
|
83
|
+
async function isVerifiedAppleReceipt(receipt: string, config: IIAPAppleConfig) {
|
|
84
|
+
try {
|
|
85
|
+
const verifyReceiptResponse = await verify(receipt, config);
|
|
86
|
+
const isVerifiedReceipt = isVerifiedReceipt(verifyReceiptResponse);
|
|
87
|
+
} catch(error) {
|
|
88
|
+
const iapAppleError = error as IAPAppleError;
|
|
89
|
+
const rejectionMessage: string = error.rejectionMessage;
|
|
90
|
+
const errorData: IVerifyReceiptResponseBody | null = error.data;
|
|
91
|
+
console.error('Error happened', rejectionMessage);
|
|
92
|
+
console.error('Details', errorData);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### getPurchasedItems
|
|
98
|
+
|
|
99
|
+
API used to get an array of PurchasedItem objects from the Apple App Store response,
|
|
100
|
+
sort by their purchase date descending.
|
|
101
|
+
Usually what we are interested in is the first item of the purchase.
|
|
102
|
+
Requires the output of `verify` to be passed.
|
|
103
|
+
See example for more details.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
|
|
107
|
+
import { verify, getPurchasedItems, IIAPAppleConfig } from 'iap-apple';
|
|
108
|
+
|
|
109
|
+
async function isVerifiedAppleReceipt(receipt: string, config: IIAPAppleConfig) {
|
|
110
|
+
try {
|
|
111
|
+
const verifyReceiptResponse = await verify(receipt, config);
|
|
112
|
+
const purchasedItems = getPurchasedItems(verifyReceiptResponse);
|
|
113
|
+
const latestPurchase = purchasedItems[0];
|
|
114
|
+
} catch(error) {
|
|
115
|
+
const iapAppleError = error as IAPAppleError;
|
|
116
|
+
const rejectionMessage: string = error.rejectionMessage;
|
|
117
|
+
const errorData: IVerifyReceiptResponseBody | null = error.data;
|
|
118
|
+
console.error('Error happened', rejectionMessage);
|
|
119
|
+
console.error('Details', errorData);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### isPurchasedItemCanceled
|
|
125
|
+
|
|
126
|
+
API used to check if a purchased item is canceled.
|
|
127
|
+
Requires the output of `getPurchasedItems` to be passed.
|
|
128
|
+
See example for more details.
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
|
|
132
|
+
import { verify, getPurchasedItems, isPurchasedItemCanceled, IIAPAppleConfig } from 'iap-apple';
|
|
133
|
+
|
|
134
|
+
async function isVerifiedAppleReceipt(receipt: string, config: IIAPAppleConfig) {
|
|
135
|
+
try {
|
|
136
|
+
const verifyReceiptResponse = await verify(receipt, config);
|
|
137
|
+
const purchasedItems = getPurchasedItems(verifyReceiptResponse);
|
|
138
|
+
const latestPurchase = purchasedItems[0];
|
|
139
|
+
const isCanceled = isPurchasedItemCanceled(latestPurchase);
|
|
140
|
+
} catch(error) {
|
|
141
|
+
const iapAppleError = error as IAPAppleError;
|
|
142
|
+
const rejectionMessage: string = error.rejectionMessage;
|
|
143
|
+
const errorData: IVerifyReceiptResponseBody | null = error.data;
|
|
144
|
+
console.error('Error happened', rejectionMessage);
|
|
145
|
+
console.error('Details', errorData);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
### isPurchasedItemExpired
|
|
152
|
+
|
|
153
|
+
API used to check if a purchased item is expired.
|
|
154
|
+
Requires the output of `getPurchasedItems` to be passed.
|
|
155
|
+
See example for more details.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
|
|
159
|
+
import { verify, getPurchasedItems, isPurchasedItemExpired, IIAPAppleConfig } from 'iap-apple';
|
|
160
|
+
|
|
161
|
+
async function isVerifiedAppleReceipt(receipt: string, config: IIAPAppleConfig) {
|
|
162
|
+
try {
|
|
163
|
+
const verifyReceiptResponse = await verify(receipt, config);
|
|
164
|
+
const purchasedItems = getPurchasedItems(verifyReceiptResponse);
|
|
165
|
+
const latestPurchase = purchasedItems[0];
|
|
166
|
+
const isExpired = isPurchasedItemExpired(latestPurchase);
|
|
167
|
+
} catch(error) {
|
|
168
|
+
const iapAppleError = error as IAPAppleError;
|
|
169
|
+
const rejectionMessage: string = error.rejectionMessage;
|
|
170
|
+
const errorData: IVerifyReceiptResponseBody | null = error.data;
|
|
171
|
+
console.error('Error happened', rejectionMessage);
|
|
172
|
+
console.error('Details', errorData);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
package/lib/internal/index.d.ts
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
import { ILogger, IReceiptInAppItem,
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function getPurchaseItem(item: IReceiptInAppItem, purchase:
|
|
4
|
-
|
|
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, }: {
|
|
1
|
+
import { ILogger, IReceiptInAppItem, IVerifyReceiptResponseBody, PurchasedItem } from '../../types';
|
|
2
|
+
export declare function isExpiredReceipt(responseData: IVerifyReceiptResponseBody): boolean;
|
|
3
|
+
export declare function getPurchaseItem(item: IReceiptInAppItem, purchase: IVerifyReceiptResponseBody): PurchasedItem;
|
|
4
|
+
export declare const verifyReceipt: ({ logger, validationEndpoint, receiptData, appSharedSecret, excludeOldTransactions, }: {
|
|
17
5
|
logger?: ILogger | null | undefined;
|
|
18
6
|
validationEndpoint: string;
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
receiptData: string;
|
|
8
|
+
appSharedSecret: string;
|
|
21
9
|
excludeOldTransactions: boolean;
|
|
22
|
-
}) => Promise<
|
|
10
|
+
}) => Promise<IVerifyReceiptResponseBody | null>;
|
package/lib/internal/index.js
CHANGED
|
@@ -47,21 +47,24 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
49
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
-
exports.
|
|
50
|
+
exports.verifyReceipt = exports.getPurchaseItem = exports.isExpiredReceipt = void 0;
|
|
51
51
|
var constants_1 = require("../../constants");
|
|
52
52
|
var request = require("superagent");
|
|
53
|
-
function
|
|
53
|
+
function prefixMessage(message) {
|
|
54
|
+
return "[iap-apple] ".concat(message);
|
|
55
|
+
}
|
|
56
|
+
function isExpiredReceipt(responseData) {
|
|
54
57
|
var date = Math.max.apply(Math, (responseData.latest_receipt_info || [])
|
|
55
58
|
.filter(function (lri) { return lri.expires_date_ms; })
|
|
56
59
|
.map(function (lri) { return parseInt(lri.expires_date_ms, 10); }));
|
|
57
60
|
if (date) {
|
|
58
|
-
return date > Date.now();
|
|
61
|
+
return date > Date.now().valueOf();
|
|
59
62
|
}
|
|
60
63
|
// old receipt
|
|
61
64
|
return false;
|
|
62
65
|
}
|
|
63
|
-
exports.
|
|
64
|
-
function
|
|
66
|
+
exports.isExpiredReceipt = isExpiredReceipt;
|
|
67
|
+
function verifyReceiptApple(url, content) {
|
|
65
68
|
return __awaiter(this, void 0, void 0, function () {
|
|
66
69
|
return __generator(this, function (_a) {
|
|
67
70
|
return [2 /*return*/, new Promise(function (resolve, reject) {
|
|
@@ -93,7 +96,7 @@ function getPurchaseItem(item, purchase) {
|
|
|
93
96
|
originalTransactionId: item.original_transaction_id,
|
|
94
97
|
bundleId: purchase.receipt.bundle_id,
|
|
95
98
|
appItemId: item.app_item_id,
|
|
96
|
-
|
|
99
|
+
originalPurchaseDateMS: parseInt(item.original_purchase_date_ms, 10),
|
|
97
100
|
purchaseDateMS: parseInt(item.purchase_date_ms, 10),
|
|
98
101
|
cancellationDateMS: item.cancellation_date_ms ? parseInt(item.cancellation_date_ms, 10) : undefined,
|
|
99
102
|
isTrialPeriod: item.is_trial_period === 'true',
|
|
@@ -101,8 +104,8 @@ function getPurchaseItem(item, purchase) {
|
|
|
101
104
|
};
|
|
102
105
|
}
|
|
103
106
|
exports.getPurchaseItem = getPurchaseItem;
|
|
104
|
-
var
|
|
105
|
-
var logger = _a.logger, validationEndpoint = _a.validationEndpoint,
|
|
107
|
+
var verifyReceipt = function (_a) {
|
|
108
|
+
var logger = _a.logger, validationEndpoint = _a.validationEndpoint, receiptData = _a.receiptData, appSharedSecret = _a.appSharedSecret, excludeOldTransactions = _a.excludeOldTransactions;
|
|
106
109
|
return __awaiter(this, void 0, void 0, function () {
|
|
107
110
|
var _this = this;
|
|
108
111
|
return __generator(this, function (_b) {
|
|
@@ -112,35 +115,35 @@ var validateReceipt = function (_a) {
|
|
|
112
115
|
switch (_a.label) {
|
|
113
116
|
case 0:
|
|
114
117
|
content = {
|
|
115
|
-
'receipt-data':
|
|
116
|
-
password:
|
|
118
|
+
'receipt-data': receiptData,
|
|
119
|
+
password: appSharedSecret,
|
|
117
120
|
'exclude-old-transactions': excludeOldTransactions,
|
|
118
121
|
};
|
|
119
|
-
logger === null || logger === void 0 ? void 0 : logger.log("
|
|
120
|
-
logger === null || logger === void 0 ? void 0 : logger.log("
|
|
122
|
+
logger === null || logger === void 0 ? void 0 : logger.log(prefixMessage("Validating against: ".concat(validationEndpoint, " endpoint")));
|
|
123
|
+
logger === null || logger === void 0 ? void 0 : logger.log(prefixMessage("Validation data: ".concat(JSON.stringify(content, null, 2))));
|
|
121
124
|
_a.label = 1;
|
|
122
125
|
case 1:
|
|
123
126
|
_a.trys.push([1, 3, , 4]);
|
|
124
|
-
return [4 /*yield*/,
|
|
127
|
+
return [4 /*yield*/, verifyReceiptApple(validationEndpoint, content)];
|
|
125
128
|
case 2:
|
|
126
129
|
data = _a.sent();
|
|
127
|
-
logger === null || logger === void 0 ? void 0 : logger.log("
|
|
130
|
+
logger === null || logger === void 0 ? void 0 : logger.log(prefixMessage("Endpoint ".concat(validationEndpoint, " response: ").concat(JSON.stringify(data, null, 2))));
|
|
128
131
|
// apple responded with error
|
|
129
132
|
if (data.status !== constants_1.RECEIPT_STATUS_ENUM.SUCCESS &&
|
|
130
133
|
data.status !== constants_1.RECEIPT_STATUS_ENUM.TEST_ENV_RECEIPT_DETECTED &&
|
|
131
134
|
data.status !== constants_1.RECEIPT_STATUS_ENUM.DATA_MALFORMED) {
|
|
132
|
-
if (data.status === constants_1.RECEIPT_STATUS_ENUM.SUBSCRIPTION_EXPIRED && !
|
|
135
|
+
if (data.status === constants_1.RECEIPT_STATUS_ENUM.SUBSCRIPTION_EXPIRED && !isExpiredReceipt(data)) {
|
|
133
136
|
/*
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
logger === null || logger === void 0 ? void 0 : logger.log('
|
|
137
|
+
detected valid subscription receipt,
|
|
138
|
+
however it was cancelled, and it has not been expired
|
|
139
|
+
status code is 21006 for both expired receipt and cancelled receipt...
|
|
140
|
+
*/
|
|
141
|
+
logger === null || logger === void 0 ? void 0 : logger.log(prefixMessage('Valid receipt, but has been cancelled (not expired yet)'));
|
|
139
142
|
// force status to be SUCCESS
|
|
140
143
|
resolve(__assign(__assign({}, data), { status: constants_1.RECEIPT_STATUS_ENUM.SUCCESS }));
|
|
141
144
|
return [2 /*return*/];
|
|
142
145
|
}
|
|
143
|
-
logger === null || logger === void 0 ? void 0 : logger.error("
|
|
146
|
+
logger === null || logger === void 0 ? void 0 : logger.error(prefixMessage("Endpoint ".concat(validationEndpoint, " failed: ").concat(JSON.stringify(data, null, 2))));
|
|
144
147
|
reject({
|
|
145
148
|
rejectionMessage: constants_1.STATUS_TO_MESSAGE_MAP[data.status] || 'Unknown',
|
|
146
149
|
data: data,
|
|
@@ -160,12 +163,12 @@ var validateReceipt = function (_a) {
|
|
|
160
163
|
return [2 /*return*/];
|
|
161
164
|
}
|
|
162
165
|
// receipt validated
|
|
163
|
-
logger === null || logger === void 0 ? void 0 : logger.log("
|
|
166
|
+
logger === null || logger === void 0 ? void 0 : logger.log(prefixMessage("Validation successful: ".concat(JSON.stringify(data, null, 2))));
|
|
164
167
|
resolve(data);
|
|
165
168
|
return [3 /*break*/, 4];
|
|
166
169
|
case 3:
|
|
167
170
|
error_1 = _a.sent();
|
|
168
|
-
logger === null || logger === void 0 ? void 0 : logger.error("
|
|
171
|
+
logger === null || logger === void 0 ? void 0 : logger.error(prefixMessage("Endpoint ".concat(validationEndpoint, " failed: ").concat(error_1)));
|
|
169
172
|
reject({
|
|
170
173
|
rejectionMessage: error_1 === null || error_1 === void 0 ? void 0 : error_1.message,
|
|
171
174
|
data: null,
|
|
@@ -179,4 +182,4 @@ var validateReceipt = function (_a) {
|
|
|
179
182
|
});
|
|
180
183
|
});
|
|
181
184
|
};
|
|
182
|
-
exports.
|
|
185
|
+
exports.verifyReceipt = verifyReceipt;
|
package/lib/shared/index.d.ts
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
|
-
import { IIAPAppleConfig,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { IIAPAppleConfig, IVerifyReceiptResponseBody, PurchasedItem } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* It takes a receipt data and a config object, and returns a promise that resolves to a validated receipt object or
|
|
4
|
+
* rejects with an error
|
|
5
|
+
* @param {string} receipt - The receipt data.
|
|
6
|
+
* @param {IIAPAppleConfig} config - IIAPAppleConfig
|
|
7
|
+
* @returns a promise.
|
|
8
|
+
*/
|
|
9
|
+
export declare function verify(receipt: string, config: IIAPAppleConfig): Promise<IVerifyReceiptResponseBody>;
|
|
10
|
+
/**
|
|
11
|
+
* It checks if the receipt is valid.
|
|
12
|
+
* @param {IVerifyReceiptResponseBody | null} verifyReceiptResponse - IVerifyReceiptResponseBody | null
|
|
13
|
+
* @returns A boolean
|
|
14
|
+
*/
|
|
15
|
+
export declare const isVerifiedReceipt: (verifyReceiptResponse: IVerifyReceiptResponseBody | null) => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* If the purchased item has been cancelled or if the expiration date has passed, then it has expired
|
|
18
|
+
* @param {PurchasedItem | null} purchasedItem - PurchasedItem | null
|
|
19
|
+
* @returns A boolean
|
|
20
|
+
*/
|
|
21
|
+
export declare const isPurchasedItemExpired: (purchasedItem: PurchasedItem | null) => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* If the purchased item has a cancellation date, then it's canceled.
|
|
24
|
+
* @param {PurchasedItem} purchasedItem - PurchasedItem - this is the purchased item object that you get from the
|
|
25
|
+
* getPurchasedItems() method.
|
|
26
|
+
* @returns A boolean value.
|
|
27
|
+
*/
|
|
28
|
+
export declare const isPurchasedItemCanceled: (purchasedItem: PurchasedItem) => boolean;
|
|
29
|
+
/**
|
|
30
|
+
* It takes a response from the Apple App Store and returns an array of PurchasedItem objects sorted by their purchase date in descending order,
|
|
31
|
+
* the latest purchase comes first.
|
|
32
|
+
* @param {IVerifyReceiptResponseBody | null} verifyReceiptResponse - IVerifyReceiptResponseBody | null
|
|
33
|
+
* @returns An array of PurchasedItem objects.
|
|
34
|
+
*/
|
|
35
|
+
export declare const getPurchasedItems: (verifyReceiptResponse: IVerifyReceiptResponseBody | null) => PurchasedItem[];
|
package/lib/shared/index.js
CHANGED
|
@@ -36,50 +36,57 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.
|
|
39
|
+
exports.getPurchasedItems = exports.isPurchasedItemCanceled = exports.isPurchasedItemExpired = exports.isVerifiedReceipt = exports.verify = void 0;
|
|
40
40
|
var internal_1 = require("../internal");
|
|
41
41
|
var constants_1 = require("../../constants");
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* It takes a receipt data and a config object, and returns a promise that resolves to a validated receipt object or
|
|
44
|
+
* rejects with an error
|
|
45
|
+
* @param {string} receipt - The receipt data.
|
|
46
|
+
* @param {IIAPAppleConfig} config - IIAPAppleConfig
|
|
47
|
+
* @returns a promise.
|
|
48
|
+
*/
|
|
49
|
+
function verify(receipt, config) {
|
|
43
50
|
return __awaiter(this, void 0, void 0, function () {
|
|
44
|
-
var logger;
|
|
51
|
+
var appleExcludeOldTransactions, logger, test, appSharedSecret;
|
|
45
52
|
var _this = this;
|
|
46
53
|
return __generator(this, function (_a) {
|
|
47
|
-
logger = config.logger;
|
|
54
|
+
appleExcludeOldTransactions = config.appleExcludeOldTransactions, logger = config.logger, test = config.test, appSharedSecret = config.appSharedSecret;
|
|
48
55
|
return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
|
|
49
|
-
var
|
|
56
|
+
var verifyReceiptResponse, err_1;
|
|
50
57
|
var _a, _b, _c;
|
|
51
58
|
return __generator(this, function (_d) {
|
|
52
59
|
switch (_d.label) {
|
|
53
60
|
case 0:
|
|
54
|
-
|
|
61
|
+
verifyReceiptResponse = null;
|
|
55
62
|
_d.label = 1;
|
|
56
63
|
case 1:
|
|
57
64
|
_d.trys.push([1, 6, , 7]);
|
|
58
|
-
if (!!
|
|
59
|
-
return [4 /*yield*/, (0, internal_1.
|
|
65
|
+
if (!!test) return [3 /*break*/, 3];
|
|
66
|
+
return [4 /*yield*/, (0, internal_1.verifyReceipt)({
|
|
60
67
|
logger: logger,
|
|
61
68
|
validationEndpoint: constants_1.PROD_PATH,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
excludeOldTransactions: Boolean(
|
|
69
|
+
receiptData: receipt,
|
|
70
|
+
appSharedSecret: appSharedSecret,
|
|
71
|
+
excludeOldTransactions: Boolean(appleExcludeOldTransactions),
|
|
65
72
|
})];
|
|
66
73
|
case 2:
|
|
67
|
-
|
|
74
|
+
verifyReceiptResponse = _d.sent();
|
|
68
75
|
_d.label = 3;
|
|
69
76
|
case 3:
|
|
70
|
-
if (!!
|
|
71
|
-
return [4 /*yield*/, (0, internal_1.
|
|
77
|
+
if (!!verifyReceiptResponse) return [3 /*break*/, 5];
|
|
78
|
+
return [4 /*yield*/, (0, internal_1.verifyReceipt)({
|
|
72
79
|
logger: logger,
|
|
73
80
|
validationEndpoint: constants_1.SANDBOX_PATH,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
excludeOldTransactions: Boolean(
|
|
81
|
+
receiptData: receipt,
|
|
82
|
+
appSharedSecret: appSharedSecret,
|
|
83
|
+
excludeOldTransactions: Boolean(appleExcludeOldTransactions),
|
|
77
84
|
})];
|
|
78
85
|
case 4:
|
|
79
|
-
|
|
86
|
+
verifyReceiptResponse = _d.sent();
|
|
80
87
|
_d.label = 5;
|
|
81
88
|
case 5:
|
|
82
|
-
if (!
|
|
89
|
+
if (!verifyReceiptResponse) {
|
|
83
90
|
reject({
|
|
84
91
|
rejectionMessage: 'Unable to validate receipt using appstore endpoints.',
|
|
85
92
|
data: null,
|
|
@@ -92,27 +99,27 @@ function validate(receipt, config) {
|
|
|
92
99
|
reject(err_1);
|
|
93
100
|
return [2 /*return*/];
|
|
94
101
|
case 7:
|
|
95
|
-
if (
|
|
96
|
-
if (((_a =
|
|
102
|
+
if (verifyReceiptResponse.status === constants_1.RECEIPT_STATUS_ENUM.SUCCESS) {
|
|
103
|
+
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) {
|
|
97
104
|
/*
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
Detected valid receipt,
|
|
106
|
+
but the receipt bought nothing
|
|
107
|
+
probably hacked: https://forums.developer.apple.com/thread/8954
|
|
108
|
+
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_
|
|
109
|
+
*/
|
|
103
110
|
reject({
|
|
104
111
|
rejectionMessage: 'Detected valid receipt, however purchase list is empty',
|
|
105
|
-
data:
|
|
112
|
+
data: verifyReceiptResponse,
|
|
106
113
|
});
|
|
107
114
|
}
|
|
108
115
|
// validated successfully
|
|
109
|
-
resolve(
|
|
116
|
+
resolve(verifyReceiptResponse);
|
|
110
117
|
return [2 /*return*/];
|
|
111
118
|
}
|
|
112
119
|
// failed to validate reject with apple message
|
|
113
120
|
reject({
|
|
114
|
-
rejectionMessage: constants_1.STATUS_TO_MESSAGE_MAP[
|
|
115
|
-
data:
|
|
121
|
+
rejectionMessage: constants_1.STATUS_TO_MESSAGE_MAP[verifyReceiptResponse.status],
|
|
122
|
+
data: verifyReceiptResponse,
|
|
116
123
|
});
|
|
117
124
|
return [2 /*return*/];
|
|
118
125
|
}
|
|
@@ -121,13 +128,23 @@ function validate(receipt, config) {
|
|
|
121
128
|
});
|
|
122
129
|
});
|
|
123
130
|
}
|
|
124
|
-
exports.
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
exports.verify = verify;
|
|
132
|
+
/**
|
|
133
|
+
* It checks if the receipt is valid.
|
|
134
|
+
* @param {IVerifyReceiptResponseBody | null} verifyReceiptResponse - IVerifyReceiptResponseBody | null
|
|
135
|
+
* @returns A boolean
|
|
136
|
+
*/
|
|
137
|
+
var isVerifiedReceipt = function (verifyReceiptResponse) {
|
|
138
|
+
return (verifyReceiptResponse === null || verifyReceiptResponse === void 0 ? void 0 : verifyReceiptResponse.status) === constants_1.RECEIPT_STATUS_ENUM.SUCCESS;
|
|
127
139
|
};
|
|
128
|
-
exports.
|
|
129
|
-
|
|
130
|
-
|
|
140
|
+
exports.isVerifiedReceipt = isVerifiedReceipt;
|
|
141
|
+
/**
|
|
142
|
+
* If the purchased item has been cancelled or if the expiration date has passed, then it has expired
|
|
143
|
+
* @param {PurchasedItem | null} purchasedItem - PurchasedItem | null
|
|
144
|
+
* @returns A boolean
|
|
145
|
+
*/
|
|
146
|
+
var isPurchasedItemExpired = function (purchasedItem) {
|
|
147
|
+
if (!(purchasedItem === null || purchasedItem === void 0 ? void 0 : purchasedItem.transactionId)) {
|
|
131
148
|
throw new Error('Detected invalid purchased item! Make sure object is defined and it has transaction id.');
|
|
132
149
|
}
|
|
133
150
|
// it has been cancelled
|
|
@@ -139,35 +156,47 @@ var isExpired = function (purchasedItem) {
|
|
|
139
156
|
return false;
|
|
140
157
|
}
|
|
141
158
|
// has expired
|
|
142
|
-
if (Date.now() - purchasedItem.expirationDateMS >= 0) {
|
|
159
|
+
if (Date.now().valueOf() - purchasedItem.expirationDateMS >= 0) {
|
|
143
160
|
return true;
|
|
144
161
|
}
|
|
145
162
|
// has not expired yet
|
|
146
163
|
return false;
|
|
147
164
|
};
|
|
148
|
-
exports.
|
|
149
|
-
|
|
150
|
-
|
|
165
|
+
exports.isPurchasedItemExpired = isPurchasedItemExpired;
|
|
166
|
+
/**
|
|
167
|
+
* If the purchased item has a cancellation date, then it's canceled.
|
|
168
|
+
* @param {PurchasedItem} purchasedItem - PurchasedItem - this is the purchased item object that you get from the
|
|
169
|
+
* getPurchasedItems() method.
|
|
170
|
+
* @returns A boolean value.
|
|
171
|
+
*/
|
|
172
|
+
var isPurchasedItemCanceled = function (purchasedItem) {
|
|
173
|
+
if (!(purchasedItem === null || purchasedItem === void 0 ? void 0 : purchasedItem.transactionId)) {
|
|
151
174
|
throw new Error('Detected invalid purchased item! Make sure object is defined and it has transaction id.');
|
|
152
175
|
}
|
|
153
176
|
return Boolean(purchasedItem.cancellationDateMS);
|
|
154
177
|
};
|
|
155
|
-
exports.
|
|
156
|
-
|
|
157
|
-
|
|
178
|
+
exports.isPurchasedItemCanceled = isPurchasedItemCanceled;
|
|
179
|
+
/**
|
|
180
|
+
* It takes a response from the Apple App Store and returns an array of PurchasedItem objects sorted by their purchase date in descending order,
|
|
181
|
+
* the latest purchase comes first.
|
|
182
|
+
* @param {IVerifyReceiptResponseBody | null} verifyReceiptResponse - IVerifyReceiptResponseBody | null
|
|
183
|
+
* @returns An array of PurchasedItem objects.
|
|
184
|
+
*/
|
|
185
|
+
var getPurchasedItems = function (verifyReceiptResponse) {
|
|
186
|
+
if (!(verifyReceiptResponse === null || verifyReceiptResponse === void 0 ? void 0 : verifyReceiptResponse.receipt)) {
|
|
158
187
|
return [];
|
|
159
188
|
}
|
|
160
189
|
var data = [];
|
|
161
|
-
var purchases =
|
|
162
|
-
var lri =
|
|
190
|
+
var purchases = verifyReceiptResponse.receipt.in_app || [];
|
|
191
|
+
var lri = verifyReceiptResponse.latest_receipt_info || verifyReceiptResponse.receipt.latest_receipt_info;
|
|
163
192
|
if (Array.isArray(lri)) {
|
|
164
193
|
purchases = purchases.concat(lri);
|
|
165
194
|
}
|
|
166
195
|
/*
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
196
|
+
we sort purchases by purchase_date_ms to make it easier
|
|
197
|
+
to weed out duplicates (items with the same original_transaction_id)
|
|
198
|
+
purchase_date_ms DESC
|
|
199
|
+
*/
|
|
171
200
|
purchases.sort(function (a, b) {
|
|
172
201
|
return parseInt(b.purchase_date_ms, 10) - parseInt(a.purchase_date_ms, 10);
|
|
173
202
|
});
|
|
@@ -179,9 +208,9 @@ var getPurchaseData = function (purchase) {
|
|
|
179
208
|
if (transactionIds[tid]) {
|
|
180
209
|
continue;
|
|
181
210
|
}
|
|
182
|
-
data.push((0, internal_1.getPurchaseItem)(item,
|
|
211
|
+
data.push((0, internal_1.getPurchaseItem)(item, verifyReceiptResponse));
|
|
183
212
|
transactionIds[tid] = true;
|
|
184
213
|
}
|
|
185
214
|
return data;
|
|
186
215
|
};
|
|
187
|
-
exports.
|
|
216
|
+
exports.getPurchasedItems = getPurchasedItems;
|
package/package.json
CHANGED
package/types/shared/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IReceiptInAppItem } from '
|
|
1
|
+
import { IReceiptInAppItem } from '../internal';
|
|
2
2
|
import { RECEIPT_STATUS_ENUM } from '../../constants';
|
|
3
3
|
export interface ILogger {
|
|
4
4
|
log: (message: string) => void;
|
|
@@ -6,8 +6,8 @@ export interface ILogger {
|
|
|
6
6
|
error: (message: string) => void;
|
|
7
7
|
}
|
|
8
8
|
export interface IIAPAppleConfig {
|
|
9
|
-
appleExcludeOldTransactions?: boolean
|
|
10
|
-
|
|
9
|
+
appleExcludeOldTransactions?: boolean;
|
|
10
|
+
appSharedSecret: string;
|
|
11
11
|
test?: boolean | undefined;
|
|
12
12
|
logger?: ILogger | null;
|
|
13
13
|
}
|
|
@@ -17,7 +17,7 @@ export interface PurchasedItem {
|
|
|
17
17
|
originalTransactionId?: string;
|
|
18
18
|
transactionId: string;
|
|
19
19
|
productId: string;
|
|
20
|
-
|
|
20
|
+
originalPurchaseDateMS?: number;
|
|
21
21
|
expirationDateMS?: number;
|
|
22
22
|
purchaseDateMS: number;
|
|
23
23
|
isTrialPeriod: boolean;
|
|
@@ -26,9 +26,9 @@ export interface PurchasedItem {
|
|
|
26
26
|
}
|
|
27
27
|
export interface IAPAppleError {
|
|
28
28
|
rejectionMessage: string;
|
|
29
|
-
data?:
|
|
29
|
+
data?: IVerifyReceiptResponseBody | null;
|
|
30
30
|
}
|
|
31
|
-
export interface
|
|
31
|
+
export interface IVerifyReceiptResponseBody {
|
|
32
32
|
status: RECEIPT_STATUS_ENUM;
|
|
33
33
|
environment: 'Sandbox' | 'Production';
|
|
34
34
|
receipt: IReceipt;
|