initial setup for stripe

This commit is contained in:
Jacob
2024-01-22 17:25:09 -05:00
parent 8213091342
commit d9e49487a3
11 changed files with 155 additions and 31 deletions

2
.gitignore vendored
View File

@@ -4,7 +4,7 @@ build/
dist/
*.pyc
*.pyo
**/env/
.env/
env/
.env
mCovo.py

5
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

12
.idea/ax3Client.iml generated Normal file
View 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>

View 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
View 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
View 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
View File

@@ -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",

View File

@@ -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"
},

View File

@@ -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}

View File

@@ -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",
},
};

View File

@@ -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 },