Merge pull request #14388 from mozilla/FXA-5594

task(admin-panel): Add ability to see canceled/inactive subscriptions
This commit is contained in:
Dan Schomburg 2022-11-03 15:10:11 -07:00 коммит произвёл GitHub
Родитель 4733af41cc 10ae238dc0
Коммит 3351a003e7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 108 добавлений и 36 удалений

Просмотреть файл

@ -16,12 +16,17 @@ import {
import { AppStoreService } from './appstore.service';
import { PlayStoreService } from './playstore.service';
import { StripeService } from './stripe.service';
import { SubscriptionsService } from './subscriptions.service';
import {
SubscriptionsService,
VALID_SUBSCRIPTION_STATUSES,
} from './subscriptions.service';
import { addDays, created } from './test.util';
describe('Subscription Service', () => {
// Stripe Service Mock
const mockFetchCustomers = jest.fn();
const subscriptionStatusTypes = VALID_SUBSCRIPTION_STATUSES;
const mockLookupLatestInvoice = jest.fn();
const mockAllAbbrevPlans = jest.fn();
const mockCreateManageSubscriptionLink = jest.fn();
@ -145,7 +150,11 @@ describe('Subscription Service', () => {
const subscriptions = await service.getSubscriptions(uid);
expect(subscriptions).toEqual([]);
expect(mockAllAbbrevPlans).toBeCalledTimes(1);
expect(mockFetchCustomers).toBeCalledWith(uid, ['subscriptions']);
expect(mockFetchCustomers).toBeCalledWith(
uid,
['subscriptions'],
subscriptionStatusTypes
);
expect(mockAppStoreGetSubscriptions).toBeCalledWith(uid);
expect(mockPlayStoreGetSubscriptions).toBeCalledWith(uid);
});
@ -213,7 +222,11 @@ describe('Subscription Service', () => {
},
]);
expect(mockAllAbbrevPlans).toBeCalledTimes(1);
expect(mockFetchCustomers).toBeCalledWith(uid, ['subscriptions']);
expect(mockFetchCustomers).toBeCalledWith(
uid,
['subscriptions'],
subscriptionStatusTypes
);
expect(mockCreateManageSubscriptionLink).toBeCalledWith(customerId);
expect(mockAppStoreGetSubscriptions).toBeCalledWith(uid);
expect(mockPlayStoreGetSubscriptions).toBeCalledWith(uid);
@ -274,7 +287,11 @@ describe('Subscription Service', () => {
},
]);
expect(mockAllAbbrevPlans).toBeCalledTimes(1);
expect(mockFetchCustomers).toBeCalledWith(uid, ['subscriptions']);
expect(mockFetchCustomers).toBeCalledWith(
uid,
['subscriptions'],
subscriptionStatusTypes
);
expect(mockAppStoreGetSubscriptions).toBeCalledWith(uid);
expect(mockPlayStoreGetSubscriptions).toBeCalledWith(uid);
});
@ -377,7 +394,11 @@ describe('Subscription Service', () => {
},
]);
expect(mockAllAbbrevPlans).toBeCalledTimes(1);
expect(mockFetchCustomers).toBeCalledWith(uid, ['subscriptions']);
expect(mockFetchCustomers).toBeCalledWith(
uid,
['subscriptions'],
subscriptionStatusTypes
);
expect(mockAppStoreGetSubscriptions).toBeCalledWith(uid);
expect(mockPlayStoreGetSubscriptions).toBeCalledWith(uid);
});

Просмотреть файл

