@springmicro/cart 0.2.0-alpha.3 → 0.2.0-alpha.4

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@springmicro/cart",
3
3
  "private": false,
4
- "version": "0.2.0-alpha.3",
4
+ "version": "0.2.0-alpha.4",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -10,7 +10,7 @@
10
10
  "registry": "https://registry.npmjs.org/"
11
11
  },
12
12
  "scripts": {
13
- "dev": "vite",
13
+ "dev": "vite build --watch",
14
14
  "build": "rm -rf dist && vite build",
15
15
  "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
16
16
  "preview": "vite preview"
@@ -23,9 +23,11 @@
23
23
  "@nanostores/persistent": "^0.10.1",
24
24
  "@nanostores/query": "^0.3.3",
25
25
  "@nanostores/react": "^0.7.2",
26
+ "@springmicro/utils": "0.2.0-alpha.4",
26
27
  "dotenv": "^16.4.5",
27
28
  "nanostores": "^0.10.3",
28
29
  "react": "^18.2.0",
30
+ "react-credit-cards-2": "^1.0.2",
29
31
  "react-dom": "^18.2.0",
30
32
  "unstorage": "^1.10.2"
31
33
  },
@@ -38,8 +40,11 @@
38
40
  "eslint": "^8.57.0",
39
41
  "eslint-plugin-react-hooks": "^4.6.0",
40
42
  "eslint-plugin-react-refresh": "^0.4.6",
43
+ "formik": "^2.4.6",
41
44
  "typescript": "^5.2.2",
42
- "vite": "^5.2.0"
45
+ "vite": "^5.2.0",
46
+ "vite-plugin-css-injected-by-js": "^3.5.1",
47
+ "yup": "^1.4.0"
43
48
  },
44
- "gitHead": "0854b518932e657d4c08fb5877c725a9425b36e9"
49
+ "gitHead": "235b44295dc41168e78c6bc31f286ae7c4587a6c"
45
50
  }
