diff --git a/src/apps.js b/src/apps.js index ce6086abb..17e218273 100644 --- a/src/apps.js +++ b/src/apps.js @@ -125,7 +125,7 @@ AppsError.BAD_FIELD = 'Bad Field'; AppsError.BAD_STATE = 'Bad State'; AppsError.PORT_RESERVED = 'Port Reserved'; AppsError.PORT_CONFLICT = 'Port Conflict'; -AppsError.BILLING_REQUIRED = 'Billing Required'; +AppsError.PLAN_LIMIT = 'Plan Limit'; AppsError.ACCESS_DENIED = 'Access denied'; AppsError.BAD_CERTIFICATE = 'Invalid certificate'; @@ -1047,9 +1047,10 @@ function purchaseApp(data, callback) { if (delError) debug('install: Failed to rollback app installation.', delError); if (error.reason === AppstoreError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, error.message)); - if (error && error.reason === AppstoreError.BILLING_REQUIRED) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); - if (error && error.reason === AppstoreError.INVALID_TOKEN) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); - if (error && error.reason === AppstoreError.LICENSE_ERROR) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); + if (error && error.reason === AppstoreError.PLAN_LIMIT) return callback(new AppsError(AppsError.PLAN_LIMIT, error.message)); + if (error && error.reason === AppstoreError.INVALID_TOKEN) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message)); + if (error && error.reason === AppstoreError.LICENSE_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message)); + if (error && error.reason === AppstoreError.NOT_REGISTERED) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message)); if (error && error.reason === AppstoreError.EXTERNAL_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message)); callback(new AppsError(AppsError.INTERNAL_ERROR, error)); @@ -1163,9 +1164,8 @@ function uninstall(appId, auditSource, callback) { appstore.unpurchaseApp(appId, { appstoreId: app.appStoreId, manifestId: app.manifest.id }, function (error) { if (error && error.reason === AppstoreError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND)); - if (error && error.reason === AppstoreError.BILLING_REQUIRED) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); - if (error && error.reason === AppstoreError.INVALID_TOKEN) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); - if (error && error.reason === AppstoreError.LICENSE_ERROR) return callback(new AppsError(AppsError.BILLING_REQUIRED, error.message)); + if (error && error.reason === AppstoreError.INVALID_TOKEN) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message)); + if (error && error.reason === AppstoreError.LICENSE_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message)); if (error && error.reason === AppstoreError.EXTERNAL_ERROR) return callback(new AppsError(AppsError.EXTERNAL_ERROR, error.message)); if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); diff --git a/src/appstore.js b/src/appstore.js index 14976e63e..36e037e6e 100644 --- a/src/appstore.js +++ b/src/appstore.js @@ -62,7 +62,7 @@ AppstoreError.EXTERNAL_ERROR = 'External Error'; AppstoreError.ALREADY_EXISTS = 'Already Exists'; AppstoreError.ACCESS_DENIED = 'Access Denied'; AppstoreError.NOT_FOUND = 'Not Found'; -AppstoreError.BILLING_REQUIRED = 'Billing Required'; // upstream 402 (subsciption_expired and subscription_required) +AppstoreError.PLAN_LIMIT = 'Plan limit reached'; // upstream 402 (subsciption_expired and subscription_required) AppstoreError.LICENSE_ERROR = 'License Error'; // upstream 422 (no license, invalid license) AppstoreError.INVALID_TOKEN = 'Invalid token'; // upstream 401 (invalid token) AppstoreError.NOT_REGISTERED = 'Not registered'; // upstream 412 (no token, not set yet) @@ -131,10 +131,10 @@ function getSubscription(callback) { const url = config.apiServerOrigin() + '/api/v1/subscription'; superagent.get(url).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); - if (result.statusCode === 401) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, 'invalid appstore token')); - if (result.statusCode === 403) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, 'wrong user')); - if (result.statusCode === 502) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, 'stripe error')); - if (result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, 'unknown error')); + if (result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); + if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR)); + if (result.statusCode === 502) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, `Stripe error: ${error.message}`)); + if (result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, `Unknown error: ${error.message}`)); callback(null, result.body); // { email, subscription } }); @@ -159,10 +159,11 @@ function purchaseApp(data, callback) { superagent.post(url).send(data).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); - if (result.statusCode === 404) return callback(new AppstoreError(AppstoreError.NOT_FOUND)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); - if (result.statusCode === 402) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED, result.body.message)); + if (result.statusCode === 404) return callback(new AppstoreError(AppstoreError.NOT_FOUND)); // appstoreId does not exist + if (result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); + if (result.statusCode === 402) return callback(new AppstoreError(AppstoreError.PLAN_LIMIT, result.body.message)); if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); + // 200 if already purchased, 201 is newly purchased if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('App purchase failed. %s %j', result.status, result.body))); callback(null); @@ -183,14 +184,14 @@ function unpurchaseApp(appId, data, callback) { superagent.get(url).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); if (result.statusCode === 404) return callback(null); // was never purchased + if (result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode !== 201 && result.statusCode !== 200) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', result.status, result.body))); superagent.del(url).send(data).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.BILLING_REQUIRED)); + if (result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); if (result.statusCode !== 204) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('App unpurchase failed. %s %j', result.status, result.body))); callback(null); @@ -277,7 +278,7 @@ function sendAliveStatus(callback) { superagent.post(url).send(data).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error)); if (result.statusCode === 404) return callback(new AppstoreError(AppstoreError.NOT_FOUND)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); + if (result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Sending alive status failed. %s %j', result.status, result.body))); @@ -297,7 +298,7 @@ function getBoxUpdate(callback) { superagent.get(url).query({ accessToken: token, boxVersion: config.version() }).timeout(10 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); + if (result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode === 204) return callback(null); // no update if (result.statusCode !== 200 || !result.body) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text))); @@ -332,7 +333,7 @@ function getAppUpdate(app, callback) { superagent.get(url).query({ accessToken: token, boxVersion: config.version(), appId: app.appStoreId, appVersion: app.manifest.version }).timeout(10 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); + if (result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode === 204) return callback(null); // no update if (result.statusCode !== 200 || !result.body) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text))); @@ -362,7 +363,7 @@ function subscribeCloudron(token, callback) { superagent.post(url).send({ domain: config.adminDomain() }).query({ accessToken: token }).timeout(30 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); - if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, 'unable to register cloudron')); + if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, `Unable to register cloudron: ${error.message}`)); // cloudronId, token, licenseKey if (!result.body.cloudronId) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, 'Invalid response - no cloudron id')); @@ -429,7 +430,7 @@ function sendFeedback(info, callback) { superagent.post(url).query({ accessToken: token }).send(info).timeout(10 * 1000).end(function (error, result) { if (error && !error.response) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, error.message)); - if (result.statusCode === 403 || result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); + if (result.statusCode === 401) return callback(new AppstoreError(AppstoreError.INVALID_TOKEN)); if (result.statusCode === 422) return callback(new AppstoreError(AppstoreError.LICENSE_ERROR, result.body.message)); if (result.statusCode !== 201) return callback(new AppstoreError(AppstoreError.EXTERNAL_ERROR, util.format('Bad response: %s %s', result.statusCode, result.text))); diff --git a/src/routes/apps.js b/src/routes/apps.js index 200d1753b..283c1eb52 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -135,7 +135,7 @@ function installApp(req, res, next) { if (error && error.reason === AppsError.PORT_RESERVED) return next(new HttpError(409, 'Port ' + error.message + ' is reserved.')); if (error && error.reason === AppsError.PORT_CONFLICT) return next(new HttpError(409, 'Port ' + error.message + ' is already in use.')); if (error && error.reason === AppsError.BAD_FIELD) return next(new HttpError(400, error.message)); - if (error && error.reason === AppsError.BILLING_REQUIRED) return next(new HttpError(402, error.message)); + if (error && error.reason === AppsError.PLAN_LIMIT) return next(new HttpError(402, error.message)); if (error && error.reason === AppsError.BAD_CERTIFICATE) return next(new HttpError(400, error.message)); if (error && error.reason === AppsError.EXTERNAL_ERROR) return next(new HttpError(424, error.message)); if (error) return next(new HttpError(500, error)); @@ -249,7 +249,7 @@ function cloneApp(req, res, next) { if (error && error.reason === AppsError.ALREADY_EXISTS) return next(new HttpError(409, error.message)); if (error && error.reason === AppsError.BAD_FIELD) return next(new HttpError(400, error.message)); if (error && error.reason === AppsError.BAD_STATE) return next(new HttpError(409, error.message)); - if (error && error.reason === AppsError.BILLING_REQUIRED) return next(new HttpError(402, 'Billing required')); + if (error && error.reason === AppsError.PLAN_LIMIT) return next(new HttpError(402, error.message)); if (error && error.reason === AppsError.BAD_CERTIFICATE) return next(new HttpError(400, error.message)); if (error && error.reason === AppsError.EXTERNAL_ERROR) return next(new HttpError(424, error.message)); if (error) return next(new HttpError(500, error)); @@ -279,7 +279,7 @@ function uninstallApp(req, res, next) { debug('Uninstalling app id:%s', req.params.id); apps.uninstall(req.params.id, auditSource.fromRequest(req), function (error) { - if (error && error.reason === AppsError.BILLING_REQUIRED) return next(new HttpError(402, 'Billing required')); + if (error && error.reason === AppsError.EXTERNAL_ERROR) return next(new HttpError(424, error)); if (error && error.reason === AppsError.NOT_FOUND) return next(new HttpError(404, 'No such app')); if (error) return next(new HttpError(500, error)); diff --git a/src/routes/support.js b/src/routes/support.js index 463d69252..483c87174 100644 --- a/src/routes/support.js +++ b/src/routes/support.js @@ -27,8 +27,7 @@ function feedback(req, res, next) { if (req.body.appId && typeof req.body.appId !== 'string') return next(new HttpError(400, 'appId must be string')); appstore.sendFeedback(_.extend({ }, req.body, { email: req.user.email, displayName: req.user.displayName }), function (error) { - if (error && error.reason === AppstoreError.BILLING_REQUIRED) return next(new HttpError(402, 'Login to App Store to create support tickets. You can also email support@cloudron.io')); - if (error) return next(new HttpError(503, 'Error contacting cloudron.io. Please email support@cloudron.io')); + if (error) return next(new HttpError(503, `Error contacting cloudron.io: ${error.message}. Please email support@cloudron.io`)); next(new HttpSuccess(201, {})); }); diff --git a/src/routes/test/apps-test.js b/src/routes/test/apps-test.js index d4232be24..7d9e488aa 100644 --- a/src/routes/test/apps-test.js +++ b/src/routes/test/apps-test.js @@ -457,7 +457,7 @@ describe('App API', function () { .query({ access_token: token }) .send({ appStoreId: APP_STORE_ID, location: APP_LOCATION, domain: DOMAIN_0.domain, portBindings: null, accessRestriction: null }) .end(function (err, res) { - expect(res.statusCode).to.equal(402); + expect(res.statusCode).to.equal(424); expect(fake1.isDone()).to.be.ok(); done(); }); diff --git a/src/routes/test/appstore-test.js b/src/routes/test/appstore-test.js index b35170c03..e062609e1 100644 --- a/src/routes/test/appstore-test.js +++ b/src/routes/test/appstore-test.js @@ -67,7 +67,7 @@ describe('Appstore Apps API', function () { superagent.get(SERVER_URL + '/api/v1/appstore/apps') .query({ access_token: token }) .end(function (error, result) { - expect(result.statusCode).to.equal(402); // billing required + expect(result.statusCode).to.equal(412); // not registered yet done(); }); }); @@ -76,7 +76,7 @@ describe('Appstore Apps API', function () { superagent.get(SERVER_URL + '/api/v1/appstore/apps/org.wordpress.cloudronapp') .query({ access_token: token }) .end(function (error, result) { - expect(result.statusCode).to.equal(402); // billing required + expect(result.statusCode).to.equal(412); // not registered yet done(); }); }); diff --git a/src/test/appstore-test.js b/src/test/appstore-test.js index 43b3e441b..1fabc1305 100644 --- a/src/test/appstore-test.js +++ b/src/test/appstore-test.js @@ -49,10 +49,10 @@ describe('Appstore', function () { beforeEach(nock.cleanAll); - it('cannot send alive status without cloudron token', function (done) { + it('cannot send alive status without registering', function (done) { appstore.sendAliveStatus(function (error) { expect(error).to.be.ok(); - expect(error.reason).to.equal(AppstoreError.INVALID_TOKEN); + expect(error.reason).to.equal(AppstoreError.NOT_REGISTERED); done(); }); });