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: PUT /user/payment API saves payment info in stripe.com #1818

Closed
wants to merge 58 commits into from

Conversation

gobengo
Copy link
Contributor

@gobengo gobengo commented Aug 31, 2022

Motivation

Parts

  • ability to create stripe customers for users and look them up
  • PUT /user/payment actually saves payment method id
  • GET /user/payment includes stripe.card card info for frontend
  • add db migration that will work

@gobengo gobengo changed the title 1661905101 api tsc and stripe feat: PUT /user/payment API saves payment info in stripe.com Aug 31, 2022
@github-actions
Copy link
Contributor

github-actions bot commented Sep 1, 2022

package-lock.json changes

Click to toggle table visibility
Name Status Previous Current
stripe ADDED - 10.7.0

@github-actions
Copy link
Contributor

github-actions bot commented Sep 1, 2022

Copy link
Contributor

@vasco-santos vasco-santos left a comment

Choose a reason for hiding this comment

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

Good job @gobengo

Did a first pass through this and left feedback. Let me know what you think

})
}
},
NodeModulesPolyfillPlugin()
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this for stripe? would be nice to have a comment if so

Copy link
Contributor

Choose a reason for hiding this comment

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

or is needed for assert?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a comment explaining how 'stripe' deps on some node things (though I think we dont actually import those parts, e.g. we use the Fetch http adapater instead of the Node one)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(afaict it wasn't related to the use of assert)

@@ -35,6 +39,7 @@ import { defaultBypassMagicLinkVariableName } from './magic.link.js'
* @property {string} [LOGTAIL_TOKEN]
* @property {string} MAINTENANCE_MODE
* @property {string} [DANGEROUSLY_BYPASS_MAGIC_AUTH]
* @property {string} [STRIPE_SECRET_KEY]
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

*/
function createStripeBillingContext (env) {
const stripeSecretKey = env.STRIPE_SECRET_KEY
assert.ok(stripeSecretKey, 'env STRIPE_SECRET_KEY is required')
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we not add assert dependency just for this?

Copy link
Contributor Author

@gobengo gobengo Sep 7, 2022

Choose a reason for hiding this comment

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

sure. so i can have this in mind in the future: what's the downside of the fairly-well-standardized assert dependency in this cf worker context? FWIW assert.ok is nice because it's also a typed as a type guard. microsoft/TypeScript#32695

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Assert will throw AssertionError and in the error handler we will need to define what user error we want to send (and whether it should have 500 code or 4xx code). We could get all this with assert, but I think it would make things more complex with current code

// this can be used to mock realistic values of a stripe.com paymentMethod id
// after fulls tripe integration, this may not be needed on the env
env.mockStripePaymentMethodId = 'pm_1LZnQ1IfErzTm2rETa7IGoVm'
Object.assign(env, (env.ENV === 'test') ? createTestBillingEnv() : createStripeBillingContext(env))
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest not adding so much stripe specific logic into env file which is quite simple at the moment. So, thinking about a createStripeBillingContext(env) factory function in utils/stripe.js which would do this validation and have functions below. We also end up simplifying a lot the import statement here and what needs to be exported from stripe file.
What do you think @gobengo ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, wondering if we could simplify a bit this to not be so env specific. If we make STRIPE_SECRET_KEY optional and use it as a way of identifying if we want a real billing context or a mock?

if (!env.STRIPE_SECRET_KEY) {
  return {
    billing: createMockBillingService(),
    customers: createTestEnvCustomerService()
  } 
}

Probably to throw an error only as:

if (!env.STRIPE_SECRET_KEY && !(env.ENV === 'test' || env.ENV === 'dev') {
  throw new Error(`Stripe secret key is required for env ${env.ENV}`)
}

thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@vasco-santos great suggestion. I implemented as
475be01
3366de2

@@ -78,7 +79,7 @@ async function loginOrRegister (request, env) {

const parsed =
data.type === 'github'
? parseGitHub(data.data, metadata)
? await parseGitHub(data.data, metadata)
Copy link
Contributor

Choose a reason for hiding this comment

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

Not promise?

Comment on lines 37 to 40
/**
* @type {BillingService}
*/
const instance = this // eslint-disable-line
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's the only way to have tsc check compatability with that BillingInterface when using jsdoc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

@gobengo gobengo Sep 7, 2022

Choose a reason for hiding this comment

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

I made it nicer, one line, no eslint-disable 67deec4 (also checked to make sure this new syntax gives us the same warning if the interface breaks)

Comment on lines 48 to 51
throw new Error('unable to get payment method')
}
if (response.deleted) {
throw new Error('unexpected response.deleted')
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these internal Errors or user input error?

Copy link
Contributor

Choose a reason for hiding this comment

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

If these can happen because user input, they should be custom errors like the ones you see in src/errors.js, otherwise they will be interpreted as 500 and throw on call alerts

Copy link
Contributor

Choose a reason for hiding this comment

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

(this is also the case for other Errors in this PR that I am not flagging)

Copy link
Contributor Author

@gobengo gobengo Sep 7, 2022

Choose a reason for hiding this comment

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

@vasco-santos this is not related to user error. These would be legitimately unexpected errors that might indicate something else was wrong in the stripe.com-hosted customer db. It shouldn't need to keep anyone up at night, but it would be nice to have a modest alarm (e.g. a sentry report) if these errors ever do come up, and throwing explicitly will give the debugger (probably me) a stack trace when that happens. Technically this case can happen (the type system told me), but it shouldn't, and if it does it is probably something that should be escalated to someone like us

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed these two untyped Errrors. The first wasn't really needed. The second I added a specific CustomerNotFound type for, and added it to the function signature so tsc helps callers be ready for it or other errors. cd82b24

Copy link
Contributor

Choose a reason for hiding this comment

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

that sounds good. Thanks for details :)

async getUserCustomer (userId) {
const { data, error } = await this._client
.from('user_customer')
.select(['customer_id'].join(','))
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for the join

Copy link
Contributor Author

Choose a reason for hiding this comment

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

according to the docs (and tsc types, which aren't being checked in db package yet), it must be a string. https://supabase.github.io/postgrest-js/classes/PostgrestQueryBuilder.html#select

(not disagreeing that the join works now at runtime, but I'd prefer to keep it here so it doesn't have to change again if/when we enable type checking #1860 )

packages/db/index.js Outdated Show resolved Hide resolved
CREATE TABLE IF NOT EXISTS user_customer
(
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL UNIQUE REFERENCES public.user (id),
Copy link
Contributor

Choose a reason for hiding this comment

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

We do queries for user_id, probably we should add an index for it? What you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good idea! 3df2067

@gobengo
Copy link
Contributor Author

gobengo commented Sep 7, 2022

@vasco-santos Thanks for your review. I believe I addressed all of your comments. (I left an 🎉 emoji on comments I acted on). wdyt?

@gobengo
Copy link
Contributor Author

gobengo commented Sep 8, 2022

I integrated some frontend work by @adamalton on top of this that actually results in a functional demo of the backend changes in this PR. It's really those changes too that we want to be deploying to staging and beyond. Let's consider this PR superceded by #1852 . @vasco-santos it's basically identical to what you reviewed already + changes based on your review.

@gobengo gobengo closed this Sep 8, 2022
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.

2 participants