Stripe can complete checkout - need to sort out order complete page

main
Ashelyn Dawn 4 years ago
parent 3639c1c804
commit 076a5d651e

@ -0,0 +1,6 @@
{
"presets": [ "next/babel" ],
"plugins": [
[ "inline-react-svg", {"svgo": { "plugins": [{"addClassesToSVGElement": {"className": "svgicon"}}]}} ]
]
}

@ -1,6 +1,7 @@
const router = module.exports = require('express-promise-router')()
const parseJSON = require('body-parser').json()
const db = require('../db')
const stripe = require('stripe')(process.env.STRIPE_PRIVATE_KEY);
const validate = require('./middleware/validators')
@ -48,3 +49,74 @@ router.post('/current/coupon', parseJSON, validate.coupon, validate.handleApiErr
const order = await db.order.addCoupon(currentTransaction, coupon)
res.json(order)
})
router.post('/current/checkout/stripe', async (req, res) => {
const order = await db.order.findForCart(req.cart.uuid);
if(!order) throw new Error("Unable to find current order");
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 numberOfItems = currentTransaction.cart.items.map(i => i.count).reduce((a,b) => a+b)
const itemPriceWithDiscount = item_total_price - (coupon_effective_discount || 0)
const shippingPrice = (free_shipping ? 0 : shipping_price)
const line_items = [{
name: `Cart Total (${numberOfItems} item${numberOfItems > 1 ? 's' : ''})`,
amount: itemPriceWithDiscount,
currency: 'usd',
quantity: 1
}, {
name: 'Shipping',
amount: shippingPrice,
currency: 'usd',
quantity: 1
}]
if(tax_price > 0)
line_items.push({
name: 'Sales Tax (Utah)',
amount: tax_price,
currency: 'usd',
quantity: 1
})
const session = await stripe.checkout.sessions.create({
client_reference_id: currentTransaction.uuid,
customer_email: req.user ? req.user.email : undefined,
payment_method_types: ['card'],
line_items,
success_url: `${process.env.EXTERNAL_URL}/store/checkout/complete?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.EXTERNAL_URL}/cancel`,
});
res.json(session.id)
})
router.post('/current/checkout/verify', parseJSON, async (req, res) => {
const {session_id} = req.body
if(!session_id) throw new Error("No session id given")
let order = await db.order.findForCart(req.cart.uuid);
if(!order) throw new Error("Unable to find current order");
const currentTransaction = order
.transactions.find(transaction => (
transaction.payment_state === 'started'
))
const {payment_intent} = await stripe.checkout.sessions.retrieve(session_id);
const payment = await stripe.paymentIntents.retrieve(payment_intent)
if(payment.status === "succeeded"){
order = await db.order.addPayment(currentTransaction, payment)
await db.session.clearCart(req.sessionObj.uuid)
}
res.json({status: payment.status, order})
})

@ -1,11 +1,17 @@
import React from 'react'
import {Icon} from '@rmwc/icon'
import styles from './styles.module.css'
export default function Button({outline, onClick, enabled, type, children}){
export default function Button({outline, onClick, enabled, type, children, icon: _icon}){
const icon = _icon && (typeof _icon === 'string' ? <Icon icon={_icon}/> : _icon)
return (
<div className={styles.formElementContainer + (outline ? ' ' + styles.outline : '')}>
<button disabled={enabled === undefined ? false : !enabled} onClick={onClick} type={type} children={children}/>
<button className={icon && styles.buttonWithIcon} disabled={enabled === undefined ? false : !enabled} onClick={onClick} type={type}>
{icon && <span className={styles.buttonIcon}>{icon}</span>}
{children}
</button>
</div>
)
}

@ -153,3 +153,15 @@
opacity: .4;
filter: saturate(.2);
}
.buttonWithIcon {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.buttonWithIcon > .buttonIcon {
width: 24px;
margin-right: 8px;
}

@ -132,3 +132,21 @@ order.addCoupon = function(transaction, coupon) {
single: true
})
}
order.addPayment = async function(transaction, paymentIntent){
const [charge] = paymentIntent.charges.data
if(!charge.paid) throw new Error("Charge was not paid")
return await dbUtil.executeFunction({
name: 'add_stripe_payment_to_transaction',
params: [
transaction.uuid,
paymentIntent.amount_received,
paymentIntent.id,
paymentIntent.receipt_email
],
returnType: 'order',
single: true
})
}

