@springmicro/cart 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +9891 -9813
- package/dist/index.umd.cjs +66 -92
- package/package.json +3 -3
- package/src/checkout/{CheckoutList.css → ReviewCartAndCalculateTaxes.css} +1 -1
- package/src/checkout/ReviewCartAndCalculateTaxes.tsx +366 -0
- package/src/checkout/components/AddCard.tsx +267 -0
- package/src/checkout/components/Address.tsx +7 -11
- package/src/checkout/components/CartList.tsx +151 -0
- package/src/checkout/components/CartProductCard.css +1 -1
- package/src/checkout/components/CartProductCard.tsx +28 -18
- package/src/checkout/components/Invoice.tsx +145 -0
- package/src/checkout/components/StatusBar.tsx +32 -0
- package/src/checkout/index.tsx +161 -0
- package/src/index.ts +7 -6
- package/src/checkout/CheckoutList.tsx +0 -272
- package/src/checkout/components/Billing.tsx +0 -353
- package/src/checkout/components/Order.tsx +0 -95
- package/src/checkout/components/index.tsx +0 -104
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
import "./CheckoutList.css";
|
|
2
|
-
import { useStore } from "@nanostores/react";
|
|
3
|
-
import { CartProductCard } from "./components/CartProductCard";
|
|
4
|
-
import React from "react";
|
|
5
|
-
import { Button, Tooltip, Typography } from "@mui/material";
|
|
6
|
-
import {
|
|
7
|
-
cartStore,
|
|
8
|
-
clearCart,
|
|
9
|
-
setOrder as cartSetOrder,
|
|
10
|
-
} from "../utils/storage";
|
|
11
|
-
import Checkout from "./components";
|
|
12
|
-
|
|
13
|
-
export default function CheckoutList({
|
|
14
|
-
apiBaseUrl,
|
|
15
|
-
emptyCartLink = "",
|
|
16
|
-
disableProductLink = false,
|
|
17
|
-
tax_rate_id,
|
|
18
|
-
}: {
|
|
19
|
-
apiBaseUrl: string;
|
|
20
|
-
emptyCartLink?: string;
|
|
21
|
-
disableProductLink?: boolean;
|
|
22
|
-
tax_rate_id?: number | string;
|
|
23
|
-
}) {
|
|
24
|
-
const cart = JSON.parse(useStore(cartStore));
|
|
25
|
-
|
|
26
|
-
const [prices, setPrices] = React.useState({});
|
|
27
|
-
const [total, setTotal] = React.useState("Loading prices...");
|
|
28
|
-
|
|
29
|
-
const [status, setStatus] = React.useState(0);
|
|
30
|
-
const [order, setOrder] = React.useState(undefined);
|
|
31
|
-
|
|
32
|
-
React.useEffect(() => {
|
|
33
|
-
const params = new URLSearchParams(window.location.search);
|
|
34
|
-
|
|
35
|
-
const sr = params.get("showReceipt");
|
|
36
|
-
const oid = params.get("orderId");
|
|
37
|
-
const or = params.get("orderRef");
|
|
38
|
-
if (oid && or) {
|
|
39
|
-
fetch(`${apiBaseUrl}/api/ecommerce/orders/${oid}/reference/${or}`).then(
|
|
40
|
-
async (res) =>
|
|
41
|
-
await res.json().then((order) => {
|
|
42
|
-
setOrder(order);
|
|
43
|
-
setStatus(
|
|
44
|
-
order.charge_id ||
|
|
45
|
-
!["pending", "awaiting_payment"].includes(order.status)
|
|
46
|
-
? 2
|
|
47
|
-
: 1
|
|
48
|
-
);
|
|
49
|
-
})
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
}, []);
|
|
53
|
-
|
|
54
|
-
// build pricing list
|
|
55
|
-
React.useEffect(() => {
|
|
56
|
-
// filter out prices that have already been queried
|
|
57
|
-
const pricesToGet = cart.items
|
|
58
|
-
.map((c) => c.price_id)
|
|
59
|
-
.filter(
|
|
60
|
-
(pId) => Object.keys(prices).findIndex((pKey) => pKey == pId) === -1
|
|
61
|
-
);
|
|
62
|
-
if (pricesToGet.length === 0) return;
|
|
63
|
-
|
|
64
|
-
const url = `${apiBaseUrl}/api/ecommerce/price?filter={'ids':[${pricesToGet.join(
|
|
65
|
-
","
|
|
66
|
-
)}]}`;
|
|
67
|
-
fetch(url, {
|
|
68
|
-
method: "GET",
|
|
69
|
-
headers: {
|
|
70
|
-
"Content-Type": "application/json",
|
|
71
|
-
},
|
|
72
|
-
})
|
|
73
|
-
.then((res) =>
|
|
74
|
-
res.json().then((data) => {
|
|
75
|
-
const pricingData = { ...prices };
|
|
76
|
-
|
|
77
|
-
data.forEach((p) => {
|
|
78
|
-
pricingData[p.id] = p;
|
|
79
|
-
});
|
|
80
|
-
setPrices(pricingData);
|
|
81
|
-
})
|
|
82
|
-
)
|
|
83
|
-
.catch(() => {});
|
|
84
|
-
}, [cart]);
|
|
85
|
-
|
|
86
|
-
React.useEffect(() => {
|
|
87
|
-
setTotal(
|
|
88
|
-
`$${(
|
|
89
|
-
cart.items
|
|
90
|
-
.map((product) => prices[product.price_id]?.unit_amount)
|
|
91
|
-
.reduce((p, c) => (c ? p + c : p), 0) / 100
|
|
92
|
-
).toFixed(2)}`
|
|
93
|
-
);
|
|
94
|
-
}, [cart, prices]);
|
|
95
|
-
|
|
96
|
-
if (status === 0 && cart.items.length === 0)
|
|
97
|
-
return (
|
|
98
|
-
<div
|
|
99
|
-
style={{
|
|
100
|
-
width: "100%",
|
|
101
|
-
display: "flex",
|
|
102
|
-
alignItems: "center",
|
|
103
|
-
flexDirection: "column",
|
|
104
|
-
gap: 8,
|
|
105
|
-
}}
|
|
106
|
-
>
|
|
107
|
-
<Typography style={{ fontSize: 32, fontWeight: 600 }}>
|
|
108
|
-
Cart is empty
|
|
109
|
-
</Typography>
|
|
110
|
-
<a className="shopping-button" href={`/${emptyCartLink}`}>
|
|
111
|
-
<Typography>BACK</Typography>
|
|
112
|
-
</a>
|
|
113
|
-
</div>
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
return (
|
|
117
|
-
<div>
|
|
118
|
-
<StatusBar status={status} />
|
|
119
|
-
<div>
|
|
120
|
-
{status === 0 && (
|
|
121
|
-
<CartSection
|
|
122
|
-
total={total}
|
|
123
|
-
setStatus={setStatus}
|
|
124
|
-
cart={cart}
|
|
125
|
-
prices={prices}
|
|
126
|
-
apiBaseUrl={apiBaseUrl}
|
|
127
|
-
setOrder={setOrder}
|
|
128
|
-
disableProductLink={disableProductLink}
|
|
129
|
-
tax_rate_id={tax_rate_id}
|
|
130
|
-
/>
|
|
131
|
-
)}
|
|
132
|
-
{status > 0 && order && (
|
|
133
|
-
<Checkout
|
|
134
|
-
apiBaseUrl={apiBaseUrl}
|
|
135
|
-
order={order}
|
|
136
|
-
onPlacement={() => {
|
|
137
|
-
clearCart();
|
|
138
|
-
setStatus(2);
|
|
139
|
-
}}
|
|
140
|
-
/>
|
|
141
|
-
)}
|
|
142
|
-
</div>
|
|
143
|
-
</div>
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function CartSection({
|
|
148
|
-
total,
|
|
149
|
-
setStatus,
|
|
150
|
-
cart,
|
|
151
|
-
prices,
|
|
152
|
-
apiBaseUrl,
|
|
153
|
-
tax_rate_id,
|
|
154
|
-
setOrder,
|
|
155
|
-
disableProductLink,
|
|
156
|
-
}) {
|
|
157
|
-
function createOrder() {
|
|
158
|
-
// If an order has already been created and hasn't been changed, get the previous order.
|
|
159
|
-
if (cart.order && cart.order.id && cart.order.reference) {
|
|
160
|
-
fetch(
|
|
161
|
-
`${apiBaseUrl}/api/ecommerce/orders/${cart.order.id}/reference/${cart.order.reference}`
|
|
162
|
-
).then((res) =>
|
|
163
|
-
res.json().then((order) => {
|
|
164
|
-
setOrder(order);
|
|
165
|
-
const currentUrl = new URL(window.location.href);
|
|
166
|
-
currentUrl.searchParams.set("orderId", order.id);
|
|
167
|
-
currentUrl.searchParams.set("orderRef", order.reference);
|
|
168
|
-
window.history.pushState({}, "", currentUrl);
|
|
169
|
-
setStatus(1);
|
|
170
|
-
})
|
|
171
|
-
);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Otherwise, create a new order.
|
|
176
|
-
fetch(apiBaseUrl + "/api/ecommerce/orders", {
|
|
177
|
-
method: "POST",
|
|
178
|
-
headers: {
|
|
179
|
-
"Content-Type": "application/json",
|
|
180
|
-
},
|
|
181
|
-
body: JSON.stringify({
|
|
182
|
-
customer_id: 1,
|
|
183
|
-
cart_data: {
|
|
184
|
-
tax_rate_id:
|
|
185
|
-
typeof tax_rate_id === "string"
|
|
186
|
-
? Number.parseInt(tax_rate_id)
|
|
187
|
-
: tax_rate_id,
|
|
188
|
-
items: cart.items.map((li) => ({
|
|
189
|
-
...li,
|
|
190
|
-
name: undefined,
|
|
191
|
-
image: undefined,
|
|
192
|
-
})),
|
|
193
|
-
},
|
|
194
|
-
}),
|
|
195
|
-
}).then((res) =>
|
|
196
|
-
res.json().then((order) => {
|
|
197
|
-
if (!order) throw "Missing order";
|
|
198
|
-
setOrder(order);
|
|
199
|
-
cartSetOrder({ id: order.id, reference: order.reference });
|
|
200
|
-
const currentUrl = new URL(window.location.href);
|
|
201
|
-
currentUrl.searchParams.set("orderId", order.id);
|
|
202
|
-
currentUrl.searchParams.set("orderRef", order.reference);
|
|
203
|
-
window.history.pushState({}, "", currentUrl);
|
|
204
|
-
setStatus(1);
|
|
205
|
-
})
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return (
|
|
210
|
-
<div style={{ display: "flex", justifyContent: "center" }}>
|
|
211
|
-
<div
|
|
212
|
-
style={{
|
|
213
|
-
display: "flex",
|
|
214
|
-
flexDirection: "column",
|
|
215
|
-
width: 600,
|
|
216
|
-
alignItems: "center",
|
|
217
|
-
boxShadow: "0px 2px 5px 2px #dfdfdfff",
|
|
218
|
-
margin: "4px",
|
|
219
|
-
padding: "2rem 0",
|
|
220
|
-
borderRadius: "8px",
|
|
221
|
-
}}
|
|
222
|
-
>
|
|
223
|
-
<Typography style={{ fontSize: "30px", fontWeight: "bold" }}>
|
|
224
|
-
Checkout
|
|
225
|
-
</Typography>
|
|
226
|
-
<Tooltip title="Total is pre-tax and includes the first payment for recurring payments.">
|
|
227
|
-
<Typography style={{ fontSize: "20px" }}>Total: {total}</Typography>
|
|
228
|
-
</Tooltip>
|
|
229
|
-
{/* <Button variant="contained" sx={{ mt: 2 }} onClick={createOrder}>
|
|
230
|
-
Confirm Order
|
|
231
|
-
</Button> */}
|
|
232
|
-
<div className="checkout-list">
|
|
233
|
-
{cart.items.map((p, i) => (
|
|
234
|
-
<CartProductCard
|
|
235
|
-
product={p}
|
|
236
|
-
i={i}
|
|
237
|
-
price={prices[p.price_id]}
|
|
238
|
-
disableProductLink={disableProductLink}
|
|
239
|
-
/>
|
|
240
|
-
))}
|
|
241
|
-
</div>
|
|
242
|
-
<Button variant="contained" sx={{ mt: 2 }} onClick={createOrder}>
|
|
243
|
-
Confirm Order
|
|
244
|
-
</Button>
|
|
245
|
-
</div>
|
|
246
|
-
</div>
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function StatusBar({ status }) {
|
|
251
|
-
return (
|
|
252
|
-
<div
|
|
253
|
-
style={{
|
|
254
|
-
display: "flex",
|
|
255
|
-
alignItems: "center",
|
|
256
|
-
flexDirection: "column",
|
|
257
|
-
}}
|
|
258
|
-
>
|
|
259
|
-
<div id="status-bar">
|
|
260
|
-
<Typography className="status-text active">REVIEW</Typography>
|
|
261
|
-
<div className={"status-bar" + (status > 0 ? " active" : "")}></div>
|
|
262
|
-
<Typography className={"status-text" + (status > 0 ? " active" : "")}>
|
|
263
|
-
PAYMENT
|
|
264
|
-
</Typography>
|
|
265
|
-
<div className={"status-bar" + (status > 1 ? " active" : "")}></div>
|
|
266
|
-
<Typography className={"status-text" + (status > 1 ? " active" : "")}>
|
|
267
|
-
ORDER PLACED
|
|
268
|
-
</Typography>
|
|
269
|
-
</div>
|
|
270
|
-
</div>
|
|
271
|
-
);
|
|
272
|
-
}
|
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
import { useFormik, FormikProps } from "formik";
|
|
2
|
-
import * as Yup from "yup";
|
|
3
|
-
import { ChangeEventHandler, useEffect } from "react";
|
|
4
|
-
import { PropsWithChildren, useState } from "react";
|
|
5
|
-
import {
|
|
6
|
-
AddressCityField,
|
|
7
|
-
AddressOrganizationNameField,
|
|
8
|
-
AddressCountryField,
|
|
9
|
-
AddressPostalCodeField,
|
|
10
|
-
AddressRegionField,
|
|
11
|
-
AddressStreetField,
|
|
12
|
-
AddressEmailField,
|
|
13
|
-
AddressValues,
|
|
14
|
-
} from "./Address";
|
|
15
|
-
import {
|
|
16
|
-
allCountries,
|
|
17
|
-
allCountriesReverse,
|
|
18
|
-
getPostalCodeDefault,
|
|
19
|
-
} from "@springmicro/utils/address";
|
|
20
|
-
import { Box, Button, Container, TextField, Typography } from "@mui/material";
|
|
21
|
-
import Cards from "react-credit-cards-2";
|
|
22
|
-
import { FocusEventHandler } from "react";
|
|
23
|
-
import {
|
|
24
|
-
formatCreditCardNumber,
|
|
25
|
-
formatCVC,
|
|
26
|
-
formatExpirationDate,
|
|
27
|
-
splitMonthYear,
|
|
28
|
-
expiryDateHasNotPassed,
|
|
29
|
-
} from "@springmicro/utils/payment";
|
|
30
|
-
|
|
31
|
-
// moved to index.ts
|
|
32
|
-
// import "react-credit-cards-2/dist/es/styles-compiled.css";
|
|
33
|
-
import { StripeLogoLink } from "./ProviderLogos";
|
|
34
|
-
|
|
35
|
-
type CCFocus = "number" | "name" | "cvc" | "expiry";
|
|
36
|
-
|
|
37
|
-
export type CreditCardValues = {
|
|
38
|
-
cvc: string;
|
|
39
|
-
expiry: string;
|
|
40
|
-
focus: CCFocus;
|
|
41
|
-
name: string;
|
|
42
|
-
number: string;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export type AddCardProps = {
|
|
46
|
-
contact?: {
|
|
47
|
-
organization?: string;
|
|
48
|
-
email: string;
|
|
49
|
-
first_name: string;
|
|
50
|
-
last_name: string;
|
|
51
|
-
};
|
|
52
|
-
stacked?: boolean;
|
|
53
|
-
error?: string;
|
|
54
|
-
address?: boolean;
|
|
55
|
-
onSubmit?: (
|
|
56
|
-
values: CreditCardValues & Partial<AddressValues>
|
|
57
|
-
) => Promise<any>;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export const AddCard = ({
|
|
61
|
-
contact,
|
|
62
|
-
error,
|
|
63
|
-
stacked = false,
|
|
64
|
-
address = true,
|
|
65
|
-
onSubmit,
|
|
66
|
-
}: AddCardProps) => {
|
|
67
|
-
const [disabled, setDisabled] = useState(true);
|
|
68
|
-
|
|
69
|
-
const initialValuesBase = {
|
|
70
|
-
cvc: "",
|
|
71
|
-
expiry: "",
|
|
72
|
-
focus: "number",
|
|
73
|
-
name: contact ? `${contact.first_name} ${contact.last_name}` : "",
|
|
74
|
-
number: "",
|
|
75
|
-
} as CreditCardValues;
|
|
76
|
-
const initialCountry = "US";
|
|
77
|
-
const initialValuesAddress = {
|
|
78
|
-
country: initialCountry ? allCountries[initialCountry] : "",
|
|
79
|
-
email: contact?.email ?? "",
|
|
80
|
-
region: "",
|
|
81
|
-
line1: "",
|
|
82
|
-
line2: "",
|
|
83
|
-
organization: contact?.organization ?? "",
|
|
84
|
-
city: "",
|
|
85
|
-
postal_code: getPostalCodeDefault(initialCountry),
|
|
86
|
-
} as AddressValues;
|
|
87
|
-
|
|
88
|
-
// const initialValues = { ...initialValuesBase, ...initialValuesAddress }
|
|
89
|
-
const initialValues = address
|
|
90
|
-
? { ...initialValuesBase, ...initialValuesAddress }
|
|
91
|
-
: initialValuesBase;
|
|
92
|
-
|
|
93
|
-
const validationSchemaBase = {
|
|
94
|
-
expiry: Yup.string()
|
|
95
|
-
// .length(7, "Expiry date must be of the format MM/YY.")
|
|
96
|
-
.test("exp-date", "Expiry date has already passed.", (string) => {
|
|
97
|
-
if (string?.length === 5) {
|
|
98
|
-
if (string.indexOf("/") !== 2) return false;
|
|
99
|
-
const [month, year] = splitMonthYear(string);
|
|
100
|
-
return expiryDateHasNotPassed(month, year);
|
|
101
|
-
} else {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
.required("Expiry date is required."),
|
|
106
|
-
number: Yup.string().required("Card Number is required."),
|
|
107
|
-
name: Yup.string().required("Cardholder Name is required."),
|
|
108
|
-
cvc: Yup.string().required("CVC is required."),
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const validationSchemaAddress = {
|
|
112
|
-
country: Yup.string().required("Country is required."),
|
|
113
|
-
email: Yup.string().email().required("Email is required."),
|
|
114
|
-
line1: Yup.string().required("Street Address 1 is required."),
|
|
115
|
-
city: Yup.string().required("City is required."),
|
|
116
|
-
postal_code: Yup.string().required("Postal Code is required."),
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const formik = useFormik({
|
|
120
|
-
initialValues,
|
|
121
|
-
validationSchema: Yup.object(
|
|
122
|
-
address
|
|
123
|
-
? {
|
|
124
|
-
...validationSchemaBase,
|
|
125
|
-
...validationSchemaAddress,
|
|
126
|
-
}
|
|
127
|
-
: validationSchemaBase
|
|
128
|
-
),
|
|
129
|
-
onSubmit: async (v, helpers) => {
|
|
130
|
-
setDisabled(true);
|
|
131
|
-
const values = { ...v };
|
|
132
|
-
if (disabled) {
|
|
133
|
-
setDisabled(false);
|
|
134
|
-
throw new Error("Attempted to submit an invalid form.");
|
|
135
|
-
}
|
|
136
|
-
if (onSubmit) {
|
|
137
|
-
if (address) {
|
|
138
|
-
// match backend
|
|
139
|
-
// @ts-expect-error value exists
|
|
140
|
-
values["street_address"] = values.line2
|
|
141
|
-
? // @ts-expect-error value exists
|
|
142
|
-
`${values.line1}\n${values.line2}`
|
|
143
|
-
: // @ts-expect-error value exists
|
|
144
|
-
values.line1;
|
|
145
|
-
// @ts-expect-error value exists
|
|
146
|
-
values["locality"] = values.city;
|
|
147
|
-
// @ts-expect-error value exists
|
|
148
|
-
delete values.line1;
|
|
149
|
-
// @ts-expect-error value exists
|
|
150
|
-
delete values.line2;
|
|
151
|
-
// @ts-expect-error value exists
|
|
152
|
-
delete values.city;
|
|
153
|
-
// @ts-expect-error value exists
|
|
154
|
-
if (Object.keys(allCountriesReverse).includes(values.country)) {
|
|
155
|
-
// @ts-expect-error value exists
|
|
156
|
-
values.country = allCountriesReverse[values.country];
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
values["card_number"] = values.number;
|
|
160
|
-
values["expiry_date"] = values.expiry;
|
|
161
|
-
delete values.focus;
|
|
162
|
-
delete values.expiry;
|
|
163
|
-
delete values.number;
|
|
164
|
-
|
|
165
|
-
const res = await onSubmit(values);
|
|
166
|
-
setDisabled(false);
|
|
167
|
-
} else {
|
|
168
|
-
setDisabled(false);
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const handleInputFocus: FocusEventHandler<HTMLInputElement> = (e) => {
|
|
174
|
-
const focusedEl = (e.target as HTMLElement).getAttribute("name") as CCFocus;
|
|
175
|
-
if (focusedEl) {
|
|
176
|
-
formik.setValues({
|
|
177
|
-
...formik.values,
|
|
178
|
-
focus: focusedEl,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
|
184
|
-
if (e.target.name === "card_number") {
|
|
185
|
-
e.target.value = formatCreditCardNumber(e.target.value);
|
|
186
|
-
} else if (e.target.name === "expiry") {
|
|
187
|
-
e.target.value = formatExpirationDate(e.target.value);
|
|
188
|
-
} else if (e.target.name === "cvc") {
|
|
189
|
-
e.target.value = formatCVC(e.target.value);
|
|
190
|
-
}
|
|
191
|
-
formik.handleChange(e);
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
setDisabled(Object.keys(formik.errors).length > 0);
|
|
196
|
-
}, [formik.errors]);
|
|
197
|
-
|
|
198
|
-
return (
|
|
199
|
-
<Container id="paymentMethodForm">
|
|
200
|
-
<form
|
|
201
|
-
onSubmit={(e) => {
|
|
202
|
-
e.preventDefault();
|
|
203
|
-
formik.handleSubmit(e);
|
|
204
|
-
}}
|
|
205
|
-
>
|
|
206
|
-
<Box
|
|
207
|
-
sx={
|
|
208
|
-
stacked
|
|
209
|
-
? {}
|
|
210
|
-
: {
|
|
211
|
-
display: "flex",
|
|
212
|
-
flexDirection: { xs: "column", sm: "row" },
|
|
213
|
-
gap: "1em",
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
>
|
|
217
|
-
<Cards
|
|
218
|
-
{...(formik.values as CreditCardValues)}
|
|
219
|
-
focused={formik.values.focus || "number"}
|
|
220
|
-
/>
|
|
221
|
-
|
|
222
|
-
<div>
|
|
223
|
-
<TextField
|
|
224
|
-
sx={stacked ? { mt: 2 } : {}}
|
|
225
|
-
fullWidth
|
|
226
|
-
type="tel"
|
|
227
|
-
name="number"
|
|
228
|
-
label="Card Number"
|
|
229
|
-
required
|
|
230
|
-
onBlur={formik.handleBlur}
|
|
231
|
-
onChange={handleInputChange}
|
|
232
|
-
onFocus={handleInputFocus}
|
|
233
|
-
variant="outlined"
|
|
234
|
-
value={formik.values.number}
|
|
235
|
-
error={Boolean(formik.touched.number && formik.errors.number)}
|
|
236
|
-
helperText={formik.touched.number && formik.errors.number}
|
|
237
|
-
/>
|
|
238
|
-
<TextField
|
|
239
|
-
sx={{ mt: 2 }}
|
|
240
|
-
fullWidth
|
|
241
|
-
type="text"
|
|
242
|
-
name="name"
|
|
243
|
-
label="Name on Card"
|
|
244
|
-
required
|
|
245
|
-
onBlur={formik.handleBlur}
|
|
246
|
-
onChange={handleInputChange}
|
|
247
|
-
onFocus={handleInputFocus}
|
|
248
|
-
variant="outlined"
|
|
249
|
-
value={formik.values.name}
|
|
250
|
-
error={Boolean(formik.touched.name && formik.errors.name)}
|
|
251
|
-
helperText={formik.touched.name && formik.errors.name}
|
|
252
|
-
/>
|
|
253
|
-
<TextField
|
|
254
|
-
sx={{ mt: 2, width: "calc(50% - 8px)", mr: 1 }}
|
|
255
|
-
type="tel"
|
|
256
|
-
name="expiry"
|
|
257
|
-
label="Valid Thru"
|
|
258
|
-
placeholder="MM/YY"
|
|
259
|
-
inputProps={{ pattern: "[0-9]{2}/[0-9]{2}" }}
|
|
260
|
-
required
|
|
261
|
-
onBlur={formik.handleBlur}
|
|
262
|
-
onChange={handleInputChange}
|
|
263
|
-
onFocus={handleInputFocus}
|
|
264
|
-
variant="outlined"
|
|
265
|
-
value={formik.values.expiry}
|
|
266
|
-
error={Boolean(formik.touched.expiry && formik.errors.expiry)}
|
|
267
|
-
helperText={
|
|
268
|
-
(formik.touched.expiry && formik.errors.expiry) || "MM/YY"
|
|
269
|
-
}
|
|
270
|
-
/>
|
|
271
|
-
<TextField
|
|
272
|
-
sx={{ mt: 2, width: "calc(50% - 8px)", ml: 1 }}
|
|
273
|
-
type="tel"
|
|
274
|
-
name="cvc"
|
|
275
|
-
label="CVC"
|
|
276
|
-
required
|
|
277
|
-
onBlur={formik.handleBlur}
|
|
278
|
-
onChange={handleInputChange}
|
|
279
|
-
onFocus={handleInputFocus}
|
|
280
|
-
variant="outlined"
|
|
281
|
-
value={formik.values.cvc}
|
|
282
|
-
error={Boolean(formik.touched.cvc && formik.errors.cvc)}
|
|
283
|
-
helperText={formik.touched.cvc && formik.errors.cvc}
|
|
284
|
-
/>
|
|
285
|
-
</div>
|
|
286
|
-
{/* {formik.errors.submit && (
|
|
287
|
-
<Typography color="error" sx={{ mt: 2 }} variant="body2">
|
|
288
|
-
{formik.errors.submit}
|
|
289
|
-
</Typography>
|
|
290
|
-
)} */}
|
|
291
|
-
</Box>
|
|
292
|
-
<Box>
|
|
293
|
-
{!address ? null : (
|
|
294
|
-
<Box>
|
|
295
|
-
<Typography variant="h6" sx={{ mt: 2, mb: 3 }}>
|
|
296
|
-
Billing Address
|
|
297
|
-
</Typography>
|
|
298
|
-
<AddressCountryField formik={formik} name="country" />
|
|
299
|
-
<AddressEmailField formik={formik} name="email" sx={{ mt: 2 }} />
|
|
300
|
-
<AddressOrganizationNameField
|
|
301
|
-
sx={{ mt: 2 }}
|
|
302
|
-
formik={formik}
|
|
303
|
-
name="organization"
|
|
304
|
-
/>
|
|
305
|
-
<AddressStreetField
|
|
306
|
-
sx={{ mt: 2 }}
|
|
307
|
-
formik={formik}
|
|
308
|
-
name="line1"
|
|
309
|
-
lineNo="1"
|
|
310
|
-
/>
|
|
311
|
-
<AddressStreetField
|
|
312
|
-
sx={{ mt: 2 }}
|
|
313
|
-
formik={formik}
|
|
314
|
-
name="line2"
|
|
315
|
-
lineNo="2"
|
|
316
|
-
required={false}
|
|
317
|
-
/>
|
|
318
|
-
<AddressCityField sx={{ mt: 2 }} formik={formik} name="city" />
|
|
319
|
-
<AddressRegionField
|
|
320
|
-
sx={{ mt: 2 }}
|
|
321
|
-
formik={formik}
|
|
322
|
-
name="region"
|
|
323
|
-
/>
|
|
324
|
-
<AddressPostalCodeField
|
|
325
|
-
sx={{ mt: 2 }}
|
|
326
|
-
formik={formik}
|
|
327
|
-
name="postal_code"
|
|
328
|
-
/>
|
|
329
|
-
</Box>
|
|
330
|
-
)}
|
|
331
|
-
</Box>
|
|
332
|
-
{error && (
|
|
333
|
-
<Typography color="error" sx={{ mt: 2 }} variant="body2">
|
|
334
|
-
{error}
|
|
335
|
-
</Typography>
|
|
336
|
-
)}
|
|
337
|
-
<Button
|
|
338
|
-
size="large"
|
|
339
|
-
sx={{ mt: 3 }}
|
|
340
|
-
variant="contained"
|
|
341
|
-
// onClick={formik.handleSubmit}
|
|
342
|
-
type="submit"
|
|
343
|
-
disabled={disabled}
|
|
344
|
-
>
|
|
345
|
-
Complete and pay
|
|
346
|
-
</Button>
|
|
347
|
-
<Box sx={{ mt: 1 }}>
|
|
348
|
-
<StripeLogoLink />
|
|
349
|
-
</Box>
|
|
350
|
-
</form>
|
|
351
|
-
</Container>
|
|
352
|
-
);
|
|
353
|
-
};
|