@@ -0,0 +1,246 @@
1
+ import { FormikProps } from "formik";
2
+ import {
3
+ allCountries,
4
+ allProvinces,
5
+ allCountriesReverse,
6
+ getRegionLabel,
7
+ getPostalCodeLabel,
8
+ getPostalCodeDefault,
9
+ provinces,
10
+ } from "@springmicro/utils/address";
11
+ import type { Alpha2Code, Province } from "@springmicro/utils/address";
12
+ import React from "react";
13
+ import { useEffect, useState } from "react";
14
+ import { SxProps } from "@mui/material";
15
+ import MenuItem from "@mui/material/MenuItem";
16
+ import Box from "@mui/material/Box";
17
+ import TextField, { StandardTextFieldProps } from "@mui/material/TextField";
18
+ import Autocomplete from "@mui/material/Autocomplete";
19
+
20
+ export type AddressValues = {
21
+ country: string;
22
+ region: string;
23
+ line1: string;
24
+ line2?: string;
25
+ organization?: string;
26
+ city: string;
27
+ postal_code: string;
28
+ };
29
+
30
+ export type AddressFieldProps = {
31
+ formik: FormikProps<AddressValues & any>; // formik object might contain other values in addition to address
32
+ name: keyof AddressValues;
33
+ sx?: SxProps;
34
+ };
35
+
36
+ export function AddressCountryField({ formik, name, sx }: AddressFieldProps) {
37
+ /**
38
+ * Selects the full country name, need to do a reverse lookup for the Alpha2Code.
39
+ */
40
+ const [inputValue, setInputValue] = useState(formik.values[name] as string);
41
+
42
+ return (
43
+ <Autocomplete
44
+ sx={{ width: 300, ...sx }}
45
+ options={Object.values(allCountries)}
46
+ autoHighlight
47
+ getOptionLabel={(option) => option}
48
+ value={formik.values[name] as string}
49
+ onChange={(e, newValue) => {
50
+ // console.log(newValue)
51
+ formik.setValues({
52
+ ...formik.values,
53
+ region: "",
54
+ country: newValue as string,
55
+ line1: "",
56
+ line2: "",
57
+ organization: "",
58
+ city: "",
59
+ postal_code: "",
60
+ });
61
+ }}
62
+ onBlur={formik.handleBlur}
63
+ inputValue={inputValue}
64
+ onInputChange={(event, newInputValue) => {
65
+ setInputValue(newInputValue);
66
+ }}
67
+ renderOption={(props, option) => (
68
+ <Box
69
+ component="li"
70
+ sx={{ "& > img": { mr: 2, flexShrink: 0 } }}
71
+ {...props}
72
+ >
73
+ <img
74
+ loading="lazy"
75
+ width="20"
76
+ src={`https://flagcdn.com/w20/${allCountriesReverse[
77
+ option
78
+ ].toLowerCase()}.png`}
79
+ srcSet={`https://flagcdn.com/w40/${allCountriesReverse[
80
+ option
81
+ ].toLowerCase()}.png 2x`}
82
+ alt=""
83
+ />
84
+ {option}
85
+ </Box>
86
+ )}
87
+ renderInput={(params) => (
88
+ <TextField
89
+ {...params}
90
+ required={true}
91
+ name={name as string}
92
+ label="Country"
93
+ />
94
+ )}
95
+ />
96
+ );
97
+ }
98
+
99
+ export function AddressRegionField({ formik, name, sx }: AddressFieldProps) {
100
+ const [addressRegionFieldOptional, setAddressRegionFieldOptional] =
101
+ useState<boolean>(false);
102
+ const country2code = allCountriesReverse[
103
+ formik.values["country"] as string
104
+ ] as Alpha2Code;
105
+
106
+ useEffect(() => {
107
+ setAddressRegionFieldOptional(
108
+ provinces.filter(
109
+ (province: Province) => province.country === country2code
110
+ ).length === 0
111
+ );
112
+ }, [country2code]);
113
+
114
+ const baseProps = {
115
+ sx: { width: 300, ...sx },
116
+ label: getRegionLabel(country2code),
117
+ name: name as string,
118
+ id: name as string,
119
+ value: formik.values[name],
120
+ onChange: formik.handleChange,
121
+ onBlur: formik.handleBlur,
122
+ };
123
+
124
+ if (!country2code || !getRegionLabel(country2code)) {
125
+ return <></>;
126
+ }
127
+
128
+ if (addressRegionFieldOptional) {
129
+ return (
130
+ <TextField
131
+ {...baseProps}
132
+ required={false}
133
+ sx={{ display: "block", ...baseProps.sx }}
134
+ />
135
+ );
136
+ } else {
137
+ return (
138
+ <TextField {...baseProps} select required={true}>
139
+ {allProvinces
140
+ .filter((val) => val.country === country2code)
141
+ .map((province, i) => (
142
+ <MenuItem key={i} value={province.value}>
143
+ {province.name}
144
+ </MenuItem>
145
+ ))}
146
+ </TextField>
147
+ );
148
+ }
149
+ }
150
+
151
+ export function AddressStreetField({
152
+ formik,
153
+ name,
154
+ sx,
155
+ required = true,
156
+ lineNo,
157
+ }: AddressFieldProps & {
158
+ required?: boolean;
159
+ lineNo?: "1" | "2" | "3" | undefined;
160
+ }) {
161
+ return (
162
+ <>
163
+ <TextField
164
+ label={lineNo ? `Street Address ${lineNo}` : "Street Address"}
165
+ sx={{ width: 400, display: "block", ...sx }}
166
+ name={name as string}
167
+ id={name as string}
168
+ value={formik.values[name]}
169
+ onChange={formik.handleChange}
170
+ onBlur={formik.handleBlur}
171
+ required={required}
172
+ />
173
+ </>
174
+ );
175
+ }
176
+
177
+ export function AddressOrganizationNameField({
178
+ formik,
179
+ name,
180
+ sx,
181
+ }: AddressFieldProps) {
182
+ return (
183
+ <TextField
184
+ label="Organization Name"
185
+ sx={{ width: 400, display: "block", ...sx }}
186
+ name={name as string}
187
+ id={name as string}
188
+ value={formik.values[name]}
189
+ onChange={formik.handleChange}
190
+ onBlur={formik.handleBlur}
191
+ required={false}
192
+ helperText="Only include organization name if you want it included on the address."
193
+ />
194
+ );
195
+ }
196
+
197
+ export function AddressCityField({ formik, name, sx }: AddressFieldProps) {
198
+ return (
199
+ <TextField
200
+ label="City"
201
+ sx={{ width: 400, display: "block", ...sx }}
202
+ name={name as string}
203
+ id={name as string}
204
+ value={formik.values[name]}
205
+ onChange={formik.handleChange}
206
+ onBlur={formik.handleBlur}
207
+ required={true}
208
+ />
209
+ );
210
+ }
211
+
212
+ export function AddressPostalCodeField({
213
+ formik,
214
+ name,
215
+ sx,
216
+ }: AddressFieldProps) {
217
+ const country2code = allCountriesReverse[
218
+ formik.values["country"] as string
219
+ ] as Alpha2Code;
220
+
221
+ React.useEffect(() => {
222
+ if (!!getPostalCodeDefault(country2code)) {
223
+ formik
224
+ .setFieldValue(name, getPostalCodeDefault(country2code))
225
+ .then(() => formik.setFieldTouched(name, true));
226
+ }
227
+ }, [country2code]);
228
+
229
+ return (
230
+ <TextField
231
+ label={getPostalCodeLabel(country2code)}
232
+ sx={{ width: 400, display: "block", ...sx }}
233
+ name={name as string}
234
+ id={name as string}
235
+ value={formik.values[name]}
236
+ onChange={formik.handleChange}
237
+ onBlur={formik.handleBlur}
238
+ required={true}
239
+ helperText={
240
+ !!getPostalCodeDefault(country2code)
241
+ ? 'Our records indicate that your country does not have a postal code system, so "00000" will be used by default.'
242
+ : ""
243
+ }
244
+ />
245
+ );
246
+ }
@@ -0,0 +1,327 @@
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
+ AddressValues,
13
+ } from "./Address";
14
+ import {
15
+ allCountries,
16
+ allCountriesReverse,
17
+ getPostalCodeDefault,
18
+ } from "@springmicro/utils/address";
19
+ import { Box, Button, TextField, Typography } from "@mui/material";
20
+ import Cards from "react-credit-cards-2";
21
+ import { FocusEventHandler } from "react";
22
+ import {
23
+ formatCreditCardNumber,
24
+ formatCVC,
25
+ formatExpirationDate,
26
+ splitMonthYear,
27
+ expiryDateHasNotPassed,
28
+ } from "@springmicro/utils/payment";
29
+
30
+ // moved to index.ts
31
+ // import "react-credit-cards-2/dist/es/styles-compiled.css";
32
+ import { StripeLogoLink } from "./ProviderLogos";
33
+
34
+ type CCFocus = "number" | "name" | "cvc" | "expiry";
35
+
36
+ export type CreditCardValues = {
37
+ cvc: string;
38
+ expiry: string;
39
+ focus: CCFocus;
40
+ name: string;
41
+ number: string;
42
+ };
43
+
44
+ export type AddCardProps = {
45
+ stacked?: boolean;
46
+ error?: string;
47
+ address?: boolean;
48
+ onSubmit?: (
49
+ values: CreditCardValues & Partial<AddressValues>
50
+ ) => Promise<any>;
51
+ };
52
+
53
+ export const AddCard = ({
54
+ error,
55
+ stacked = false,
56
+ address = true,
57
+ onSubmit,
58
+ }: AddCardProps) => {
59
+ const [disabled, setDisabled] = useState(true);
60
+
61
+ const initialValuesBase = {
62
+ cvc: "",
63
+ expiry: "",
64
+ focus: "number",
65
+ name: "",
66
+ number: "",
67
+ } as CreditCardValues;
68
+ const initialCountry = "US";
69
+ const initialValuesAddress = {
70
+ country: initialCountry ? allCountries[initialCountry] : "",
71
+ region: "",
72
+ line1: "",
73
+ line2: "",
74
+ organization: "",
75
+ city: "",
76
+ postal_code: getPostalCodeDefault(initialCountry),
77
+ } as AddressValues;
78
+
79
+ // const initialValues = { ...initialValuesBase, ...initialValuesAddress }
80
+ const initialValues = address
81
+ ? { ...initialValuesBase, ...initialValuesAddress }
82
+ : initialValuesBase;
83
+
84
+ const validationSchemaBase = {
85
+ expiry: Yup.string()
86
+ // .length(7, "Expiry date must be of the format MM/YY.")
87
+ .test("exp-date", "Expiry date has already passed.", (string) => {
88
+ if (string?.length === 5) {
89
+ if (string.indexOf("/") !== 2) return false;
90
+ const [month, year] = splitMonthYear(string);
91
+ return expiryDateHasNotPassed(month, year);
92
+ } else {
93
+ return true;
94
+ }
95
+ })
96
+ .required("Expiry date is required."),
97
+ number: Yup.string().required("Card Number is required."),
98
+ name: Yup.string().required("Cardholder Name is required."),
99
+ cvc: Yup.string().required("CVC is required."),
100
+ };
101
+
102
+ const validationSchemaAddress = {
103
+ country: Yup.string().required("Country is required."),
104
+ line1: Yup.string().required("Street Address 1 is required."),
105
+ city: Yup.string().required("City is required."),
106
+ postal_code: Yup.string().required("Postal Code is required."),
107
+ };
108
+
109
+ const formik = useFormik({
110
+ initialValues,
111
+ validationSchema: Yup.object(
112
+ address
113
+ ? {
114
+ ...validationSchemaBase,
115
+ ...validationSchemaAddress,
116
+ }
117
+ : validationSchemaBase
118
+ ),
119
+ onSubmit: async (values, helpers) => {
120
+ if (disabled) {
121
+ throw new Error("Attempted to submit an invalid form.");
122
+ }
123
+ if (onSubmit) {
124
+ if (address) {
125
+ // match backend
126
+ // @ts-ignore
127
+ values["street_address"] = values.line2
128
+ ? // @ts-ignore
129
+ `${values.line1}\n${values.line2}`
130
+ : // @ts-ignore
131
+ values.line1;
132
+ // @ts-ignore
133
+ values["locality"] = values.city;
134
+ // @ts-ignore
135
+ delete values.line1;
136
+ // @ts-ignore
137
+ delete values.line2;
138
+ // @ts-ignore
139
+ delete values.city;
140
+ }
141
+ values["card_number"] = values.number;
142
+ values["expiry_date"] = values.expiry;
143
+ delete values.focus;
144
+ delete values.expiry;
145
+ delete values.number;
146
+
147
+ const res = await onSubmit(values);
148
+ }
149
+ },
150
+ });
151
+
152
+ const handleInputFocus: FocusEventHandler<HTMLInputElement> = (e) => {
153
+ const focusedEl = (e.target as HTMLElement).getAttribute("name") as CCFocus;
154
+ if (focusedEl) {
155
+ formik.setValues({
156
+ ...formik.values,
157
+ focus: focusedEl,
158
+ });
159
+ }
160
+ };
161
+
162
+ const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
163
+ if (e.target.name === "card_number") {
164
+ e.target.value = formatCreditCardNumber(e.target.value);
165
+ } else if (e.target.name === "expiry") {
166
+ e.target.value = formatExpirationDate(e.target.value);
167
+ } else if (e.target.name === "cvc") {
168
+ e.target.value = formatCVC(e.target.value);
169
+ }
170
+ formik.handleChange(e);
171
+ };
172
+
173
+ useEffect(() => {
174
+ setDisabled(Object.keys(formik.errors).length > 0);
175
+ }, [formik.errors]);
176
+
177
+ return (
178
+ <>
179
+ <form
180
+ onSubmit={(e) => {
181
+ e.preventDefault();
182
+ formik.handleSubmit();
183
+ }}
184
+ >
185
+ <Box
186
+ sx={
187
+ stacked
188
+ ? {}
189
+ : {
190
+ display: "flex",
191
+ flexDirection: { xs: "column", sm: "row" },
192
+ gap: "1em",
193
+ }
194
+ }
195
+ >
196
+ <Cards
197
+ {...(formik.values as CreditCardValues)}
198
+ focused={formik.values.focus || "number"}
199
+ />
200
+
201
+ <div>
202
+ <TextField
203
+ sx={stacked ? { mt: 2 } : {}}
204
+ fullWidth
205
+ type="tel"
206
+ name="number"
207
+ label="Card Number"
208
+ onBlur={formik.handleBlur}
209
+ onChange={handleInputChange}
210
+ onFocus={handleInputFocus}
211
+ variant="outlined"
212
+ value={formik.values.number}
213
+ error={Boolean(formik.touched.number && formik.errors.number)}
214
+ helperText={formik.touched.number && formik.errors.number}
215
+ />
216
+ <TextField
217
+ sx={{ mt: 2 }}
218
+ fullWidth
219
+ type="text"
220
+ name="name"
221
+ label="Name on Card"
222
+ onBlur={formik.handleBlur}
223
+ onChange={handleInputChange}
224
+ onFocus={handleInputFocus}
225
+ variant="outlined"
226
+ value={formik.values.name}
227
+ error={Boolean(formik.touched.name && formik.errors.name)}
228
+ helperText={formik.touched.name && formik.errors.name}
229
+ />
230
+ <TextField
231
+ sx={{ mt: 2, width: "calc(50% - 8px)", mr: 1 }}
232
+ type="tel"
233
+ name="expiry"
234
+ label="Valid Thru"
235
+ placeholder="MM/YY"
236
+ inputProps={{ pattern: "[0-9]{2}/[0-9]{2}" }}
237
+ required
238
+ onBlur={formik.handleBlur}
239
+ onChange={handleInputChange}
240
+ onFocus={handleInputFocus}
241
+ variant="outlined"
242
+ value={formik.values.expiry}
243
+ error={Boolean(formik.touched.expiry && formik.errors.expiry)}
244
+ helperText={
245
+ (formik.touched.expiry && formik.errors.expiry) || "MM/YY"
246
+ }
247
+ />
248
+ <TextField
249
+ sx={{ mt: 2, width: "calc(50% - 8px)", ml: 1 }}
250
+ type="tel"
251
+ name="cvc"
252
+ label="CVC"
253
+ onBlur={formik.handleBlur}
254
+ onChange={handleInputChange}
255
+ onFocus={handleInputFocus}
256
+ variant="outlined"
257
+ value={formik.values.cvc}
258
+ error={Boolean(formik.touched.cvc && formik.errors.cvc)}
259
+ helperText={formik.touched.cvc && formik.errors.cvc}
260
+ />
261
+ </div>
262
+ {/* {formik.errors.submit && (
263
+ <Typography color="error" sx={{ mt: 2 }} variant="body2">
264
+ {formik.errors.submit}
265
+ </Typography>
266
+ )} */}
267
+ </Box>
268
+ <Box>
269
+ {!address ? null : (
270
+ <Box>
271
+ <Typography variant="h6" sx={{ mt: 2, mb: 3 }}>
272
+ Billing Address
273
+ </Typography>
274
+ <AddressCountryField formik={formik} name="country" />
275
+ <AddressOrganizationNameField
276
+ sx={{ mt: 2 }}
277
+ formik={formik}
278
+ name="organization"
279
+ />
280
+ <AddressStreetField
281
+ sx={{ mt: 2 }}
282
+ formik={formik}
283
+ name="line1"
284
+ lineNo="1"
285
+ />
286
+ <AddressStreetField
287
+ sx={{ mt: 2 }}
288
+ formik={formik}
289
+ name="line2"
290
+ lineNo="2"
291
+ required={false}
292
+ />
293
+ <AddressCityField sx={{ mt: 2 }} formik={formik} name="city" />
294
+ <AddressRegionField
295
+ sx={{ mt: 2 }}
296
+ formik={formik}
297
+ name="region"
298
+ />
299
+ <AddressPostalCodeField
300
+ sx={{ mt: 2 }}
301
+ formik={formik}
302
+ name="postal_code"
303
+ />
304
+ </Box>
305
+ )}
306
+ </Box>
307
+ {error && (
308
+ <Typography color="error" sx={{ mt: 2 }} variant="body2">
309
+ {error}
310
+ </Typography>
311
+ )}
312
+ <Button
313
+ size="large"
314
+ sx={{ mt: 3 }}
315
+ variant="contained"
316
+ type="submit"
317
+ disabled={disabled}
318
+ >
319
+ Continue
320
+ </Button>
321
+ <Box sx={{ mt: 1 }}>
322
+ <StripeLogoLink />
323
+ </Box>
324
+ </form>
325
+ </>
326
+ );
327
+ };
@@ -0,0 +1,93 @@
1
+ import { createSvgIcon } from "@mui/material";
2
+ // Source: https://brandfolder.com/s/99gctvbpwgvzbc7mz3j9g4x
3
+
4
+ export const PoweredByStripe = createSvgIcon(
5
+ <svg
6
+ id="Layer_1"
7
+ data-name="Layer 1"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ viewBox="0 0 150 34"
10
+ >
11
+ <defs>
12
+ <style>{`.cls-1{fill: #635bff}`}</style>
13
+ </defs>
14
+ <title>Powered by Stripe - blurple</title>
15
+ <path
16
+ className="cls-1"
17
+ d="M146,0H3.73A3.73,3.73,0,0,0,0,3.73V30.27A3.73,3.73,0,0,0,3.73,34H146a4,4,0,0,0,4-4V4A4,4,0,0,0,146,0Zm3,30a3,3,0,0,1-3,3H3.73A2.74,2.74,0,0,1,1,30.27V3.73A2.74,2.74,0,0,1,3.73,1H146a3,3,0,0,1,3,3Z"
18
+ />
19
+ <path
20
+ className="cls-1"
21
+ d="M17.07,11.24h-4.3V22h1.92V17.84h2.38c2.4,0,3.9-1.16,3.9-3.3S19.47,11.24,17.07,11.24Zm-.1,5H14.69v-3.3H17c1.38,0,2.11.59,2.11,1.65S18.35,16.19,17,16.19Z"
22
+ />
23
+ <path
24
+ className="cls-1"
25
+ d="M25.1,14a3.77,3.77,0,0,0-3.8,4.09,3.81,3.81,0,1,0,7.59,0A3.76,3.76,0,0,0,25.1,14Zm0,6.67c-1.22,0-2-1-2-2.58s.76-2.58,2-2.58,2,1,2,2.58S26.31,20.66,25.1,20.66Z"
26
+ />
27
+ <polygon
28
+ className="cls-1"
29
+ points="36.78 19.35 35.37 14.13 33.89 14.13 32.49 19.35 31.07 14.13 29.22 14.13 31.59 22.01 33.15 22.01 34.59 16.85 36.03 22.01 37.59 22.01 39.96 14.13 38.18 14.13 36.78 19.35"
30
+ />
31
+ <path
32
+ className="cls-1"
33
+ d="M44,14a3.83,3.83,0,0,0-3.75,4.09,3.79,3.79,0,0,0,3.83,4.09A3.47,3.47,0,0,0,47.49,20L46,19.38a1.78,1.78,0,0,1-1.83,1.26A2.12,2.12,0,0,1,42,18.47h5.52v-.6C47.54,15.71,46.32,14,44,14Zm-1.93,3.13A1.92,1.92,0,0,1,44,15.5a1.56,1.56,0,0,1,1.69,1.62Z"
34
+ />
35
+ <path
36
+ className="cls-1"
37
+ d="M50.69,15.3V14.13h-1.8V22h1.8V17.87a1.89,1.89,0,0,1,2-2,4.68,4.68,0,0,1,.66,0v-1.8c-.14,0-.3,0-.51,0A2.29,2.29,0,0,0,50.69,15.3Z"
38
+ />
39
+ <path
40
+ className="cls-1"
41
+ d="M57.48,14a3.83,3.83,0,0,0-3.75,4.09,3.79,3.79,0,0,0,3.83,4.09A3.47,3.47,0,0,0,60.93,20l-1.54-.59a1.78,1.78,0,0,1-1.83,1.26,2.12,2.12,0,0,1-2.1-2.17H61v-.6C61,15.71,59.76,14,57.48,14Zm-1.93,3.13a1.92,1.92,0,0,1,1.92-1.62,1.56,1.56,0,0,1,1.69,1.62Z"
42
+ />
43
+ <path
44
+ className="cls-1"
45
+ d="M67.56,15a2.85,2.85,0,0,0-2.26-1c-2.21,0-3.47,1.85-3.47,4.09s1.26,4.09,3.47,4.09a2.82,2.82,0,0,0,2.26-1V22h1.8V11.24h-1.8Zm0,3.35a2,2,0,0,1-2,2.28c-1.31,0-2-1-2-2.52s.7-2.52,2-2.52c1.11,0,2,.81,2,2.29Z"
46
+ />
47
+ <path
48
+ className="cls-1"
49
+ d="M79.31,14A2.88,2.88,0,0,0,77,15V11.24h-1.8V22H77v-.83a2.86,2.86,0,0,0,2.27,1c2.2,0,3.46-1.86,3.46-4.09S81.51,14,79.31,14ZM79,20.6a2,2,0,0,1-2-2.28v-.47c0-1.48.84-2.29,2-2.29,1.3,0,2,1,2,2.52S80.25,20.6,79,20.6Z"
50
+ />
51
+ <path
52
+ className="cls-1"
53
+ d="M86.93,19.66,85,14.13H83.1L86,21.72l-.3.74a1,1,0,0,1-1.14.79,4.12,4.12,0,0,1-.6,0v1.51a4.62,4.62,0,0,0,.73.05,2.67,2.67,0,0,0,2.78-2l3.24-8.62H88.82Z"
54
+ />
55
+ <path
56
+ className="cls-1"
57
+ d="M125,12.43a3,3,0,0,0-2.13.87l-.14-.69h-2.39V25.53l2.72-.59V21.81a3,3,0,0,0,1.93.7c1.94,0,3.72-1.59,3.72-5.11C128.71,14.18,126.91,12.43,125,12.43Zm-.65,7.63a1.61,1.61,0,0,1-1.28-.52l0-4.11a1.64,1.64,0,0,1,1.3-.55c1,0,1.68,1.13,1.68,2.58S125.36,20.06,124.35,20.06Z"
58
+ />
59
+ <path
60
+ className="cls-1"
61
+ d="M133.73,12.43c-2.62,0-4.21,2.26-4.21,5.11,0,3.37,1.88,5.08,4.56,5.08a6.12,6.12,0,0,0,3-.73V19.64a5.79,5.79,0,0,1-2.7.62c-1.08,0-2-.39-2.14-1.7h5.38c0-.15,0-.74,0-1C137.71,14.69,136.35,12.43,133.73,12.43Zm-1.47,4.07c0-1.26.77-1.79,1.45-1.79s1.4.53,1.4,1.79Z"
62
+ />
63
+ <path
64
+ className="cls-1"
65
+ d="M113,13.36l-.17-.82h-2.32v9.71h2.68V15.67a1.87,1.87,0,0,1,2.05-.58V12.54A1.8,1.8,0,0,0,113,13.36Z"
66
+ />
67
+ <path
68
+ className="cls-1"
69
+ d="M99.46,15.46c0-.44.36-.61.93-.61a5.9,5.9,0,0,1,2.7.72V12.94a7,7,0,0,0-2.7-.51c-2.21,0-3.68,1.18-3.68,3.16,0,3.1,4.14,2.6,4.14,3.93,0,.52-.44.69-1,.69a6.78,6.78,0,0,1-3-.9V22a7.38,7.38,0,0,0,3,.64c2.26,0,3.82-1.15,3.82-3.16C103.62,16.12,99.46,16.72,99.46,15.46Z"
70
+ />
71
+ <path
72
+ className="cls-1"
73
+ d="M107.28,10.24l-2.65.58v8.93a2.77,2.77,0,0,0,2.82,2.87,4.16,4.16,0,0,0,1.91-.37V20c-.35.15-2.06.66-2.06-1V15h2.06V12.66h-2.06Z"
74
+ />
75
+ <polygon
76
+ className="cls-1"
77
+ points="116.25 11.7 118.98 11.13 118.98 8.97 116.25 9.54 116.25 11.7"
78
+ />
79
+ <rect className="cls-1" x="116.25" y="12.61" width="2.73" height="9.64" />
80
+ </svg>,
81
+ "PoweredByStripe"
82
+ );
83
+
84
+ export const StripeLogoLink = ({ height }: { height?: string }) => {
85
+ return (
86
+ <a href="https://stripe.com" target="_blank">
87
+ <PoweredByStripe
88
+ sx={{ width: "unset", height: height || "1em" }}
89
+ viewBox="0 0 150 34"
90
+ />
91
+ </a>
92
+ );
93
+ };