@ -82,3 +82,15 @@ session.end = async (session_uuid) => {
const {rows} = await pg.query(query)
return joinjs.map(rows, mappings, 'sessionMap', 'session_')[0];
}
session.clearCart = async (session_uuid) => {
const query = {
text: 'select * from sos.clear_cart($1)',
values: [session_uuid]
}
debug(query);
const {rows} = await pg.query(query)
return joinjs.map(rows, mappings, 'sessionMap', 'session_')[0];
}

@ -211,7 +211,7 @@ create table sos."payment_stripe" (
payment_type sos.payment_type_enum check (payment_type = 'stripe'),
foreign key (payment_uuid, payment_type) references sos."payment" (payment_uuid, payment_type),
stripe_transaction_id text not null,
stripe_payment_intent_id text unique not null,
stripe_reciept_email citext not null
);

@ -77,11 +77,44 @@ create or replace view sos.v_cart_price as
) carts
group by cart_uuid;
create or replace view sos.v_payment as
select
payment.*,
payment_stripe.stripe_payment_intent_id,
payment_stripe.stripe_reciept_email
from sos."payment"
left join sos."payment_ks_reward" on payment_ks_reward.payment_uuid = payment.payment_uuid and payment_ks_reward.payment_type = payment.payment_type
left join sos."payment_stripe" on payment_stripe.payment_uuid = payment.payment_uuid and payment_stripe.payment_type = payment.payment_type;
create or replace view sos.v_transaction_paid as
select
transaction_uuid,
coalesce(sum(payment_value_cents), 0) as transaction_amount_paid_cents
from
sos."transaction"
left join sos.v_payment on transaction_uuid = payment_transaction_uuid
group by transaction_uuid;
-- TODO: add coupon, delivery
create or replace view sos.v_order as
select * from sos."order"
select
"order".*,
"transaction".*,
(
transaction_item_total_price
- transaction_coupon_effective_discount
+ case when (coupon_free_shipping != true) then transaction_shipping_price else 0 end
+ coalesce(transaction_tax_price, 0)
) as transaction_computed_price,
"coupon".*,
"address".*,
v_transaction_paid.transaction_amount_paid_cents,
v_payment.*,
v_cart.*
from sos."order"
left join sos."transaction" on transaction_order_uuid = order_uuid
left join sos."coupon" on transaction_coupon_uuid = coupon_uuid
left join sos."address" on order_address_uuid = address_uuid
left join sos.v_transaction_paid on "transaction".transaction_uuid = v_transaction_paid.transaction_uuid
left join sos.v_payment on "transaction".transaction_uuid = payment_transaction_uuid
left join sos.v_cart on cart_uuid = transaction_cart_uuid;

