Skip to content
This repository has been archived by the owner on Aug 11, 2024. It is now read-only.

Commit

Permalink
Starting to refactor the payment code!
Browse files Browse the repository at this point in the history
  • Loading branch information
wpf500 committed May 16, 2024
1 parent e194904 commit a1fd867
Show file tree
Hide file tree
Showing 18 changed files with 238 additions and 192 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"test": "jest --setupFiles dotenv/config"
},
"dependencies": {
"@beabee/beabee-common": "^0.21.0",
"@beabee/beabee-common": "^0.21.1-alpha.2",
"@captchafox/node": "^1.2.0",
"@inquirer/prompts": "^3.3.0",
"@sendgrid/mail": "^8.1.0",
Expand Down Expand Up @@ -131,10 +131,10 @@
"rimraf": "^5.0.5",
"sass": "1.57.*",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"tsc-watch": "^6.0.4",
"tsconfig": "^7.0.0",
"typescript": "^5.3.3",
"ts-node": "^10.9.2"
"typescript": "^5.3.3"
},
"browserslist": [
"last 1 versions",
Expand All @@ -155,4 +155,4 @@
"postcss": "8.4.32"
}
}
}
}
3 changes: 1 addition & 2 deletions src/api/dto/PaymentFlowDto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { PaymentFlowParams } from "@beabee/beabee-common";
import { IsOptional, IsString } from "class-validator";

import { PaymentFlowParams } from "@type/index";

export class GetPaymentFlowDto implements PaymentFlowParams {
@IsOptional()
@IsString()
Expand Down
40 changes: 14 additions & 26 deletions src/core/providers/payment/GCProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import moment from "moment";

import gocardless from "@core/lib/gocardless";
import { log as mainLogger } from "@core/logging";
import { getActualAmount } from "@core/utils";
import {
updateSubscription,
createSubscription,
Expand All @@ -17,19 +16,20 @@ import {
} from "@core/utils/payment/gocardless";
import { calcRenewalDate } from "@core/utils/payment";

import NoPaymentMethod from "@api/errors/NoPaymentMethod";

import { PaymentProvider } from ".";

import Contact from "@models/Contact";

import NoPaymentMethod from "@api/errors/NoPaymentMethod";

import config from "@config";

import {
CompletedPaymentFlow,
ContributionInfo,
PaymentForm,
UpdateContributionResult
UpdateContributionResult,
UpdatePaymentMethodResult
} from "@type/index";

const log = mainLogger.child({ app: "gc-payment-provider" });
Expand Down Expand Up @@ -153,18 +153,11 @@ export default class GCProvider extends PaymentProvider {
expiryDate
});

this.data.subscriptionId = subscription.id!;
this.data.payFee = paymentForm.payFee;
this.data.nextAmount = startNow
? null
: {
monthly: paymentForm.monthlyAmount,
chargeable: Number(subscription.amount)
};

await this.updateData();

return { startNow, expiryDate: moment.utc(expiryDate).toDate() };
return {
startNow,
expiryDate: moment.utc(expiryDate).toDate(),
subscriptionId: subscription.id!
};
}

