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

Cannot find module @/utils/braintree/braintree or its corresponding type declarations.ts(2307) #723

Open
playfantasydraw opened this issue Aug 31, 2024 · 0 comments

Comments

@playfantasydraw
Copy link

I have an app that uses Next.js. I attached my braintree.ts file. Also attached my Signup.tsx, where I want my Braintree payment methods to be displayed. And finally I attached my route.ts. My route.ts has the issue: Cannot find module @/utils/braintree/braintree or its corresponding type declarations.ts(2307).

Can you please advise on the best steps to solve this issue?

Signup.tsx

'use client';

import Button from '@/components/ui/Button';
import React, { useEffect, useState } from 'react';
import Link from 'next/link';
import { signUp } from '@/utils/auth-helpers/server';
import { handleRequest } from '@/utils/auth-helpers/client';
import { useRouter } from 'next/navigation';
import Script from 'next/script';

interface SignUpProps {
  allowEmail: boolean;
  redirectMethod: string;
}

export default function SignUp({ allowEmail, redirectMethod }: SignUpProps) {
  const router = redirectMethod === 'client' ? useRouter() : null;
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [showBraintree, setShowBraintree] = useState(false);
  const [braintreeInstance, setBraintreeInstance] = useState<any>(null);

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setIsSubmitting(true);
    // Here, instead of calling handleRequest, we'll just show the Braintree UI
    setShowBraintree(true);
    setIsSubmitting(false);
  };

  useEffect(() => {
    if (showBraintree && (window as any).braintree) {
      (window as any).braintree.dropin.create({
        authorization: process.env.NEXT_PUBLIC_BRAINTREE_TOKENIZATION_KEY,
        container: '#dropin-container'
      }, (createErr: any, instance: any) => {
        if (createErr) {
          console.error(createErr);
          return;
        }
        setBraintreeInstance(instance);
      });
    }
  }, [showBraintree]);

  const handlePayment = async () => {
    if (!braintreeInstance) {
      console.error('Braintree instance not available');
      return;
    }

    try {
      const { nonce } = await braintreeInstance.requestPaymentMethod();
      console.log('Payment nonce:', nonce);
      // Here you would typically send the nonce to your server
      // along with the user's email and password to complete the sign-up process
      alert('Payment method successfully created! Sign-up would be completed here.');
      // After successful sign-up and payment, you might want to redirect the user
      if (router) router.push('/dashboard');
    } catch (error) {
      console.error('Payment error:', error);
      alert('Payment failed. Please try again.');
    }
  };

  return (
    <div className="my-8">
      <form
        noValidate={true}
        className="mb-4"
        onSubmit={(e) => handleSubmit(e)}
      >
        <div className="grid gap-2">
          <div className="grid gap-1">
            <label htmlFor="email">Email</label>
            <input
              id="email"
              placeholder="[email protected]"
              type="email"
              name="email"
              autoCapitalize="none"
              autoComplete="email"
              autoCorrect="off"
              className="w-full p-3 rounded-md bg-zinc-800"
            />
            <label htmlFor="password">Password</label>
            <input
              id="password"
              placeholder="Password"
              type="password"
              name="password"
              autoComplete="current-password"
              className="w-full p-3 rounded-md bg-zinc-800"
            />
          </div>
          <Button
            variant="slim"
            type="submit"
            className="mt-1"
            loading={isSubmitting}
          >
            Continue to Payment
          </Button>
        </div>
      </form>
      {showBraintree && (
        <div id="dropin-wrapper">
          <div id="dropin-container"></div>
          <Button onClick={handlePayment} variant="slim" className="mt-4">
            Complete Sign Up and Pay
          </Button>
        </div>
      )}
      <p>Already have an account?</p>
      <p>
        <Link href="/signin/password_signin" className="font-light text-sm">
          Sign in with email and password
        </Link>
      </p>
      {allowEmail && (
        <p>
          <Link href="/signin/email_signin" className="font-light text-sm">
            Sign in via magic link
          </Link>
        </p>
      )}
      <Script
        src="https://js.braintreegateway.com/web/dropin/1.33.0/js/dropin.min.js"
        strategy="lazyOnload"
      />
    </div>
  );
}