@ -644,3 +644,78 @@ begin
return query select * from sos.v_order where order_uuid = _order_uuid;
end; $function$;
create or replace function sos.add_stripe_payment_to_transaction(_transaction_uuid uuid, _payment_value_cents integer, _stripe_intent_id text, _stripe_reciept_email citext)
returns setof sos.v_order
language plpgsql
as $function$
declare
_payment_uuid uuid;
_is_paid integer;
_order_uuid uuid;
begin
-- Get the transaction's order
select transaction_order_uuid into _order_uuid
from sos."transaction"
where transaction_uuid = _transaction_uuid;
if _order_uuid is null then
raise 'Transaction has no order';
end if;
insert into sos."payment" (
payment_type,
payment_value_cents,
payment_transaction_uuid
) values (
'stripe',
_payment_value_cents,
_transaction_uuid
) returning payment_uuid into _payment_uuid;
insert into sos."payment_stripe" (
payment_uuid,
payment_type,
stripe_payment_intent_id,
stripe_reciept_email
) values (
_payment_uuid,
'stripe',
_stripe_intent_id,
_stripe_reciept_email
);
select
count(*) into _is_paid
from sos.v_order
where transaction_uuid = _transaction_uuid
and transaction_computed_price <= transaction_amount_paid_cents;
if _is_paid > 0 then
update sos."transaction" set (
transaction_payment_state,
transaction_completion_time
) = (
'completed',
now()
)
where
transaction_uuid = _transaction_uuid;
end if;
return query select * from sos.v_order where order_uuid = _order_uuid;
end; $function$;
create or replace function sos.clear_cart(_session_uuid uuid)
returns setof sos.v_session
language plpgsql
as $function$
begin
update sos."session"
set session_cart = null
where session_uuid = _session_uuid
and session_time_logged_out is null
and now() < (select session_time_last_active + session_timeout_length);
return query select * from sos.v_session where session_uuid = _session_uuid;
end; $function$;

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="windows-1252"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<path d="M409.713,38.624C385.842,11.584,342.642,0,287.377,0H126.993c-11.296,0-20.896,8.16-22.688,19.2L37.522,439.392 c-1.312,8.288,5.152,15.776,13.6,15.776h99.008l24.864-156.48l-0.768,4.928c1.76-11.04,11.328-19.2,22.624-19.2h47.04 c92.448,0,164.8-37.248,185.952-144.992c0.64-3.2,1.632-9.344,1.632-9.344C437.489,90.208,431.441,63.168,409.713,38.624z"/>
</g>
</g>
<g>
<g>
<path d="M456.529,150.496c-22.976,106.08-96.288,162.208-212.64,162.208h-42.176L170.225,512h68.416 c9.888,0,18.304-7.136,19.84-16.832l0.8-4.224l15.744-98.912l1.024-5.44c1.536-9.696,9.952-16.832,19.808-16.832h12.512 c80.864,0,144.16-32.576,162.656-126.816C478.449,205.12,474.865,173.408,456.529,150.496z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -21,7 +21,8 @@ class CustomResolver {
const allowedShortcutDirs = [
'styles',
'hooks',
'components'
'components',
'images'
]
// If it's not the right pattern, or it's outside our list of directories
@ -66,5 +67,8 @@ module.exports = withImages({
config.resolve.plugins.push(new CustomResolver())
return config
},
env: {
STRIPE_PUBLIC_KEY: process.env.STRIPE_PUBLIC_KEY
}
})

140
package-lock.json generated

@ -1273,11 +1273,21 @@
"resolved": "https://registry.npmjs.org/@rmwc/types/-/types-6.0.5.tgz",
"integrity": "sha512-G4NZxakwsTMFxxpEWX90MOlZtVNzCwooLUg7FH7Oh5OJCnKWGmXK8ZMF59vvIEn7zCiEpVroKhOxNpMw9TIUTw=="
},
"@stripe/stripe-js": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.4.0.tgz",
"integrity": "sha512-ZvrD4s0T2VcZLXIll0eO9YO/Gnr2sgHgycqvSVN5A3ok/USesD9SL6j1vX9wTy7DVbm0vvQQE01Jap6PjP1IVw=="
},
"@types/classnames": {
"version": "2.2.10",
"resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.10.tgz",
"integrity": "sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ=="
},
"@types/node": {
"version": "13.13.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.0.tgz",
"integrity": "sha512-WE4IOAC6r/yBZss1oQGM5zs2D7RuKR6Q+w+X2SouPofnWn+LbCqClRyhO3ZE7Ix8nmFgo/oVuuE01cJT2XB13A=="
},
"@webassemblyjs/ast": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
@ -1860,6 +1870,18 @@
"object.assign": "^4.1.0"
}
},
"babel-plugin-inline-react-svg": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/babel-plugin-inline-react-svg/-/babel-plugin-inline-react-svg-1.1.1.tgz",
"integrity": "sha512-KCCzSKJUigDXd/dxJDE6uNyVTYE46FiTt8Md3vpYHtbADeTjOLJq5LkmaVpISplxKCK25VZU8sha2Km6uIEFJA==",
"requires": {
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/parser": "^7.0.0",
"lodash.isplainobject": "^4.0.6",
"resolve": "^1.10.0",
"svgo": "^0.7.2"
}
},
"babel-plugin-syntax-jsx": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
@ -2399,6 +2421,46 @@
"safe-buffer": "^5.0.1"
}
},
"clap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz",
"integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==",
"requires": {
"chalk": "^1.1.3"
},
"dependencies": {
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "^2.0.0"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
}
}
},
"class-utils": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
@ -2480,6 +2542,14 @@
"shallow-clone": "^3.0.0"
}
},
"coa": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz",
"integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=",
"requires": {
"q": "^1.1.2"
}
},
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
@ -2968,6 +3038,22 @@
"postcss": "^7.0.18"
}
},
"csso": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz",
"integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=",
"requires": {
"clap": "^1.0.9",
"source-map": "^0.5.3"
},
"dependencies": {
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
}
}
},
"cyclist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
@ -4625,6 +4711,11 @@
"resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
},
"lodash.template": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
@ -6585,6 +6676,11 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
@ -7598,6 +7694,15 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
},
"stripe": {
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/stripe/-/stripe-8.44.0.tgz",
"integrity": "sha512-aZfvseFM6P21nsJpAVSSVeXhLZKbleihbQJijSSUWlRM4TekmqdBd0DevIVEj5yDHtvx9IM8xeuYJZkWhISFuQ==",
"requires": {
"@types/node": ">=8.1.0",
"qs": "^6.6.0"
}
},
"style-loader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.0.0.tgz",
@ -7686,6 +7791,36 @@
"has-flag": "^3.0.0"
}
},
"svgo": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz",
"integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=",
"requires": {
"coa": "~1.0.1",
"colors": "~1.1.2",
"csso": "~2.3.1",
"js-yaml": "~3.7.0",
"mkdirp": "~0.5.1",
"sax": "~1.2.1",
"whet.extend": "~0.9.9"
},
"dependencies": {
"esprima": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
"integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE="
},
"js-yaml": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz",
"integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=",
"requires": {
"argparse": "^1.0.7",
"esprima": "^2.6.0"
}
}
}
},
"tapable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
@ -9005,6 +9140,11 @@
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
"integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
},
"whet.extend": {
"version": "0.9.9",
"resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz",
"integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE="
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

@ -15,7 +15,9 @@
"@easypost/api": "^3.8.1",
"@rmwc/button": "^6.0.14",
"@rmwc/icon": "^6.0.12",
"@stripe/stripe-js": "^1.4.0",
"axios": "^0.19.2",
"babel-plugin-inline-react-svg": "^1.1.1",
"base64-async": "^2.1.3",
"bcrypt": "^3.0.8",
"body-parser": "^1.19.0",
@ -35,6 +37,7 @@
"react": "^16.12.0",
"react-dom": "^16.12.0",
"sharp": "^0.24.1",
"stripe": "^8.44.0",
"use-measure": "^0.3.0",
"validator": "^12.2.0"
}

