vercel / nextjs-subscription-payments

Clone, deploy, and fully customize a SaaS subscription application with Next.js.
https://subscription-payments.vercel.app/
MIT License
6.23k stars 1.27k forks source link

Not require service role key #129

Closed ComputerDen closed 1 year ago

ComputerDen commented 1 year ago

Hey, Im trying to set this up so that i dont have to use the service role key, i beleieve i have the rls set up properly(i get no errors) and the supabase client swapped to the regular one in the supabase-admin.ts file i am running it localy, is that an issue?

it lets me go through the process of adding my card and then returns me to the page but it does not insert a new customer or any of the rest of the functions, it does add the details into stripe but nothing on the other end. no errors i can track. i had to change the createOrRetrieveCustomer so that i could use the rls to can anyone point me in the right direction?

`import { supabaseClient, User } from '@supabase/auth-helpers-nextjs'; import { stripe } from './stripe'; import { toDateTime } from './helpers'; import { Customer, UserDetails, Price, Product } from 'types'; import Stripe from 'stripe';

export const supabase = supabaseClient;

const upsertProductRecord = async (product: Stripe.Product) => { const productData: Product = { id: product.id, active: product.active, name: product.name, description: product.description ?? undefined, image: product.images?.[0] ?? null, metadata: product.metadata };

const { error } = await supabase .from('products') .upsert([productData]); if (error) throw error; console.log(Product inserted/updated: ${product.id}); };

const upsertPriceRecord = async (price: Stripe.Price) => { const priceData: Price = { id: price.id, product_id: typeof price.product === 'string' ? price.product : '', active: price.active, currency: price.currency, description: price.nickname ?? undefined, type: price.type, unit_amount: price.unit_amount ?? undefined, interval: price.recurring?.interval, interval_count: price.recurring?.interval_count, trial_period_days: price.recurring?.trial_period_days, metadata: price.metadata };

const { error } = await supabase .from('prices') .upsert([priceData]); if (error) throw error; console.log(Price inserted/updated: ${price.id}); };

const createOrRetrieveCustomer = async ({ email, uuid }: { email: string; uuid: string; }) => { let { data, error } = await supabase .from('customers') .select('stripe_customer_id') if (error) { // No customer record found, let's create one. console.log(error); const customerData: { metadata: { supabaseUUID: string }; email?: string } = { metadata: { supabaseUUID: uuid } }; if (email) customerData.email = email; const customer = await stripe.customers.create(customerData); // Now insert the customer ID into our Supabase mapping table. const { error: supabaseError } = await supabase .from('customers') .insert([{ id: uuid, stripe_customer_id: customer.id }]); if (supabaseError) {console.log(supabaseError); throw supabaseError;} console.log(New customer created and inserted for ${uuid}.); return customer.id; } if (data) return data; };

/**

const manageSubscriptionStatusChange = async ( subscriptionId: string, customerId: string, createAction = false ) => { // Get customer's UUID from mapping table. const { data: customerData, error: noCustomerError } = await supabase .from('customers') .select('id') .eq('stripe_customer_id', customerId) .single(); if (noCustomerError) throw noCustomerError;

const { id: uuid } = customerData || {};

const subscription = await stripe.subscriptions.retrieve(subscriptionId, { expand: ['default_payment_method'] }); // Upsert the latest status of the subscription object. const subscriptionData = { id: subscription.id, user_id: uuid, metadata: subscription.metadata, status: subscription.status, price_id: subscription.items.data[0].price.id, //TODO check quantity on subscription // @ts-ignore quantity: subscription.quantity, cancel_at_period_end: subscription.cancel_at_period_end, cancel_at: subscription.cancel_at ? toDateTime(subscription.cancel_at) : null, canceled_at: subscription.canceled_at ? toDateTime(subscription.canceled_at) : null, current_period_start: toDateTime(subscription.current_period_start), current_period_end: toDateTime(subscription.current_period_end), created: toDateTime(subscription.created), ended_at: subscription.ended_at ? toDateTime(subscription.ended_at) : null, trial_start: subscription.trial_start ? toDateTime(subscription.trial_start) : null, trial_end: subscription.trial_end ? toDateTime(subscription.trial_end) : null };

const { error } = await supabase .from('subscriptions') .upsert([subscriptionData]); if (error) throw error; console.log( Inserted/updated subscription [${subscription.id}] for user [${uuid}] );

// For a new subscription copy the billing details to the customer object. // NOTE: This is a costly operation and should happen at the very end. if (createAction && subscription.default_payment_method && uuid) //@ts-ignore await copyBillingDetailsToCustomer( uuid, subscription.default_payment_method as Stripe.PaymentMethod ); };

export { upsertProductRecord, upsertPriceRecord, createOrRetrieveCustomer, manageSubscriptionStatusChange }; `

ComputerDen commented 1 year ago

sorry im an idiot forgot to start the stripe webhook again

thorwebdev commented 1 year ago

You do require the service role key for managing Stripe customer IDs. You can't use RLS to secure this as you can't allow the user to insert/update their own customer ID. This is something that you need to do in a secure server-side environment with a service role key. You need that secure server-side environment anyway for interacting with the Stripe API.

ComputerDen commented 1 year ago

You do require the service role key for managing Stripe customer IDs. You can't use RLS to secure this as you can't allow the user to insert/update their own customer ID. This is something that you need to do in a secure server-side environment with a service role key. You need that secure server-side environment anyway for interacting with the Stripe API.

that makes sense thankyou, the way its currently being done with the setup through vercel. is that a secure enough environment, or should i be looking at setting up something different for the a live product? im just scared of having the service role key store anywhere and had hoped to not need it

thorwebdev commented 1 year ago

@ComputerDen by not prefixing the env var with NEXT_PUBLIC you can be assured that nextjs will not expose your secret API keys client side. This is why you never use that prefix for secret keys, e.g. see https://github.com/vercel/nextjs-subscription-payments/blob/main/.env.local.example#L4

And the nextjs docs: https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser