From 076a5d651eeedb34ba59b9e5ceb05a0af6dc5d3c Mon Sep 17 00:00:00 2001 From: Ashelyn Dawn Date: Sat, 18 Apr 2020 01:08:34 -0600 Subject: [PATCH] Stripe can complete checkout - need to sort out order complete page --- .babelrc | 6 ++ api/orders.js | 72 +++++++++++++++ components/form/button.js | 10 ++- components/form/styles.module.css | 12 +++ db/models/order.js | 18 ++++ db/models/session.js | 12 +++ db/sql/1-tables.sql | 2 +- db/sql/2-views.sql | 35 +++++++- db/sql/3-functions.sql | 75 ++++++++++++++++ images/icons/paypal.svg | 44 ++++++++++ next.config.js | 6 +- package-lock.json | 140 ++++++++++++++++++++++++++++++ package.json | 3 + pages/store/category/[slug].js | 1 - pages/store/checkout/complete.js | 16 ++++ pages/store/checkout/index.js | 42 ++++++--- styles/layout.css | 6 ++ 17 files changed, 482 insertions(+), 18 deletions(-) create mode 100644 .babelrc create mode 100644 images/icons/paypal.svg create mode 100644 pages/store/checkout/complete.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..ed117ca --- /dev/null +++ b/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ "next/babel" ], + "plugins": [ + [ "inline-react-svg", {"svgo": { "plugins": [{"addClassesToSVGElement": {"className": "svgicon"}}]}} ] + ] +} diff --git a/api/orders.js b/api/orders.js index 4fd931b..88b8050 100644 --- a/api/orders.js +++ b/api/orders.js @@ -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}) +}) diff --git a/components/form/button.js b/components/form/button.js index 8a1ad86..edae49c 100644 --- a/components/form/button.js +++ b/components/form/button.js @@ -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) + return (
-
) } diff --git a/components/form/styles.module.css b/components/form/styles.module.css index 65d8632..f4290ef 100644 --- a/components/form/styles.module.css +++ b/components/form/styles.module.css @@ -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; +} diff --git a/db/models/order.js b/db/models/order.js index 3e969da..11f0fb1 100644 --- a/db/models/order.js +++ b/db/models/order.js @@ -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 + }) +} diff --git a/db/models/session.js b/db/models/session.js index 9205b3f..4c710d2 100644 --- a/db/models/session.js +++ b/db/models/session.js @@ -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]; +} diff --git a/db/sql/1-tables.sql b/db/sql/1-tables.sql index 8ed6853..be452be 100644 --- a/db/sql/1-tables.sql +++ b/db/sql/1-tables.sql @@ -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 ); diff --git a/db/sql/2-views.sql b/db/sql/2-views.sql index b2eb652..3aca3d2 100644 --- a/db/sql/2-views.sql +++ b/db/sql/2-views.sql @@ -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; diff --git a/db/sql/3-functions.sql b/db/sql/3-functions.sql index 7420909..58a221b 100644 --- a/db/sql/3-functions.sql +++ b/db/sql/3-functions.sql @@ -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$; diff --git a/images/icons/paypal.svg b/images/icons/paypal.svg new file mode 100644 index 0000000..fa945ca --- /dev/null +++ b/images/icons/paypal.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/next.config.js b/next.config.js index 9ece116..cd3e512 100644 --- a/next.config.js +++ b/next.config.js @@ -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 } }) diff --git a/package-lock.json b/package-lock.json index 9a0c543..68c8aad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 0a43cd5..6d8fe23 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/pages/store/category/[slug].js b/pages/store/category/[slug].js index 5ee8238..06edae3 100644 --- a/pages/store/category/[slug].js +++ b/pages/store/category/[slug].js @@ -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) diff --git a/pages/store/checkout/complete.js b/pages/store/checkout/complete.js new file mode 100644 index 0000000..df1eebb --- /dev/null +++ b/pages/store/checkout/complete.js @@ -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 ( +
+      {JSON.stringify(order, null, 2)}
+    
+ ) +} diff --git a/pages/store/checkout/index.js b/pages/store/checkout/index.js index ca147e7..0ad5c4b 100644 --- a/pages/store/checkout/index.js +++ b/pages/store/checkout/index.js @@ -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 ( <>

Checkout

@@ -155,14 +173,14 @@ export default function CheckoutSummary({order: _order}){ { total_price ? ( - <> - - - +
+ + +
) : (
- - + {/* Hit API endpoint to get Stripe session, and then redirect */} +
) } diff --git a/styles/layout.css b/styles/layout.css index 53bf691..e860a6a 100644 --- a/styles/layout.css +++ b/styles/layout.css @@ -110,3 +110,9 @@ table tr.strikethrough::after { top: 50%; border-top: solid 1px black; } + +.svgicon { + width: 24px; + height: 24px; + fill: currentColor; +}