Skip to content
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

Feat/channel aware stripe #2058

Merged

Conversation

martijnvdbrug
Copy link
Collaborator

@martijnvdbrug martijnvdbrug commented Feb 28, 2023

This PR implements config for Stripe per channel: A user can set ApiKey and webhooks per channel via the Admin UI.

image

It would be nice to encrypt the ApiKey based on an env var, before storing it in the DB, but I can't find a way to hook into the saving of handler args.

The PR also includes a Stripe dev-server to test out changes during development:
image

E2e tests are still missing the webhook mocking, but that's not part of this PR. I've manually verified payment flow with the dev-server and local tunnel:
image
(admin UI looks nice!)

@martijnvdbrug martijnvdbrug changed the base branch from master to major March 3, 2023 07:17
],
UpdatePromotionResult: ['MissingConditionsError', 'Promotion'],
},
export interface PossibleTypesResultData {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I regenerated types. How to prettify everything?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a pre-commit hook that applies Prettier to all changed files. Is that not working?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, yarn husky install gives me error Command "husky" not found.. I see the dependency. But this is something for later. I will try to manually lint or prettier

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NVM, it is working now, not sure why. I did get an Out Of Memory so I had to change the command to NODE_OPTIONS=\"--max-old-space-size=8096\" lint-staged

@martijnvdbrug martijnvdbrug marked this pull request as ready for review March 3, 2023 09:42
@michaelbromley
Copy link
Member

Thanks for this @martijnvdbrug! I'll review it soon when I get some time to dedicate to this.

In the mean time I'll invite @vrosa to see if he has any feedback, as the original author of the Stripe integration.

@michaelbromley
Copy link
Member

By the way, I just pushed a lot of changes to the major branch, including all the TypeORM breaking changes. I'm afraid you'll need to do a bit of extra work to integrate these changes.

I made some notes on the main changes in the TypeORM APIs:

Ref: https://typeorm.io/changelog#030httpsgithubcomtypeormtypeormpull8616-2022-03-17

