Merge pull request #16607 from mozilla/FXA-8939

feat(payments-paypal): Create PaypalManager getCustomerBillingAgreementId
This commit is contained in:
Lisa Chan 2024-03-26 17:10:28 -04:00 коммит произвёл GitHub
Родитель 49d7770c38 2a77b82c98
Коммит 500b2a0b09
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 82 добавлений и 26 удалений

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

@ -21,6 +21,11 @@ import {
import { PayPalClient } from './paypal.client';
import { PayPalManager } from './paypal.manager';
import { BillingAgreementStatus } from './paypal.types';
import {
PaypalCustomerNotFoundError,
PaypalCustomerMultipleRecordsError,
} from './paypalCustomer/paypalCustomer.error';
import { ResultPaypalCustomerFactory } from './paypalCustomer/paypalCustomer.factories';
import { PaypalCustomerManager } from './paypalCustomer/paypalCustomer.manager';
describe('PaypalManager', () => {
@ -147,6 +152,48 @@ describe('PaypalManager', () => {
});
});
describe('getCustomerBillingAgreementId', () => {
it("returns the customer's current PayPal billing agreement ID", async () => {
const mockPayPalCustomer = ResultPaypalCustomerFactory();
const mockStripeCustomer = CustomerFactory();
paypalCustomerManager.fetchPaypalCustomersByUid = jest
.fn()
.mockResolvedValueOnce([mockPayPalCustomer]);
const result = await paypalManager.getCustomerBillingAgreementId(
mockStripeCustomer
);
expect(result).toEqual(mockPayPalCustomer.billingAgreementId);
});
it('throws PaypalCustomerNotFoundError if no PayPal customer record', async () => {
const mockStripeCustomer = CustomerFactory();
paypalCustomerManager.fetchPaypalCustomersByUid = jest
.fn()
.mockResolvedValueOnce([]);
expect(
paypalManager.getCustomerBillingAgreementId(mockStripeCustomer)
).rejects.toBeInstanceOf(PaypalCustomerNotFoundError);
});
it('throws PaypalCustomerMultipleRecordsError if more than one PayPal customer found', async () => {
const mockPayPalCustomer1 = ResultPaypalCustomerFactory();
const mockPayPalCustomer2 = ResultPaypalCustomerFactory();
const mockStripeCustomer = CustomerFactory();
paypalCustomerManager.fetchPaypalCustomersByUid = jest
.fn()
.mockResolvedValueOnce([mockPayPalCustomer1, mockPayPalCustomer2]);
expect(
paypalManager.getCustomerBillingAgreementId(mockStripeCustomer)
).rejects.toBeInstanceOf(PaypalCustomerMultipleRecordsError);
});
});
describe('getCustomerPayPalSubscriptions', () => {
it('return customer subscriptions where collection method is send_invoice', async () => {
const mockPayPalSubscription = SubscriptionFactory({

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

@ -12,6 +12,10 @@ import {
import { AccountDatabase } from '@fxa/shared/db/mysql/account';
import { PayPalClient } from './paypal.client';
import { BillingAgreement, BillingAgreementStatus } from './paypal.types';
import {
PaypalCustomerNotFoundError,
PaypalCustomerMultipleRecordsError,
} from './paypalCustomer/paypalCustomer.error';
import { PaypalCustomerManager } from './paypalCustomer/paypalCustomer.manager';
@Injectable()
@ -54,11 +58,27 @@ export class PayPalManager {
};
}
/**
* Retrieves the customers current paypal billing agreement ID from the
* auth database via the Paypal repository
*/
async getCustomerBillingAgreementId(customer: Stripe.Customer) {
const paypalCustomer =
await this.paypalCustomerManager.fetchPaypalCustomersByUid(
customer.metadata.userid
);
const firstRecord = paypalCustomer.at(0);
if (!firstRecord)
throw new PaypalCustomerNotFoundError(customer.metadata.uid);
if (paypalCustomer.length > 1)
throw new PaypalCustomerMultipleRecordsError(customer.metadata.uid);
return firstRecord.billingAgreementId;
}
/**
* Retrieves PayPal subscriptions
*
* @param customer
* @returns
*/
async getCustomerPayPalSubscriptions(customer: Stripe.Customer) {
const subscriptions = await this.stripeManager.getSubscriptions(
@ -82,10 +102,6 @@ export class PayPalManager {
/**
* Process an invoice when amount is greater than minimum amount
*
* @param customer
* @param invoice
* @param ipaddress
*/
async processNonZeroInvoice(
customer: Stripe.Customer,
@ -99,8 +115,6 @@ export class PayPalManager {
/**
* Finalize and process a draft invoice that has no amounted owed.
*
* @param invoice
*/
async processZeroInvoice(invoiceId: string) {
// It appears for subscriptions that do not require payment, the invoice
@ -113,8 +127,6 @@ export class PayPalManager {
* Process an invoice
* If amount is less than minimum amount, call processZeroInvoice
* If amount is greater than minimum amount, call processNonZeroInvoice (legacy PaypalHelper processInvoice)
*
* @param invoice
*/
async processInvoice(invoice: Stripe.Invoice) {
const amountInCents = invoice.amount_due;

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

@ -23,7 +23,7 @@ export class PaypalCustomerNotCreatedError extends PaypalCustomerManagerError {
}
export class PaypalCustomerNotFoundError extends PaypalCustomerManagerError {
constructor(uid: string, cause: Error) {
constructor(uid: string, cause?: Error) {
super('PaypalCustomer not found', {
info: {
uid,
@ -60,3 +60,14 @@ export class PaypalCustomerNotDeletedError extends PaypalCustomerManagerError {
});
}
}
export class PaypalCustomerMultipleRecordsError extends PaypalCustomerManagerError {
constructor(uid: string, cause?: Error) {
super('Multiple PaypalCustomer records', {
info: {
uid,
},
cause,
});
}
}

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

@ -20,9 +20,6 @@ export class StripeClient {
/**
* Retrieves a customer record directly from Stripe
*
* @param customerId The Stripe customer ID of the customer to fetch
* @returns The customer record for the customerId provided
*/
async fetchCustomer(customerId: string) {
return this.stripe.customers.retrieve(customerId);
@ -44,9 +41,6 @@ export class StripeClient {
/**
* Retrieves subscriptions directly from Stripe
*
* @param customerId
* @returns
*/
async fetchSubscriptions(customerId: string) {
return this.stripe.subscriptions.list({

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

@ -22,9 +22,6 @@ export class StripeManager {
/**
* Retrieves a customer record
*
* @param customerId
* @returns
*/
async fetchActiveCustomer(customerId: string) {
const customer = await this.client.fetchCustomer(customerId);
@ -44,9 +41,6 @@ export class StripeManager {
/**
* Returns minimum amount for valid currency
* Throws error for invalid currency
*
* @param currency
* @returns
*/
getMinimumAmount(currency: string): number {
if (STRIPE_MINIMUM_CHARGE_AMOUNTS[currency]) {
@ -58,8 +52,6 @@ export class StripeManager {
/**
* Retrieves subscriptions
*
* @param customerId
*/
async getSubscriptions(customerId: string) {
return this.client.fetchSubscriptions(customerId);