async cancelContribution(keepMandate: boolean): Promise<void> {
Expand All @@ -191,36 +184,31 @@ export default class GCProvider extends PaymentProvider {

async updatePaymentMethod(
completedPaymentFlow: CompletedPaymentFlow
): Promise<void> {
): Promise<UpdatePaymentMethodResult> {
log.info("Update payment source for " + this.contact.id, {
userId: this.contact.id,
data: this.data,
completedPaymentFlow
});

const hadSubscription = !!this.data.subscriptionId;
const mandateId = this.data.mandateId;

this.data.subscriptionId = null;
this.data.customerId = completedPaymentFlow.customerId;
this.data.mandateId = completedPaymentFlow.mandateId;

// Save before cancelling to stop the webhook triggering a cancelled email
await this.updateData();

if (mandateId) {
// This will also cancel the subscription
await gocardless.mandates.cancel(mandateId);
}

// Recreate the subscription if the user had one
if (hadSubscription && this.data.period && this.data.monthlyAmount) {
await this.updateContribution({
const res = await this.updateContribution({
monthlyAmount: this.data.monthlyAmount,
period: this.data.period,
payFee: !!this.data.payFee,
prorate: false
});
return { subscriptionId: res.subscriptionId };
} else {
return {};
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/core/providers/payment/ManualProvider.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import Contact from "@models/Contact";

import { PaymentProvider } from ".";

import {
CompletedPaymentFlow,
ContributionInfo,
PaymentForm,
UpdateContributionResult
UpdateContributionResult,
UpdatePaymentMethodResult
} from "@type/index";

export default class ManualProvider extends PaymentProvider {
Expand Down Expand Up @@ -36,7 +38,7 @@ export default class ManualProvider extends PaymentProvider {
}
async updatePaymentMethod(
completedPaymentFlow: CompletedPaymentFlow
): Promise<void> {
): Promise<UpdatePaymentMethodResult> {
throw new Error("Method not implemented.");
}
async permanentlyDeleteContact(): Promise<void> {
Expand Down
68 changes: 24 additions & 44 deletions src/core/providers/payment/StripeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ import { ContributionType, PaymentSource } from "@beabee/beabee-common";
import { add } from "date-fns";
import Stripe from "stripe";

import { PaymentProvider } from ".";

import { stripe } from "@core/lib/stripe";
import { log as mainLogger } from "@core/logging";
import { getActualAmount } from "@core/utils";
import { calcRenewalDate, getChargeableAmount } from "@core/utils/payment";
import { calcRenewalDate } from "@core/utils/payment";
import {
createSubscription,
deleteSubscription,
getCustomerDataFromCompletedFlow,
manadateToSource,
updateSubscription
} from "@core/utils/payment/stripe";
Expand All @@ -19,13 +17,16 @@ import Contact from "@models/Contact";

import NoPaymentMethod from "@api/errors/NoPaymentMethod";

import { PaymentProvider } from ".";

import config from "@config";

import {
CompletedPaymentFlow,
ContributionInfo,
PaymentForm,
UpdateContributionResult
UpdateContributionResult,
UpdatePaymentMethodResult
} from "@type/index";

const log = mainLogger.child({ app: "stripe-payment-provider" });
Expand Down Expand Up @@ -74,33 +75,22 @@ export default class StripeProvider extends PaymentProvider {
await this.updateData();
}

async updatePaymentMethod(flow: CompletedPaymentFlow): Promise<void> {
const paymentMethod = await stripe.paymentMethods.retrieve(flow.mandateId);
const address = paymentMethod.billing_details.address;

const customerData: Stripe.CustomerUpdateParams = {
invoice_settings: {
default_payment_method: flow.mandateId
},
address: address
? {
line1: address.line1 || "",
...(address.city && { city: address.city }),
...(address.country && { country: address.country }),
...(address.line2 && { line2: address.line2 }),
...(address.postal_code && { postal_code: address.postal_code }),
...(address.state && { state: address.state })
}
: null
};
async updatePaymentMethod(
flow: CompletedPaymentFlow
): Promise<UpdatePaymentMethodResult> {
const customerData = await getCustomerDataFromCompletedFlow(flow);
let customerId = this.data.customerId;

if (this.data.customerId) {
log.info("Attach new payment source to " + this.data.customerId);
// Attach payment method to existing customer
if (customerId) {
log.info("Attach new payment source to " + customerId);
await stripe.paymentMethods.attach(flow.mandateId, {
customer: this.data.customerId
customer: customerId
});
await stripe.customers.update(this.data.customerId, customerData);
} else {
await stripe.customers.update(customerId, customerData);
}
// Create a new customer
else {
log.info("Create new customer");
const customer = await stripe.customers.create({
email: this.contact.email,
Expand All @@ -117,7 +107,7 @@ export default class StripeProvider extends PaymentProvider {
// }),
...customerData
});
this.data.customerId = customer.id;
customerId = customer.id;
}

if (this.data.mandateId) {
Expand All @@ -127,7 +117,7 @@ export default class StripeProvider extends PaymentProvider {

this.data.mandateId = flow.mandateId;

await this.updateData();
return { customerId };
}

async updateContribution(
Expand All @@ -148,7 +138,7 @@ export default class StripeProvider extends PaymentProvider {
this.data.customerId,
{
...paymentForm,
monthlyAmount: this.contact.contributionMonthlyAmount || 0
monthlyAmount: this.data.monthlyAmount || 0
},
this.method,
calcRenewalDate(this.contact)
Expand All @@ -160,23 +150,13 @@ export default class StripeProvider extends PaymentProvider {
const { subscription, startNow } =
await this.updateOrCreateContribution(paymentForm);

this.data.subscriptionId = subscription.id;
this.data.payFee = paymentForm.payFee;
this.data.nextAmount = startNow
? null
: {
chargeable: getChargeableAmount(paymentForm, this.method),
monthly: paymentForm.monthlyAmount
};

await this.updateData();

return {
startNow,
expiryDate: add(
new Date(subscription.current_period_end * 1000),
config.gracePeriod
)
),
subscriptionId: subscription.id
};
}

Expand Down
5 changes: 3 additions & 2 deletions src/core/providers/payment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
CompletedPaymentFlow,
ContributionInfo,
PaymentForm,
UpdateContributionResult
UpdateContributionResult,
UpdatePaymentMethodResult
} from "@type/index";

export abstract class PaymentProvider {
Expand Down Expand Up @@ -44,7 +45,7 @@ export abstract class PaymentProvider {

abstract updatePaymentMethod(
completedPaymentFlow: CompletedPaymentFlow
): Promise<void>;
): Promise<UpdatePaymentMethodResult>;

abstract permanentlyDeleteContact(): Promise<void>;
}
Loading

0 comments on commit a1fd867

Please sign in to comment.