-
-
Notifications
You must be signed in to change notification settings - Fork 901
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
refactor Stripe webhook #200
Conversation
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.
Left comments, nice improvements!
We will also want to add some info to the docs regarding the Stripe API version, right?
Co-authored-by: Martin Šošić <[email protected]>
…-lang/open-saas into refactor-subscription-logic
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.
Answered some of the questions/dilemmas I was tagged on.
I might look at more stuff later :)
import { HttpError } from 'wasp/server'; | ||
import { emailSender } from 'wasp/server/email'; | ||
|
||
const getCustomerIdStringOrThrow = (userStripeId: unknown): string => { |
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.
Arrow functions
This is the original explanation: wasp-lang/wasp#487
But I wrote it back when we were using JavaScript (there's an important caveat for TypeScript, also explained in Effective TypeScript, Item 12).
Anyway, I am no longer sure what to advocate. I like the "important stuff on top benefit", but perhaps I'd put more weight on consistency- You'll definitely have to use arrows a lot (that's how our hooks and operations work), so you might as well use them all the time.
unknown
and validation
We should do validation as early as possible (i.e., when we receive the request). As soon as we ensure something has the type we expect it to have, we should stop validating the data and just rely on TypeScript.
The rule is: Make sure something is of type T
before you say it's of type T
. In other words, all validation should happen before the assertion, not after it.
Therefore, this function shouldn't be doing the validation. The validation should be done before the event.data.object as Stripe.Checkout.Session
expression:
- If receiving malformed data is considered normal (i.e., it's the user's direct input, Stripe can return the data in multiple formats, some customers don't have IDs...), we shouldn't raise an exception. We should instead parse the data and propagate the appropriate error to the user (using normal control flow).
- If receiving malformed isn't considered normal (i.e., Stripe accidentally broke its API), we should assert the data has the proper format and throw an exception otherwise. I assume this is the case here. To do such an assertion, you can either use a library (e.g., Zod), or write it yourself. But since already using Zod, I recommend sticking with that.
Let me know if you get stuck, I'm happy to help out!
type UserStripePaymentDetails = { | ||
userStripeId: string; | ||
subscriptionPlan?: SubscriptionPlanId | ||
subscriptionStatus?: SubscriptionStatusOptions; | ||
numOfCreditsPurchased?: number; | ||
datePaid?: Date; | ||
}; |
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.
I don't know the domain that well, but something about this type seems like it's another candidate for what Martin talked about here: https://github.com/wasp-lang/open-saas/pull/200/files#r1664746512
I could be wrong though.
export const updateUserStripePaymentDetails = async (args: UserStripePaymentDetails, userDelegate: PrismaUserDelegate ) => { | ||
let data: any = {}; | ||
if (args.datePaid) data.datePaid = args.datePaid; | ||
if (args.subscriptionPlan) data.subscriptionPlan = args.subscriptionPlan; | ||
if (args.subscriptionStatus) data.subscriptionStatus = args.subscriptionStatus; | ||
if (args.numOfCreditsPurchased) { | ||
data.credits = { increment: args.numOfCreditsPurchased }; | ||
} | ||
|
||
return await userDelegate.update({ | ||
where: { | ||
stripeId: args.userStripeId, | ||
}, | ||
data | ||
}); | ||
}; |
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.
To fix this, check Effective TypeScript (2nd edition), item 21.
Also, we should find a better name for data
and type it properly (without using any
).
I'll help you out here since it's a little weird and the book doesn't do it optimally (I'll assume the type of data
is Data
):
const data: Data = {
...args.datePaid && { datePaid: args.datePaid },
...args.subscriptionPlan && { subscriptionPlan: args.subscriptionPlan },
...subscriptionStatus && { subscriptionStatus: args.subscriptionStatus },
...args.numberOfCreditsPurchased && { credits: { increment: args.numberOfCreditsPurchased } },
}
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.
@sodic I was considering this, but was wondering if it indeed is a better solution. Maybe I have to read that item 21 which I don't ahve time for now, but I gave up on this different approach as it didn't seem to improve anything, and some people might say it is more convoluted even (I hit such resistance in the past in our PRs when I tried to do this).
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.
Yes, I'm fairly confident that you'll agree with this approach once you read Item 21, so I suggest we follow it.
The benefits will become more apparent when data
gets a proper type.
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.
I left some comments and links to material that should make them clearer, but you might have trouble figuring out some of my suggestions.
If that happens, call me up and I'll elaborate (or we can even jump on a call).
@vincanger Pro tip: When you want to link a pull request to an issue, use GitHub's special words. This way, once you merge the PR, the issue automatically gets closed. In this case, you can edit your description to say "Closes #199" or "Fixes #199" instead of "addresses issue #199". You'll see how GitHub immediately underlines it and add the PR under the issue's title |
@sodic @vincanger I wanted to try out myself some things I was suggesting (mostly regarding consolidating types), so that I don't suggest stuff that is not doable or won't look good once it is done, and I got pulled in and ended up implementing stuff. First I ended up with this PR: #217, and then I, on top of that one, made a bit different attempt with this one: #218 . I am not sure which one I prefer, maybe the first one, but it is tight: both are a bit clunky in some aspects, but also both work quite well I think. Please take a look and see if there is anything useful you can use. Also, since I will be traveling in the following days, and @sodic already got quite involved, I suggest @sodic that you take over the reviewing of this PR and drive it to the end with @vincanger . I think I covered all I could from my side, and you will be able to contribute some extra knowledge on the TS side. |
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.
Thanks for all your comments and help here @sodic @Martinsos -- this was a chunky boy!
There was a lot of new insight here for me, so it took me a while to get a grasp of everything.
I merged Martin's ideas from PR #217 for consolidating types. I think we're close to being done here.
I also added docs on stripe api/client versioning. The only thing left to do is create the new app_diff
but I think I will do this in a new PR at another time because of there have been so many big changes and I don't want to rush it.
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.
@vincanger left some extra comments.
Pls go through the "conversation" tab in Github and resolve all the comments that are still open but can be resolved!
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, we got it all in a good shape!
updated docs and app_diff as well |
Fixes issue #199