route.ts

import Stripe from 'stripe';
import { stripe } from '@/utils/stripe/config';
import {
  upsertProductRecord,
  upsertPriceRecord,
  manageSubscriptionStatusChange,
  deleteProductRecord,
  deletePriceRecord
} from '@/utils/supabase/admin';
import { gateway } from '@/utils/braintree/braintree';

const relevantEvents = new Set([
  'product.created',
  'product.updated',
  'product.deleted',
  'price.created',
  'price.updated',
  'price.deleted',
  'checkout.session.completed',
  'customer.subscription.created',
  'customer.subscription.updated',
  'customer.subscription.deleted'
]);

export async function POST(req: Request) {
  if (req.headers.get('Content-Type') === 'application/json') {
    const body = await req.json();
    const { paymentMethodNonce } = body;

    if (!paymentMethodNonce) {
      return new Response('Payment method nonce is required', { status: 400 });
    }

    try {
      const result = await gateway.transaction.sale({
        amount: '10.00',
        paymentMethodNonce: paymentMethodNonce,
        options: {
          submitForSettlement: true
        }
      });

      if (result.success) {
        return new Response(JSON.stringify({ success: true, transaction: result.transaction }), {
          status: 200,
          headers: { 'Content-Type': 'application/json' }
        });
      } else {
        return new Response(JSON.stringify({ success: false, error: result.message }), {
          status: 400,
          headers: { 'Content-Type': 'application/json' }
        });
      }
    } catch (error) {
      console.error('Braintree transaction error:', error);
      return new Response('An error occurred while processing the payment', { status: 500 });
    }
  } else {
    const body = await req.text();
    const sig = req.headers.get('stripe-signature') as string;
    const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
    let event: Stripe.Event;

    try {
      if (!sig || !webhookSecret)
        return new Response('Webhook secret not found.', { status: 400 });
      event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
      console.log(`🔔  Webhook received: ${event.type}`);
    } catch (err: any) {
      console.log(`❌ Error message: ${err.message}`);
      return new Response(`Webhook Error: ${err.message}`, { status: 400 });
    }

    if (relevantEvents.has(event.type)) {
      try {
        switch (event.type) {
          case 'product.created':
          case 'product.updated':
            await upsertProductRecord(event.data.object as Stripe.Product);
            break;
          case 'price.created':
          case 'price.updated':
            await upsertPriceRecord(event.data.object as Stripe.Price);
            break;
          case 'price.deleted':
            await deletePriceRecord(event.data.object as Stripe.Price);
            break;
          case 'product.deleted':
            await deleteProductRecord(event.data.object as Stripe.Product);
            break;
          case 'customer.subscription.created':
          case 'customer.subscription.updated':
          case 'customer.subscription.deleted':
            const subscription = event.data.object as Stripe.Subscription;
            await manageSubscriptionStatusChange(
              subscription.id,
              subscription.customer as string,
              event.type === 'customer.subscription.created'
            );
            break;
          case 'checkout.session.completed':
            const checkoutSession = event.data.object as Stripe.Checkout.Session;
            if (checkoutSession.mode === 'subscription') {
              const subscriptionId = checkoutSession.subscription;
              await manageSubscriptionStatusChange(
                subscriptionId as string,
                checkoutSession.customer as string,
                true
              );
            }
            break;
          default:
            throw new Error('Unhandled relevant event!');
        }
      } catch (error) {
        console.log(error);
        return new Response(
          'Webhook handler failed. View your Next.js function logs.',
          {
            status: 400
          }
        );
      }
    } else {
      return new Response(`Unsupported event type: ${event.type}`, {
        status: 400
      });
    }
    return new Response(JSON.stringify({ received: true }));
  }
}

braintree.ts

import braintree from 'braintree';

export const gateway = new braintree.BraintreeGateway({
  environment: braintree.Environment.Sandbox,
  merchantId: process.env.BRAINTREE_MERCHANT_ID!,
  publicKey: process.env.BRAINTREE_PUBLIC_KEY!,
  privateKey: process.env.BRAINTREE_PRIVATE_KEY!
});
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

No branches or pull requests

1 participant