You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
8.7 KiB
JavaScript
240 lines
8.7 KiB
JavaScript
import {useState, useRef} from 'react'
|
|
import Link from 'next/link'
|
|
import Head from 'next/head'
|
|
import Router from 'next/router'
|
|
import getConfig from 'next/config'
|
|
import styles from './style.module.css'
|
|
import {Input, Button} from '~/components/form'
|
|
import useUser from '~/hooks/useUser'
|
|
import axios from 'axios'
|
|
import { loadStripe } from '@stripe/stripe-js';
|
|
|
|
const { publicRuntimeConfig: {STRIPE_PUBLIC_KEY} } = getConfig()
|
|
const stripePromise = loadStripe(STRIPE_PUBLIC_KEY);
|
|
import PaypalIcon from '../../../images/icons/paypal.svg'
|
|
|
|
CheckoutSummary.getInitialProps = async function({ctx: {axios}}){
|
|
const {data: addresses} = await axios.get(`/api/orders/addresses`)
|
|
const {data: order} = await axios.get(`/api/orders/current`)
|
|
return {order, addresses}
|
|
}
|
|
|
|
export default function CheckoutSummary({order: _order, addresses}){
|
|
const user = useUser();
|
|
const [order, updateOrder] = useState(_order)
|
|
const [couponError, setCouponError] = useState(null)
|
|
|
|
const currentTransaction = order
|
|
.transactions.find(transaction => (
|
|
transaction.payment_state === 'started'
|
|
))
|
|
|
|
const {item_total_price, shipping_price, tax_price, coupon_effective_discount} = currentTransaction
|
|
const {free_shipping} = currentTransaction.coupon || {}
|
|
const total_price =
|
|
(item_total_price && shipping_price && tax_price)
|
|
? item_total_price + (free_shipping ? 0 : shipping_price) + tax_price - (coupon_effective_discount || 0)
|
|
: null
|
|
|
|
const formatMoney = money => {
|
|
if (money === undefined || money === null) return null;
|
|
|
|
return '$' + (money / 100).toFixed(2)
|
|
}
|
|
|
|
// For coupon input
|
|
const couponRef = useRef()
|
|
const onCouponSubmit = async ev => {
|
|
if(ev) ev.preventDefault()
|
|
|
|
setCouponError(null)
|
|
|
|
const code = couponRef.current?.value
|
|
|
|
try {
|
|
const {data: updatedOrder} = await axios.post(`/api/orders/current/coupon`, {code})
|
|
updateOrder(updatedOrder)
|
|
} catch (err) {
|
|
console.log(err.response.data.errors)
|
|
const message = err?.response?.data?.error?.message
|
|
|| err.response?.data?.errors?.find(e => e.param === 'code')?.msg
|
|
|| 'Unknown error applying coupon'
|
|
|
|
setCouponError(message)
|
|
}
|
|
}
|
|
|
|
// For address selection
|
|
const onAddressSelect = uuid => async ev => {
|
|
if(ev) ev.preventDefault()
|
|
|
|
const {data: updatedOrder} = await axios.post(`/api/orders/current/address/${uuid}`)
|
|
updateOrder(updatedOrder)
|
|
}
|
|
|
|
// For Stripe checkout
|
|
const handleStripeButton = async ev => {
|
|
if(ev) ev.preventDefault()
|
|
|
|
// Create a stripe session on server
|
|
const {data: sessionId} = await axios.post('/api/orders/current/checkout/stripe')
|
|
|
|
// Allow Stripe to finish initializing
|
|
const stripe = await stripePromise;
|
|
|
|
const {error} = await stripe.redirectToCheckout({sessionId})
|
|
|
|
if(error)
|
|
alert(error.message)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Head>
|
|
<title>Checkout | Society of Socks</title>
|
|
</Head>
|
|
<h2>Checkout</h2>
|
|
<div className={styles.checkoutSection}>
|
|
<h3>Address</h3>
|
|
<div className={styles.horizContainer}>
|
|
{
|
|
order.address
|
|
? (
|
|
<div>
|
|
<p style={{marginTop: 0, fontWeight: 'bold'}}>Shipping To:</p>
|
|
<p style={{margin: 0}}>{order.address.name}</p>
|
|
<p style={{margin: 0}}>{order.address.street1}</p>
|
|
<p style={{margin: 0}}>{order.address.street2}</p>
|
|
<p style={{margin: 0}}>{order.address.city}, {order.address.state}, {order.address.zip}</p>
|
|
<p style={{marginBottom: '-20px'}}><Link href="/store/checkout/address"><a>(Edit Address)</a></Link></p>
|
|
</div>
|
|
)
|
|
: (
|
|
<>
|
|
{!user && (
|
|
<>
|
|
<div style={{textAlign: 'center'}}>
|
|
<p>
|
|
<Link href="/login"><a>Log in</a></Link> to use your<br/>saved addresses
|
|
</p>
|
|
</div>
|
|
<span className={styles.horizDivider}>
|
|
OR
|
|
</span>
|
|
</>
|
|
)}
|
|
{user && addresses && !!addresses.length && (
|
|
<>
|
|
<div style={{textAlign: 'center'}}>
|
|
<span>Select a previous address:</span>
|
|
{addresses.map(address => (
|
|
<div className={styles.addressCard}>
|
|
<span>
|
|
{address.name || address.company} / {address.street1}<br/>
|
|
{address.street2 && <> {address.street2} <br/> </>}
|
|
{address.city} / {address.state} / {address.zip}
|
|
</span>
|
|
|
|
<button style={{textAlign: 'center'}} className="buttonLink" onClick={onAddressSelect(address.uuid)}>
|
|
Use this address
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<span className={styles.horizDivider}>
|
|
OR
|
|
</span>
|
|
</>
|
|
)}
|
|
|
|
<div>
|
|
<Button style={{width: '100px'}} onClick={()=>Router.push('/store/checkout/address')}>Input Address</Button>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|
|
</div>
|
|
</div>
|
|
<div className={styles.checkoutSection}>
|
|
<h3>Coupon (optional)</h3>
|
|
{
|
|
currentTransaction.coupon
|
|
? (
|
|
<>
|
|
<p style={{textAlign: 'center'}}>
|
|
Using coupon code: <strong>{currentTransaction.coupon.code}</strong><br/>
|
|
(
|
|
{coupon_effective_discount > 0 && `Discounting by ${formatMoney(coupon_effective_discount)}`}
|
|
{coupon_effective_discount > 0 && free_shipping && ' & '}
|
|
{free_shipping && `${coupon_effective_discount > 0 ? 'f' : 'F'}ree shipping`}
|
|
)
|
|
</p>
|
|
</>
|
|
)
|
|
: (
|
|
<div className={styles.horizContainer} style={{maxWidth:'400px', margin: '0 auto'}}>
|
|
<Input inputRef={couponRef} label="" name="coupon" onChange={() => setCouponError(null)} error={couponError} />
|
|
<div style={{alignSelf:'flex-start', maxWidth: '120px', marginLeft: '8px'}}>
|
|
<Button onClick={onCouponSubmit} outline>Apply Coupon</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
</div>
|
|
<div className={styles.checkoutSection}>
|
|
<h3>Total</h3>
|
|
<div className={styles.horizContainer}>
|
|
<div>
|
|
<table>
|
|
<tbody>
|
|
{currentTransaction.cart.items.map(({uuid, count, item}) => (
|
|
<tr key={uuid}>
|
|
<td>{item.name} {count > 1 ? `(${count})` : ''}:</td>
|
|
<td>{formatMoney(count * item.price_cents)}</td>
|
|
</tr>
|
|
))}
|
|
{coupon_effective_discount > 0 && (
|
|
<tr>
|
|
<td>Coupon:</td>
|
|
<td>- {formatMoney(coupon_effective_discount)}</td>
|
|
</tr>
|
|
)}
|
|
<tr className={free_shipping ? 'strikethrough' : undefined}>
|
|
<td>Shipping:</td>
|
|
<td>{formatMoney(shipping_price) || '-'}</td>
|
|
</tr>
|
|
{tax_price && (
|
|
<tr>
|
|
<td>Sales tax:</td>
|
|
<td>{formatMoney(tax_price)}</td>
|
|
</tr>
|
|
)}
|
|
<tr style={{fontWeight: 'bold'}}>
|
|
<td>Total:</td>
|
|
<td>{formatMoney(total_price) || '-'}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div className={styles.paymentButtons}>
|
|
{
|
|
total_price
|
|
? (
|
|
<div>
|
|
<Button outline icon="credit_card" onClick={handleStripeButton}>Pay with Card</Button>
|
|
<Button outline icon={<PaypalIcon/>}>Pay with Paypal</Button>
|
|
</div>
|
|
) : (
|
|
<div style={{pointerEvents: 'none', opacity: .4}}>
|
|
<Button outline icon="credit_card">Pay with Card</Button> {/* Hit API endpoint to get Stripe session, and then redirect */}
|
|
<Button outline icon={<PaypalIcon/>}>Pay with Paypal</Button>
|
|
</div>
|
|
)
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|