Merge pull request #5403 from vIiRuS/iOS-IAP

Enable in app purchase for iOS
This commit is contained in:
Blade Barringer
2015-06-15 07:42:57 -05:00
3 changed files with 325 additions and 36 deletions
+281
View File
@@ -0,0 +1,281 @@
'use strict'
app = require('../../website/src/server')
rewire = require('rewire')
sinon = require('sinon')
inApp = rewire('../../website/src/controllers/payments/iap')
iapMock = { }
inApp.__set__('iap', iapMock)
describe 'In-App Purchases', ->
describe 'Android', ->
req = {
body: {
transaction: {
reciept: 'foo'
signature: 'sig'
}
}
}
res = {
locals: { user: { _id: 'user' } }
json: sinon.spy()
}
next = -> true
paymentSpy = sinon.spy()
before ->
inApp.__set__('payments.buyGems', paymentSpy)
afterEach ->
paymentSpy.reset()
res.json.reset()
context 'successful app purchase', ->
before ->
iapMock.setup = (cb)-> return cb(null)
iapMock.validate = (iapGoogle, iapBodyReciept, cb)-> return cb(null, true)
iapMock.isValidated = (googleRes)-> return googleRes
iapMock.GOOGLE = 'google'
it 'calls res.json with succesful result object', ->
expectedResObj = {
ok: true
data: true
}
inApp.androidVerify(req, res, next)
expect(res.json).to.be.calledOnce
expect(res.json).to.be.calledWith(expectedResObj)
it 'calls payments.buyGems function', ->
inApp.androidVerify(req, res, next)
expect(paymentSpy).to.be.calledOnce
expect(paymentSpy).to.be.calledWith({user: res.locals.user, paymentMethod:'IAP GooglePlay'})
context 'error in setup', ->
before ->
iapMock.setup = (cb)-> return cb("error in setup")
it 'calls res.json with setup error object', ->
expectedResObj = {
ok: false
data: 'IAP Error'
}
inApp.androidVerify(req, res, next)
expect(res.json).to.be.calledOnce
expect(res.json).to.be.calledWith(expectedResObj)
it 'does not calls payments.buyGems function', ->
inApp.androidVerify(req, res, next)
expect(paymentSpy).to.not.be.called
context 'error in validation', ->
before ->
iapMock.setup = (cb)-> return cb(null)
iapMock.validate = (iapGoogle, iapBodyReciept, cb)-> return cb('error in validation', true)
it 'calls res.json with validation error object', ->
expectedResObj = {
ok: false
data: {
code: 6778001
message: 'error in validation'
}
}
inApp.androidVerify(req, res, next)
expect(res.json).to.be.calledOnce
expect(res.json).to.be.calledWith(expectedResObj)
it 'does not calls payments.buyGems function', ->
inApp.androidVerify(req, res, next)
expect(paymentSpy).to.not.be.called
context 'iap is not valid', ->
before ->
iapMock.setup = (cb)-> return cb(null)
iapMock.validate = (iapGoogle, iapBodyReciept, cb)-> return cb(null, false)
iapMock.isValidated = (googleRes)-> return googleRes
it 'does not call res.json', ->
inApp.androidVerify(req, res, next)
expect(res.json).to.not.be.called
it 'does not calls payments.buyGems function', ->
inApp.androidVerify(req, res, next)
expect(paymentSpy).to.not.be.called
describe 'iOS', ->
req = { body: { transaction: { reciept: 'foo' } } }
res = {
locals: { user: { _id: 'user' } }
json: sinon.spy()
}
next = -> true
paymentSpy = sinon.spy()
before ->
inApp.__set__('payments.buyGems', paymentSpy)
afterEach ->
paymentSpy.reset()
res.json.reset()
context 'successful app purchase', ->
before ->
iapMock.setup = (cb)-> return cb(null)
iapMock.validate = (iapApple, iapBodyReciept, cb)-> return cb(null, true)
iapMock.isValidated = (appleRes)-> return appleRes
iapMock.getPurchaseData = (appleRes)->
return [{ productId: 'com.habitrpg.ios.Habitica.20gems' }]
iapMock.APPLE = 'apple'
it 'calls res.json with succesful result object', ->
expectedResObj = {
ok: true
data: true
}
inApp.iosVerify(req, res, next)
expect(res.json).to.be.calledOnce
expect(res.json).to.be.calledWith(expectedResObj)
it 'calls payments.buyGems function', ->
inApp.iosVerify(req, res, next)
expect(paymentSpy).to.be.calledOnce
expect(paymentSpy).to.be.calledWith({user: res.locals.user, paymentMethod:'IAP AppleStore'})
context 'error in setup', ->
before ->
iapMock.setup = (cb)-> return cb("error in setup")
it 'calls res.json with setup error object', ->
expectedResObj = {
ok: false
data: 'IAP Error'
}
inApp.iosVerify(req, res, next)
expect(res.json).to.be.calledOnce
expect(res.json).to.be.calledWith(expectedResObj)
it 'does not calls payments.buyGems function', ->
inApp.iosVerify(req, res, next)
expect(paymentSpy).to.not.be.called
context 'error in validation', ->
before ->
iapMock.setup = (cb)-> return cb(null)
iapMock.validate = (iapApple, iapBodyReciept, cb)-> return cb('error in validation', true)
it 'calls res.json with validation error object', ->
expectedResObj = {
ok: false
data: {
code: 6778001
message: 'error in validation'
}
}
inApp.iosVerify(req, res, next)
expect(res.json).to.be.calledOnce
expect(res.json).to.be.calledWith(expectedResObj)
it 'does not calls payments.buyGems function', ->
inApp.iosVerify(req, res, next)
expect(paymentSpy).to.not.be.called
context 'iap is not valid', ->
before ->
iapMock.setup = (cb)-> return cb(null)
iapMock.validate = (iapApple, iapBodyReciept, cb)-> return cb(null, false)
iapMock.isValidated = (appleRes)-> return appleRes
it 'does not call res.json', ->
inApp.iosVerify(req, res, next)
expectedResObj = {
ok: false
data: {
code: 6778001
message: 'Invalid receipt'
}
}
expect(res.json).to.be.calledOnce
expect(res.json).to.be.calledWith(expectedResObj)
it 'does not calls payments.buyGems function', ->
inApp.iosVerify(req, res, next)
expect(paymentSpy).to.not.be.called
context 'iap is valid but has no purchaseDataList', ->
before ->
iapMock.setup = (cb)-> return cb(null)
iapMock.validate = (iapApple, iapBodyReciept, cb)-> return cb(null, true)
iapMock.isValidated = (appleRes)-> return appleRes
iapMock.getPurchaseData = (appleRes)->
return []
iapMock.APPLE = 'apple'
it 'calls res.json with succesful result object', ->
expectedResObj = {
ok: false
data: {
code: 6778001
message: 'Incorrect receipt content'
}
}
inApp.iosVerify(req, res, next)
expect(res.json).to.be.calledOnce
expect(res.json).to.be.calledWith(expectedResObj)
it 'does not calls payments.buyGems function', ->
inApp.iosVerify(req, res, next)
expect(paymentSpy).to.not.be.called
context 'iap is valid, has purchaseDataList, but productId does not match', ->
before ->
iapMock.setup = (cb)-> return cb(null)
iapMock.validate = (iapApple, iapBodyReciept, cb)-> return cb(null, true)
iapMock.isValidated = (appleRes)-> return appleRes
iapMock.getPurchaseData = (appleRes)->
return [{ productId: 'com.another.company' }]
iapMock.APPLE = 'apple'
it 'calls res.json with incorrect reciept obj', ->
expectedResObj = {
ok: false
data: {
code: 6778001
message: 'Incorrect receipt content'
}
}
inApp.iosVerify(req, res, next)
expect(res.json).to.be.calledOnce
expect(res.json).to.be.calledWith(expectedResObj)
it 'does not calls payments.buyGems function', ->
inApp.iosVerify(req, res, next)
expect(paymentSpy).to.not.be.called
+43 -35
View File
@@ -6,7 +6,7 @@ var nconf = require('nconf');
var inAppPurchase = require('in-app-purchase');
inAppPurchase.config({
// this is the path to the directory containing iap-sanbox/iap-live files
googlePublicKeyPath: nconf.get("IAP_GOOGLE_KEYDIR")
googlePublicKeyPath: nconf.get("IAP_GOOGLE_KEYDIR")
});
// Validation ERROR Codes
@@ -24,15 +24,11 @@ exports.androidVerify = function(req, res, next) {
ok: false,
data: 'IAP Error'
};
console.error('IAP Setup ERROR');
console.error(error);
res.json(resObj);
return;
return res.json(resObj);
}
/*
google receipt must be provided as an object
{
@@ -44,7 +40,7 @@ exports.androidVerify = function(req, res, next) {
data: iapBody.transaction.receipt,
signature: iapBody.transaction.signature
};
// iap is ready
iap.validate(iap.GOOGLE, testObj, function (err, googleRes) {
if (err) {
@@ -56,9 +52,7 @@ exports.androidVerify = function(req, res, next) {
}
};
res.json(resObj);
console.error(err);
return;
return res.json(resObj);
}
if (iap.isValidated(googleRes)) {
@@ -69,16 +63,13 @@ exports.androidVerify = function(req, res, next) {
payments.buyGems({user:user, paymentMethod:'IAP GooglePlay'});
// yay good!
res.json(resObj);
return res.json(resObj);
}
});
});
};
exports.iosVerify = function(req, res, next) {
console.info(req.body);
var iapBody = req.body;
var user = res.locals.user;
@@ -89,15 +80,11 @@ exports.iosVerify = function(req, res, next) {
data: 'IAP Error'
};
console.error('IAP Setup ERROR');
console.error(error);
return res.json(resObj);
res.json(resObj);
return;
}
// iap is ready
//iap is ready
iap.validate(iap.APPLE, iapBody.transaction.receipt, function (err, appleRes) {
if (err) {
var resObj = {
@@ -108,22 +95,43 @@ exports.iosVerify = function(req, res, next) {
}
};
res.json(resObj);
console.error(err);
return;
return res.json(resObj);
}
if (iap.isValidated(appleRes)) {
var purchaseDataList = iap.getPurchaseData(appleRes);
if (purchaseDataList.length > 0) {
if (purchaseDataList[0].productId === "com.habitrpg.ios.Habitica.20gems") {
//Correct receipt
payments.buyGems({user:user, paymentMethod:'IAP AppleStore'});
var resObj = {
ok: true,
data: appleRes
};
// yay good!
return res.json(resObj);
}
}
//wrong receipt content
var resObj = {
ok: true,
data: appleRes
ok: false,
data: {
code: INVALID_PAYLOAD,
message: "Incorrect receipt content"
}
};
payments.buyGems({user:user, paymentMethod:'IAP AppleStore'});
// yay good!
res.json(resObj);
return res.json(resObj);
}
//invalid receipt
var resObj = {
ok: false,
data: {
code: INVALID_PAYLOAD,
message: "Invalid receipt"
}
};
return res.json(resObj);
});
});
};
};
+1 -1
View File
@@ -18,7 +18,7 @@ router.post("/stripe/subscribe/edit", auth.auth, i18n.getUserLanguage, payments.
router.get("/stripe/subscribe/cancel", auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribeCancel);
router.post("/iap/android/verify", auth.authWithUrl, /*i18n.getUserLanguage, */payments.iapAndroidVerify);
router.post("/iap/ios/verify", /*auth.authWithUrl, i18n.getUserLanguage, */ payments.iapIosVerify);
router.post("/iap/ios/verify", auth.auth, /*i18n.getUserLanguage, */ payments.iapIosVerify);
router.get("/api/v2/coupons/valid-discount/:code", /*auth.authWithUrl, i18n.getUserLanguage, */ payments.validCoupon);