-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
473 - Refactor and change out the donation flow to use Stripe.SetupIntents #479
Conversation
* added error logging to stripe client * added type for stripe config * removed unneded import --------- Co-authored-by: quantum-grit <quantum-grit@gmail.com>
* added: explicit version for stripe api client * added: maxNetworkRetries 2 to stripe client * added: full stripe error log --------- Co-authored-by: quantum-grit <quantum-grit@gmail.com>
Co-authored-by: quantum-grit <quantum-grit@gmail.com>
…ow-recurring-payments-setup-intent
✅ Tests will run for this PR. Once they succeed it can be merged. |
@dimitur2204 @igoychev The PR looks awesome to me. Can't wait to test it on staging |
Yep, we are just pushing out the Expense Reports and Bank Imports and this one will come next! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great improvement and also puts things in good order!
Please see a few comments that need adjustments and we are good to go.
imports: [ConfigModule, HttpModule], | ||
providers: [ | ||
PaypalService, | ||
{ | ||
provide: CampaignService, | ||
useValue: {}, | ||
}, | ||
{ | ||
provide: DonationsService, | ||
useValue: {}, | ||
}, | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DonationsService depends on StripeClient and including it here makes Paypal depend on Stripe too, which is not a good practice.
It looks like if we move createSubscriptionDonation from DonationsService to the StripeService will resolve the situation.
Also wen resolving the dependencies, it will be better to add DonationsModule in the imports[] section, so that there is no need to include its services in the providers section.
imports: [ | ||
StripeClientModule.forRootAsync(StripeClientModule, { | ||
inject: [ConfigService], | ||
useFactory: StripeConfigFactory.useFactory, | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you might need to add the ConfigModule in imports[] section, to indicate who is providing the ConfigService
customer: customer.id, | ||
}) | ||
const paymentIntent = await this.stripeClient.paymentIntents.create({ | ||
amount: Math.round(Number(setupIntent.metadata.amount)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you share what is the need of the round here? asking if it could create rounding errors with the amount
import { DeepMockProxy } from 'jest-mock-extended/lib/mjs/Mock' | ||
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended' | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice side improvement!
@StripeWebhookHandler('payment_intent.canceled') | ||
async handlePaymentIntentCancelled(event: Stripe.Event) { | ||
const paymentIntent: Stripe.PaymentIntent = event.data.object as Stripe.PaymentIntent | ||
Logger.log( | ||
'[ handlePaymentIntentCancelled ]', | ||
paymentIntent, | ||
paymentIntent.metadata as DonationMetadata, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The need of handling payment_intent.canceled was for recording if the person didn't complete the payment after his initial desire to donate.
Could you share how we are recording that information after this handler is removed?
const mockedupdateDonationPayment = jest | ||
.spyOn(campaignService, 'updateDonationPayment') | ||
.spyOn(donationService, 'createDonation') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[consistency] mockedupdateDonationPayment needs to become mockedcreateDonation
const mockedupdateDonationPayment = jest | ||
.spyOn(campaignService, 'updateDonationPayment') | ||
.spyOn(donationService, 'createDonation') | ||
.mockImplementation(() => Promise.resolve('')) | ||
.mockName('updateDonationPayment') | ||
.mockName('createDonation') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test is for cancelling the subscription, yet it seems to expect a createDonation function to be called?
const product = await this.stripeClient.products.create({ | ||
name: `Donation of ${subscriptionPaymentDto.amount}`, | ||
description: `Donation of ${subscriptionPaymentDto.amount} to campaign ${subscriptionPaymentDto.campaignId} by person ${person.email}`, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per Stripe documentation the product should be the name of the campaign - see 3rd bullet point here: https://stripe.com/docs/products-prices/how-products-and-prices-work#what-is-a-product
In that regard the product name and description should contain only campaign related information and should not include the price or person details, so that we don't create too many products for each client and each price. Also if easy, we could also pass the campaign url as parameter to the create product function.
const invoice = await this.stripeClient.invoices.retrieve( | ||
subscription.latest_invoice as string, | ||
{ | ||
expand: ['payment_intent'], | ||
}, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just noticed that we can skip the invoice.retrieve request if we pass pass the "expand: ['latest_invoice.payment_intent']," to the subscription.create function so that it is returned there
closing without merge, as the code changed quite a lot after this pull request |
Closes #428 #466
Motivation and context
Changed out the donation flow to now use the
Stripe.SetupIntent
in order to solve a problem that occured with the PaymentIntent and recurring donations, now when we do not use theCheckoutSession
.This is an overview of the new flow in a simplified version (recurring donations will be part of a future PR):
Changes
Added a new
stripe.module
that is concerned with all functionality regarding communication with stripe and using the stripe client. Provides better separation of concerns.Changed out how the
donations
are created. With the new implementation they are created directly in theDonation.succeeded
state after the PaymentIntent was confirmed successfully and a charge was made, but this is subject to change if needed. The creation of the Donations is now reliant on the metadata provided on the Setup/Payment Intent.Changed out some methods into services that made more sense for them to be in.
Removed old checkout session implementation.
Testing
I have only adjusted current tests and removed old ones, that were testing parts that do not exist now.
New endpoints
POST
/setup-intent
- Creates a new SetupIntentPOST
/setup-intent/:id
- Updates SetupIntentPOST
/setup-intent/:id/finalize
- Finalizes SetupIntent by creating a Customer, attaching a PaymentMethod and returning a confirmed PaymentIntentAll endpoints concerning communication with Stripe have been moved to the
stripe.controller
Potential problems
There might be some confusion between the
StripeModule
that comes fromgolevelup
and our new Stripe module.The donations being instantiated in status
Donation.succeeded
means that failed and possibly donations that might take a long time to process for some reason might be problematic. However, all the tests I did with many different Stripe test cards are passing!