Skip to content

Commit 3e02bf2

Browse files
Added Stripe payment API support
1 parent fbe2438 commit 3e02bf2

37 files changed

+1590
-243
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
# misc
1616
.DS_Store
17+
client/.env
1718
client/.env.local
1819
client/.env.development.local
1920
client/.env.test.local

client/package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6-
"@material-ui/core": "latest",
6+
"@material-ui/core": "^5.0.0-alpha.4",
77
"@material-ui/icons": "latest",
88
"@material-ui/lab": "^4.0.0-alpha.56",
9+
"@stripe/react-stripe-js": "^1.1.2",
10+
"@stripe/stripe-js": "^1.8.0",
911
"axios": "^0.19.2",
12+
"env-cmd": "^10.1.0",
1013
"fontsource-roboto": "^2.1.3",
1114
"js-base64": "^2.5.2",
1215
"js-cookie": "^2.2.1",
@@ -21,17 +24,19 @@
2124
"react-redux": "^7.2.0",
2225
"react-router-dom": "^5.2.0",
2326
"react-scripts": "latest",
27+
"react-stripe-checkout": "^2.6.3",
2428
"redux": "^4.0.5",
2529
"redux-form": "^8.3.6",
2630
"redux-form-material-ui": "^5.0.0-beta.3",
2731
"redux-thunk": "^2.3.0",
2832
"semantic-ui-css": "^2.4.1",
33+
"stripe": "^8.78.0",
2934
"styled-components": "^5.1.1",
3035
"swiper": "^5.4.2"
3136
},
3237
"scripts": {
33-
"start": "react-scripts start",
34-
"build": "react-scripts build",
38+
"start": "env-cmd -f .env react-scripts start",
39+
"build": "env-cmd -f .env react-scripts build",
3540
"test": "react-scripts test",
3641
"eject": "react-scripts eject"
3742
},
@@ -51,6 +56,7 @@
5156
]
5257
},
5358
"devDependencies": {
59+
"dotenv": "^8.2.0",
5460
"semantic-ui-react": "^0.88.2"
5561
}
5662
}

client/src/actions/index.js

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ import {
88
LOAD_FILTER_PRODUCTS,
99
LOAD_FILTER_ATTRIBUTES,
1010
INTERNAL_SERVER_ERROR_CODE,
11-
BAD_REQUEST_ERROR_CODE, SAVE_QUERY_STATE, SAVE_QUERY_STATUS, SHIPPING_ADDRESS_CONFIRMED,
11+
BAD_REQUEST_ERROR_CODE,
12+
SAVE_QUERY_STATUS,
13+
SHIPPING_ADDRESS_CONFIRMED,
14+
PAYMENT_INFO_CONFIRMED,
1215
} from './types';
13-
import authApi from "../api/authServiceApi";
1416
import history from "../history";
1517
import {Base64} from 'js-base64';
1618
import Cookies from 'js-cookie';
17-
import commonServiceApi from "../api/commonServiceApi";
1819
import log from "loglevel";
20+
import {commonServiceAPI, authServiceAPI, paymentServiceAPI} from "../api/service_api";
21+
import axios from 'axios';
1922

2023
export const setTokenFromCookie = tokenId => {
2124
log.info(`[ACTION]: setTokenFromCookie tokenId = ${tokenId}`)
@@ -33,13 +36,21 @@ export const setShippingAddress = payload => {
3336
}
3437
}
3538

