initial setup for stripe
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,7 +4,7 @@ build/
|
||||
dist/
|
||||
*.pyc
|
||||
*.pyo
|
||||
**/env/
|
||||
.env/
|
||||
env/
|
||||
.env
|
||||
mCovo.py
|
||||
|
||||
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
12
.idea/ax3Client.iml
generated
Normal file
12
.idea/ax3Client.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/ax3Client.iml" filepath="$PROJECT_DIR$/.idea/ax3Client.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
29
package-lock.json
generated
29
package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "client",
|
||||
"name": "novodraft-client",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "client",
|
||||
"name": "novodraft-client",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
@@ -14,6 +14,7 @@
|
||||
"bootstrap": "^5.3.2",
|
||||
"classnames": "^2.3.2",
|
||||
"docx": "^8.2.4",
|
||||
"dotenv": "^16.3.2",
|
||||
"firebase": "^10.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.8.0",
|
||||
@@ -24,6 +25,7 @@
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-spinners": "^0.13.8",
|
||||
"stripe": "^14.13.0",
|
||||
"uuid": "^9.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
@@ -7849,6 +7851,17 @@
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.3.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz",
|
||||
"integrity": "sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/motdotla/dotenv?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv-expand": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
|
||||
@@ -17275,6 +17288,18 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/stripe": {
|
||||
"version": "14.13.0",
|
||||
"resolved": "https://registry.npmjs.org/stripe/-/stripe-14.13.0.tgz",
|
||||
"integrity": "sha512-uLOfWtBUL1amJCTpKCXWrHntFOSaO2PWb/2hsxV/OlXLr0bz5MyU8IW1pFlmZqpw6hBqAW5Fad7Ty7xRxDYrzA==",
|
||||
"dependencies": {
|
||||
"@types/node": ">=8.1.0",
|
||||
"qs": "^6.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.*"
|
||||
}
|
||||
},
|
||||
"node_modules/style-loader": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"bootstrap": "^5.3.2",
|
||||
"classnames": "^2.3.2",
|
||||
"docx": "^8.2.4",
|
||||
"dotenv": "^16.3.2",
|
||||
"firebase": "^10.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.8.0",
|
||||
@@ -20,6 +21,7 @@
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-spinners": "^0.13.8",
|
||||
"stripe": "^14.13.0",
|
||||
"uuid": "^9.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
|
||||
@@ -17,25 +17,34 @@ import {
|
||||
isFormDataHasErrors,
|
||||
} from "../../Utils/Form";
|
||||
import { objectMap } from "../../Utils/Object";
|
||||
import { signupfields } from "../../Constants/Fields/SignupFields";
|
||||
import { signupFields } from "../../Constants/Fields/SignupFields";
|
||||
import { paymentfields } from "../../Constants/Fields/PaymentFields";
|
||||
import PaymentModal from "../Modals/PaymentModal";
|
||||
import "../../styles/signup.scss";
|
||||
import Stripe from 'stripe';
|
||||
|
||||
const SignupPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [notice, setNotice] = useState("");
|
||||
const fieldsChunkSize = 2;
|
||||
const [isBusy, setIsBusy] = useState(false);
|
||||
const [data, setData] = useState(getFormDataDefaults(signupfields));
|
||||
const [data, setData] = useState(getFormDataDefaults(signupFields));
|
||||
const [paymentData, setPaymentData] = useState(
|
||||
getFormDataDefaults(paymentfields)
|
||||
);
|
||||
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
||||
const modalText = "Description of monthly and annual subscriptions";
|
||||
|
||||
// this could also go in App.js if needed in the future
|
||||
const stripe = new Stripe('pk_test_51NNoasBi8p7FeGFrI5SfpM6HuNMoxwImx6NRKyKbgbt6OPxMxQDiZ9I1GqvDa9qUwB3D3jlJOng6MyjPppWofxzP00Exvr8dBy')
|
||||
|
||||
const apiUrl =
|
||||
process.env.NODE_ENV === "development"
|
||||
? process.env.REACT_APP_API_DEV
|
||||
: process.env.REACT_APP_API_PROD;
|
||||
|
||||
const handleChangeInput = (e, name) => {
|
||||
const newData = handleFormDataChange(e, name, data, signupfields);
|
||||
const newData = handleFormDataChange(e, name, data, signupFields);
|
||||
if (newData !== null) {
|
||||
setData(newData);
|
||||
}
|
||||
@@ -54,7 +63,7 @@ const SignupPage = () => {
|
||||
};
|
||||
|
||||
const validateData = () => {
|
||||
const newData = getValidatedFormData(data, signupfields);
|
||||
const newData = getValidatedFormData(data, signupFields);
|
||||
const hasErrors = isFormDataHasErrors(newData);
|
||||
setData(newData);
|
||||
return hasErrors ? null : objectMap(({ value }) => value, newData);
|
||||
@@ -125,10 +134,45 @@ const SignupPage = () => {
|
||||
setShowPaymentModal(true);
|
||||
};
|
||||
|
||||
async function handleStripeAuthorization(paymentDataValues) {
|
||||
async function handleStripeAuthorization(paymentDataValues, customerDataValues) {
|
||||
// Do: calls to Stripe API
|
||||
// if success, return some truthy value or
|
||||
// handle failure in handleSignup
|
||||
|
||||
let error = false;
|
||||
|
||||
const token = await stripe.tokens.create({
|
||||
card: {
|
||||
number: paymentDataValues.cardNumber,
|
||||
exp_month: paymentDataValues.cardExpirationMonth,
|
||||
exp_year: paymentDataValues.cardExpirationYear,
|
||||
cvc: paymentDataValues.cardCvvCode,
|
||||
name: paymentDataValues.cardFirstName + ' ' + paymentDataValues.cardLastName,
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
let response = await fetch(`${apiUrl}/create-subscription`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: 'monthly', // TODO: replace this with the user-chosen value later when we have the UI for it
|
||||
customerData: {
|
||||
email: customerDataValues.email,
|
||||
name: customerDataValues.firstName + ' ' + customerDataValues.lastName,
|
||||
},
|
||||
token,
|
||||
}),
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
error = true;
|
||||
}
|
||||
|
||||
return !error;
|
||||
}
|
||||
|
||||
const handleSignup = async (e) => {
|
||||
@@ -144,25 +188,29 @@ const SignupPage = () => {
|
||||
}
|
||||
|
||||
setIsBusy(true);
|
||||
const paymentSuccess = handleStripeAuthorization(paymentDataValues);
|
||||
if (paymentSuccess) {
|
||||
try {
|
||||
const userCredential = await createUserWithEmailAndPassword(
|
||||
auth,
|
||||
dataValues.email,
|
||||
dataValues.password
|
||||
);
|
||||
const user = userCredential.user;
|
||||
|
||||
try {
|
||||
const userCredential = await createUserWithEmailAndPassword(
|
||||
auth,
|
||||
dataValues.email,
|
||||
dataValues.password
|
||||
);
|
||||
const user = userCredential.user;
|
||||
|
||||
const paymentSuccess = await handleStripeAuthorization(paymentDataValues, dataValues);
|
||||
|
||||
if(paymentSuccess) {
|
||||
await saveUserData(user.uid, dataValues);
|
||||
navigate("/");
|
||||
} catch (error) {
|
||||
setIsBusy(false);
|
||||
setNotice(
|
||||
error.message || "Sorry, something went wrong. Please try again."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
//handle payment failure
|
||||
else {
|
||||
// handle payment failure
|
||||
}
|
||||
} catch (error) {
|
||||
setIsBusy(false);
|
||||
setNotice(
|
||||
error.message || "Sorry, something went wrong. Please try again."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -174,7 +222,7 @@ const SignupPage = () => {
|
||||
</div>
|
||||
<Form className="signup-form">
|
||||
{splitEvery(
|
||||
Object.keys(signupfields).slice(0, -2),
|
||||
Object.keys(signupFields).slice(0, -2),
|
||||
fieldsChunkSize
|
||||
).map((names, j) => (
|
||||
<Row key={`row${j}`}>
|
||||
@@ -189,11 +237,11 @@ const SignupPage = () => {
|
||||
message={data[name].message}
|
||||
label={
|
||||
data[name].value.length === 0
|
||||
? signupfields[name].label
|
||||
? signupFields[name].label
|
||||
: ""
|
||||
}
|
||||
type={signupfields[name].type}
|
||||
values={signupfields[name].values}
|
||||
type={signupFields[name].type}
|
||||
values={signupFields[name].values}
|
||||
disabled={isBusy}
|
||||
/>
|
||||
</Col>
|
||||
@@ -207,7 +255,7 @@ const SignupPage = () => {
|
||||
type="password"
|
||||
label={
|
||||
data.password.value.length === 0
|
||||
? signupfields.password.label
|
||||
? signupFields.password.label
|
||||
: ""
|
||||
}
|
||||
value={data.password.value}
|
||||
@@ -225,7 +273,7 @@ const SignupPage = () => {
|
||||
type="password"
|
||||
label={
|
||||
data.confirmPassword.value.length === 0
|
||||
? signupfields.confirmPassword.label
|
||||
? signupFields.confirmPassword.label
|
||||
: ""
|
||||
}
|
||||
value={data.confirmPassword.value}
|
||||
|
||||
@@ -23,4 +23,16 @@ export const paymentfields = {
|
||||
maxLength: 5,
|
||||
type: "cardCvvCode",
|
||||
},
|
||||
cardExpirationMonth: {
|
||||
required: true,
|
||||
label: "Card Expiration Month",
|
||||
maxLength: 2,
|
||||
type: "cardExpirationMonth",
|
||||
},
|
||||
cardExpirationYear: {
|
||||
required: true,
|
||||
label: "Card Expiration Year",
|
||||
maxLength: 4,
|
||||
type: "cardExpirationYear",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const signupfields = {
|
||||
export const signupFields = {
|
||||
firstName: { required: true, label: "First Name", maxLength: 45 },
|
||||
lastName: { required: true, label: "Last Name", maxLength: 45 },
|
||||
firm: { required: true, label: "Firm", minLength: 2, maxLength: 45 },
|
||||
Reference in New Issue
Block a user