@ -6,6 +6,7 @@ import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { MozLoggerService } from 'fxa-shared/nestjs/logger/logger.service';
import { AbbrevPlan } from 'fxa-shared/subscriptions/types';
import Stripe from 'stripe';
import { MozSubscription } from '../gql/model/moz-subscription.model';
import { AppStoreService } from './appstore.service';
import { PlayStoreService } from './playstore.service';
@ -16,6 +17,20 @@ import {
StripeFormatter,
} from './subscriptions.formatters';
/**
* List of valid of subscription statuses. This should be all known
* stripe subscription types.
*/
export const VALID_SUBSCRIPTION_STATUSES: Stripe.Subscription.Status[] = [
'active',
'canceled',
'incomplete',
'incomplete_expired',
'past_due',
'trialing',
'unpaid',
];
/**
* Provides access to account subscriptions
*/
@ -89,9 +104,12 @@ export class SubscriptionsService {
return;
}
const customer = await this.stripeService.fetchCustomer(uid, [
'subscriptions',
]);
const customer = await this.stripeService.fetchCustomer(
uid,
['subscriptions'],
VALID_SUBSCRIPTION_STATUSES
);
for (const subscription of customer?.subscriptions?.data || []) {
// Inspired by code in auth-server payments ;]
const plan = await plans.find(

Просмотреть файл

@ -621,28 +621,46 @@ describe('StripeFirestore', () => {
});
describe('retrieveCustomerSubscriptions', () => {
it('retrieves customer subscriptions', async () => {
const subscriptionSnap = {
docs: [{ data: () => ({ ...customer.subscriptions.data[0] }) }],
};
customerCollectionDbRef.where = sinon.fake.returns({
get: sinon.fake.resolves({
empty: false,
docs: [
{
ref: {
collection: sinon.fake.returns({
get: sinon.fake.resolves(subscriptionSnap),
}),
describe('retrieves customer subscriptions', () => {
beforeEach(() => {
const subscriptionSnap = {
docs: [{ data: () => ({ ...customer.subscriptions.data[0] }) }],
};
customerCollectionDbRef.where = sinon.fake.returns({
get: sinon.fake.resolves({
empty: false,
docs: [
{
ref: {
collection: sinon.fake.returns({
get: sinon.fake.resolves(subscriptionSnap),
}),
},
},
},
],
}),
],
}),
});
});
it('without status filter', async () => {
const subscriptions =
await stripeFirestore.retrieveCustomerSubscriptions(customer.id);
assert.deepEqual(subscriptions, [customer.subscriptions.data[0]]);
});
it('with status filter', async () => {
const subscriptions =
await stripeFirestore.retrieveCustomerSubscriptions(customer.id, [
'active',
]);
assert.deepEqual(subscriptions, [customer.subscriptions.data[0]]);
});
it('with empty status filter', async () => {
const subscriptions =
await stripeFirestore.retrieveCustomerSubscriptions(customer.id, []);
assert.deepEqual(subscriptions, []);
});
const subscriptions = await stripeFirestore.retrieveCustomerSubscriptions(
customer.id
);
assert.deepEqual(subscriptions, [customer.subscriptions.data[0]]);
});
it('retrieves only active customer subscriptions', async () => {

Просмотреть файл

@ -6275,7 +6275,8 @@ describe('StripeHelper', () => {
);
sinon.assert.calledOnceWithExactly(
stripeHelper.stripeFirestore.retrieveCustomerSubscriptions,
customer.id
customer.id,
undefined
);
});
@ -6298,7 +6299,8 @@ describe('StripeHelper', () => {
);
sinon.assert.calledOnceWithExactly(
stripeHelper.stripeFirestore.retrieveCustomerSubscriptions,
customer.id
customer.id,
undefined
);
});

Просмотреть файл

@ -258,8 +258,15 @@ export class StripeFirestore {
/**
* Retrieve all the customer subscriptions from Firestore.
* @param customerId - The target customer
* @param statusFilter - Optional list of subscription statuses to filter by. Only
* subscriptions with status contained in this list will be
* returned. Defaults to ACTIVE_SUBSCRIPTION_STATUSES.
*/
async retrieveCustomerSubscriptions(customerId: string) {
async retrieveCustomerSubscriptions(
customerId: string,
statusFilter: Stripe.Subscription.Status[] = ACTIVE_SUBSCRIPTION_STATUSES
) {
const customerSnap = await this.customerCollectionDbRef
.where('id', '==', customerId)
.get();
@ -275,7 +282,7 @@ export class StripeFirestore {
.get();
return subscriptionSnap.docs
.map((doc) => doc.data() as Stripe.Subscription)
.filter((sub) => ACTIVE_SUBSCRIPTION_STATUSES.includes(sub.status));
.filter((sub) => statusFilter.includes(sub.status));
}
/**

Просмотреть файл

@ -208,7 +208,8 @@ export abstract class StripeHelper {
| 'subscriptions'
| 'invoice_settings.default_payment_method'
| 'tax'
)[]
)[],
statusFilter?: Stripe.Subscription.Status[]
): Promise<Stripe.Customer | void> {
const { stripeCustomerId } = (await getAccountCustomerByUid(uid)) || {};
if (!stripeCustomerId) {
@ -218,7 +219,8 @@ export abstract class StripeHelper {
// By default this has subscriptions expanded.
let customer = await this.expandResource<Stripe.Customer>(
stripeCustomerId,
CUSTOMER_RESOURCE
CUSTOMER_RESOURCE,
statusFilter
);
if (customer.deleted) {
@ -468,7 +470,8 @@ export abstract class StripeHelper {
*/
async expandResource<T>(
resource: string | T,
resourceType: typeof VALID_RESOURCE_TYPES[number]
resourceType: typeof VALID_RESOURCE_TYPES[number],
statusFilter?: Stripe.Subscription.Status[]
): Promise<T> {
if (typeof resource !== 'string') {
return resource;
@ -493,7 +496,10 @@ export abstract class StripeHelper {
return customer;
}
const subscriptions =
await this.stripeFirestore.retrieveCustomerSubscriptions(resource);
await this.stripeFirestore.retrieveCustomerSubscriptions(
resource,
statusFilter
);
(customer as any).subscriptions = {
data: subscriptions as any,
has_more: false,