39+
export const setPaymentInfo = payload => {
40+
log.info(`[ACTION]: setPaymentInfo payload = ${JSON.stringify(payload)}`)
41+
return {
42+
type: PAYMENT_INFO_CONFIRMED,
43+
payload: payload
44+
}
45+
}
46+
3647
export const signIn = formValues => async (dispatch) => {
3748
log.info(`[ACTION]: signIn API is invoked formValues = ${formValues}`)
3849

3950
const hash = Base64.encode(`${formValues.Username}:${formValues.Password}`);
40-
authApi.defaults.headers.common['Authorization'] = `Basic ${hash}`
41-
authApi.defaults.timeout = 5000;
42-
const response = await authApi.post('/authenticate').catch(err => {
51+
authServiceAPI.defaults.headers.common['Authorization'] = `Basic ${hash}`
52+
authServiceAPI.defaults.timeout = 5000;
53+
const response = await authServiceAPI.post('/authenticate').catch(err => {
4354
log.info(`[ACTION]: dispatch HANDLE_SIGN_IN_ERROR err.message = ${err.message}`)
4455
dispatch({type: HANDLE_SIGN_IN_ERROR, payload: err.message});
4556
});
@@ -67,8 +78,8 @@ export const signOut = () => {
6778

6879
export const signUp = formValues => async (dispatch) => {
6980
log.info(`[ACTION]: signUp API = ${JSON.stringify(formValues)}.`)
70-
authApi.defaults.timeout = 15000;
71-
const response = await authApi.post('/signup', {
81+
authServiceAPI.defaults.timeout = 15000;
82+
const response = await authServiceAPI.post('/signup', {
7283
'username': formValues.Username,
7384
'password': formValues.Password,
7485
'firstname': formValues.FirstName,
@@ -92,14 +103,42 @@ export const signUp = formValues => async (dispatch) => {
92103
}
93104
}
94105

106+
export const sendPaymentToken = (token) => async dispatch => {
107+
log.info(`Token = ${JSON.stringify(token)}`)
108+
let config = {
109+
method: 'post',
110+
url: 'http://localhost:9050/payment',
111+
headers: {
112+
'Content-Type': 'application/json'
113+
},
114+
data : JSON.stringify(token)
115+
};
116+
117+
axios(config)
118+
.then(function (response) {
119+
console.log(JSON.stringify(response.data));
120+
})
121+
.catch(function (error) {
122+
console.log(error);
123+
});
124+
125+
// const response = await paymentServiceAPI.get("/payment", config).catch(err => {
126+
// log.info(`[ACTION]: unable to fetch response for API = ${err}`)
127+
// // dispatch({type: type, payload: {isLoading: false, statusCode: INTERNAL_SERVER_ERROR_CODE}});
128+
// // responseError = true
129+
// });
130+
// log.info(`response = ${JSON.stringify(response)}`)
131+
}
132+
133+
95134
export const getDataViaAPI = (type, uri) => async dispatch => {
96135
log.info(`[ACTION]: invokeAndDispatchAPIData Calling API = ${uri}.`)
97136

98137
if (uri) {
99-
commonServiceApi.defaults.timeout = 15000;
138+
commonServiceAPI.defaults.timeout = 15000;
100139
uri = uri.replace(/\s/g, '')
101140
let responseError = false
102-
const response = await commonServiceApi.get(uri)
141+
const response = await commonServiceAPI.get(uri)
103142
.catch(err => {
104143
log.info(`[ACTION]: unable to fetch response for API = ${uri}`)
105144
dispatch({type: type, payload: {isLoading: false, statusCode: INTERNAL_SERVER_ERROR_CODE}});
@@ -133,7 +172,7 @@ export const loadFilterAttributes = filterQuery => async dispatch => {
133172
if (filterQuery) {
134173
let removedSpacesFromFilterQuery = filterQuery.replace(/\s/g, '')
135174
let uri = `/filter${removedSpacesFromFilterQuery}`
136-
const response = await commonServiceApi.get(uri);
175+
const response = await commonServiceAPI.get(uri);
137176
if (response != null) {
138177
log.trace(`[ACTION]: Filter = ${JSON.stringify(response.data)}`)
139178

client/src/actions/types.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export const ADD_SELECTED_CATEGORY = "ADD_SELECTED_CATEGORY";
2525
export const REMOVE_SELECTED_CATEGORY = "REMOVE_SELECTED_CATEGORY";
2626
export const SAVE_SORT_LIST = "SAVE_SORT_LIST";
2727
export const SHIPPING_ADDRESS_CONFIRMED = "SHIPPING_ADDRESS_CONFIRMED";
28+
export const SHIPPING_OPTION_CONFIRMED = "SHIPPING_OPTION_CONFIRMED";
29+
export const PAYMENT_INFO_CONFIRMED = "PAYMENT_INFO_CONFIRMED";
2830

2931
// js cookie IDs
3032
export const HANDLE_TOKEN_ID = "HANDLE_TOKEN_ID";

client/src/api/authServiceApi.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

client/src/api/commonServiceApi.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

client/src/api/service_api.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import axios from 'axios';
2+
3+
export const paymentServiceAPI = axios.create({
4+
baseURL: 'http://localhost:6000'
5+
})
6+
7+
export const authServiceAPI = axios.create({
8+
baseURL: 'http://localhost:7000'
9+
})
10+
11+
export const commonServiceAPI = axios.create({
12+
baseURL: 'http://localhost:9000'
13+
})

client/src/components/routes/checkout/checkout.js

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import {Paper, Grid, Hidden} from "@material-ui/core";
44
import PriceDetails from "../priceDetails";
55
import ShippingAddress from "./shippingAddress";
66
import ShippingOptions from "./shippingOptions";
7-
import {withStyles} from '@material-ui/core/styles';
7+
import {makeStyles, withStyles} from '@material-ui/core/styles';
88
import MuiAccordion from '@material-ui/core/Accordion';
99
import MuiAccordionSummary from '@material-ui/core/AccordionSummary';
1010
import MuiAccordionDetails from '@material-ui/core/AccordionDetails';
1111
import {useSelector} from "react-redux";
12+
import {Elements, ElementsConsumer} from "@stripe/react-stripe-js";
13+
import {loadStripe} from "@stripe/stripe-js/pure";
14+
import Payment from "./payment"
15+
import PaymentButton from "./paymentButton";
1216

1317
const checkoutBgColor = "#80808033"
1418

@@ -54,11 +58,25 @@ const AccordionDetails = withStyles((theme) => ({
5458
},
5559
}))(MuiAccordionDetails);
5660

61+
const useGridStyles = makeStyles((theme) => ({
62+
root: {
63+
backgroundColor: checkoutBgColor,
64+
paddingBottom: "5rem",
65+
[theme.breakpoints.down('md')]: {
66+
height: "-webkit-fill-available"
67+
}
68+
},
69+
}));
70+
5771
const shippingAddressPanel = 'shipAddrPanel'
5872
const shippingOptionPanel = 'shipOptPanel'
73+
const paymentPanel = "paymentPanel"
74+
const stripePromise = loadStripe("pk_test_51H805OJVXUotWOgTatoPJeLPXI2ZehJ0s4QSQK0WjEJKx1PNH3mJxeUkAQOSjRpusTVcxyh5bDuulBildNrp3MWn005xEkAdJ4")
5975

6076
function Checkout() {
6177
const shippingAddress = useSelector(state => state.shippingAddressReducer)
78+
const shippingOption = useSelector(state => state.shippingOptionReducer)
79+
const classes = useGridStyles();
6280

6381
const renderTitle = title => {
6482
return (
@@ -75,12 +93,9 @@ function Checkout() {
7593
log.info("[Checkout] Rendering Checkout Component.")
7694

7795
return (
78-
<Grid container justify={"center"} style={{
79-
backgroundColor: checkoutBgColor,
80-
paddingBottom: "5rem"
81-
}}>
96+
<Grid container justify={"center"} classes={{root: classes.root}}>
8297

83-
<Grid item xs={12} sm={11} md={7}>
98+
<Grid item xs={12} sm={11} md={5}>
8499

85100
<Accordion square expanded>
86101
<AccordionSummary aria-controls={`${shippingAddressPanel}-content`}
@@ -102,6 +117,22 @@ function Checkout() {
102117
</AccordionDetails>
103118
</Accordion>
104119

120+
{/*<Accordion square expanded={true}>*/}
121+
{/* <AccordionSummary aria-controls={`${paymentPanel}-content`}*/}
122+
{/* id={`${paymentPanel}-header`}>*/}
123+
{/* {renderTitle("Payment")}*/}
124+
{/* </AccordionSummary>*/}
125+
{/* <AccordionDetails>*/}
126+
{/* <Elements stripe={stripePromise}>*/}
127+
{/* <ElementsConsumer>*/}
128+
{/* {({elements, stripe}) => (*/}
129+
{/* <Payment elements={elements} stripe={stripe} />*/}
130+
{/* )}*/}
131+
{/* </ElementsConsumer>*/}
132+
{/* </Elements>*/}
133+
{/* </AccordionDetails>*/}
134+
{/*</Accordion>*/}
135+
105136
</Grid>
106137

107138
<Hidden smDown>
@@ -117,9 +148,13 @@ function Checkout() {
117148
</Hidden>
118149

119150
<Hidden xsDown>
120-
<Grid item sm={11} md={3} style={{height: "fit-content", marginTop: "1rem", position: "sticky", top: 80}}>
151+
<Grid item sm={11} md={3}
152+
style={{height: "fit-content", marginTop: "1rem", position: "sticky", top: 80}}>
121153
<Paper square style={{width: "inherit"}}>
122-
<PriceDetails buttonName="PLACE ORDER"/>
154+
<PriceDetails buttonName="PLACE ORDER"
155+
disabled={!shippingOption.submitted}
156+
/>
157+
<PaymentButton/>
123158
</Paper>
124159
</Grid>
125160
</Hidden>

client/src/components/routes/checkout/continueButton.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function ContinueButton(props) {
1717
<Grid item container justify="flex-end" style={{padding: "30px 40px 30px 0"}}>
1818
<Button variant="contained" size="large" style={{
1919
...continueButtonStyle, width: "27%"
20-
}} type={props.type} disabled={props.action}>
20+
}} type={props.type} disabled={props.action} onClick={props.buttonHandler}>
2121
CONTINUE
2222
</Button>
2323
</Grid>
@@ -29,7 +29,7 @@ function ContinueButton(props) {
2929
<Grid item container justify="center" style={{paddingTop: 30, margin: 0}}>
3030
<Button variant="contained" size="large" style={{
3131
...continueButtonStyle, width: "85%"
32-
}} type={props.type} disabled={props.action}>
32+
}} type={props.type} disabled={props.action} onClick={props.buttonHandler}>
3333
CONTINUE
3434
</Button>
3535
</Grid>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React, {Component} from 'react';
2+
import log from 'loglevel';
3+
import {MenuItem, Grid, Card, CardContent, Divider} from "@material-ui/core";
4+
import ContinueButton from "./continueButton";
5+
import {withStyles} from "@material-ui/core/styles";
6+
import {Field, reduxForm} from "redux-form";
7+
import {connect} from "react-redux";
8+
import {TextField} from "redux-form-material-ui"
9+
import {stateCodes} from "../../../constants/stateCodes";
10+
import DeleteIcon from '@material-ui/icons/Delete';
11+
import Button from '@material-ui/core/Button';
12+
import {setShippingAddress} from "../../../actions"
13+
import IconButton from '@material-ui/core/IconButton';
14+
import Modal from "../../ui/modal";
15+
import {ModalConfirmation} from "../../ui/modalConfirmation";
16+
import {labelTextFieldStyles, textFieldStyles} from "../../../styles/js/formStyles";
17+
18+
export const FormTextField = (props) => {
19+
20+
const renderReduxFormField = () => {
21+
let required = true
22+
if (!props.validationRules) {
23+
required = false
24+
}
25+
26+
return (
27+
<Field
28+
name={props.name}
29+
label={props.label}
30+
component={TextField}
31+
variant="outlined"
32+
fullWidth
33+
size="medium"
34+
style={textFieldStyles}
35+
validate={props.validationRules}
36+
required={required}
37+
/>
38+
)
39+
}
40+
41+
const renderShrinkLabelReduxFormField = () => {
42+
return (
43+
<Field
44+
name={props.name}
45+
label={props.label}
46+
component={TextField}
47+
variant="outlined"
48+
fullWidth
49+
size="medium"
50+
style={labelTextFieldStyles}
51+
placeholder={props.placeholder}
52+
InputLabelProps={{shrink: true}}
53+
validate={props.validationRules}
54+
required
55+
InputProps={props.inputProps}
56+
/>
57+
)
58+
}
59+
60+
return (
61+
<>
62+
{props.fixedLabel ? renderShrinkLabelReduxFormField(): renderReduxFormField()}
63+
</>
64+
)
65+
}

0 commit comments

Comments
 (0)