forked from parse-community/parse-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathIAPValidationRouter.js
123 lines (108 loc) · 3.81 KB
/
IAPValidationRouter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import PromiseRouter from '../PromiseRouter';
const request = require('../request');
const rest = require('../rest');
import Parse from 'parse/node';
// TODO move validation logic in IAPValidationController
const IAP_SANDBOX_URL = 'https://sandbox.itunes.apple.com/verifyReceipt';
const IAP_PRODUCTION_URL = 'https://buy.itunes.apple.com/verifyReceipt';
const APP_STORE_ERRORS = {
21000: 'The App Store could not read the JSON object you provided.',
21002: 'The data in the receipt-data property was malformed or missing.',
21003: 'The receipt could not be authenticated.',
21004: 'The shared secret you provided does not match the shared secret on file for your account.',
21005: 'The receipt server is not currently available.',
21006: 'This receipt is valid but the subscription has expired.',
21007: 'This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.',
21008: 'This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.',
};
function appStoreError(status) {
status = parseInt(status);
var errorString = APP_STORE_ERRORS[status] || 'unknown error.';
return { status: status, error: errorString };
}
function validateWithAppStore(url, receipt) {
return request({
url: url,
method: 'POST',
body: { 'receipt-data': receipt },
headers: {
'Content-Type': 'application/json',
},
}).then(httpResponse => {
const body = httpResponse.data;
if (body && body.status === 0) {
// No need to pass anything, status is OK
return;
}
// receipt is from test and should go to test
throw body;
});
}
function getFileForProductIdentifier(productIdentifier, req) {
return rest
.find(
req.config,
req.auth,
'_Product',
{ productIdentifier: productIdentifier },
undefined,
req.info.clientSDK,
req.info.context
)
.then(function (result) {
const products = result.results;
if (!products || products.length != 1) {
// Error not found or too many
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
}
var download = products[0].download;
return Promise.resolve({ response: download });
});
}
export class IAPValidationRouter extends PromiseRouter {
handleRequest(req) {
let receipt = req.body?.receipt;
const productIdentifier = req.body?.productIdentifier;
if (!receipt || !productIdentifier) {
// TODO: Error, malformed request
throw new Parse.Error(Parse.Error.INVALID_JSON, 'missing receipt or productIdentifier');
}
// Transform the object if there
// otherwise assume it's in Base64 already
if (typeof receipt == 'object') {
if (receipt['__type'] == 'Bytes') {
receipt = receipt.base64;
}
}
if (process.env.TESTING == '1' && req.body?.bypassAppStoreValidation) {
return getFileForProductIdentifier(productIdentifier, req);
}
function successCallback() {
return getFileForProductIdentifier(productIdentifier, req);
}
function errorCallback(error) {
return Promise.resolve({ response: appStoreError(error.status) });
}
return validateWithAppStore(IAP_PRODUCTION_URL, receipt).then(
() => {
return successCallback();
},
error => {
if (error.status == 21007) {
return validateWithAppStore(IAP_SANDBOX_URL, receipt).then(
() => {
return successCallback();
},
error => {
return errorCallback(error);
}
);
}
return errorCallback(error);
}
);
}
mountRoutes() {
this.route('POST', '/validate_purchase', this.handleRequest);
}
}