  • TypeORM upgrade requires Node.JS version 14+
  • changing where null to use IsNull()
  • changing return types to null rather than undefined
  • change findOne(id) to findOne({ where: { id } })
  • change where: { order: myOrder } to where: { order: { id: myOrder.id }}
  • change findInIds(ids) to find({ where: { id: In(ids) } })

@vrosa
Copy link
Contributor

vrosa commented Mar 12, 2023

Thanks for this @martijnvdbrug! I'll review it soon when I get some time to dedicate to this.

In the mean time I'll invite @vrosa to see if he has any feedback, as the original author of the Stripe integration.

Thanks for the heads up, I'll have a closer look later this week if that's ok.

@martijnvdbrug
Copy link
Collaborator Author

@michaelbromley Not sure why the generated types changed in core. Looks like formatting. How can I make sure formatting is up to date?

If I run yarn format . in the root of the project it changes a lot of files that are not part of this PR. Also, didn't formatting used to be automatic on commit or push?

@martijnvdbrug
Copy link
Collaborator Author

@michaelbromley Is the major development ready? I can't build the major branch locally, which results in not being able to build the payments-plugin, because of type mismatches (DocumentNode, probably because of version mismatches)

@michaelbromley
Copy link
Member

@martijnvdbrug You're right, it was in a broken state after a recent merge from master. Should be working now hopefully!

@@ -87,7 +87,7 @@
"hooks": {
"commit-msg": "commitlint -e $HUSKY_GIT_PARAMS",
"post-commit": "git update-index --again",
"pre-commit": "lint-staged",
"pre-commit": "NODE_OPTIONS=\"--max-old-space-size=8096\" lint-staged",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is an Ubuntu specific issue, but the OOM also happens in my own repo's with eslint, and this is how I fixed it. https://stackoverflow.com/questions/53230823/fatal-error-ineffective-mark-compacts-near-heap-limit-allocation-failed-javas

@martijnvdbrug
Copy link
Collaborator Author

Strange, e2e tests here are also failing with `Error: Cannot find module '@ardatan/aggregate-error'
Require stack:

  • /home/martijn/git/vendure/node_modules/@nestjs/graphql/node_modules/@graphql-tools/utils/index.cjs.js`
    Is it caching node_modules somehow? Yarn.lock is not part of this PR

@michaelbromley
Copy link
Member

Not sure why, maybe caching? Anyway, I found a few more issues with e2e tests and just pushed some changes to major.

const paymentIntent = event.data.object as Stripe.PaymentIntent;
const { metadata: { channelToken, orderCode, orderId } = {} } = paymentIntent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Destructuring here is going to throw an error in case paymentIntent is null or undefined. These new lines should be moved to after the if (!paymentIntent) check, since retrieving the order depends on a valid payment intent in the first place:

const paymentIntent = event.data.object as Stripe.PaymentIntent;
if (!paymentIntent) {
  Logger.error(noPaymentIntentErrorMessage, loggerCtx);
  response.status(HttpStatus.BAD_REQUEST).send(noPaymentIntentErrorMessage);
  return;
}

const { metadata: { channelToken, orderCode, orderId } = {} } = paymentIntent;
// more code

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. fixed

const order = await this.orderService.findOneByCode(ctx, orderCode);
if (!order) {
throw Error(`Unable to find order ${orderCode}, unable to settle payment ${paymentIntent.id}!`);
try {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reckon it makes the most sense to have this check right after an order is retrieved to ensure we're processing an actual message from Stripe before proceeding any further:

if (!order) {
  throw Error(`Unable to find order ${orderCode}, unable to settle payment ${paymentIntent.id}!`);
}

try {
  event = this.stripeService.constructEventFromPayload(request.rawBody, signature);
}
// more code

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also valid feedback. Fixed

@@ -50,39 +72,47 @@ export const stripePaymentMethodHandler = new PaymentMethodHandler({
},

async createRefund(ctx, input, amount, order, payment, args): Promise<CreateRefundResult> {
const result = await stripeService.createRefund(payment.transactionId, amount);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of interest, why did you remove createRefund from StripeService and moved the code here? The idea was to encapsulate interactions with the Stripe object inside the service so the handler doesn't need to know about the Stripe API, e.g. stripe.refunds.create.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why I did it: simplicity, the function was just proxying to the stripe client.

I have changed it back, encapsulation the stripe client inside the service is also a good point.

export class VendureStripeClient extends Stripe {

constructor(
public apiKey: string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a minor thing but from what I could tell apiKey doesn't need to be public as it's not referenced anywhere unlike webhookSecret here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it can be private.

@martijnvdbrug
Copy link
Collaborator Author

martijnvdbrug commented Mar 29, 2023

@vrosa @michaelbromley thanks for the thorough feedback on this PR. I've incorporated all feedback and updated with the latest Major branch again.

(Still the same errors in e2e, that seem unrelated to these changes)

@martijnvdbrug martijnvdbrug requested review from vrosa and michaelbromley and removed request for michaelbromley and vrosa March 29, 2023 07:36
@yasserlens
Copy link
Contributor

I'd very much appreciate having an update on where this is at the moment - I eagerly need it to launch a new store on the same vendure instance that already serves another store. Thank you for your work on this! 🙏

@michaelbromley
Copy link
Member

Hi everyone. Apologies for the wait - I had quite a lot on my plate preparing the v2 beta release.

@martijnvdbrug thanks for this incredible effort and also @vrosa for your thorough review.

This is ready to merge, but if would be good in Martijn can provide a paragraph on the breaking change that I can use in the merge commit, which will subsequently appear in the changelog.

Also if you feel it would be helpful, you can provide an expanded explanation which I can then put in the migration guide for v1->v2.

@martijnvdbrug
Copy link
Collaborator Author

@michaelbromley Breaking change docs:

Updating Stripe from v1.x to v2

The Stripe plugin has been made channel aware. This means your api key and webhook secret are now stored in the database, per channel, instead of environment variables.

To migrate to v2 of the Stripe plugin from @vendure/payments you need to:

  1. Remove the apiKey and webhookSigningSecret from the plugin initialization in vendure-config.ts:
-StripePlugin.init({
-    apiKey: process.env.YOUR_STRIPE_SECRET_KEY,
-    webhookSigningSecret: process.env.YOUR_STRIPE_WEBHOOK_SIGNING_SECRET,
-    storeCustomersInStripe: true,
-}),
+StripePlugin.init({
+    storeCustomersInStripe: true,
+ }),
  1. Start the server and login as administrator.
  2. For each channel that you'd like to use Stripe payments, you need to create a payment method with payment handler Stripe payment and the apiKey and webhookSigningSecret belonging to that channel's Stripe account.

image

@michaelbromley michaelbromley merged commit 3b88702 into vendure-ecommerce:major Apr 21, 2023
@michaelbromley
Copy link
Member

Thanks @martijnvdbrug!

This will be in 2.0.0-beta.2, expected next week 🥳

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants