mirror of
https://github.com/overleaf/overleaf.git
synced 2025-03-15 04:24:52 +00:00
Merge pull request #2609 from overleaf/ta-cmg-recurly-email-update
Add UI to Update Recurly Email GitOrigin-RevId: 920a741fd9b4312f031bdd40e3d6bec48f1bd579
This commit is contained in:
parent
506543d6a0
commit
f5e2983a6b
12 changed files with 213 additions and 6 deletions
|
@ -574,6 +574,27 @@ module.exports = RecurlyWrapper = {
|
|||
)
|
||||
},
|
||||
|
||||
updateAccountEmailAddress(accountId, newEmail, callback) {
|
||||
const data = {
|
||||
email: newEmail
|
||||
}
|
||||
const requestBody = RecurlyWrapper._buildXml('account', data)
|
||||
|
||||
RecurlyWrapper.apiRequest(
|
||||
{
|
||||
url: `accounts/${accountId}`,
|
||||
method: 'PUT',
|
||||
body: requestBody
|
||||
},
|
||||
(error, response, body) => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
RecurlyWrapper._parseAccountXml(body, callback)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
getAccountActiveCoupons(accountId, callback) {
|
||||
return RecurlyWrapper.apiRequest(
|
||||
{
|
||||
|
|
|
@ -326,6 +326,18 @@ module.exports = SubscriptionController = {
|
|||
)
|
||||
},
|
||||
|
||||
updateAccountEmailAddress(req, res, next) {
|
||||
const user = AuthenticationController.getSessionUser(req)
|
||||
RecurlyWrapper.updateAccountEmailAddress(user._id, user.email, function(
|
||||
error
|
||||
) {
|
||||
if (error) {
|
||||
return next(new HttpErrors.InternalServerError({}).withCause(error))
|
||||
}
|
||||
res.sendStatus(200)
|
||||
})
|
||||
},
|
||||
|
||||
reactivateSubscription(req, res, next) {
|
||||
const user = AuthenticationController.getSessionUser(req)
|
||||
logger.log({ user_id: user._id }, 'reactivating subscription')
|
||||
|
|
|
@ -127,6 +127,12 @@ module.exports = {
|
|||
SubscriptionController.processUpgradeToAnnualPlan
|
||||
)
|
||||
|
||||
webRouter.post(
|
||||
'/user/subscription/account/email',
|
||||
AuthenticationController.requireLogin(),
|
||||
SubscriptionController.updateAccountEmailAddress
|
||||
)
|
||||
|
||||
// Currently used in acceptance tests only, as a way to trigger the syncing logic
|
||||
return publicApiRouter.post(
|
||||
'/user/:user_id/features/sync',
|
||||
|
|
|
@ -223,7 +223,8 @@ module.exports = {
|
|||
: undefined
|
||||
),
|
||||
trial_ends_at: recurlySubscription.trial_ends_at,
|
||||
activeCoupons: recurlyCoupons
|
||||
activeCoupons: recurlyCoupons,
|
||||
account: recurlySubscription.account
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
if (personalSubscription.recurly)
|
||||
include ./_personal_subscription_recurly
|
||||
include ./_personal_subscription_recurly_sync_email
|
||||
else
|
||||
include ./_personal_subscription_custom
|
||||
|
||||
hr
|
||||
hr
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
-if (user.email !== personalSubscription.recurly.account.email)
|
||||
div
|
||||
hr
|
||||
form(async-form="updateAccountEmailAddress", name="updateAccountEmailAddress", action='/user/subscription/account/email', method="POST")
|
||||
input(name='_csrf', type='hidden', value=csrfToken)
|
||||
.form-group
|
||||
form-messages(for="updateAccountEmailAddress")
|
||||
.alert.alert-success(ng-show="updateAccountEmailAddress.response.success")
|
||||
| #{translate('recurly_email_updated')}
|
||||
div(ng-hide="updateAccountEmailAddress.response.success")
|
||||
p !{translate("recurly_email_update_needed", { recurlyEmail: "<i>" + personalSubscription.recurly.account.email + "</i>", userEmail: "<i>" + user.email + "</i>" })}
|
||||
.actions
|
||||
button.btn-primary.btn(
|
||||
type='submit',
|
||||
ng-disabled="updateAccountEmailAddress.inflight"
|
||||
)
|
||||
span(ng-show="!updateAccountEmailAddress.inflight") #{translate("update")}
|
||||
span(ng-show="updateAccountEmailAddress.inflight") #{translate("updating")}...
|
|
@ -0,0 +1,43 @@
|
|||
const { expect } = require('chai')
|
||||
const async = require('async')
|
||||
const User = require('./helpers/User')
|
||||
const RecurlySubscription = require('./helpers/RecurlySubscription')
|
||||
require('./helpers/MockV1Api')
|
||||
|
||||
describe('Subscriptions', function() {
|
||||
describe('update', function() {
|
||||
beforeEach(function(done) {
|
||||
this.recurlyUser = new User()
|
||||
async.series(
|
||||
[
|
||||
cb => this.recurlyUser.ensureUserExists(cb),
|
||||
cb => {
|
||||
this.recurlySubscription = new RecurlySubscription({
|
||||
adminId: this.recurlyUser._id,
|
||||
account: {
|
||||
email: 'stale-recurly@email.com'
|
||||
}
|
||||
})
|
||||
this.recurlySubscription.ensureExists(cb)
|
||||
},
|
||||
cb => this.recurlyUser.login(cb)
|
||||
],
|
||||
done
|
||||
)
|
||||
})
|
||||
|
||||
it('updates the email address for the account', function(done) {
|
||||
let url = '/user/subscription/account/email'
|
||||
|
||||
this.recurlyUser.request.post({ url }, (error, { statusCode }) => {
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
expect(statusCode).to.equal(200)
|
||||
// the actual email update is not tested as the mocked Recurly API
|
||||
// doesn't handle it
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -62,7 +62,8 @@ describe('Subscriptions', function() {
|
|||
describe('when the user has a subscription with recurly', function() {
|
||||
beforeEach(function(done) {
|
||||
MockRecurlyApi.accounts['mock-account-id'] = this.accounts = {
|
||||
hosted_login_token: 'mock-login-token'
|
||||
hosted_login_token: 'mock-login-token',
|
||||
email: 'mock@email.com'
|
||||
}
|
||||
MockRecurlyApi.subscriptions[
|
||||
'mock-subscription-id'
|
||||
|
@ -138,7 +139,12 @@ describe('Subscriptions', function() {
|
|||
tax: 100,
|
||||
taxRate: 0.2,
|
||||
trial_ends_at: new Date(2018, 6, 7),
|
||||
trialEndsAtFormatted: '7th July 2018'
|
||||
trialEndsAtFormatted: '7th July 2018',
|
||||
account: {
|
||||
account_code: 'mock-account-id',
|
||||
email: 'mock@email.com',
|
||||
hosted_login_token: 'mock-login-token'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -176,6 +182,12 @@ describe('Subscriptions', function() {
|
|||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('should return Recurly account email', function() {
|
||||
expect(this.data.personalSubscription.recurly.account.email).to.equal(
|
||||
'mock@email.com'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the user has a subscription without recurly', function() {
|
||||
|
|
|
@ -14,6 +14,7 @@ let MockRecurlyApi
|
|||
const express = require('express')
|
||||
const app = express()
|
||||
const bodyParser = require('body-parser')
|
||||
const SubscriptionController = require('../../../../app/src/Features/Subscription/SubscriptionController')
|
||||
|
||||
app.use(bodyParser.json())
|
||||
|
||||
|
@ -69,11 +70,30 @@ module.exports = MockRecurlyApi = {
|
|||
<account>
|
||||
<account_code>${req.params.id}</account_code>
|
||||
<hosted_login_token>${account.hosted_login_token}</hosted_login_token>
|
||||
<email>${account.email}</email>
|
||||
</account>\
|
||||
`)
|
||||
}
|
||||
})
|
||||
|
||||
app.put(
|
||||
'/accounts/:id',
|
||||
SubscriptionController.recurlyNotificationParser, // required to parse XML requests
|
||||
(req, res, next) => {
|
||||
const account = this.accounts[req.params.id]
|
||||
if (account == null) {
|
||||
return res.status(404).end()
|
||||
} else {
|
||||
return res.send(`\
|
||||
<account>
|
||||
<account_code>${req.params.id}</account_code>
|
||||
<email>${account.email}</email>
|
||||
</account>\
|
||||
`)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
app.get('/coupons/:code', (req, res, next) => {
|
||||
const coupon = this.coupons[req.params.code]
|
||||
if (coupon == null) {
|
||||
|
|
|
@ -10,6 +10,9 @@ class RecurlySubscription {
|
|||
this.uuid = ObjectId().toString()
|
||||
this.accountId = this.subscription.admin_id.toString()
|
||||
this.state = options.state || 'active'
|
||||
this.account = {
|
||||
email: options.account && options.account.email
|
||||
}
|
||||
}
|
||||
|
||||
ensureExists(callback) {
|
||||
|
@ -22,7 +25,10 @@ class RecurlySubscription {
|
|||
account_id: this.accountId,
|
||||
state: this.state
|
||||
})
|
||||
MockRecurlyApi.addAccount({ id: this.accountId })
|
||||
MockRecurlyApi.addAccount({
|
||||
id: this.accountId,
|
||||
email: this.account.email
|
||||
})
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -257,6 +257,49 @@ describe('RecurlyWrapper', function() {
|
|||
})
|
||||
})
|
||||
|
||||
describe('updateAccountEmailAddress', function() {
|
||||
beforeEach(function(done) {
|
||||
this.recurlyAccountId = 'account-id-123'
|
||||
this.newEmail = 'example@overleaf.com'
|
||||
this.apiRequest = sinon
|
||||
.stub(this.RecurlyWrapper, 'apiRequest')
|
||||
.callsFake((options, callback) => {
|
||||
this.requestOptions = options
|
||||
callback(null, {}, fixtures['accounts/104'])
|
||||
})
|
||||
|
||||
this.RecurlyWrapper.updateAccountEmailAddress(
|
||||
this.recurlyAccountId,
|
||||
this.newEmail,
|
||||
(error, recurlyAccount) => {
|
||||
this.recurlyAccount = recurlyAccount
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
return this.RecurlyWrapper.apiRequest.restore()
|
||||
})
|
||||
|
||||
it('sends correct XML', function() {
|
||||
this.apiRequest.called.should.equal(true)
|
||||
const { body } = this.apiRequest.lastCall.args[0]
|
||||
expect(body).to.equal(`\
|
||||
<account>
|
||||
<email>example@overleaf.com</email>
|
||||
</account>\
|
||||
`)
|
||||
this.requestOptions.url.should.equal(`accounts/${this.recurlyAccountId}`)
|
||||
this.requestOptions.method.should.equal('PUT')
|
||||
})
|
||||
|
||||
it('should return the updated account', function() {
|
||||
should.exist(this.recurlyAccount)
|
||||
this.recurlyAccount.account_code.should.equal('104')
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateSubscription', function() {
|
||||
beforeEach(function(done) {
|
||||
this.recurlySubscriptionId = 'subscription-id-123'
|
||||
|
|
|
@ -115,7 +115,9 @@ describe('SubscriptionController', function() {
|
|||
},
|
||||
'settings-sharelatex': this.settings,
|
||||
'../User/UserGetter': this.UserGetter,
|
||||
'./RecurlyWrapper': (this.RecurlyWrapper = {}),
|
||||
'./RecurlyWrapper': (this.RecurlyWrapper = {
|
||||
updateAccountEmailAddress: sinon.stub().yields()
|
||||
}),
|
||||
'./FeaturesUpdater': (this.FeaturesUpdater = {}),
|
||||
'./GroupPlansData': (this.GroupPlansData = {}),
|
||||
'./V1SubscriptionManager': (this.V1SubscriptionManager = {}),
|
||||
|
@ -477,6 +479,28 @@ describe('SubscriptionController', function() {
|
|||
})
|
||||
})
|
||||
|
||||
describe('updateAccountEmailAddress via put', function() {
|
||||
beforeEach(function(done) {
|
||||
this.res = {
|
||||
sendStatus() {
|
||||
return done()
|
||||
}
|
||||
}
|
||||
sinon.spy(this.res, 'sendStatus')
|
||||
this.SubscriptionController.updateAccountEmailAddress(this.req, this.res)
|
||||
})
|
||||
|
||||
it('should send the user and subscriptionId to RecurlyWrapper', function() {
|
||||
this.RecurlyWrapper.updateAccountEmailAddress
|
||||
.calledWith(this.user._id, this.user.email)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('shouldrespond with 200', function() {
|
||||
this.res.sendStatus.calledWith(200).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('reactivateSubscription', function() {
|
||||
beforeEach(function(done) {
|
||||
this.res = {
|
||||
|
|
Loading…
Reference in a new issue