@ -6,7 +6,6 @@ import Card from '~/components/card'
import styles from './style.module.css'
Category.getInitialProps = async function({ctx: {axios, query: {slug}}}){
console.log(slug)
const {data: category} = await axios.get(`/api/categories/by-slug/${slug}`)
console.log(category)

@ -0,0 +1,16 @@
import {useEffect} from 'react'
import Router from 'next/router'
CheckoutComplete.getInitialProps = async function({ctx: {query: {session_id}, axios}}){
const {data: {status, order}} = await axios.post('/api/orders/current/checkout/verify', {session_id})
return {status, order}
}
export default function CheckoutComplete({status, order}){
return (
<pre>
{JSON.stringify(order, null, 2)}
</pre>
)
}

@ -2,10 +2,13 @@ import {useState, useRef} from 'react'
import Link from 'next/link'
import Router from 'next/router'
import styles from './style.module.css'
import {Icon} from '@rmwc/icon'
import {Input, Button} from '~/components/form'
import useUser from '~/hooks/useUser'
import axios from 'axios'
import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe(process.env.STRIPE_PUBLIC_KEY);
import PaypalIcon from '../../../images/icons/paypal.svg'
// TODO: Load previous addresses
CheckoutSummary.getInitialProps = async function({ctx: {axios}}){
@ -24,6 +27,10 @@ export default function CheckoutSummary({order: _order}){
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;
@ -31,11 +38,6 @@ export default function CheckoutSummary({order: _order}){
return '$' + (money / 100).toFixed(2)
}
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
// For coupon input
const couponRef = useRef()
const onCouponSubmit = async ev => {
@ -46,6 +48,22 @@ export default function CheckoutSummary({order: _order}){
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 (
<>
<h2>Checkout</h2>
@ -155,14 +173,14 @@ export default function CheckoutSummary({order: _order}){
{
total_price
? (
<>
<Button outline>Pay with Card</Button>
<Button outline>Pay with Paypal</Button>
</>
<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 disabled outline>Pay with Card</Button>
<Button disabled outline>Pay with Paypal</Button>
<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>
)
}

@ -110,3 +110,9 @@ table tr.strikethrough::after {
top: 50%;
border-top: solid 1px black;
}
.svgicon {
width: 24px;
height: 24px;
fill: currentColor;
}

Loading…
Cancel
Save