mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 04:14:18 +00:00
Implements custom checkout along with ordercloud provider
This commit is contained in:
parent
a7c13d2d42
commit
30744c5a6c
@ -25,3 +25,4 @@ NEXT_PUBLIC_VENDURE_SHOP_API_URL=
|
||||
NEXT_PUBLIC_VENDURE_LOCAL_URL=
|
||||
|
||||
NEXT_PUBLIC_ORDERCLOUD_CLIENT_ID=
|
||||
STRIPE_SECRET=
|
||||
|
@ -1,4 +1,3 @@
|
||||
import cn from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import { FC } from 'react'
|
||||
import CartItem from '@components/cart/CartItem'
|
||||
@ -7,24 +6,34 @@ import { useUI } from '@components/ui/context'
|
||||
import useCart from '@framework/cart/use-cart'
|
||||
import usePrice from '@framework/product/use-price'
|
||||
import ShippingWidget from '../ShippingWidget'
|
||||
import useCheckout from '@framework/checkout/use-checkout'
|
||||
import PaymentWidget from '../PaymentWidget'
|
||||
import SidebarLayout from '@components/common/SidebarLayout'
|
||||
import s from './CheckoutSidebarView.module.css'
|
||||
|
||||
const CheckoutSidebarView: FC = () => {
|
||||
const { setSidebarView } = useUI()
|
||||
const { data } = useCart()
|
||||
const { setSidebarView, closeSidebar } = useUI()
|
||||
const { data: cartData } = useCart()
|
||||
const { data: checkoutData, submit: onCheckout } = useCheckout();
|
||||
|
||||
async function handleSubmit(event: React.ChangeEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
|
||||
await onCheckout();
|
||||
|
||||
closeSidebar();
|
||||
}
|
||||
|
||||
const { price: subTotal } = usePrice(
|
||||
data && {
|
||||
amount: Number(data.subtotalPrice),
|
||||
currencyCode: data.currency.code,
|
||||
cartData && {
|
||||
amount: Number(cartData.subtotalPrice),
|
||||
currencyCode: cartData.currency.code,
|
||||
}
|
||||
)
|
||||
const { price: total } = usePrice(
|
||||
data && {
|
||||
amount: Number(data.totalPrice),
|
||||
currencyCode: data.currency.code,
|
||||
cartData && {
|
||||
amount: Number(cartData.totalPrice),
|
||||
currencyCode: cartData.currency.code,
|
||||
}
|
||||
)
|
||||
|
||||
@ -38,22 +47,22 @@ const CheckoutSidebarView: FC = () => {
|
||||
<Text variant="sectionHeading">Checkout</Text>
|
||||
</Link>
|
||||
|
||||
<PaymentWidget onClick={() => setSidebarView('PAYMENT_VIEW')} />
|
||||
<ShippingWidget onClick={() => setSidebarView('SHIPPING_VIEW')} />
|
||||
<PaymentWidget isValid={checkoutData?.hasPayment} onClick={() => setSidebarView('PAYMENT_VIEW')} />
|
||||
<ShippingWidget isValid={checkoutData?.hasShipping} onClick={() => setSidebarView('SHIPPING_VIEW')} />
|
||||
|
||||
<ul className={s.lineItemsList}>
|
||||
{data!.lineItems.map((item: any) => (
|
||||
{cartData!.lineItems.map((item: any) => (
|
||||
<CartItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
currencyCode={data!.currency.code}
|
||||
currencyCode={cartData!.currency.code}
|
||||
variant="display"
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex-shrink-0 px-6 py-6 sm:px-6 sticky z-20 bottom-0 w-full right-0 left-0 bg-accent-0 border-t text-sm">
|
||||
<form onSubmit={handleSubmit} className="flex-shrink-0 px-6 py-6 sm:px-6 sticky z-20 bottom-0 w-full right-0 left-0 bg-accent-0 border-t text-sm">
|
||||
<ul className="pb-2">
|
||||
<li className="flex justify-between py-1">
|
||||
<span>Subtotal</span>
|
||||
@ -74,14 +83,11 @@ const CheckoutSidebarView: FC = () => {
|
||||
</div>
|
||||
<div>
|
||||
{/* Once data is correcly filled */}
|
||||
{/* <Button Component="a" width="100%">
|
||||
Confirm Purchase
|
||||
</Button> */}
|
||||
<Button Component="a" width="100%" variant="ghost" disabled>
|
||||
Continue
|
||||
<Button type="submit" width="100%" disabled={!checkoutData?.hasPayment || !checkoutData?.hasShipping}>
|
||||
Confirm Purchase
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</SidebarLayout>
|
||||
)
|
||||
}
|
||||
|
@ -1,83 +1,123 @@
|
||||
import { FC } from 'react'
|
||||
import cn from 'classnames'
|
||||
|
||||
import useCheckout from '@framework/checkout/use-checkout'
|
||||
import { Button, Text } from '@components/ui'
|
||||
import { useUI } from '@components/ui/context'
|
||||
import s from './PaymentMethodView.module.css'
|
||||
import SidebarLayout from '@components/common/SidebarLayout'
|
||||
|
||||
import s from './PaymentMethodView.module.css'
|
||||
|
||||
interface Form extends HTMLFormElement {
|
||||
cardHolder: HTMLInputElement
|
||||
cardNumber: HTMLInputElement
|
||||
cardExpireDate: HTMLInputElement
|
||||
cardCvc: HTMLInputElement
|
||||
firstName: HTMLInputElement
|
||||
lastName: HTMLInputElement
|
||||
company: HTMLInputElement
|
||||
streetNumber: HTMLInputElement
|
||||
zipCode: HTMLInputElement
|
||||
city: HTMLInputElement
|
||||
country: HTMLSelectElement
|
||||
}
|
||||
|
||||
const PaymentMethodView: FC = () => {
|
||||
const { setSidebarView } = useUI()
|
||||
const [, {addPayment}] = useCheckout()
|
||||
|
||||
async function handleSubmit(event: React.ChangeEvent<Form>) {
|
||||
event.preventDefault();
|
||||
|
||||
await addPayment({
|
||||
cardHolder: event.target.cardHolder.value,
|
||||
cardNumber: event.target.cardNumber.value,
|
||||
cardExpireDate: event.target.cardExpireDate.value,
|
||||
cardCvc: event.target.cardCvc.value,
|
||||
firstName: event.target.firstName.value,
|
||||
lastName: event.target.lastName.value,
|
||||
company: event.target.company.value,
|
||||
streetNumber: event.target.streetNumber.value,
|
||||
zipCode: event.target.zipCode.value,
|
||||
city: event.target.city.value,
|
||||
country: event.target.country.value
|
||||
});
|
||||
|
||||
setSidebarView('CHECKOUT_VIEW')
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarLayout handleBack={() => setSidebarView('CHECKOUT_VIEW')}>
|
||||
<div className="px-4 sm:px-6 flex-1">
|
||||
<Text variant="sectionHeading"> Payment Method</Text>
|
||||
<div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Cardholder Name</label>
|
||||
<input className={s.input} />
|
||||
</div>
|
||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||
<div className={cn(s.fieldset, 'col-span-7')}>
|
||||
<label className={s.label}>Card Number</label>
|
||||
<input className={s.input} />
|
||||
<form className="h-full" onSubmit={handleSubmit}>
|
||||
<SidebarLayout handleBack={() => setSidebarView('CHECKOUT_VIEW')}>
|
||||
<div className="px-4 sm:px-6 flex-1">
|
||||
<Text variant="sectionHeading"> Payment Method</Text>
|
||||
<div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Cardholder Name</label>
|
||||
<input name="cardHolder" className={s.input} />
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-3')}>
|
||||
<label className={s.label}>Expires</label>
|
||||
<input className={s.input} placeholder="MM/YY" />
|
||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||
<div className={cn(s.fieldset, 'col-span-7')}>
|
||||
<label className={s.label}>Card Number</label>
|
||||
<input name="cardNumber" className={s.input} />
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-3')}>
|
||||
<label className={s.label}>Expires</label>
|
||||
<input name="cardExpireDate" className={s.input} placeholder="MM/YY" />
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-2')}>
|
||||
<label className={s.label}>CVC</label>
|
||||
<input name="cardCvc" className={s.input} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-2')}>
|
||||
<label className={s.label}>CVC</label>
|
||||
<input className={s.input} />
|
||||
<hr className="border-accent-2 my-6" />
|
||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>First Name</label>
|
||||
<input name="firstName" className={s.input} />
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>Last Name</label>
|
||||
<input name="lastName" className={s.input} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="border-accent-2 my-6" />
|
||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>First Name</label>
|
||||
<input className={s.input} />
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Company (Optional)</label>
|
||||
<input name="company" className={s.input} />
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>Last Name</label>
|
||||
<input className={s.input} />
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Street and House Number</label>
|
||||
<input name="streetNumber" className={s.input} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Company (Optional)</label>
|
||||
<input className={s.input} />
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Street and House Number</label>
|
||||
<input className={s.input} />
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Apartment, Suite, Etc. (Optional)</label>
|
||||
<input className={s.input} />
|
||||
</div>
|
||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>Postal Code</label>
|
||||
<input className={s.input} />
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Apartment, Suite, Etc. (Optional)</label>
|
||||
<input className={s.input} name="apartment" />
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>City</label>
|
||||
<input className={s.input} />
|
||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>Postal Code</label>
|
||||
<input name="zipCode" className={s.input} />
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>City</label>
|
||||
<input name="city" className={s.input} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Country/Region</label>
|
||||
<select name="country" className={s.select}>
|
||||
<option>Hong Kong</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Country/Region</label>
|
||||
<select className={s.select}>
|
||||
<option>Hong Kong</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sticky z-20 bottom-0 w-full right-0 left-0 py-12 bg-accent-0 border-t border-accent-2 px-6">
|
||||
<Button Component="a" width="100%" variant="ghost">
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</SidebarLayout>
|
||||
<div className="sticky z-20 bottom-0 w-full right-0 left-0 py-12 bg-accent-0 border-t border-accent-2 px-6">
|
||||
<Button type="submit" width="100%" variant="ghost">
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</SidebarLayout>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { FC } from 'react'
|
||||
import s from './PaymentWidget.module.css'
|
||||
import { ChevronRight, CreditCard } from '@components/icons'
|
||||
import { ChevronRight, CreditCard, Check } from '@components/icons'
|
||||
|
||||
interface ComponentProps {
|
||||
onClick?: () => any
|
||||
onClick?: () => any;
|
||||
isValid?: boolean;
|
||||
}
|
||||
|
||||
const PaymentWidget: FC<ComponentProps> = ({ onClick }) => {
|
||||
/* Shipping Address
|
||||
Only available with checkout set to true -
|
||||
const PaymentWidget: FC<ComponentProps> = ({ onClick, isValid }) => {
|
||||
/* Shipping Address
|
||||
Only available with checkout set to true -
|
||||
This means that the provider does offer checkout functionality. */
|
||||
return (
|
||||
<div onClick={onClick} className={s.root}>
|
||||
@ -20,7 +21,7 @@ const PaymentWidget: FC<ComponentProps> = ({ onClick }) => {
|
||||
{/* <span>VISA #### #### #### 2345</span> */}
|
||||
</div>
|
||||
<div>
|
||||
<ChevronRight />
|
||||
{isValid ? <Check /> : <ChevronRight />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,77 +1,115 @@
|
||||
import { FC } from 'react'
|
||||
import cn from 'classnames'
|
||||
import s from './ShippingView.module.css'
|
||||
|
||||
import Button from '@components/ui/Button'
|
||||
import { useUI } from '@components/ui/context'
|
||||
import SidebarLayout from '@components/common/SidebarLayout'
|
||||
import useCheckout from '@framework/checkout/use-checkout'
|
||||
|
||||
import s from './ShippingView.module.css'
|
||||
|
||||
interface Form extends HTMLFormElement {
|
||||
cardHolder: HTMLInputElement
|
||||
cardNumber: HTMLInputElement
|
||||
cardExpireDate: HTMLInputElement
|
||||
cardCvc: HTMLInputElement
|
||||
firstName: HTMLInputElement
|
||||
lastName: HTMLInputElement
|
||||
company: HTMLInputElement
|
||||
streetNumber: HTMLInputElement
|
||||
zipCode: HTMLInputElement
|
||||
city: HTMLInputElement
|
||||
country: HTMLSelectElement
|
||||
}
|
||||
|
||||
const PaymentMethodView: FC = () => {
|
||||
const { setSidebarView } = useUI()
|
||||
const [, {addAddress}] = useCheckout()
|
||||
|
||||
async function handleSubmit(event: React.ChangeEvent<Form>) {
|
||||
event.preventDefault();
|
||||
|
||||
await addAddress({
|
||||
type: event.target.type.value,
|
||||
firstName: event.target.firstName.value,
|
||||
lastName: event.target.lastName.value,
|
||||
company: event.target.company.value,
|
||||
streetNumber: event.target.streetNumber.value,
|
||||
apartments: event.target.streetNumber.value,
|
||||
zipCode: event.target.zipCode.value,
|
||||
city: event.target.city.value,
|
||||
country: event.target.country.value
|
||||
});
|
||||
|
||||
setSidebarView('CHECKOUT_VIEW')
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarLayout handleBack={() => setSidebarView('CHECKOUT_VIEW')}>
|
||||
<div className="px-4 sm:px-6 flex-1">
|
||||
<h2 className="pt-1 pb-8 text-2xl font-semibold tracking-wide cursor-pointer inline-block">
|
||||
Shipping
|
||||
</h2>
|
||||
<div>
|
||||
<div className="flex flex-row my-3 items-center">
|
||||
<input className={s.radio} type="radio" />
|
||||
<span className="ml-3 text-sm">Same as billing address</span>
|
||||
</div>
|
||||
<div className="flex flex-row my-3 items-center">
|
||||
<input className={s.radio} type="radio" />
|
||||
<span className="ml-3 text-sm">
|
||||
Use a different shipping address
|
||||
</span>
|
||||
</div>
|
||||
<hr className="border-accent-2 my-6" />
|
||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>First Name</label>
|
||||
<input className={s.input} />
|
||||
<form className="h-full" onSubmit={handleSubmit}>
|
||||
<SidebarLayout handleBack={() => setSidebarView('CHECKOUT_VIEW')}>
|
||||
<div className="px-4 sm:px-6 flex-1">
|
||||
<h2 className="pt-1 pb-8 text-2xl font-semibold tracking-wide cursor-pointer inline-block">
|
||||
Shipping
|
||||
</h2>
|
||||
<div>
|
||||
<div className="flex flex-row my-3 items-center">
|
||||
<input name="type" className={s.radio} type="radio" />
|
||||
<span className="ml-3 text-sm">Same as billing address</span>
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>Last Name</label>
|
||||
<input className={s.input} />
|
||||
<div className="flex flex-row my-3 items-center">
|
||||
<input name="type" className={s.radio} type="radio" />
|
||||
<span className="ml-3 text-sm">
|
||||
Use a different shipping address
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Company (Optional)</label>
|
||||
<input className={s.input} />
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Street and House Number</label>
|
||||
<input className={s.input} />
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Apartment, Suite, Etc. (Optional)</label>
|
||||
<input className={s.input} />
|
||||
</div>
|
||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>Postal Code</label>
|
||||
<input className={s.input} />
|
||||
<hr className="border-accent-2 my-6" />
|
||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>First Name</label>
|
||||
<input name="firstName" className={s.input} />
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>Last Name</label>
|
||||
<input name="lastName" className={s.input} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>City</label>
|
||||
<input className={s.input} />
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Company (Optional)</label>
|
||||
<input name="company" className={s.input} />
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Street and House Number</label>
|
||||
<input name="streetNumber" className={s.input} />
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Apartment, Suite, Etc. (Optional)</label>
|
||||
<input name="apartments" className={s.input} />
|
||||
</div>
|
||||
<div className="grid gap-3 grid-flow-row grid-cols-12">
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>Postal Code</label>
|
||||
<input name="zipCode" className={s.input} />
|
||||
</div>
|
||||
<div className={cn(s.fieldset, 'col-span-6')}>
|
||||
<label className={s.label}>City</label>
|
||||
<input name="city" className={s.input} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Country/Region</label>
|
||||
<select name="country" className={s.select}>
|
||||
<option>Hong Kong</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.fieldset}>
|
||||
<label className={s.label}>Country/Region</label>
|
||||
<select className={s.select}>
|
||||
<option>Hong Kong</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sticky z-20 bottom-0 w-full right-0 left-0 py-12 bg-accent-0 border-t border-accent-2 px-6">
|
||||
<Button Component="a" width="100%" variant="ghost">
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</SidebarLayout>
|
||||
<div className="sticky z-20 bottom-0 w-full right-0 left-0 py-12 bg-accent-0 border-t border-accent-2 px-6">
|
||||
<Button type="submit" width="100%" variant="ghost">
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</SidebarLayout>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { FC } from 'react'
|
||||
import s from './ShippingWidget.module.css'
|
||||
import { ChevronRight, MapPin } from '@components/icons'
|
||||
import { ChevronRight, MapPin, Check } from '@components/icons'
|
||||
import cn from 'classnames'
|
||||
|
||||
interface ComponentProps {
|
||||
onClick?: () => any
|
||||
isValid?: boolean;
|
||||
}
|
||||
|
||||
const ShippingWidget: FC<ComponentProps> = ({ onClick }) => {
|
||||
/* Shipping Address
|
||||
Only available with checkout set to true -
|
||||
const ShippingWidget: FC<ComponentProps> = ({ onClick, isValid }) => {
|
||||
/* Shipping Address
|
||||
Only available with checkout set to true -
|
||||
This means that the provider does offer checkout functionality. */
|
||||
return (
|
||||
<div onClick={onClick} className={s.root}>
|
||||
@ -24,7 +25,7 @@ const ShippingWidget: FC<ComponentProps> = ({ onClick }) => {
|
||||
</span> */}
|
||||
</div>
|
||||
<div>
|
||||
<ChevronRight />
|
||||
{isValid ? <Check /> : <ChevronRight />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,25 +1,39 @@
|
||||
import type { CheckoutSchema } from '../../types/checkout'
|
||||
import type { GetAPISchema } from '..'
|
||||
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
|
||||
const checkoutEndpoint: GetAPISchema<
|
||||
any,
|
||||
CheckoutSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers } = ctx
|
||||
const { req, res, handlers, config } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['checkout'],
|
||||
GET: handlers['getCheckout'],
|
||||
POST: handlers['submitCheckout'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const { cookies } = req
|
||||
const cartId = cookies[config.cartCookie]
|
||||
|
||||
try {
|
||||
const body = null
|
||||
return await handlers['checkout']({ ...ctx, body })
|
||||
// Create checkout
|
||||
if (req.method === 'GET') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['getCheckout']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Create checkout
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['submitCheckout']({ ...ctx, body })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
|
63
framework/commerce/api/endpoints/customer/address.ts
Normal file
63
framework/commerce/api/endpoints/customer/address.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import type { CustomerAddressSchema } from '../../../types/customer/address'
|
||||
import type { GetAPISchema } from '../..'
|
||||
|
||||
import { CommerceAPIError } from '../../utils/errors'
|
||||
import isAllowedOperation from '../../utils/is-allowed-operation'
|
||||
|
||||
const customerShippingEndpoint: GetAPISchema<
|
||||
any,
|
||||
CustomerAddressSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['getAddresses'],
|
||||
POST: handlers['addItem'],
|
||||
PUT: handlers['updateItem'],
|
||||
DELETE: handlers['removeItem'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const { cookies } = req
|
||||
const cartId = cookies[config.cartCookie]
|
||||
|
||||
try {
|
||||
// Return current cart info
|
||||
if (req.method === 'GET') {
|
||||
const body = { cartId }
|
||||
return await handlers['getAddresses']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Create or add an item to the cart
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['addItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Update item in cart
|
||||
if (req.method === 'PUT') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['updateItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Remove an item from the cart
|
||||
if (req.method === 'DELETE') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['removeItem']({ ...ctx, body })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
export default customerShippingEndpoint
|
63
framework/commerce/api/endpoints/customer/card.ts
Normal file
63
framework/commerce/api/endpoints/customer/card.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import type { CustomerCardSchema } from '../../../types/customer/card'
|
||||
import type { GetAPISchema } from '../..'
|
||||
|
||||
import { CommerceAPIError } from '../../utils/errors'
|
||||
import isAllowedOperation from '../../utils/is-allowed-operation'
|
||||
|
||||
const customerCardEndpoint: GetAPISchema<
|
||||
any,
|
||||
CustomerCardSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['getCards'],
|
||||
POST: handlers['addItem'],
|
||||
PUT: handlers['updateItem'],
|
||||
DELETE: handlers['removeItem'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const { cookies } = req
|
||||
const cartId = cookies[config.cartCookie]
|
||||
|
||||
try {
|
||||
// Create or add a payment
|
||||
if (req.method === 'GET') {
|
||||
const body = { ...req.body }
|
||||
return await handlers['getCards']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Create or add an item to the cart
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['addItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Update item in cart
|
||||
if (req.method === 'PUT') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['updateItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Remove an item from the cart
|
||||
if (req.method === 'DELETE') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['removeItem']({ ...ctx, body })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
|
||||
export default customerCardEndpoint
|
@ -1,7 +1,7 @@
|
||||
import type { CustomerSchema } from '../../types/customer'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
import type { CustomerSchema } from '../../../types/customer'
|
||||
import { CommerceAPIError } from '../../utils/errors'
|
||||
import isAllowedOperation from '../../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '../..'
|
||||
|
||||
const customerEndpoint: GetAPISchema<
|
||||
any,
|
@ -9,6 +9,8 @@ import type { SignupSchema } from '../types/signup'
|
||||
import type { ProductsSchema } from '../types/product'
|
||||
import type { WishlistSchema } from '../types/wishlist'
|
||||
import type { CheckoutSchema } from '../types/checkout'
|
||||
import type { CustomerCardSchema } from '../types/customer/card'
|
||||
import type { CustomerAddressSchema } from '../types/customer/address'
|
||||
import {
|
||||
defaultOperations,
|
||||
OPERATIONS,
|
||||
@ -25,6 +27,8 @@ export type APISchemas =
|
||||
| ProductsSchema
|
||||
| WishlistSchema
|
||||
| CheckoutSchema
|
||||
| CustomerCardSchema
|
||||
| CustomerAddressSchema
|
||||
|
||||
export type GetAPISchema<
|
||||
C extends CommerceAPI<any>,
|
||||
|
19
framework/commerce/checkout/use-checkout.tsx
Normal file
19
framework/commerce/checkout/use-checkout.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import useGetCheckout from "./use-get-checkout"
|
||||
import useSubmitCheckout from "./use-submit-checkout";
|
||||
import useAddPayment from "../customer/card/use-add-item"
|
||||
import useAddShipping from "../customer/address/use-add-item"
|
||||
|
||||
export type UseCheckout = any;
|
||||
|
||||
function useCheckout(): UseCheckout {
|
||||
const state = useGetCheckout()
|
||||
const actions = {
|
||||
submit: useSubmitCheckout(),
|
||||
addPayment: useAddPayment(),
|
||||
addShipping: useAddShipping()
|
||||
}
|
||||
|
||||
return {...state, ...actions}
|
||||
}
|
||||
|
||||
export default useCheckout
|
34
framework/commerce/checkout/use-get-checkout.ts
Normal file
34
framework/commerce/checkout/use-get-checkout.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import type { SWRHook, HookFetcherFn } from '../utils/types'
|
||||
import type { GetCheckoutHook } from '../types/checkout'
|
||||
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
import { useHook, useSWRHook } from '../utils/use-hook'
|
||||
import { Provider, useCommerce } from '..'
|
||||
|
||||
export type UseGetCheckout<
|
||||
H extends SWRHook<GetCheckoutHook<any>> = SWRHook<GetCheckoutHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<GetCheckoutHook> = async ({
|
||||
options,
|
||||
input: { cartId },
|
||||
fetch,
|
||||
}) => {
|
||||
return cartId ? await fetch(options) : null
|
||||
}
|
||||
|
||||
const fn = (provider: Provider) => provider.checkout?.useGetCheckout!
|
||||
|
||||
const useGetCheckout: UseGetCheckout = (input) => {
|
||||
const hook = useHook(fn)
|
||||
const { cartCookie } = useCommerce()
|
||||
const fetcherFn = hook.fetcher ?? fetcher
|
||||
const wrapper: typeof fetcher = (context) => {
|
||||
context.input.cartId = Cookies.get(cartCookie)
|
||||
return fetcherFn(context)
|
||||
}
|
||||
return useSWRHook({ ...hook, fetcher: wrapper })(input)
|
||||
}
|
||||
|
||||
export default useGetCheckout
|
21
framework/commerce/checkout/use-submit-checkout.tsx
Normal file
21
framework/commerce/checkout/use-submit-checkout.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import type { HookFetcherFn, MutationHook } from '../utils/types'
|
||||
import type { SubmitCheckoutHook } from '../types/checkout'
|
||||
import type { Provider } from '..'
|
||||
|
||||
import { useHook, useMutationHook } from '../utils/use-hook'
|
||||
import { mutationFetcher } from '../utils/default-fetcher'
|
||||
|
||||
export type UseSubmitCheckout<
|
||||
H extends MutationHook<SubmitCheckoutHook<any>> = MutationHook<SubmitCheckoutHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<SubmitCheckoutHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.checkout?.useSubmitCheckout!
|
||||
|
||||
const useSubmitCheckout: UseSubmitCheckout = (...args) => {
|
||||
const hook = useHook(fn)
|
||||
return useMutationHook({ fetcher, ...hook })(...args)
|
||||
}
|
||||
|
||||
export default useSubmitCheckout
|
21
framework/commerce/customer/address/use-add-item.tsx
Normal file
21
framework/commerce/customer/address/use-add-item.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import type { HookFetcherFn, MutationHook } from '../../utils/types'
|
||||
import type { AddItemHook } from '../../types/customer/address'
|
||||
import type { Provider } from '../..'
|
||||
|
||||
import { useHook, useMutationHook } from '../../utils/use-hook'
|
||||
import { mutationFetcher } from '../../utils/default-fetcher'
|
||||
|
||||
export type UseAddItem<
|
||||
H extends MutationHook<AddItemHook<any>> = MutationHook<AddItemHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.customer?.address?.useAddItem!
|
||||
|
||||
const useAddItem: UseAddItem = (...args) => {
|
||||
const hook = useHook(fn)
|
||||
return useMutationHook({ fetcher, ...hook })(...args)
|
||||
}
|
||||
|
||||
export default useAddItem
|
32
framework/commerce/customer/address/use-addresses.tsx
Normal file
32
framework/commerce/customer/address/use-addresses.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import Cookies from 'js-cookie'
|
||||
import { useHook, useSWRHook } from '../../utils/use-hook'
|
||||
import type { SWRHook, HookFetcherFn } from '../../utils/types'
|
||||
import type { GetAddressesHook } from '../../types/customer/address'
|
||||
import { Provider, useCommerce } from '../..'
|
||||
|
||||
export type UseAddresses<
|
||||
H extends SWRHook<GetAddressesHook<any>> = SWRHook<GetAddressesHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<GetAddressesHook> = async ({
|
||||
options,
|
||||
input: { cartId },
|
||||
fetch,
|
||||
}) => {
|
||||
return cartId ? await fetch(options) : null
|
||||
}
|
||||
|
||||
const fn = (provider: Provider) => provider.customer?.address.useAddresses!
|
||||
|
||||
const useAddresses: UseAddresses = (input) => {
|
||||
const hook = useHook(fn)
|
||||
const { cartCookie } = useCommerce()
|
||||
const fetcherFn = hook.fetcher ?? fetcher
|
||||
const wrapper: typeof fetcher = (context) => {
|
||||
context.input.cartId = Cookies.get(cartCookie)
|
||||
return fetcherFn(context)
|
||||
}
|
||||
return useSWRHook({ ...hook, fetcher: wrapper })(input)
|
||||
}
|
||||
|
||||
export default useAddresses
|
20
framework/commerce/customer/address/use-remove-item.tsx
Normal file
20
framework/commerce/customer/address/use-remove-item.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { useHook, useMutationHook } from '../../utils/use-hook'
|
||||
import { mutationFetcher } from '../../utils/default-fetcher'
|
||||
import type { HookFetcherFn, MutationHook } from '../../utils/types'
|
||||
import type { RemoveItemHook } from '../../types/customer/address'
|
||||
import type { Provider } from '../..'
|
||||
|
||||
export type UseRemoveItem<
|
||||
H extends MutationHook<RemoveItemHook<any>> = MutationHook<RemoveItemHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.customer?.address?.useRemoveItem!
|
||||
|
||||
const useRemoveItem: UseRemoveItem = (input) => {
|
||||
const hook = useHook(fn)
|
||||
return useMutationHook({ fetcher, ...hook })(input)
|
||||
}
|
||||
|
||||
export default useRemoveItem
|
20
framework/commerce/customer/address/use-update-item.tsx
Normal file
20
framework/commerce/customer/address/use-update-item.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { useHook, useMutationHook } from '../../utils/use-hook'
|
||||
import { mutationFetcher } from '../../utils/default-fetcher'
|
||||
import type { HookFetcherFn, MutationHook } from '../../utils/types'
|
||||
import type { UpdateItemHook } from '../../types/customer/address'
|
||||
import type { Provider } from '../..'
|
||||
|
||||
export type UseUpdateItem<
|
||||
H extends MutationHook<UpdateItemHook<any>> = MutationHook<UpdateItemHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider?.customer?.address?.useUpdateItem!
|
||||
|
||||
const useUpdateItem: UseUpdateItem = (input) => {
|
||||
const hook = useHook(fn)
|
||||
return useMutationHook({ fetcher, ...hook })(input)
|
||||
}
|
||||
|
||||
export default useUpdateItem
|
21
framework/commerce/customer/card/use-add-item.tsx
Normal file
21
framework/commerce/customer/card/use-add-item.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import type { HookFetcherFn, MutationHook } from '../../utils/types'
|
||||
import type { AddItemHook } from '../../types/customer/card'
|
||||
import type { Provider } from '../..'
|
||||
|
||||
import { useHook, useMutationHook } from '../../utils/use-hook'
|
||||
import { mutationFetcher } from '../../utils/default-fetcher'
|
||||
|
||||
export type UseAddItem<
|
||||
H extends MutationHook<AddItemHook<any>> = MutationHook<AddItemHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.customer?.card?.useAddItem!
|
||||
|
||||
const useAddItem: UseAddItem = (...args) => {
|
||||
const hook = useHook(fn)
|
||||
return useMutationHook({ fetcher, ...hook })(...args)
|
||||
}
|
||||
|
||||
export default useAddItem
|
32
framework/commerce/customer/card/use-cards.tsx
Normal file
32
framework/commerce/customer/card/use-cards.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import Cookies from 'js-cookie'
|
||||
import { useHook, useSWRHook } from '../../utils/use-hook'
|
||||
import type { SWRHook, HookFetcherFn } from '../../utils/types'
|
||||
import type { GetCardsHook } from '../../types/customer/card'
|
||||
import { Provider, useCommerce } from '../..'
|
||||
|
||||
export type UseCards<
|
||||
H extends SWRHook<GetCardsHook<any>> = SWRHook<GetCardsHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<GetCardsHook> = async ({
|
||||
options,
|
||||
input: { cartId },
|
||||
fetch,
|
||||
}) => {
|
||||
return cartId ? await fetch(options) : null
|
||||
}
|
||||
|
||||
const fn = (provider: Provider) => provider.customer?.card.useCards!
|
||||
|
||||
const useCards: UseCards = (input) => {
|
||||
const hook = useHook(fn)
|
||||
const { cartCookie } = useCommerce()
|
||||
const fetcherFn = hook.fetcher ?? fetcher
|
||||
const wrapper: typeof fetcher = (context) => {
|
||||
context.input.cartId = Cookies.get(cartCookie)
|
||||
return fetcherFn(context)
|
||||
}
|
||||
return useSWRHook({ ...hook, fetcher: wrapper })(input)
|
||||
}
|
||||
|
||||
export default useCards
|
20
framework/commerce/customer/card/use-remove-item.tsx
Normal file
20
framework/commerce/customer/card/use-remove-item.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { useHook, useMutationHook } from '../../utils/use-hook'
|
||||
import { mutationFetcher } from '../../utils/default-fetcher'
|
||||
import type { HookFetcherFn, MutationHook } from '../../utils/types'
|
||||
import type { RemoveItemHook } from '../../types/customer/card'
|
||||
import type { Provider } from '../..'
|
||||
|
||||
export type UseRemoveItem<
|
||||
H extends MutationHook<RemoveItemHook<any>> = MutationHook<RemoveItemHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider.customer?.card?.useRemoveItem!
|
||||
|
||||
const useRemoveItem: UseRemoveItem = (input) => {
|
||||
const hook = useHook(fn)
|
||||
return useMutationHook({ fetcher, ...hook })(input)
|
||||
}
|
||||
|
||||
export default useRemoveItem
|
20
framework/commerce/customer/card/use-update-item.tsx
Normal file
20
framework/commerce/customer/card/use-update-item.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { useHook, useMutationHook } from '../../utils/use-hook'
|
||||
import { mutationFetcher } from '../../utils/default-fetcher'
|
||||
import type { HookFetcherFn, MutationHook } from '../../utils/types'
|
||||
import type { UpdateItemHook } from '../../types/customer/card'
|
||||
import type { Provider } from '../..'
|
||||
|
||||
export type UseUpdateItem<
|
||||
H extends MutationHook<UpdateItemHook<any>> = MutationHook<UpdateItemHook>
|
||||
> = ReturnType<H['useHook']>
|
||||
|
||||
export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher
|
||||
|
||||
const fn = (provider: Provider) => provider?.customer?.card?.useUpdateItem!
|
||||
|
||||
const useUpdateItem: UseUpdateItem = (input) => {
|
||||
const hook = useHook(fn)
|
||||
return useMutationHook({ fetcher, ...hook })(input)
|
||||
}
|
||||
|
||||
export default useUpdateItem
|
@ -15,6 +15,7 @@ import type {
|
||||
Signup,
|
||||
Login,
|
||||
Logout,
|
||||
Checkout,
|
||||
} from '@commerce/types'
|
||||
|
||||
import type { Fetcher, SWRHook, MutationHook } from './utils/types'
|
||||
@ -29,6 +30,11 @@ export type Provider = CommerceConfig & {
|
||||
useUpdateItem?: MutationHook<Cart.UpdateItemHook>
|
||||
useRemoveItem?: MutationHook<Cart.RemoveItemHook>
|
||||
}
|
||||
checkout?: {
|
||||
useSubmitCheckout?: MutationHook<Checkout.SubmitCheckoutHook>
|
||||
useGetCheckout?: SWRHook<Checkout.GetCheckoutHook>
|
||||
useCheckout?: any;
|
||||
}
|
||||
wishlist?: {
|
||||
useWishlist?: SWRHook<Wishlist.GetWishlistHook>
|
||||
useAddItem?: MutationHook<Wishlist.AddItemHook>
|
||||
@ -36,6 +42,18 @@ export type Provider = CommerceConfig & {
|
||||
}
|
||||
customer?: {
|
||||
useCustomer?: SWRHook<Customer.CustomerHook>
|
||||
card: {
|
||||
useCards?: SWRHook<Customer.Card.GetCardsHook>
|
||||
useAddItem?: MutationHook<Customer.Card.AddItemHook>
|
||||
useUpdateItem?: MutationHook<Customer.Card.UpdateItemHook>
|
||||
useRemoveItem?: MutationHook<Customer.Card.RemoveItemHook>
|
||||
}
|
||||
address: {
|
||||
useAddresses?: SWRHook<Customer.Address.GetAddressesHook>
|
||||
useAddItem?: MutationHook<Customer.Address.AddItemHook>
|
||||
useUpdateItem?: MutationHook<Customer.Address.UpdateItemHook>
|
||||
useRemoveItem?: MutationHook<Customer.Address.RemoveItemHook>
|
||||
}
|
||||
}
|
||||
products?: {
|
||||
useSearch?: SWRHook<Product.SearchProductsHook>
|
||||
|
@ -1,10 +1,48 @@
|
||||
export type CheckoutSchema = {
|
||||
// Index
|
||||
export type CheckoutTypes = {
|
||||
card?: any;
|
||||
address?: any;
|
||||
checkout?: any;
|
||||
hasPayment?: boolean;
|
||||
hasShipping?: boolean;
|
||||
}
|
||||
|
||||
export type SubmitCheckoutHook<T extends CheckoutTypes = CheckoutTypes> = {
|
||||
data: T
|
||||
input?: T
|
||||
fetcherInput: T
|
||||
body: { item: T }
|
||||
actionInput: T
|
||||
}
|
||||
|
||||
export type GetCheckoutHook<T extends CheckoutTypes = CheckoutTypes> = {
|
||||
data: T['checkout'] | null
|
||||
input: {}
|
||||
fetcherInput: { cartId?: string }
|
||||
swrState: { isEmpty: boolean }
|
||||
}
|
||||
|
||||
export type CheckoutHooks<T extends CheckoutTypes = CheckoutTypes> = {
|
||||
submitCheckout: SubmitCheckoutHook<T>
|
||||
getCheckout: GetCheckoutHook<T>
|
||||
}
|
||||
|
||||
export type GetCheckoutHandler<T extends CheckoutTypes = CheckoutTypes> = GetCheckoutHook<T> & {
|
||||
body: { cartId: string }
|
||||
}
|
||||
|
||||
export type SubmitCheckoutHandler<T extends CheckoutTypes = CheckoutTypes> = SubmitCheckoutHook<T> & {
|
||||
body: { cartId: string }
|
||||
}
|
||||
|
||||
export type CheckoutHandlers<T extends CheckoutTypes = CheckoutTypes> = {
|
||||
getCheckout: GetCheckoutHandler<T>
|
||||
submitCheckout: SubmitCheckoutHandler<T>
|
||||
}
|
||||
|
||||
export type CheckoutSchema<T extends CheckoutTypes = CheckoutTypes> = {
|
||||
endpoint: {
|
||||
options: {}
|
||||
handlers: {
|
||||
checkout: {
|
||||
data: null
|
||||
}
|
||||
}
|
||||
handlers: CheckoutHandlers<T>
|
||||
}
|
||||
}
|
||||
|
93
framework/commerce/types/customer/address.ts
Normal file
93
framework/commerce/types/customer/address.ts
Normal file
@ -0,0 +1,93 @@
|
||||
export interface Address {
|
||||
id: string;
|
||||
mask: string;
|
||||
}
|
||||
|
||||
export interface AddressFields {
|
||||
type: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
company: string;
|
||||
streetNumber: string;
|
||||
apartments: string;
|
||||
zipCode: string;
|
||||
city: string;
|
||||
country: string;
|
||||
}
|
||||
|
||||
export type CustomerAddressTypes = {
|
||||
address?: Address;
|
||||
fields: AddressFields;
|
||||
}
|
||||
|
||||
export type GetAddressesHook<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
||||
data: T['address'] | null
|
||||
input: {}
|
||||
fetcherInput: { cartId?: string }
|
||||
swrState: { isEmpty: boolean }
|
||||
}
|
||||
|
||||
export type AddItemHook<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
||||
data: T['address']
|
||||
input?: T['fields']
|
||||
fetcherInput: T['fields']
|
||||
body: { item: T['fields'] }
|
||||
actionInput: T['fields']
|
||||
}
|
||||
|
||||
export type UpdateItemHook<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
||||
data: T['address'] | null
|
||||
input: { item?: T['fields']; wait?: number }
|
||||
fetcherInput: { itemId: string; item: T['fields'] }
|
||||
body: { itemId: string; item: T['fields'] }
|
||||
actionInput: T['fields'] & { id: string }
|
||||
}
|
||||
|
||||
export type RemoveItemHook<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
||||
data: T['address'] | null
|
||||
input: { item?: T['fields'] }
|
||||
fetcherInput: { itemId: string }
|
||||
body: { itemId: string }
|
||||
actionInput: { id: string }
|
||||
}
|
||||
|
||||
export type CustomerAddressHooks<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
||||
getAddresses: GetAddressesHook<T>
|
||||
addItem: AddItemHook<T>
|
||||
updateItem: UpdateItemHook<T>
|
||||
removeItem: RemoveItemHook<T>
|
||||
}
|
||||
|
||||
export type AddresssHandler<T extends CustomerAddressTypes = CustomerAddressTypes> = GetAddressesHook<T> & {
|
||||
body: { cartId?: string }
|
||||
}
|
||||
|
||||
export type AddItemHandler<T extends CustomerAddressTypes = CustomerAddressTypes> = AddItemHook<T> & {
|
||||
body: { cartId: string }
|
||||
}
|
||||
|
||||
export type UpdateItemHandler<T extends CustomerAddressTypes = CustomerAddressTypes> =
|
||||
UpdateItemHook<T> & {
|
||||
data: T['address']
|
||||
body: { cartId: string }
|
||||
}
|
||||
|
||||
export type RemoveItemHandler<T extends CustomerAddressTypes = CustomerAddressTypes> =
|
||||
RemoveItemHook<T> & {
|
||||
body: { cartId: string }
|
||||
}
|
||||
|
||||
|
||||
export type CustomerAddressHandlers<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
||||
getAddresses: GetAddressesHook<T>
|
||||
addItem: AddItemHandler<T>
|
||||
updateItem: UpdateItemHandler<T>
|
||||
removeItem: RemoveItemHandler<T>
|
||||
}
|
||||
|
||||
export type CustomerAddressSchema<T extends CustomerAddressTypes = CustomerAddressTypes> = {
|
||||
endpoint: {
|
||||
options: {}
|
||||
handlers: CustomerAddressHandlers<T>
|
||||
}
|
||||
}
|
96
framework/commerce/types/customer/card.ts
Normal file
96
framework/commerce/types/customer/card.ts
Normal file
@ -0,0 +1,96 @@
|
||||
export interface Card {
|
||||
id: string;
|
||||
mask: string;
|
||||
provider: string;
|
||||
}
|
||||
|
||||
export interface CardFields {
|
||||
cardHolder: string;
|
||||
cardNumber: string;
|
||||
cardExpireDate: string;
|
||||
cardCvc: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
company: string;
|
||||
streetNumber: string;
|
||||
zipCode: string;
|
||||
city: string;
|
||||
country: string;
|
||||
}
|
||||
|
||||
export type CustomerCardTypes = {
|
||||
card?: Card;
|
||||
fields: CardFields;
|
||||
}
|
||||
|
||||
export type GetCardsHook<T extends CustomerCardTypes = CustomerCardTypes> = {
|
||||
data: T['card'] | null
|
||||
input: {}
|
||||
fetcherInput: { cartId?: string }
|
||||
swrState: { isEmpty: boolean }
|
||||
}
|
||||
|
||||
export type AddItemHook<T extends CustomerCardTypes = CustomerCardTypes> = {
|
||||
data: T['card']
|
||||
input?: T['fields']
|
||||
fetcherInput: T['fields']
|
||||
body: { item: T['fields'] }
|
||||
actionInput: T['fields']
|
||||
}
|
||||
|
||||
export type UpdateItemHook<T extends CustomerCardTypes = CustomerCardTypes> = {
|
||||
data: T['card'] | null
|
||||
input: { item?: T['fields']; wait?: number }
|
||||
fetcherInput: { itemId: string; item: T['fields'] }
|
||||
body: { itemId: string; item: T['fields'] }
|
||||
actionInput: T['fields'] & { id: string }
|
||||
}
|
||||
|
||||
export type RemoveItemHook<T extends CustomerCardTypes = CustomerCardTypes> = {
|
||||
data: T['card'] | null
|
||||
input: { item?: T['fields'] }
|
||||
fetcherInput: { itemId: string }
|
||||
body: { itemId: string }
|
||||
actionInput: { id: string }
|
||||
}
|
||||
|
||||
export type CustomerCardHooks<T extends CustomerCardTypes = CustomerCardTypes> = {
|
||||
getCards: GetCardsHook<T>
|
||||
addItem: AddItemHook<T>
|
||||
updateItem: UpdateItemHook<T>
|
||||
removeItem: RemoveItemHook<T>
|
||||
}
|
||||
|
||||
export type CardsHandler<T extends CustomerCardTypes = CustomerCardTypes> = GetCardsHook<T> & {
|
||||
body: { cartId?: string }
|
||||
}
|
||||
|
||||
export type AddItemHandler<T extends CustomerCardTypes = CustomerCardTypes> = AddItemHook<T> & {
|
||||
body: { cartId: string }
|
||||
}
|
||||
|
||||
export type UpdateItemHandler<T extends CustomerCardTypes = CustomerCardTypes> =
|
||||
UpdateItemHook<T> & {
|
||||
data: T['card']
|
||||
body: { cartId: string }
|
||||
}
|
||||
|
||||
export type RemoveItemHandler<T extends CustomerCardTypes = CustomerCardTypes> =
|
||||
RemoveItemHook<T> & {
|
||||
body: { cartId: string }
|
||||
}
|
||||
|
||||
|
||||
export type CustomerCardHandlers<T extends CustomerCardTypes = CustomerCardTypes> = {
|
||||
getCards: GetCardsHook<T>
|
||||
addItem: AddItemHandler<T>
|
||||
updateItem: UpdateItemHandler<T>
|
||||
removeItem: RemoveItemHandler<T>
|
||||
}
|
||||
|
||||
export type CustomerCardSchema<T extends CustomerCardTypes = CustomerCardTypes> = {
|
||||
endpoint: {
|
||||
options: {}
|
||||
handlers: CustomerCardHandlers<T>
|
||||
}
|
||||
}
|
@ -1,3 +1,6 @@
|
||||
export * as Card from "./card"
|
||||
export * as Address from "./address"
|
||||
|
||||
// TODO: define this type
|
||||
export type Customer = any
|
||||
|
@ -1 +1,3 @@
|
||||
COMMERCE_PROVIDER=ordercloud
|
||||
|
||||
NEXT_PUBLIC_ORDERCLOUD_CLIENT_ID=
|
||||
|
35
framework/ordercloud/api/endpoints/checkout/get-checkout.ts
Normal file
35
framework/ordercloud/api/endpoints/checkout/get-checkout.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import type { CheckoutEndpoint } from '.'
|
||||
|
||||
const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
|
||||
res,
|
||||
body: {cartId},
|
||||
config: { restFetch },
|
||||
}) => {
|
||||
// Return an error if no item is present
|
||||
if (!cartId) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Missing cookie' }],
|
||||
})
|
||||
}
|
||||
|
||||
// Register credit card
|
||||
const payments = await restFetch('GET', `/orders/Outgoing/${cartId}/payments`).then(
|
||||
(response: {Items: unknown[]}) => response.Items
|
||||
)
|
||||
|
||||
const address = await restFetch('GET', `/orders/Outgoing/${cartId}`).then(
|
||||
(response: {ShippingAddressID: string}) => response.ShippingAddressID
|
||||
)
|
||||
|
||||
// Return cart and errors
|
||||
res.status(200).json({
|
||||
data: {
|
||||
hasPayment: payments.length > 0,
|
||||
hasShipping: Boolean(address)
|
||||
},
|
||||
errors: []
|
||||
})
|
||||
}
|
||||
|
||||
export default getCheckout
|
@ -1 +1,23 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
||||
import type { CheckoutSchema } from '../../../types/checkout'
|
||||
import type { OrdercloudAPI } from '../..'
|
||||
|
||||
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||
import checkoutEndpoint from '@commerce/api/endpoints/checkout'
|
||||
|
||||
import getCheckout from './get-checkout'
|
||||
import submitCheckout from './submit-checkout'
|
||||
|
||||
export type CheckoutAPI = GetAPISchema<OrdercloudAPI, CheckoutSchema>
|
||||
export type CheckoutEndpoint = CheckoutAPI['endpoint']
|
||||
|
||||
export const handlers: CheckoutEndpoint['handlers'] = {
|
||||
getCheckout,
|
||||
submitCheckout,
|
||||
}
|
||||
|
||||
const checkoutApi = createEndpoint<CheckoutAPI>({
|
||||
handler: checkoutEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
||||
export default checkoutApi
|
||||
|
@ -0,0 +1,23 @@
|
||||
import type { CheckoutEndpoint } from '.'
|
||||
|
||||
const submitCheckout: CheckoutEndpoint['handlers']['submitCheckout'] = async ({
|
||||
res,
|
||||
body: { cartId },
|
||||
config: { restFetch },
|
||||
}) => {
|
||||
// Return an error if no item is present
|
||||
if (!cartId) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Missing item' }],
|
||||
})
|
||||
}
|
||||
|
||||
// Submit order
|
||||
await restFetch('POST', `/orders/Outgoing/${cartId}/submit`, {})
|
||||
|
||||
// Return cart and errors
|
||||
res.status(200).json({ data: null, errors: [] })
|
||||
}
|
||||
|
||||
export default submitCheckout
|
@ -0,0 +1,49 @@
|
||||
import type { CustomerAddressEndpoint } from '.'
|
||||
|
||||
const addItem: CustomerAddressEndpoint['handlers']['addItem'] = async ({
|
||||
res,
|
||||
body: { item, cartId },
|
||||
config: { restFetch },
|
||||
}) => {
|
||||
// Return an error if no item is present
|
||||
if (!item) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Missing item' }],
|
||||
})
|
||||
}
|
||||
|
||||
// Return an error if no item is present
|
||||
if (!cartId) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Cookie not found' }],
|
||||
})
|
||||
}
|
||||
|
||||
// Register address
|
||||
const address = await restFetch('POST', `/me/addresses`, {
|
||||
"AddressName": "main address",
|
||||
"CompanyName": item.company,
|
||||
"FirstName": item.firstName,
|
||||
"LastName": item.lastName,
|
||||
"Street1": item.streetNumber,
|
||||
"Street2": item.streetNumber,
|
||||
"City": item.city,
|
||||
"State": item.city,
|
||||
"Zip": item.zipCode,
|
||||
"Country": item.country.slice(0, 2).toLowerCase(),
|
||||
"Shipping": true
|
||||
}).then(
|
||||
(response: {ID: string}) => response.ID
|
||||
)
|
||||
|
||||
// Assign address to order
|
||||
await restFetch('PATCH', `/orders/Outgoing/${cartId}`, {
|
||||
ShippingAddressID: address
|
||||
})
|
||||
|
||||
return res.status(200).json({ data: null, errors: [] })
|
||||
}
|
||||
|
||||
export default addItem
|
@ -0,0 +1,9 @@
|
||||
import type { CustomerAddressEndpoint } from '.'
|
||||
|
||||
const getCards: CustomerAddressEndpoint['handlers']['getAddresses'] = async ({
|
||||
res,
|
||||
}) => {
|
||||
return res.status(200).json({ data: null, errors: [] })
|
||||
}
|
||||
|
||||
export default getCards
|
27
framework/ordercloud/api/endpoints/customer/address/index.ts
Normal file
27
framework/ordercloud/api/endpoints/customer/address/index.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type { CustomerAddressSchema } from '../../../../types/customer/address'
|
||||
import type { OrdercloudAPI } from '../../..'
|
||||
|
||||
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||
import customerAddressEndpoint from '@commerce/api/endpoints/customer/address'
|
||||
|
||||
import getAddresses from './get-addresses'
|
||||
import addItem from './add-item'
|
||||
import updateItem from './update-item'
|
||||
import removeItem from './remove-item'
|
||||
|
||||
export type CustomerAddressAPI = GetAPISchema<OrdercloudAPI, CustomerAddressSchema>
|
||||
export type CustomerAddressEndpoint = CustomerAddressAPI['endpoint']
|
||||
|
||||
export const handlers: CustomerAddressEndpoint['handlers'] = {
|
||||
getAddresses,
|
||||
addItem,
|
||||
updateItem,
|
||||
removeItem,
|
||||
}
|
||||
|
||||
const customerAddressApi = createEndpoint<CustomerAddressAPI>({
|
||||
handler: customerAddressEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
||||
export default customerAddressApi
|
@ -0,0 +1,9 @@
|
||||
import type { CustomerAddressEndpoint } from '.'
|
||||
|
||||
const removeItem: CustomerAddressEndpoint['handlers']['removeItem'] = async ({
|
||||
res,
|
||||
}) => {
|
||||
return res.status(200).json({ data: null, errors: [] })
|
||||
}
|
||||
|
||||
export default removeItem
|
@ -0,0 +1,9 @@
|
||||
import type { CustomerAddressEndpoint } from '.'
|
||||
|
||||
const updateItem: CustomerAddressEndpoint['handlers']['updateItem'] = async ({
|
||||
res,
|
||||
}) => {
|
||||
return res.status(200).json({ data: null, errors: [] })
|
||||
}
|
||||
|
||||
export default updateItem
|
61
framework/ordercloud/api/endpoints/customer/card/add-item.ts
Normal file
61
framework/ordercloud/api/endpoints/customer/card/add-item.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import type { CustomerCardEndpoint } from '.'
|
||||
import type { OredercloudCreditCard } from '../../../../types/customer/card'
|
||||
|
||||
import Stripe from "stripe"
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET as string, {
|
||||
apiVersion: "2020-08-27"
|
||||
})
|
||||
|
||||
const addItem: CustomerCardEndpoint['handlers']['addItem'] = async ({
|
||||
res,
|
||||
body: { item, cartId },
|
||||
config: { restFetch },
|
||||
}) => {
|
||||
// Return an error if no item is present
|
||||
if (!item) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Missing item' }],
|
||||
})
|
||||
}
|
||||
|
||||
// Return an error if no item is present
|
||||
if (!cartId) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Cookie not found' }],
|
||||
})
|
||||
}
|
||||
|
||||
// Get token
|
||||
const token = await stripe.tokens.create({
|
||||
card: {
|
||||
number: item.cardNumber,
|
||||
exp_month: item.cardExpireDate.split('/')[0],
|
||||
exp_year: item.cardExpireDate.split('/')[1],
|
||||
cvc: item.cardCvc
|
||||
}
|
||||
}).then((res: {id: string}) => res.id)
|
||||
|
||||
// Register credit card
|
||||
const creditCard = await restFetch('POST', `/me/creditcards`, {
|
||||
"Token": token,
|
||||
"CardType": "credit",
|
||||
"PartialAccountNumber": item.cardNumber.slice(-4),
|
||||
"CardholderName": item.cardHolder,
|
||||
"ExpirationDate": item.cardExpireDate
|
||||
}).then(
|
||||
(response: OredercloudCreditCard) => response.ID
|
||||
)
|
||||
|
||||
// Assign payment to order
|
||||
await restFetch('POST', `/orders/Outgoing/${cartId}/payments`, {
|
||||
"Type": "CreditCard",
|
||||
CreditCardID: creditCard
|
||||
})
|
||||
|
||||
return res.status(200).json({ data: null, errors: [] })
|
||||
}
|
||||
|
||||
export default addItem
|
@ -0,0 +1,9 @@
|
||||
import type { CustomerCardEndpoint } from '.'
|
||||
|
||||
const getCards: CustomerCardEndpoint['handlers']['getCards'] = async ({
|
||||
res,
|
||||
}) => {
|
||||
return res.status(200).json({ data: null, errors: [] })
|
||||
}
|
||||
|
||||
export default getCards
|
27
framework/ordercloud/api/endpoints/customer/card/index.ts
Normal file
27
framework/ordercloud/api/endpoints/customer/card/index.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type { CustomerCardSchema } from '../../../../types/customer/card'
|
||||
import type { OrdercloudAPI } from '../../..'
|
||||
|
||||
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||
import customerCardEndpoint from '@commerce/api/endpoints/customer/card'
|
||||
|
||||
import getCards from './get-cards'
|
||||
import addItem from './add-item'
|
||||
import updateItem from './update-item'
|
||||
import removeItem from './remove-item'
|
||||
|
||||
export type CustomerCardAPI = GetAPISchema<OrdercloudAPI, CustomerCardSchema>
|
||||
export type CustomerCardEndpoint = CustomerCardAPI['endpoint']
|
||||
|
||||
export const handlers: CustomerCardEndpoint['handlers'] = {
|
||||
getCards,
|
||||
addItem,
|
||||
updateItem,
|
||||
removeItem,
|
||||
}
|
||||
|
||||
const customerCardApi = createEndpoint<CustomerCardAPI>({
|
||||
handler: customerCardEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
||||
export default customerCardApi
|
@ -0,0 +1,9 @@
|
||||
import type { CustomerCardEndpoint } from '.'
|
||||
|
||||
const removeItem: CustomerCardEndpoint['handlers']['removeItem'] = async ({
|
||||
res,
|
||||
}) => {
|
||||
return res.status(200).json({ data: null, errors: [] })
|
||||
}
|
||||
|
||||
export default removeItem
|
@ -0,0 +1,9 @@
|
||||
import type { CustomerCardEndpoint } from '.'
|
||||
|
||||
const updateItem: CustomerCardEndpoint['handlers']['updateItem'] = async ({
|
||||
res,
|
||||
}) => {
|
||||
return res.status(200).json({ data: null, errors: [] })
|
||||
}
|
||||
|
||||
export default updateItem
|
@ -94,6 +94,8 @@ export async function fetchData<T>(
|
||||
return fetchData(opts, retries + 1)
|
||||
}
|
||||
|
||||
console.log('dataResponse.text:', await dataResponse.textConverted());
|
||||
|
||||
// Get the body of it
|
||||
const error = await dataResponse.json()
|
||||
|
||||
|
2
framework/ordercloud/checkout/index.ts
Normal file
2
framework/ordercloud/checkout/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as useCheckout } from './use-submit-checkout'
|
||||
export { default as useGetCheckout } from './use-get-checkout'
|
6
framework/ordercloud/checkout/use-checkout.tsx
Normal file
6
framework/ordercloud/checkout/use-checkout.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import useCheckout, { UseCheckout } from '@commerce/checkout/use-checkout'
|
||||
|
||||
export default useCheckout as UseCheckout
|
||||
|
||||
export const handler = useCheckout
|
||||
|
33
framework/ordercloud/checkout/use-get-checkout.tsx
Normal file
33
framework/ordercloud/checkout/use-get-checkout.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import type { GetCheckoutHook } from '@commerce/types/checkout'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { SWRHook } from '@commerce/utils/types'
|
||||
import useGetCheckout, { UseGetCheckout } from '@commerce/checkout/use-get-checkout'
|
||||
|
||||
export default useGetCheckout as UseGetCheckout<typeof handler>
|
||||
|
||||
export const handler: SWRHook<GetCheckoutHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/checkout',
|
||||
method: 'GET',
|
||||
},
|
||||
useHook: ({ useData }) =>
|
||||
function useHook(input) {
|
||||
const response = useData({
|
||||
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||
})
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
Object.create(response, {
|
||||
isEmpty: {
|
||||
get() {
|
||||
return (response.data?.lineItems?.length ?? 0) <= 0
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
}),
|
||||
[response]
|
||||
)
|
||||
},
|
||||
}
|
36
framework/ordercloud/checkout/use-submit-checkout.tsx
Normal file
36
framework/ordercloud/checkout/use-submit-checkout.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import type { SubmitCheckoutHook } from '@commerce/types/checkout'
|
||||
import type { MutationHook } from '@commerce/utils/types'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import useSubmitCheckout, { UseSubmitCheckout } from '@commerce/checkout/use-submit-checkout'
|
||||
|
||||
export default useSubmitCheckout as UseSubmitCheckout<typeof handler>
|
||||
|
||||
export const handler: MutationHook<SubmitCheckoutHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/checkout',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
// @TODO: Make form validations in here, import generic error like import { CommerceError } from '@commerce/utils/errors'
|
||||
// Get payment and delivery information in here
|
||||
|
||||
const data = await fetch({
|
||||
...options,
|
||||
body: { item },
|
||||
})
|
||||
|
||||
return data
|
||||
},
|
||||
useHook: ({ fetch }) =>
|
||||
function useHook() {
|
||||
return useCallback(
|
||||
async function onSubmitCheckout(input) {
|
||||
const data = await fetch({ input })
|
||||
|
||||
return data
|
||||
},
|
||||
[fetch]
|
||||
)
|
||||
},
|
||||
}
|
@ -5,6 +5,6 @@
|
||||
"cart": true,
|
||||
"search": false,
|
||||
"customerAuth": false,
|
||||
"customCheckout": false
|
||||
"customCheckout": true
|
||||
}
|
||||
}
|
||||
|
4
framework/ordercloud/customer/address/index.ts
Normal file
4
framework/ordercloud/customer/address/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { default as useAddresses } from './use-addresses'
|
||||
export { default as useAddItem } from './use-add-item'
|
||||
export { default as useRemoveItem } from './use-remove-item'
|
||||
export { default as useUpdateItem } from './use-update-item'
|
48
framework/ordercloud/customer/address/use-add-item.tsx
Normal file
48
framework/ordercloud/customer/address/use-add-item.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import type { AddItemHook } from '@commerce/types/customer/address'
|
||||
import type { MutationHook } from '@commerce/utils/types'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import { CommerceError } from '@commerce/utils/errors'
|
||||
import useAddItem, { UseAddItem } from '@commerce/customer/address/use-add-item'
|
||||
import useAddresses from './use-addresses'
|
||||
|
||||
export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/address',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
if (
|
||||
item.quantity &&
|
||||
(!Number.isInteger(item.quantity) || item.quantity! < 1)
|
||||
) {
|
||||
throw new CommerceError({
|
||||
message: 'The item quantity has to be a valid integer greater than 0',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({
|
||||
...options,
|
||||
body: { item },
|
||||
})
|
||||
|
||||
return data
|
||||
},
|
||||
useHook: ({ fetch }) =>
|
||||
function useHook() {
|
||||
const { mutate } = useAddresses()
|
||||
|
||||
return useCallback(
|
||||
async function addItem(input) {
|
||||
const data = await fetch({ input })
|
||||
|
||||
await mutate(data, false)
|
||||
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
33
framework/ordercloud/customer/address/use-addresses.tsx
Normal file
33
framework/ordercloud/customer/address/use-addresses.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import type { GetAddressesHook } from '@commerce/types/customer/address'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { SWRHook } from '@commerce/utils/types'
|
||||
import useAddresses, { UseAddresses } from '@commerce/customer/address/use-addresses'
|
||||
|
||||
export default useAddresses as UseAddresses<typeof handler>
|
||||
|
||||
export const handler: SWRHook<GetAddressesHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/address',
|
||||
method: 'GET',
|
||||
},
|
||||
useHook: ({ useData }) =>
|
||||
function useHook(input) {
|
||||
const response = useData({
|
||||
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||
})
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
Object.create(response, {
|
||||
isEmpty: {
|
||||
get() {
|
||||
return (response.data?.lineItems?.length ?? 0) <= 0
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
}),
|
||||
[response]
|
||||
)
|
||||
},
|
||||
}
|
60
framework/ordercloud/customer/address/use-remove-item.tsx
Normal file
60
framework/ordercloud/customer/address/use-remove-item.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import type {
|
||||
MutationHookContext,
|
||||
HookFetcherContext,
|
||||
} from '@commerce/utils/types'
|
||||
import type { Cart, LineItem, RemoveItemHook } from '@commerce/types/cart'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { ValidationError } from '@commerce/utils/errors'
|
||||
import useRemoveItem, { UseRemoveItem } from '@commerce/customer/address/use-remove-item'
|
||||
|
||||
import useAddresses from './use-addresses'
|
||||
|
||||
export type RemoveItemFn<T = any> = T extends LineItem
|
||||
? (input?: RemoveItemActionInput<T>) => Promise<Cart | null | undefined>
|
||||
: (input: RemoveItemActionInput<T>) => Promise<Cart | null>
|
||||
|
||||
export type RemoveItemActionInput<T = any> = T extends LineItem
|
||||
? Partial<RemoveItemHook['actionInput']>
|
||||
: RemoveItemHook['actionInput']
|
||||
|
||||
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/address',
|
||||
method: 'DELETE',
|
||||
},
|
||||
async fetcher({
|
||||
input: { itemId },
|
||||
options,
|
||||
fetch,
|
||||
}: HookFetcherContext<RemoveItemHook>) {
|
||||
return await fetch({ ...options, body: { itemId } })
|
||||
},
|
||||
useHook: ({ fetch }: MutationHookContext<RemoveItemHook>) =>
|
||||
function useHook<T extends LineItem | undefined = undefined>(
|
||||
ctx: { item?: T } = {}
|
||||
) {
|
||||
const { item } = ctx
|
||||
const { mutate } = useAddresses()
|
||||
const removeItem: RemoveItemFn<LineItem> = async (input) => {
|
||||
const itemId = input?.id ?? item?.id
|
||||
|
||||
if (!itemId) {
|
||||
throw new ValidationError({
|
||||
message: 'Invalid input used for this operation',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({ input: { itemId } })
|
||||
|
||||
await mutate(data, false)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
|
||||
},
|
||||
}
|
93
framework/ordercloud/customer/address/use-update-item.tsx
Normal file
93
framework/ordercloud/customer/address/use-update-item.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import type {
|
||||
HookFetcherContext,
|
||||
MutationHookContext,
|
||||
} from '@commerce/utils/types'
|
||||
import type { UpdateItemHook, LineItem } from '@commerce/types/cart'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import { MutationHook } from '@commerce/utils/types'
|
||||
import { ValidationError } from '@commerce/utils/errors'
|
||||
import useUpdateItem, { UseUpdateItem } from '@commerce/customer/address/use-update-item'
|
||||
|
||||
import { handler as removeItemHandler } from './use-remove-item'
|
||||
import useAddresses from './use-addresses'
|
||||
|
||||
export type UpdateItemActionInput<T = any> = T extends LineItem
|
||||
? Partial<UpdateItemHook['actionInput']>
|
||||
: UpdateItemHook['actionInput']
|
||||
|
||||
export default useUpdateItem as UseUpdateItem<any>
|
||||
|
||||
export const handler: MutationHook<any> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/address',
|
||||
method: 'PUT',
|
||||
},
|
||||
async fetcher({
|
||||
input: { itemId, item },
|
||||
options,
|
||||
fetch,
|
||||
}: HookFetcherContext<UpdateItemHook>) {
|
||||
if (Number.isInteger(item.quantity)) {
|
||||
// Also allow the update hook to remove an item if the quantity is lower than 1
|
||||
if (item.quantity! < 1) {
|
||||
return removeItemHandler.fetcher({
|
||||
options: removeItemHandler.fetchOptions,
|
||||
input: { itemId },
|
||||
fetch,
|
||||
})
|
||||
}
|
||||
} else if (item.quantity) {
|
||||
throw new ValidationError({
|
||||
message: 'The item quantity has to be a valid integer',
|
||||
})
|
||||
}
|
||||
|
||||
return await fetch({
|
||||
...options,
|
||||
body: { itemId, item },
|
||||
})
|
||||
},
|
||||
useHook: ({ fetch }: MutationHookContext<UpdateItemHook>) =>
|
||||
function useHook<T extends LineItem | undefined = undefined>(
|
||||
ctx: {
|
||||
item?: T
|
||||
wait?: number
|
||||
} = {}
|
||||
) {
|
||||
const { item } = ctx
|
||||
const { mutate } = useAddresses() as any
|
||||
|
||||
return useCallback(
|
||||
debounce(async (input: UpdateItemActionInput<T>) => {
|
||||
const itemId = input.id ?? item?.id
|
||||
const productId = input.productId ?? item?.productId
|
||||
const variantId = input.productId ?? item?.variantId
|
||||
|
||||
if (!itemId || !productId) {
|
||||
throw new ValidationError({
|
||||
message: 'Invalid input used for this operation',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({
|
||||
input: {
|
||||
itemId,
|
||||
item: {
|
||||
productId,
|
||||
variantId: variantId || '',
|
||||
quantity: input.quantity,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await mutate(data, false)
|
||||
|
||||
return data
|
||||
}, ctx.wait ?? 500),
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
4
framework/ordercloud/customer/card/index.ts
Normal file
4
framework/ordercloud/customer/card/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { default as useCards } from './use-cards'
|
||||
export { default as useAddItem } from './use-add-item'
|
||||
export { default as useRemoveItem } from './use-remove-item'
|
||||
export { default as useUpdateItem } from './use-update-item'
|
48
framework/ordercloud/customer/card/use-add-item.tsx
Normal file
48
framework/ordercloud/customer/card/use-add-item.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import type { AddItemHook } from '@commerce/types/customer/card'
|
||||
import type { MutationHook } from '@commerce/utils/types'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import { CommerceError } from '@commerce/utils/errors'
|
||||
import useAddItem, { UseAddItem } from '@commerce/customer/card/use-add-item'
|
||||
import useCards from './use-cards'
|
||||
|
||||
export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/card',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
if (
|
||||
item.quantity &&
|
||||
(!Number.isInteger(item.quantity) || item.quantity! < 1)
|
||||
) {
|
||||
throw new CommerceError({
|
||||
message: 'The item quantity has to be a valid integer greater than 0',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({
|
||||
...options,
|
||||
body: { item },
|
||||
})
|
||||
|
||||
return data
|
||||
},
|
||||
useHook: ({ fetch }) =>
|
||||
function useHook() {
|
||||
const { mutate } = useCards()
|
||||
|
||||
return useCallback(
|
||||
async function addItem(input) {
|
||||
const data = await fetch({ input })
|
||||
|
||||
await mutate(data, false)
|
||||
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
33
framework/ordercloud/customer/card/use-cards.tsx
Normal file
33
framework/ordercloud/customer/card/use-cards.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import type { GetCardsHook } from '@commerce/types/customer/card'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { SWRHook } from '@commerce/utils/types'
|
||||
import useCard, { UseCards } from '@commerce/customer/card/use-cards'
|
||||
|
||||
export default useCard as UseCards<typeof handler>
|
||||
|
||||
export const handler: SWRHook<GetCardsHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/card',
|
||||
method: 'GET',
|
||||
},
|
||||
useHook: ({ useData }) =>
|
||||
function useHook(input) {
|
||||
const response = useData({
|
||||
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||
})
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
Object.create(response, {
|
||||
isEmpty: {
|
||||
get() {
|
||||
return (response.data?.lineItems?.length ?? 0) <= 0
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
}),
|
||||
[response]
|
||||
)
|
||||
},
|
||||
}
|
60
framework/ordercloud/customer/card/use-remove-item.tsx
Normal file
60
framework/ordercloud/customer/card/use-remove-item.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import type {
|
||||
MutationHookContext,
|
||||
HookFetcherContext,
|
||||
} from '@commerce/utils/types'
|
||||
import type { Cart, LineItem, RemoveItemHook } from '@commerce/types/customer/card'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { ValidationError } from '@commerce/utils/errors'
|
||||
import useRemoveItem, { UseRemoveItem } from '@commerce/customer/card/use-remove-item'
|
||||
|
||||
import useCards from './use-cards'
|
||||
|
||||
export type RemoveItemFn<T = any> = T extends LineItem
|
||||
? (input?: RemoveItemActionInput<T>) => Promise<Cart | null | undefined>
|
||||
: (input: RemoveItemActionInput<T>) => Promise<Cart | null>
|
||||
|
||||
export type RemoveItemActionInput<T = any> = T extends LineItem
|
||||
? Partial<RemoveItemHook['actionInput']>
|
||||
: RemoveItemHook['actionInput']
|
||||
|
||||
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/card',
|
||||
method: 'DELETE',
|
||||
},
|
||||
async fetcher({
|
||||
input: { itemId },
|
||||
options,
|
||||
fetch,
|
||||
}: HookFetcherContext<RemoveItemHook>) {
|
||||
return await fetch({ ...options, body: { itemId } })
|
||||
},
|
||||
useHook: ({ fetch }: MutationHookContext<RemoveItemHook>) =>
|
||||
function useHook<T extends LineItem | undefined = undefined>(
|
||||
ctx: { item?: T } = {}
|
||||
) {
|
||||
const { item } = ctx
|
||||
const { mutate } = useCards()
|
||||
const removeItem: RemoveItemFn<LineItem> = async (input) => {
|
||||
const itemId = input?.id ?? item?.id
|
||||
|
||||
if (!itemId) {
|
||||
throw new ValidationError({
|
||||
message: 'Invalid input used for this operation',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({ input: { itemId } })
|
||||
|
||||
await mutate(data, false)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
|
||||
},
|
||||
}
|
93
framework/ordercloud/customer/card/use-update-item.tsx
Normal file
93
framework/ordercloud/customer/card/use-update-item.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import type {
|
||||
HookFetcherContext,
|
||||
MutationHookContext,
|
||||
} from '@commerce/utils/types'
|
||||
import type { UpdateItemHook, LineItem } from '@commerce/types/customer/card'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import { MutationHook } from '@commerce/utils/types'
|
||||
import { ValidationError } from '@commerce/utils/errors'
|
||||
import useUpdateItem, { UseUpdateItem } from '@commerce/customer/card/use-update-item'
|
||||
|
||||
import { handler as removeItemHandler } from './use-remove-item'
|
||||
import useCards from './use-cards'
|
||||
|
||||
export type UpdateItemActionInput<T = any> = T extends LineItem
|
||||
? Partial<UpdateItemHook['actionInput']>
|
||||
: UpdateItemHook['actionInput']
|
||||
|
||||
export default useUpdateItem as UseUpdateItem<any>
|
||||
|
||||
export const handler: MutationHook<any> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/card',
|
||||
method: 'PUT',
|
||||
},
|
||||
async fetcher({
|
||||
input: { itemId, item },
|
||||
options,
|
||||
fetch,
|
||||
}: HookFetcherContext<UpdateItemHook>) {
|
||||
if (Number.isInteger(item.quantity)) {
|
||||
// Also allow the update hook to remove an item if the quantity is lower than 1
|
||||
if (item.quantity! < 1) {
|
||||
return removeItemHandler.fetcher({
|
||||
options: removeItemHandler.fetchOptions,
|
||||
input: { itemId },
|
||||
fetch,
|
||||
})
|
||||
}
|
||||
} else if (item.quantity) {
|
||||
throw new ValidationError({
|
||||
message: 'The item quantity has to be a valid integer',
|
||||
})
|
||||
}
|
||||
|
||||
return await fetch({
|
||||
...options,
|
||||
body: { itemId, item },
|
||||
})
|
||||
},
|
||||
useHook: ({ fetch }: MutationHookContext<UpdateItemHook>) =>
|
||||
function useHook<T extends LineItem | undefined = undefined>(
|
||||
ctx: {
|
||||
item?: T
|
||||
wait?: number
|
||||
} = {}
|
||||
) {
|
||||
const { item } = ctx
|
||||
const { mutate } = useCards() as any
|
||||
|
||||
return useCallback(
|
||||
debounce(async (input: UpdateItemActionInput<T>) => {
|
||||
const itemId = input.id ?? item?.id
|
||||
const productId = input.productId ?? item?.productId
|
||||
const variantId = input.productId ?? item?.variantId
|
||||
|
||||
if (!itemId || !productId) {
|
||||
throw new ValidationError({
|
||||
message: 'Invalid input used for this operation',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({
|
||||
input: {
|
||||
itemId,
|
||||
item: {
|
||||
productId,
|
||||
variantId: variantId || '',
|
||||
quantity: input.quantity,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await mutate(data, false)
|
||||
|
||||
return data
|
||||
}, ctx.wait ?? 500),
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { handler as useCart } from './cart/use-cart'
|
||||
import { handler as useAddItem } from './cart/use-add-item'
|
||||
import { handler as useUpdateItem } from './cart/use-update-item'
|
||||
import { handler as useRemoveItem } from './cart/use-remove-item'
|
||||
import { handler as useAddCartItem } from './cart/use-add-item'
|
||||
import { handler as useUpdateCartItem } from './cart/use-update-item'
|
||||
import { handler as useRemoveCartItem } from './cart/use-remove-item'
|
||||
|
||||
import { handler as useCustomer } from './customer/use-customer'
|
||||
import { handler as useSearch } from './product/use-search'
|
||||
@ -10,6 +10,20 @@ import { handler as useLogin } from './auth/use-login'
|
||||
import { handler as useLogout } from './auth/use-logout'
|
||||
import { handler as useSignup } from './auth/use-signup'
|
||||
|
||||
import { handler as useCheckout } from './checkout/use-checkout'
|
||||
import { handler as useSubmitCheckout } from './checkout/use-submit-checkout'
|
||||
import { handler as useGetCheckout } from './checkout/use-get-checkout'
|
||||
|
||||
import { handler as useCards } from './customer/card/use-cards'
|
||||
import { handler as useAddCardItem } from './customer/card/use-add-item'
|
||||
import { handler as useUpdateCardItem } from './customer/card/use-update-item'
|
||||
import { handler as useRemoveCardItem } from './customer/card/use-remove-item'
|
||||
|
||||
import { handler as useAddresses } from './customer/address/use-addresses'
|
||||
import { handler as useAddAddressItem } from './customer/address/use-add-item'
|
||||
import { handler as useUpdateAddressItem } from './customer/address/use-update-item'
|
||||
import { handler as useRemoveAddressItem } from './customer/address/use-remove-item'
|
||||
|
||||
import { CART_COOKIE, LOCALE } from './constants'
|
||||
import { default as fetcher } from './fetcher'
|
||||
|
||||
@ -17,8 +31,32 @@ export const ordercloudProvider = {
|
||||
locale: LOCALE,
|
||||
cartCookie: CART_COOKIE,
|
||||
fetcher,
|
||||
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
|
||||
customer: { useCustomer },
|
||||
cart: {
|
||||
useCart,
|
||||
useAddItem: useAddCartItem,
|
||||
useUpdateItem: useUpdateCartItem,
|
||||
useRemoveItem: useRemoveCartItem
|
||||
},
|
||||
checkout: {
|
||||
useCheckout,
|
||||
useSubmitCheckout,
|
||||
useGetCheckout,
|
||||
},
|
||||
customer: {
|
||||
useCustomer,
|
||||
card: {
|
||||
useCards,
|
||||
useAddItem: useAddCardItem,
|
||||
useUpdateItem: useUpdateCardItem,
|
||||
useRemoveItem: useRemoveCardItem
|
||||
},
|
||||
address: {
|
||||
useAddresses,
|
||||
useAddItem: useAddAddressItem,
|
||||
useUpdateItem: useUpdateAddressItem,
|
||||
useRemoveItem: useRemoveAddressItem
|
||||
}
|
||||
},
|
||||
products: { useSearch },
|
||||
auth: { useLogin, useLogout, useSignup },
|
||||
}
|
||||
|
4
framework/ordercloud/types/checkout.ts
Normal file
4
framework/ordercloud/types/checkout.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import * as Core from '@commerce/types/checkout'
|
||||
|
||||
export type CheckoutTypes = Core.CheckoutTypes
|
||||
export type CheckoutSchema = Core.CheckoutSchema<CheckoutTypes>
|
31
framework/ordercloud/types/customer/address.ts
Normal file
31
framework/ordercloud/types/customer/address.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import * as Core from '@commerce/types/customer/address'
|
||||
|
||||
export type CustomerAddressTypes = Core.CustomerAddressTypes
|
||||
export type CustomerAddressSchema = Core.CustomerAddressSchema<CustomerAddressTypes>
|
||||
|
||||
export interface OrdercloudAddress {
|
||||
ID: string;
|
||||
"FromCompanyID": string;
|
||||
"ToCompanyID": string;
|
||||
"FromUserID": string;
|
||||
"BillingAddressID": null,
|
||||
"BillingAddress": null,
|
||||
"ShippingAddressID": null,
|
||||
"Comments": null,
|
||||
"LineItemCount": number;
|
||||
"Status": string;
|
||||
"DateCreated": string;
|
||||
"DateSubmitted": null,
|
||||
"DateApproved": null,
|
||||
"DateDeclined": null,
|
||||
"DateCanceled": null,
|
||||
"DateCompleted": null,
|
||||
"LastUpdated": string;
|
||||
"Subtotal": number
|
||||
"ShippingCost": number
|
||||
"TaxCost": number
|
||||
"PromotionDiscount": number
|
||||
"Total": number
|
||||
"IsSubmitted": false,
|
||||
"xp": null
|
||||
}
|
16
framework/ordercloud/types/customer/card.ts
Normal file
16
framework/ordercloud/types/customer/card.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import * as Core from '@commerce/types/customer/card'
|
||||
|
||||
export type CustomerCardTypes = Core.CustomerCardTypes
|
||||
export type CustomerCardSchema = Core.CustomerCardSchema<CustomerCardTypes>
|
||||
|
||||
export interface OredercloudCreditCard {
|
||||
"ID": string;
|
||||
"Editable": boolean;
|
||||
"Token": string;
|
||||
"DateCreated": string;
|
||||
"CardType": string;
|
||||
"PartialAccountNumber": string;
|
||||
"CardholderName": string;
|
||||
"ExpirationDate": string;
|
||||
"xp": null
|
||||
}
|
@ -43,6 +43,7 @@
|
||||
"react-fast-marquee": "^1.1.4",
|
||||
"react-merge-refs": "^1.1.0",
|
||||
"react-use-measure": "^2.0.4",
|
||||
"stripe": "^8.174.0",
|
||||
"swell-js": "^4.0.0-next.0",
|
||||
"swr": "^0.5.6",
|
||||
"tabbable": "^5.2.0",
|
||||
|
4
pages/api/customer/address.ts
Normal file
4
pages/api/customer/address.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import customerAddressApi from '@framework/api/endpoints/customer/address'
|
||||
import commerce from '@lib/api/commerce'
|
||||
|
||||
export default customerAddressApi(commerce)
|
4
pages/api/customer/card.ts
Normal file
4
pages/api/customer/card.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import customerCardApi from '@framework/api/endpoints/customer/card'
|
||||
import commerce from '@lib/api/commerce'
|
||||
|
||||
export default customerCardApi(commerce)
|
@ -23,8 +23,8 @@
|
||||
"@components/*": ["components/*"],
|
||||
"@commerce": ["framework/commerce"],
|
||||
"@commerce/*": ["framework/commerce/*"],
|
||||
"@framework": ["framework/local"],
|
||||
"@framework/*": ["framework/local/*"]
|
||||
"@framework": ["framework/ordercloud"],
|
||||
"@framework/*": ["framework/ordercloud/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
|
||||
|
20
yarn.lock
20
yarn.lock
@ -1170,6 +1170,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
|
||||
integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
|
||||
|
||||
"@types/node@>=8.1.0":
|
||||
version "16.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.1.tgz#0611b37db4246c937feef529ddcc018cf8e35708"
|
||||
integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
@ -5686,6 +5691,13 @@ qs@6.7.0:
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||
|
||||
qs@^6.6.0:
|
||||
version "6.10.1"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
|
||||
integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
querystring-es3@0.2.1, querystring-es3@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
||||
@ -6487,6 +6499,14 @@ strip-json-comments@~2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||
|
||||
stripe@^8.174.0:
|
||||
version "8.174.0"
|
||||
resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.174.0.tgz#91d2e61b0217b1ee9fde2842582e0f1cf1dddc94"
|
||||
integrity sha512-UFU5TuYH7XwUmSllUIcIKhhsvvhhjw9D6ZwVdfB74wU4VOOaWBiQqszkw6chaEFpdulUmbcAH5eZltV3HwOi7g==
|
||||
dependencies:
|
||||
"@types/node" ">=8.1.0"
|
||||
qs "^6.6.0"
|
||||
|
||||
styled-jsx@3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-3.3.2.tgz#2474601a26670a6049fb4d3f94bd91695b3ce018"
|
||||
|
Loading…
x
Reference in New Issue
Block a user