mrvautin / nextjs-checkout

A superfast shopping cart built with Next.js and Prisma.
https://nextjs-checkout.markmoffat.com
11 stars 9 forks source link

Integration with Square payment [Feature request] #1

Open Timee2023 opened 10 months ago

Timee2023 commented 10 months ago

Hi, feature request

Possible for you to add integration with Square payments?

Timee2023 commented 10 months ago

Hi, thanks for integrating square payments into the application:

I've been trying to run this using mongoDB. I've been able to look at prisma configuration for mongoDB and have updated it as such:

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["mongodb"]
}

datasource db {
  provider = "mongodb"
  url      = env("DATABASE_CONNECTION_STRING")
}

model orders {
  id                 String   @id @default(uuid()) @map("_id")
  created_at         DateTime @default(now())
  status             String
  cart               Json     @db.Json
  transaction_id     String?
  checkout_id        String?
  paid               Boolean?
  gateway            String
  totalUniqueItems   Int
  totalAmount        Int
  customerId         String @default(uuid())
  customer           customers? @relation(fields: [customerId], references: [id])
}

model products {
  id          String    @id @default(uuid()) @map("_id")
  created_at  DateTime? @default(now())
  name        String
  permalink   String
  summary     String
  description String
  price       Float    // Change to Float
  images      images[]
  enabled     Boolean
}

model customers {
  id         String   @id @default(uuid()) @map("_id")
  created_at DateTime @default(now())
  email      String
  phone      String
  firstName  String
  lastName   String
  address1   String
  suburb     String
  state      String
  postcode   String
  country    String
  orders     orders[]
}

model users {
  id         String   @id @default(uuid()) @map("_id")
  created_at DateTime @default(now())
  name       String
  email      String
  enabled    Boolean
}

model images {
  id         String     @id @default(uuid()) @map("_id")
  created_at DateTime   @default(now())
  url        String
  alt        String
  filename   String
  order      Int
  productId  String     @default(uuid())
  product    products?  @relation(fields: [productId], references: [id], onDelete: Cascade)
}

model discounts {
  id         String   @id @default(uuid()) @map("_id")
  created_at DateTime @default(now())
  name       String
  code       String
  type       discountType @default(amount)
  value      Float    @default(0) // Change to Float
  enabled    Boolean
  start_at   DateTime @default(now())
  end_at     DateTime @default(now())
}

enum discountType {
  amount
  percent
}

my db connection string has been updated: Github client id has been updated Github secret has been updated

DATABASE_CONNECTION_STRING=mongodb://localhost:27017/nextjs-checkout GITHUB_CLIENT_ID mygithubclientid GITHUB_SECRET mygithubsecret

I don't see anything currently in my mongodb database, additionally, I'm trying to access the admin panel/dashboard.

For my OAuth I've set up my homepage as: http://localhost:3000/ I'm not sure what should be the callback url but I put: http://localhost:3000/admin/dashboard

Once i log in with github i get redirected back to the sign in page always

Any help is appreciated thank you

mrvautin commented 10 months ago

Hi @Timee2023, thanks for the feedback on the docs. I've updated readme here: https://github.com/mrvautin/nextjs-checkout?tab=readme-ov-file#github-authentication

The doco is pretty raw. If you find anything else missing please let me know or submit a PR.

Timee2023 commented 10 months ago

Hi @Timee2023, thanks for the feedback on the docs. I've updated readme here: https://github.com/mrvautin/nextjs-checkout?tab=readme-ov-file#github-authentication

The doco is pretty raw. If you find anything else missing please let me know or submit a PR.

I wasn't necessarily able to get it working with mongoDB unfortunately. I believe the configurations I altered weren't exactly correct but I tried to follow everything that Prisma stated for mongo, using the configuration settings I mentioned above.

I decided to use instead PostreSQL, like yourself, and have successfully been able to get the database working with my configuration.

I proceeded to alter the OAuth setting with correct directories for callback URL, etc and have been able to access the Admin dashboard and Authentication works.

The AWS S3 service unfortunately is where I'm running into problems (For the photos). I did create a bucket and was able to generate an ID and Access Key. However, this didn't end up working after placing these keys/Ids in my .env files. I tried a few times with different buckets and generating new keys but wasn't successful. I do believe there's quite a few configuration settings on the S3 service that may need to be set correctly.

Thank you.

mrvautin commented 10 months ago

Hi @Timee2023 - I recall AWS being a bit touchy to setup. I've created a little guide here: https://github.com/mrvautin/nextjs-checkout?tab=readme-ov-file#images

Let me know how you go.

Timee2023 commented 10 months ago

Hi @Timee2023 - I recall AWS being a bit touchy to setup. I've created a little guide here: https://github.com/mrvautin/nextjs-checkout?tab=readme-ov-file#images

Let me know how you go.

Hey, just got back to configuring this and found a workaround:

Just configuring the .env with AWS S3 BUCKET_NAME, ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY didn't actually work.

The error I had gotten in the console was:

err Error: Region is missing

This was thrown from the config.js file within the aws-sdk:


"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NODE_REGION_CONFIG_FILE_OPTIONS = exports.NODE_REGION_CONFIG_OPTIONS = exports.REGION_INI_NAME = exports.REGION_ENV_NAME = void 0;
exports.REGION_ENV_NAME = "AWS_REGION";
exports.REGION_INI_NAME = "region";
exports.NODE_REGION_CONFIG_OPTIONS = {
    environmentVariableSelector: (env) => env[exports.REGION_ENV_NAME],
    configFileSelector: (profile) => profile[exports.REGION_INI_NAME],
    default: () => {
        throw new Error("Region is missing");
    },
};
exports.NODE_REGION_CONFIG_FILE_OPTIONS = {
    preferredFile: "credentials",

}; 

