Skip to content
Permalink
Browse files

feat(payments): add /oauth/subscriptions/customer auth server route

- tweaks to subhub client to better match [subhub][] API updates.
- disable one of the error cases in in-memory subhub stub

[subhub]: https://github.com/mozilla/subhub/blob/master/subhub/subhub_api.yaml#L183

issue mozilla#1086
  • Loading branch information...
lmorchard committed May 13, 2019
1 parent 5062bba commit 1d8246d60eb2e75d498a65dfd92882401766a335
@@ -82,6 +82,7 @@ see [`mozilla/fxa-js-client`](https://github.com/mozilla/fxa-js-client).
* [POST /oauth/subscriptions/active (:lock: oauthToken)](#post-subscriptionsactive)
* [DELETE /oauth/subscriptions/active/{subscriptionId} (:lock: oauthToken)](#delete-subscriptionsactivesubscriptionid)
* [POST /oauth/subscriptions/updatePayment (:lock: oauthToken)](#post-subscriptionsupdatepayment)
* [GET /oauth/subscriptions/customer (:lock: oauthToken)](#get-subscriptionscustomer)
* [Token codes](#token-codes)
* [POST /session/verify/token (:lock: sessionToken)](#post-sessionverifytoken)
* [Totp](#totp)
@@ -3048,6 +3049,11 @@ Cancel an active subscription for the user.
:lock: authenticated with OAuth bearer token
Update the user's default payment method using a payment token.

#### GET /oauth/subscriptions/customer

:lock: authenticated with OAuth bearer token
Returns customer details, including limited payment information.

### Token codes

#### POST /session/verify/token
@@ -164,6 +164,29 @@ module.exports = (log, db, config, customs, push, oauthdb, subhub) => {
return {};
}
},
{
method: 'GET',
path: '/oauth/subscriptions/customer',
options: {
auth: {
payload: false,
strategy: 'oauthToken'
},
response: {
schema: isA.object().keys({
payment_type: isA.string(),
last4: isA.number(),
exp_month: isA.number(),
exp_year: isA.number()
})
}
},
handler: async function (request) {
log.begin('subscriptions.getCustomer', request);
const { uid } = handleAuth(request.auth);
return subhub.getCustomer(uid);
}
},
{
method: 'DELETE',
path: '/oauth/subscriptions/active/{subscriptionId}',
@@ -220,10 +220,7 @@ module.exports = function (log, config) {
} catch (err) {
if (err.statusCode === 404) {
log.error('subhub.getCustomer.1', { uid, err });
// TODO: update with subhub createSubscription error response for invalid uid
if (err.message === 'invalid uid') {
throw error.unknownCustomer(uid);
}
throw error.unknownCustomer(uid);
}
throw err;
}
@@ -265,6 +262,13 @@ function buildStubAPI(log, config) {
const storage = { subscriptions: {} };
const subscriptionsKey = (uid, sub_id) => `${uid}|${sub_id}`;

const customer = {
payment_type: 'card',
last4: 8675,
exp_month: 8,
exp_year: 2020
};

return {
isStubAPI: true,

@@ -297,18 +301,25 @@ function buildStubAPI(log, config) {

async cancelSubscription(uid, sub_id) {
const key = subscriptionsKey(uid, sub_id);
if (! storage.subscriptions[key]) {
throw error.unknownSubscription(sub_id);
/*
FIXME: since FxA subs can be in the DB but mock subhub subs are in RAM,
this can throw after a local dev server restart.
if (! storage.subscriptions[key]) {throw
error.unknownSubscription(sub_id);
}
*/
delete storage.subscriptions[key];
return {};
},

async getCustomer(uid) {
return { this_is_a_customer: true };
return customer;
},

async updateCustomer(uid, pmt_token) {
// HACK: Update the payment_type to at least show some change
customer.payment_type = pmt_token;
return {};
},
};
@@ -20,6 +20,12 @@ const SUBSCRIPTIONS_MANAGEMENT_SCOPE =
const TEST_EMAIL = 'test@email.com';
const UID = uuid.v4('binary').toString('hex');
const NOW = Date.now();
const CUSTOMER = {
payment_type: 'card',
last4: 8675,
exp_month: 8,
exp_year: 2020
};
const PLANS = [
{
plan_id: 'firefox_pro_basic_823',
@@ -109,6 +115,7 @@ describe('subscriptions', () => {
});

subhub = mocks.mockSubHub({
getCustomer: sinon.spy(async () => CUSTOMER),
listPlans: sinon.spy(async () => PLANS),
createSubscription: sinon.spy(
async (uid, token, plan_id) => ({ sub_id: SUBSCRIPTION_ID_1 })
@@ -175,6 +182,41 @@ describe('subscriptions', () => {
});
});

describe('GET /oauth/subscriptions/customer', () => {
it('should fetch customer information', async () => {
const res = await runTest('/oauth/subscriptions/customer', requestOptions);
assert.equal(subhub.getCustomer.callCount, 1);
assert.equal(subhub.getCustomer.args[0][0], UID);
assert.deepEqual(res, CUSTOMER);
});

it('should report error for unknown customer', async () => {
subhub.getCustomer = sinon.spy(async () => {
throw error.unknownCustomer(UID);
});
try {
await runTest('/oauth/subscriptions/customer', requestOptions);
assert.fail();
} catch (err) {
assert.equal(subhub.getCustomer.callCount, 1);
assert.equal(subhub.getCustomer.args[0][0], UID);
assert.deepEqual(err.errno, error.ERRNO.UNKNOWN_SUBSCRIPTION_CUSTOMER);
}
});

it('should correctly handle payment backend failure', async () => {
subhub.getCustomer = sinon.spy(async () => {
throw error.backendServiceFailure();
});
try {
await runTest('/oauth/subscriptions/customer', requestOptions);
assert.fail();
} catch (err) {
assert.equal(err.errno, error.ERRNO.BACKEND_SERVICE_FAILURE);
}
});
});

describe('POST /oauth/subscriptions/active', () => {
it('should support creation of a new subscription', async () => {
const res = await runTest(
@@ -303,7 +303,10 @@ describe('subscriptions', () => {
it('should yield customer details', async () => {
// TODO: update with final customer schema from subhub
const expected = {
this_is_a_customer: true
payment_type: 'card',
last4: 8675,
exp_month: 8,
exp_year: 2020
};
mockServer.get(`/customer/${UID}`).reply(200, expected);
const { subhub } = makeSubject();

0 comments on commit 1d8246d

Please sign in to comment.
You can’t perform that action at this time.