Solves the issue of not being able to look up an order after clearing the session cart

main
Ashelyn Dawn 5 years ago
parent a834eb441b
commit a6ed5bc430

@ -5,10 +5,15 @@ const stripe = require('stripe')(process.env.STRIPE_PRIVATE_KEY);
const validate = require('./middleware/validators') const validate = require('./middleware/validators')
router.get('/', async (req, res) => {
const orders = await db.order.findAllForSession(req.session.uuid)
res.json(orders)
})
router.use(require('./middleware/ensureCart')) router.use(require('./middleware/ensureCart'))
router.put('/', async (req, res) => { router.put('/', async (req, res) => {
const order = await db.order.create(req.cart.uuid); const order = await db.order.create(req.cart.uuid, req.sessionObj.uuid);
res.json(order) res.json(order)
}) })
@ -66,17 +71,23 @@ router.post('/current/checkout/stripe', async (req, res) => {
const itemPriceWithDiscount = item_total_price - (coupon_effective_discount || 0) const itemPriceWithDiscount = item_total_price - (coupon_effective_discount || 0)
const shippingPrice = (free_shipping ? 0 : shipping_price) const shippingPrice = (free_shipping ? 0 : shipping_price)
const line_items = [{ const line_items = []
name: `Cart Total (${numberOfItems} item${numberOfItems > 1 ? 's' : ''})`,
amount: itemPriceWithDiscount, if(itemPriceWithDiscount > 0)
currency: 'usd', line_items.push({
quantity: 1 name: `Cart Total (${numberOfItems} item${numberOfItems > 1 ? 's' : ''})`,
}, { amount: itemPriceWithDiscount,
name: 'Shipping', currency: 'usd',
amount: shippingPrice, quantity: 1
currency: 'usd', })
quantity: 1
}] if(shippingPrice > 0)
line_items.push({
name: 'Shipping',
amount: shippingPrice,
currency: 'usd',
quantity: 1
})
if(tax_price > 0) if(tax_price > 0)
line_items.push({ line_items.push({
@ -86,12 +97,16 @@ router.post('/current/checkout/stripe', async (req, res) => {
quantity: 1 quantity: 1
}) })
// TODO: We need to handle this case, mark it paid or something?
if(line_items.length < 1)
res.json(null)
const session = await stripe.checkout.sessions.create({ const session = await stripe.checkout.sessions.create({
client_reference_id: currentTransaction.uuid, client_reference_id: currentTransaction.uuid,
customer_email: req.user ? req.user.email : undefined, customer_email: req.user ? req.user.email : undefined,
payment_method_types: ['card'], payment_method_types: ['card'],
line_items, line_items,
success_url: `${process.env.EXTERNAL_URL}/store/checkout/complete?session_id={CHECKOUT_SESSION_ID}`, success_url: `${process.env.EXTERNAL_URL}/store/checkout/verify?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.EXTERNAL_URL}/cancel`, cancel_url: `${process.env.EXTERNAL_URL}/cancel`,
}); });
@ -118,5 +133,7 @@ router.post('/current/checkout/verify', parseJSON, async (req, res) => {
await db.session.clearCart(req.sessionObj.uuid) await db.session.clearCart(req.sessionObj.uuid)
} }
// TODO: Save stockchange record for purchase
res.json({status: payment.status, order}) res.json({status: payment.status, order})
}) })

@ -9,7 +9,8 @@ module.exports = [{
'date_uploaded' 'date_uploaded'
], ],
associations: [ associations: [
{name: 'uploader', mapId: 'userMap', columnPrefix: 'user_'} // TODO: Uploader should not be included in non-admin API responses
{name: 'uploader', mapId: 'userMap', columnPrefix: 'uploader_'}
] ]
},{ },{
mapId: 'itemMap', mapId: 'itemMap',

@ -26,7 +26,8 @@ module.exports = [{
], ],
associations: [ associations: [
{name: 'cart', mapId: 'cartMap', columnPrefix: 'cart_'}, {name: 'cart', mapId: 'cartMap', columnPrefix: 'cart_'},
{name: 'coupon', mapId: 'couponMap', columnPrefix: 'coupon_'} {name: 'coupon', mapId: 'couponMap', columnPrefix: 'coupon_'},
{name: 'payment', mapId: 'paymentMap', columnPrefix: 'payment_'}
] ]
},{ },{
mapId: 'addressMap', mapId: 'addressMap',
@ -56,4 +57,21 @@ module.exports = [{
'per_sock_discount_cents', 'per_sock_discount_cents',
'number_of_socks_free' 'number_of_socks_free'
] ]
},{
mapId: 'paymentMap',
idProperty: 'uuid',
properties: [
'type',
'time',
'value_cents'
],
associations: [
{name: 'stripe', mapId: 'paymentStripeMap', columnPrefix: 'stripe_'}
]
},{
mapId: 'paymentStripeMap',
idProperty: 'payment_intent_id',
properties: [
'reciept_email'
]
}] }]