To resolve this, in the .env file I had added:

AWS_REGION=us-east-1 (My region)

This resolved the problem and allowed me to upload images successfully and they appear in my AWS S3 bucket.

Screenshotgit

My .env now contains the following AWS configurations:

AWS_REGION=my-region
AWS_S3_BUCKET_NAME=nextjs-checkout
AWS_ACCESS_KEY_ID=my-key
AWS_SECRET_ACCESS_KEY=my-key
mrvautin commented 10 months ago

Good find! I've added some code and updated the README instructions to set the region. Not sure how mine worked without setting this value.

If you find anything else, please let me know.

Timee2023 commented 10 months ago

@mrvautin There's a search issue when searching for products that actually exist, in addition, generating their appropiate image in the search result

In SearchResult.tsx located in components/SearchResults.tsx there was a runtime error:

Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading '0')

In particular, this section of code:

                 <img
                                alt={product.images[0].alt}
                                className="card-img-top"
                                height={300}
                                src={product.images[0].url}
                                style={{
                                    width: 'auto',
                                    height: 'auto',
                                }}
                                width={300}
                 />

Specifically this line:

 src={product.images[0].url}

I have rectified the issue by adding modifying the code slightly within the div of card product-card and changed it to this:


 {product.images && product.images.length > 0 && (
                            <img
                                alt={product.images[0].alt}
                                className="card-img-top"
                                height={300}
                                src={product.images[0].url}
                                style={{
                                    width: 'auto',
                                    height: 'auto',
                                }}
                                width={300}
                            />
                            )}

Upon searching further on the web it appears the mistake arises from the code because it is accessing properties of an object that's undefined or an empty array

Modifying the code does generate the appropiate search result. However, there's no image. The code provided in the SearchResults.tsx does look it's supported but I couldn't find an appropiate solution for that

image

mrvautin commented 10 months ago

@Timee2023 Good fine. I'm looking to rework and build out the tests. There are next to no tests right now.

I've fixed the issue where the images were not getting returned in the search results here.

Timee2023 commented 10 months ago

Thanks. Those were all the errors I have discovered so far.

I have made some updates to SearchResult.tsx personally for myself. Firstly, you can now click on the photos. Secondly, when you search for items, you can see the product summary and add it to your basket. Rather than clicking the product name and then adding it to the cart. I believe this mainly adds convenience.


/* eslint-disable @next/next/no-img-element */
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import Link from 'next/link';
import { useCart } from 'react-use-cart';
import { toast } from 'react-toastify';
import { currency } from '../lib/helpers';

const SearchResult = () => {
    const router = useRouter();
    const { addItem } = useCart();
    const [searchTerm, setSearchTerm] = useState<any>();
    const [searchResults, setSearchResults] = useState<any>();

    useEffect(() => {
        if (!router.isReady) {
            return;
        }

        searchProducts();
    }, [router.isReady]);

    function searchProducts() {
        const searchQuery = router.query.keyword;
        setSearchTerm(searchQuery);
        fetch('/api/search', {
            method: 'POST',
            cache: 'no-cache',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                searchTerm: searchQuery,
            }),
        })
            .then(response => response.json())
            .then(data => {
                if (data.error) {
                    toast(data.error, {
                        hideProgressBar: false,
                        autoClose: 2000,
                        type: 'error',
                    });
                    setSearchResults([]);
                    return;
                }
                setSearchResults(data);
            })
            .catch(err => {
                console.log('Payload error:' + err);
            });
    }

    if (!searchResults) {
        return <></>;
    }

    const mainImage = (product) => {
        const imgProps = {
            alt: 'product image',
            className: 'card-img-top',
            style: {
                width: '100%',
                height: '100%',
            },
        };

        if (product.images.length === 0) {
            return (
                <Link href={`/product/${product.permalink}`}>
                    <div>
                        <img {...imgProps} />
                    </div>
                </Link>
            );
        }

        return (
            <Link href={`/product/${product.permalink}`}>
                <div>
                    <img
                        {...imgProps}
                        alt={product.images[0].alt}
                        src={product.images[0].url}
                    />
                </div>
            </Link>
        );
    };

    return (
        <>
            <div className="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
                <h5>
                    Showing {searchResults.length} results for &apos;
                    {searchTerm}&apos;
                </h5>
            </div>
            <div className="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
                {searchResults.map(product => (
                    <div className="col" key={product.id}>
                        <div className="card product-card">
                            {mainImage(product)}
                            <div className="card-body">
                                <div className="card-text">
                                    <Link
                                        className="link-secondary"
                                        href={`/product/${product.permalink}`}
                                    >
                                        <h2 className="h4">{product.name}</h2>
                                    </Link>
                                    <span className="h6 text-danger">
                                        {currency(product.price / 100)}
                                    </span>
                                    <p>{product.summary}</p>
                                    <div className="d-flex justify-content-between">
                                        <div className="btn-group flex-fill">
                                            <button
                                                className="btn btn-dark"
                                                onClick={() => {
                                                    addItem(product);
                                                    toast('Cart updated', {
                                                        hideProgressBar: false,
                                                        autoClose: 2000,
                                                        type: 'success',
                                                    });
                                                }}
                                            >
                                                Add to cart
                                            </button>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                ))}
            </div>
        </>
    );
};

export default SearchResult;
mrvautin commented 10 months ago

@Timee2023 Awesome! I'd love to see the finished website when you have completed it.