in-app-purchase-iec-develop
Version:
In-App-Purchase validation and subscription management for iOS, Android, Amazon, and Windows
176 lines (160 loc) • 6.21 kB
JavaScript
var constants = require('../constants');
var request = require('request');
var fs = require('fs');
var responseData = require('./responseData');
var verbose = require('./verbose');
var VER = '2.0';
var SECRET = '{developerSecret}';
var UID = '{userId}';
var PTOKEN = '{purchaseToken}';
var ERRORS = {
VALIDATION: {
400: 'The transaction represented by this Purchase Token is no longer valid.',
404: 'Unknown operation exception.',
496: 'Invalid sharedSecret',
497: 'Invalid User ID',
498: 'Invalid Purchase Token',
499: 'The Purchase Token was created with credentials that have expired, use renew to generate a valid purchase token.',
500: 'There was an Internal Server Error'
},
RENEW: {
400: 'Bad Request',
404: 'Unknown operation exception.',
496: 'Invalid sharedSecret',
497: 'Invalid User ID',
498: 'Invalid Purchase Token',
500: 'There is an Internal Server Error'
}
};
var VALIDATION_PATH = 'https://appstore-sdk.amazon.com/version/' +
VER + '/verify/developer/' + SECRET + '/user/' + UID + '/purchaseToken/' + PTOKEN;
var RENEW_PATH = 'https://appstore-sdk.amazon.com/version/' +
VER + '/renew/developer/' + SECRET + '/user/' + UID + '/purchaseToken/' + PTOKEN;
var S_VAL_PATH = VALIDATION_PATH;
var S_R_PATH = RENEW_PATH;
var NAME = '<Amazon>';
var config;
module.exports.readConfig = function (configIn) {
config = configIn;
// Apply any default settings to Request.
if ('requestDefaults' in configIn) {
request = request.defaults(configIn.requestDefaults);
}
verbose.setup(config);
};
module.exports.setup = function (cb) {
if (!config || !config.secret) {
return cb();
}
fs.exists(config.secret, function (exists) {
var secret = '';
if (!exists) {
// use the string value literally
secret = config.secret;
VALIDATION_PATH = VALIDATION_PATH.replace(SECRET, config.secret);
RENEW_PATH = RENEW_PATH.replace(SECRET, config.secret);
verbose.log(NAME, 'Secret:', config.secret);
return cb();
}
// assume it as a file path
fs.readFile(config.secret, 'UTF-8', function (error, val) {
if (error) {
return cb(error);
}
secret = val.replace(/(\r|\n)/g, '');
VALIDATION_PATH = VALIDATION_PATH.replace(SECRET, secret);
RENEW_PATH = RENEW_PATH.replace(SECRET, secret);
verbose.log(NAME, 'Secret:', secret);
cb();
});
});
};
/*
receipt: {
userId: <user ID amazon in-app-purchase-server responds with>
receiptId: <receipt ID from amazon>
}
*/
module.exports.validatePurchase = function (dSecret, receipt, cb) {
var rpath = RENEW_PATH;
var path;
// override secret with dSecret to allow dynamically fed secret to validate
if (dSecret) {
verbose.log(NAME, 'Use dynamically fed secret:', dSecret);
rpath = S_R_PATH.replace(SECRET, dSecret);
var vpath = S_VAL_PATH.replace(SECRET, dSecret);
path = vpath.replace(UID, receipt.userId);
} else {
path = VALIDATION_PATH.replace(UID, receipt.userId);
}
path = path.replace(PTOKEN, receipt.receiptId);
verbose.log(NAME, 'Validate:', path, receipt);
send(path, ERRORS.VALIDATION, function (error, res) {
if (error) {
if (res === 499) {
// must be renewed and re-tried
var renew = rpath.replace(UID, receipt.userId);
renew = renew.replace(PTOKEN, receipt.receiptId);
verbose.log(NAME, 'Purchase must be renewed (' + res + '):', renew);
send(renew, ERRORS.RENEW, function (error, renewed) {
if (error) {
var renewedErrorRes = {
status: renewed,
message: ERRORS.RENEW[renewed] || 'Unkown'
};
verbose.log(NAME, 'Failed to renew purchase:', renewedErrorRes);
return cb(error, renewedErrorRes);
}
var renewedReceipt = {
receiptId: renewed.purchaseToken,
userId: receipt.userId
};
verbose.log(NAME, 'Purchase renewed:', renewedReceipt);
module.exports.validatePurchase(renewedReceipt, cb);
});
return;
}
var errorRes = {
status: res,
message: ERRORS.VALIDATION[res] || 'Unknown'
};
verbose.log(NAME, 'Validation failed:', errorRes);
return cb(error, errorRes);
}
verbose.log(NAME, 'Validation successful:', res);
cb(null, res);
});
};
module.exports.getPurchaseData = function (purchase, options) {
if (!purchase || !purchase.purchaseToken) {
return null;
}
var now = Date.now();
if (options && options.ignoreExpired && purchase.expirationDate <= now) {
return [];
}
var obj = responseData.parse(purchase);
obj.transactionId = purchase.purchaseToken;
obj.productId = purchase.sku;
obj.purchaseData = purchase.itemType;
obj.quantity = 1;
obj.purchaseDate = purchase.startDate || now;
obj.expirationDate = purchase.endDate || 0;
return [ obj ];
};
function send(path, errorMap, cb) {
request.get(path, function (error, response, body) {
var errorMsg = errorMap[response.statusCode];
if (errorMsg) {
return cb(new Error(errorMsg + ': ' + path), response.statusCode);
}
if (error) {
error.message += ': ' + path;
return cb(error);
}
var res = JSON.parse(body);
res.status = 0;
res.service = constants.SERVICES.AMAZON;
cb(null, res);
});
}