@ -9,10 +9,10 @@ const easypost = new (require('@easypost/api'))(process.env.EASYPOST_API_KEY);
const order = module.exports = {} const order = module.exports = {}
order.create = async function(cart_uuid){ order.create = async function(cart_uuid, session_uuid){
const query = { const query = {
text: 'select * from sos.create_order($1)', text: 'select * from sos.create_order($1, $2)',
values: [cart_uuid] values: [cart_uuid, session_uuid]
} }
debug(query); debug(query);
@ -33,6 +33,18 @@ order.findForCart = async function(cart_uuid) {
return joinjs.map(rows, mappings, 'orderMap', 'order_')[0]; return joinjs.map(rows, mappings, 'orderMap', 'order_')[0];
} }
order.findAllForSession = async function(session_uuid) {
const query = {
text: 'select * from sos.find_orders_for_session($1)',
values: [session_uuid]
}
debug(query)
const {rows} = await pg.query(query)
return joinjs.map(rows, mappings, 'orderMap', 'order_');
}
order.addAddress = async function (transaction, address){ order.addAddress = async function (transaction, address){
// Get parcel size // Get parcel size
const parcel = { const parcel = {

@ -171,6 +171,7 @@ create table sos."coupon" (
create table sos."transaction" ( create table sos."transaction" (
transaction_uuid uuid primary key default uuid_generate_v4(), transaction_uuid uuid primary key default uuid_generate_v4(),
transaction_order_uuid uuid references sos."order" (order_uuid), transaction_order_uuid uuid references sos."order" (order_uuid),
transaction_session_uuid uuid not null references sos."session" (session_uuid),
transaction_cart_uuid uuid references sos."cart" (cart_uuid), transaction_cart_uuid uuid references sos."cart" (cart_uuid),
transaction_coupon_uuid uuid references sos."coupon" (coupon_uuid), transaction_coupon_uuid uuid references sos."coupon" (coupon_uuid),
transaction_start_time timestamptz not null default now(), transaction_start_time timestamptz not null default now(),

@ -5,7 +5,7 @@ create or replace view sos.v_item as
"image".image_featured, "image".image_featured,
"image".image_mime_type, "image".image_mime_type,
"image".image_date_uploaded, "image".image_date_uploaded,
"user".user_email, "user".user_email as uploader_email,
coalesce(num_added - num_removed, 0) as item_number_in_stock coalesce(num_added - num_removed, 0) as item_number_in_stock
from sos."item" from sos."item"
left join sos."image" on item.item_uuid = image.image_item_uuid left join sos."image" on item.item_uuid = image.image_item_uuid

@ -402,13 +402,14 @@ begin
return query select * from sos.v_item where item_uuid = _item_uuid; return query select * from sos.v_item where item_uuid = _item_uuid;
end; $function$; end; $function$;
create or replace function sos.create_order(_cart_uuid uuid) create or replace function sos.create_order(_cart_uuid uuid, _session_uuid uuid)
returns setof sos.v_order returns setof sos.v_order
language plpgsql language plpgsql
as $function$ as $function$
declare declare
_completed_transactions integer; _completed_transactions integer;
_order_uuid uuid; _order_uuid uuid;
_existing_transaction uuid;
_cart_price integer; _cart_price integer;
begin begin
-- Check for completed transactions -- Check for completed transactions
@ -422,11 +423,18 @@ begin
end if; end if;
-- Check for existing transaction for this cart? -- Check for existing transaction for this cart?
select transaction_order_uuid into _order_uuid select transaction_order_uuid, transaction_uuid into _order_uuid, _existing_transaction
from sos."transaction" from sos."transaction"
where transaction_cart_uuid = _cart_uuid where transaction_cart_uuid = _cart_uuid
and transaction_payment_state = 'started'; and transaction_payment_state = 'started';
-- Update existing transaction to current session
if _existing_transaction is not null then
update sos."transaction" set
transaction_session_uuid = _session_uuid
where transaction_uuid = _existing_transaction;
end if;
-- If no existing order, create an order and a transaction -- If no existing order, create an order and a transaction
if _order_uuid is null then if _order_uuid is null then
-- Create order -- Create order
@ -442,11 +450,13 @@ begin
insert into sos."transaction" ( insert into sos."transaction" (
transaction_order_uuid, transaction_order_uuid,
transaction_cart_uuid, transaction_cart_uuid,
transaction_item_total_price transaction_item_total_price,
transaction_session_uuid
) values ( ) values (
_order_uuid, _order_uuid,
_cart_uuid, _cart_uuid,
_cart_price _cart_price,
_session_uuid
); );
end if; end if;
@ -495,6 +505,22 @@ begin
return query select * from sos.v_order where order_uuid = _order_uuid; return query select * from sos.v_order where order_uuid = _order_uuid;
end; $function$; end; $function$;
create or replace function sos.find_orders_for_session(_session_uuid uuid)
returns setof sos.v_order
language plpgsql
as $function$
begin
return query
select "order".* from sos."transaction"
left join sos.v_order "order" on "order".order_uuid = "transaction".transaction_order_uuid
where "transaction".transaction_session_uuid = _session_uuid
and (
"transaction".transaction_payment_state = 'started'
or
"transaction".transaction_payment_state = 'completed'
);
end; $function$;
create or replace function sos.create_address(_name text, _company text, _street1 text, _street2 text, _city text, _state text, _zip text, _country text, _phone text, _easypost_id text) create or replace function sos.create_address(_name text, _company text, _street1 text, _street2 text, _city text, _state text, _zip text, _country text, _phone text, _easypost_id text)
returns setof sos."address" returns setof sos."address"
language plpgsql language plpgsql

@ -1,16 +1,37 @@
import {useEffect} from 'react' import {DateTime} from 'luxon'
import Router from 'next/router'
CheckoutComplete.getInitialProps = async function({ctx: {query: {session_id}, axios}}){ CheckoutComplete.getInitialProps = async function({ctx: {query: {session_id}, axios}}){
const {data: {status, order}} = await axios.post('/api/orders/current/checkout/verify', {session_id}) const {data: orders} = await axios.get('/api/orders')
return {status, order}
const mostRecentOrder = orders.sort(sortOrders)[0]
return {order: mostRecentOrder}
} }
export default function CheckoutComplete({status, order}){ export default function CheckoutComplete({order}){
return ( return (
<pre> <pre>
{JSON.stringify(order, null, 2)} {JSON.stringify(order, null, 2)}
</pre> </pre>
) )
} }
function parsePaymentTime({payment}){
if(typeof payment.time === 'string')
payment.time = DateTime.fromISO(payment.time)
return payment.time
}
function sortTransactions(a,b){
const timeA = parsePaymentTime(a)
const timeB = parsePaymentTime(b)
return timeB.diff(timeA).as('seconds')
}
function sortOrders(a,b){
const timePaidA = parsePaymentTime(a.transactions.sort(sortTransactions)[0])
const timePaidB = parsePaymentTime(b.transactions.sort(sortTransactions)[0])
return timePaidB.diff(timePaidA).as('seconds')
}

@ -0,0 +1,39 @@
import {useState, useEffect} from 'react'
import axios from 'axios'
import Router from 'next/router'
CheckoutComplete.getInitialProps = async function({ctx: {query: {session_id}}}){
return {session_id}
}
export default function CheckoutComplete({session_id}){
const [loading, setLoading] = useState(true)
useEffect(()=>{
(async ()=>{
const {data: {status}} = await axios.post('/api/orders/current/checkout/verify', {session_id})
if(status === "succeeded")
Router.push('/store/checkout/complete')
else
setLoading(false)
})()
}, [])
if(loading)
return (
<>
<h2>Checkout</h2>
<p style={{textAlign: 'center'}}>Verifying your payment . . .</p>
</>
)
return (
<>
<h2>Checkout</h2>
<p style={{textAlign: 'center'}}>There was a problem with your payment.</p>
</>
)
}
Loading…
Cancel
Save