@stripe/react-stripe-js#useElements JavaScript Examples
The following examples show how to use
@stripe/react-stripe-js#useElements.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: StripeForm.jsx From saasgear with MIT License | 5 votes |
StripeForm = ({
onSubmitSuccess,
className,
onGoBack,
apiLoading,
apiError,
submitText = 'Submit',
}) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState('');
const stripe = useStripe();
const elements = useElements();
useEffect(() => {
setIsSubmitting(apiLoading);
}, [apiLoading]);
useEffect(() => {
setError(apiError);
}, [apiError]);
async function onSubmit(e) {
e.preventDefault();
setIsSubmitting(true);
try {
const card = elements.getElement(CardNumberElement);
const result = await stripe.createToken(card);
if (result.error) {
setError(result.error.message);
} else {
onSubmitSuccess(result.token.id);
}
} catch (err) {
setError(err.toString());
}
setIsSubmitting(false);
}
return (
<StripeFormContainer onSubmit={onSubmit} className={className}>
<div>
{onGoBack && <BackButton type="button" onClick={onGoBack} />}
<div>
<FormGroup>
<FormGroupLabel htmlFor="street_address">
Card Number
</FormGroupLabel>
<CardNumberEl className="card-el" />
</FormGroup>
<RowGroup>
<FormGroupCardExpire>
<FormGroupLabel htmlFor="first_name">Expiration</FormGroupLabel>
<CardExpiryElementEl />
</FormGroupCardExpire>
<FormGroupCardCvc>
<FormGroupLabel htmlFor="last_name">CVC</FormGroupLabel>
<CardCvcElementEl />
</FormGroupCardCvc>
</RowGroup>
</div>
</div>
{error && (
<p className="text-red-500 text-xs italic mt-1 text-center">{error}</p>
)}
<SubmitButton
type="submit"
disabled={isSubmitting}
color="primary"
width="100%"
>
{isSubmitting ? 'Please wait' : submitText}
</SubmitButton>
</StripeFormContainer>
);
}
Example #2
Source File: checkout.js From turqV2 with GNU General Public License v3.0 | 5 votes |
function CheckoutForm({location, dispatch, isComplete, isSuccess, isFetching}) {
const stripe = useStripe();
const elements = useElements();
const [cardName, setCardName] = useState("");
const [amount, setAmount] = useState(0);
useEffect(() => {
if (location.state.contest === undefined) {
toast.error("No contest");
}
}, [location]);
const handleSubmit = async () => {
if (!stripe || !elements) {
// Stripe.js has not loaded yet. Make sure to disable
// form submission until Stripe.js has loaded.
return;
}
if (amount === undefined || amount === null || amount === 0) {
toast.error("Payment Failed: Please select a donation amount");
return;
} else if (amount < 1) {
toast.error("Payment Failed: Minimum donation is $1.00");
return;
} else if (cardName === undefined || cardName === null || cardName === "") {
toast.error("Payment Failed: Please Provide the name on your credit card");
return
}
dispatch(payment(location.state.contest, amount, elements.getElement(CardNumberElement), cardName, stripe))
};
const handleChange = (event) => {
setCardName(event.target.value)
}
return(
<>
{ !isFetching && isComplete && isSuccess
? <Redirect to={THANKYOU_URL} />
: <Layout fullWidth>
<div className="checkout-page">
<Grid container spacing={5} justify="center" alignItems="stretch">
<Grid container item xs={12} md={6}>
<Donation setAmount={setAmount} />
</Grid>
<Grid container item xs={12} md={6}>
<Checkout
handleChange={handleChange}
handleSubmit={handleSubmit}
cardName={cardName}
stripe={stripe}
isFetching={isFetching}
/>
</Grid>
<Grid container item xs={12} justify="center">
<Grid item>
<img src="/images/stripe-blurple.png" alt="Powered by Stripe" style={{height:50}}/>
</Grid>
</Grid>
</Grid>
</div>
</Layout>
}
</>
)
}
Example #3
Source File: CheckoutForm.js From jamstack-ecommerce with MIT License | 5 votes |
export default function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const checkoutSubmit = async()=>{
const response = await fetch("/.netlify/functions/checkout", {method: "post"});
const data = await response.json();
console.log("DAta = ",data);
const result = await stripe.confirmCardPayment(data.client_secret, {
payment_method: {
card: elements.getElement(CardNumberElement),
billing_details: {
name: 'Zia Khan',
email: "[email protected]"
},
}
})
console.log("Result = ",result);
}
return (
<div>
Checkout Form
<div>
{/*<CardElement options={CARD_ELEMENT_OPTIONS} />*/ }
<CardNumberElement options={CARD_ELEMENT_OPTIONS}/>
<CardExpiryElement options={CARD_ELEMENT_OPTIONS}/>
<CardCvcElement options={CARD_ELEMENT_OPTIONS}/>
</div>
<button onClick={checkoutSubmit}>
Checkout
</button>
</div>
)
}
Example #4
Source File: HomePage.js From tutorial-code with MIT License | 5 votes |
function HomePage() {
const classes = useStyles();
// State
const [email, setEmail] = useState('');
const stripe = useStripe();
const elements = useElements();
const handleSubmitPay = async (event) => {
if (!stripe || !elements) {
// Stripe.js has not yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
const res = await axios.post('http://localhost:5000/pay', {email: email});
const clientSecret = res.data['client_secret'];
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
email: email,
},
},
});
if (result.error) {
// Show error to your customer (e.g., insufficient funds)
console.log(result.error.message);
} else {
// The payment has been processed!
if (result.paymentIntent.status === 'succeeded') {
// Show a success message to your customer
// There's a risk of the customer closing the window before callback
// execution. Set up a webhook or plugin to listen for the
// payment_intent.succeeded event that handles any business critical
// post-payment actions.
console.log('You got 500$!');
}
}
};
return (
<Card className={classes.root}>
<CardContent className={classes.content}>
<TextField
label='Email'
id='outlined-email-input'
helperText={`Email you'll recive updates and receipts on`}
margin='normal'
variant='outlined'
type='email'
required
value={email}
onChange={(e) => setEmail(e.target.value)}
fullWidth
/>
<CardInput />
<div className={classes.div}>
<Button variant="contained" color="primary" className={classes.button} onClick={handleSubmitPay}>
Pay
</Button>
<Button variant="contained" color="primary" className={classes.button}>
Subscription
</Button>
</div>
</CardContent>
</Card>
);
}
Example #5
Source File: HomePage.js From tutorial-code with MIT License | 5 votes |
function HomePage() {
const classes = useStyles();
// State
const [email, setEmail] = useState('');
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (event) => {
if (!stripe || !elements) {
// Stripe.js has not yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
const res = await axios.post('http://localhost:3000/pay', {email: email});
const clientSecret = res.data['client_secret'];
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
email: email,
},
},
});
if (result.error) {
// Show error to your customer (e.g., insufficient funds)
console.log(result.error.message);
} else {
// The payment has been processed!
if (result.paymentIntent.status === 'succeeded') {
console.log('Money is in the bank!');
// Show a success message to your customer
// There's a risk of the customer closing the window before callback
// execution. Set up a webhook or plugin to listen for the
// payment_intent.succeeded event that handles any business critical
// post-payment actions.
}
}
};
return (
<Card className={classes.root}>
<CardContent className={classes.content}>
<TextField
label='Email'
id='outlined-email-input'
helperText={`Email you'll recive updates and receipts on`}
margin='normal'
variant='outlined'
type='email'
required
value={email}
onChange={(e) => setEmail(e.target.value)}
fullWidth
/>
<CardInput />
<div className={classes.div}>
<Button variant="contained" color="primary" className={classes.button} onClick={handleSubmit}>
Pay
</Button>
<Button variant="contained" color="primary" className={classes.button}>
Subscription
</Button>
</div>
</CardContent>
</Card>
);
}
Example #6
Source File: payment-form.js From create-magic-app with MIT License | 4 votes |
export default function PaymentForm({ email }) {
const [succeeded, setSucceeded] = useState(false);
const [error, setError] = useState(null);
const [processing, setProcessing] = useState("");
const [disabled, setDisabled] = useState(true);
const [clientSecret, setClientSecret] = useState("");
const [customerID, setCustomerID] = useState("");
const [, setLifetimeAccess] = useContext(LifetimeContext);
const stripe = useStripe();
const elements = useElements();
const history = useHistory();
useEffect(() => {
// Create PaymentIntent as soon as the page loads
fetch(`${process.env.REACT_APP_SERVER_URL}/create-payment-intent`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
})
.then((res) => {
return res.json();
})
.then((data) => {
setClientSecret(data.clientSecret);
setCustomerID(data.customer);
setLifetimeAccess(true);
});
}, [email]);
const cardStyle = {
style: {
base: {
color: "#32325d",
fontFamily: "Arial, sans-serif",
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#32325d",
},
},
invalid: {
color: "#fa755a",
iconColor: "#fa755a",
},
},
};
const handleChange = async (event) => {
// Listen for changes in the CardElement
// and display any errors as the customer types their card details
setDisabled(event.empty);
setError(event.error ? event.error.message : "");
};
const handleSubmit = async (ev) => {
ev.preventDefault();
setProcessing(true);
const payload = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
},
});
if (payload.error) {
setError(`Payment failed ${payload.error.message}`);
setProcessing(false);
} else {
setError(null);
setProcessing(false);
setSucceeded(true);
// Update Stripe customer info to include metadata
// which will help us determine whether or not they
// are a Lifetime Access member.
fetch(`${process.env.REACT_APP_SERVER_URL}/update-customer`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ customerID }),
})
.then((res) => {
return res.json();
})
.then((data) => {
console.log("Updated Stripe customer object: ", data);
history.push("/premium-content");
});
}
};
return (
<>
<form id="payment-form" onSubmit={handleSubmit}>
<CardElement
id="card-element"
options={cardStyle}
onChange={handleChange}
/>
<button disabled={processing || disabled || succeeded} id="submit">
<span id="button-text">{processing ? "Pay" : "Pay"}</span>
</button>
{/* Show any error that happens when processing the payment */}
{error && (
<div className="card-error" role="alert">
{error}confirmCardPayment
</div>
)}
</form>
<style>{`
#root {
align-items: center;
}
p {
margin-top:
}
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
font-size: 16px;
-webkit-font-smoothing: antialiased;
display: flex;
justify-content: center;
align-content: center;
height: 100vh;
width: 100vw;
}
form {
width: 30vw;
align-self: center;
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
border-radius: 7px;
padding: 40px;
}
input {
border-radius: 6px;
margin-bottom: 6px;
padding: 12px;
border: 1px solid rgba(50, 50, 93, 0.1);
max-height: 44px;
font-size: 16px;
width: 100%;
background: white;
box-sizing: border-box;
}
.result-message {
line-height: 22px;
font-size: 16px;
}
.result-message a {
color: rgb(89, 111, 214);
font-weight: 600;
text-decoration: none;
}
.hidden {
display: none;
}
#card-error {
color: rgb(105, 115, 134);
font-size: 16px;
line-height: 20px;
margin-top: 12px;
text-align: center;
}
#card-element {
border-radius: 4px 4px 0 0;
padding: 12px;
border: 1px solid rgba(50, 50, 93, 0.1);
max-height: 44px;
width: 100%;
background: white;
box-sizing: border-box;
}
#payment-request-button {
margin-bottom: 32px;
}
/* Buttons and links */
button {
background: #5469d4;
font-family: Arial, sans-serif;
color: #ffffff;
border-radius: 0 0 4px 4px;
border: 0;
padding: 12px 16px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
display: block;
transition: all 0.2s ease;
width: 100%;
}
button:hover {
filter: contrast(115%);
}
button:disabled {
opacity: 0.5;
cursor: default;
}
/* spinner/processing state, errors */
.spinner,
.spinner:before,
.spinner:after {
border-radius: 50%;
}
.spinner {
color: #ffffff;
font-size: 22px;
text-indent: -99999px;
margin: 0px auto;
position: relative;
width: 20px;
height: 20px;
box-shadow: inset 0 0 0 2px;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
}
.spinner:before,
.spinner:after {
position: absolute;
content: "";
}
.spinner:before {
width: 10.4px;
height: 20.4px;
background: #5469d4;
border-radius: 20.4px 0 0 20.4px;
top: -0.2px;
left: -0.2px;
-webkit-transform-origin: 10.4px 10.2px;
transform-origin: 10.4px 10.2px;
-webkit-animation: loading 2s infinite ease 1.5s;
animation: loading 2s infinite ease 1.5s;
}
.spinner:after {
width: 10.4px;
height: 10.2px;
background: #5469d4;
border-radius: 0 10.2px 10.2px 0;
top: -0.1px;
left: 10.2px;
-webkit-transform-origin: 0px 10.2px;
transform-origin: 0px 10.2px;
-webkit-animation: loading 2s infinite ease;
animation: loading 2s infinite ease;
}
@keyframes loading {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@media only screen and (max-width: 600px) {
form {
width: 80vw;
}
}
`}</style>
</>
);
}
Example #7
Source File: CartTable.js From shopping-cart-fe with MIT License | 4 votes |
CartTable = ({ cartContents, totalPrice, store }) => {
// Hooks for Redux & Stripe
const dispatch = useDispatch();
const stripe = useStripe();
const elements = useElements();
// Getting the Store ID to get their Stripe Client ID
// Ternary is used for Jest tests
const getStoreID = localStorage.getItem('storeUrl')
? localStorage.getItem('storeUrl').split('-')
: '5f1645a717a4730004f569c3';
// State for holding the Name & Phone
const [ userInfo, setUserInfo ] = useState({
fullName: '',
phoneNumber: ''
});
//State for holding the Store Info (Grabbed from redux through props)
const [ storeInfo, setStoreInfo ] = useState({
storeColor: '',
storeLogo: ''
});
useEffect(
() => {
setStoreInfo({
...storeInfo,
storeLogo: store.logo,
storeColor: store.color
});
},
[ store ]
);
//Payload that will be sent to stripe (Stores Owners Client ID)
const [ paymentPayload, setPaymentPayload ] = useState({
price: totalPrice(cartContents),
clientID: ''
});
//State to toggle the checkout
const [ toggleCheckout, setToggleCheckout ] = useState(false);
// Framer Motion Variants (Used for animating the Checkout Card)
const variants = {
hidden: { opacity: 0, bottom: -500 },
visible: { opacity: 1, bottom: 0 }
};
//Making a request to get the store ID
useEffect(() => {
axios
.get(`https://shopping-cart-be.herokuapp.com/api/auth/pk/${getStoreID[1]}`)
.then((res) => {
console.log('super res', res);
setPaymentPayload({ ...paymentPayload, clientID: res.data });
})
.catch((error) => console.log(error));
}, []);
//Order payload only works for one item without a variant -- Needs fixing
const orderPayload = {
orderItem: [
{
product: cartContents.length > 0 ? cartContents[0].productId : '',
quantity: cartContents.length > 0 ? cartContents[0].quantity : '',
chosenVariant: {
price: cartContents.length > 0 ? cartContents[0].price : ''
}
}
]
};
setTimeout(() => {
console.log('cartContents', cartContents);
}, 1000);
const [ message, setMessage ] = useState();
// On submit -> Takes the payload object and POSTs it to the server.
// If sent properly the server will return a secret. This is used below to varify the transaction
const submitHandler = async (event) => {
console.log('payment Payload', paymentPayload);
event.preventDefault();
// ensure stripe & elements are loaded
if (!stripe || !elements) {
return;
}
//Make a payment-intent POST request
axios
.post('https://shopping-cart-be.herokuapp.com/api/stripe/payment/create-payment-intent', paymentPayload)
.then((res) => {
console.log('orderPayload', orderPayload);
axios
.post(`https://shopping-cart-be.herokuapp.com/api/store/${getStoreID[1]}/order`, orderPayload)
.then((res) => {
setMessage('Payment Confirmed!');
console.log(orderPayload);
console.log(res.data);
setTimeout(() => {
history.push(`/success/${res.data._id}`);
}, 2000);
})
.catch((error) => console.log(error));
stripe.confirmCardPayment(res.data.clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
name: userInfo.fullName,
phone: userInfo.phoneNumber
}
}
});
//Creates order for our database
})
.catch((error) => console.log(error));
};
const increment = (id) => {
dispatch(creators.increment(id));
};
const decrement = (id) => {
console.log('isDispatching --', id);
dispatch(creators.decrement(id));
};
const removeItem = (item) => {
console.log('isDispatching item', item);
dispatch(creators.subtractFromCart(item));
};
const arr = cartContents.map((cart) =>
cart.variantDetails.reduce((sum, item) => {
return sum + item.price;
}, 0)
);
const numbers = cartContents.reduce((sum, item) => {
return sum + item.quantity;
}, 0);
function changeHandler(e) {
e.preventDefault();
setUserInfo({ ...userInfo, [e.target.name]: e.target.value });
}
//Style for the Stripe Elements
const CARD_ELEMENT_OPTIONS = {
style: {
base: {
color: '#32325d',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
}
};
return (
<div className="cartMasterContainer">
{cartContents.length > 0 ? (
<div>
<div className="tableHeader">
<p className="productTitle" data-testid="CartTable">
Product
</p>
<p className="priceTitle"> Price</p>
<p className="quantityTitle"> Quantity</p>
<p className="totalTitle"> Total</p>
</div>
<div />
</div>
) : (
<div className="emptyCart">
<img src={emptyCartGraphic} alt="cartImage" />
<h1>This is awkward... </h1>
<h2>You don’t have’t any items in your cart </h2>
</div>
)}
{cartContents ? (
cartContents.map((cv) => {
return (
<div>
<div className="cartProductCard">
<div className="productSection">
<img className="cartImage" src={cv.images[0]} alt="cartImage" />
<div className="productInfo">
<h3 data-testid="productName"> {cv.productName} </h3>
<p>{cv.variantDetails[0].option}</p>
</div>
</div>
{cv.variantDetails[0].price ? (
<h3 data-testid="price">${cv.variantDetails[0].price}</h3>
) : (
<h3 data-testid="price">${cv.price}</h3>
)}
<img src={times_icon} alt="cartImage" />
<div className="quantityContainer">
<img
style={{ background: `${storeInfo.storeColor}` }}
className="quantityBTN"
alt="cartImage"
src={subtract_icon}
onClick={() => {
decrement(cv.productId);
}}
/>
<div className="quantityCounter">
<h3>{cv.quantity}</h3>
</div>
<img
className="quantityBTN"
style={{ background: `${storeInfo.storeColor}` }}
src={add_icon}
alt="cartImage"
onClick={() => {
increment(cv.productId);
}}
/>
</div>
<img className="equalsIcon" alt="cartImage" src={equals_icon} />
{cv.variantDetails[0].option ? (
<h3>
$
{Number.parseFloat(cv.variantDetails[0].price * cv.quantity).toFixed(2)}
</h3>
) : (
<h3>${Number.parseFloat(cv.price * cv.quantity).toFixed(2)}</h3>
)}
<img
className="deleteIcon"
src={delete_icon}
alt="cartImage"
onClick={() => {
console.log('click fired', cv);
removeItem(cv);
}}
/>
</div>
</div>
);
})
) : (
''
)}
{cartContents.length > 0 ? (
<div className="totalPrice">
<div className="total" data-testid="total">
Total: ${totalPrice(cartContents)}
</div>
<button
style={{ background: `${storeInfo.storeColor}` }}
onClick={() => {
setToggleCheckout(!toggleCheckout);
}}
>
Checkout
</button>
<motion.div
initial={'hidden'}
animate={toggleCheckout ? 'visible' : 'hidden'}
variants={variants}
className="checkoutCard"
>
<img className="checkoutLogo" src={storeInfo.storeLogo} />
<h2> Store Checkout </h2>
<h3>
All transactions are secured through Stripe! Once payment is confirmed you will be directed
to a confirmation screen
</h3>
<div className="checkoutElements">
<form onSubmit={submitHandler}>
<div className="inputContainer">
<input
name="fullName"
type="text"
placeholder="Enter Full Name"
value={userInfo.fullName}
onChange={changeHandler}
/>
<input
name="phoneNumber"
type="number"
min="9"
placeholder="Enter Phone Number"
value={userInfo.phoneNumber}
onChange={changeHandler}
/>
</div>
<div className="elementContainer">
<CardElement options={CARD_ELEMENT_OPTIONS} />
</div>
<button
style={
!userInfo.fullName || !userInfo.phoneNumber ? (
{ background: `#d1d1d1` }
) : (
{ background: `${storeInfo.storeColor}` }
)
}
disabled={!userInfo.fullName || !userInfo.phoneNumber}
type="submit"
>
Submit Payment: ${totalPrice(cartContents)}{' '}
</button>
</form>
</div>
<Message message={message} />
</motion.div>
</div>
) : (
''
)}
</div>
);
}
Example #8
Source File: Subscribe.js From subscription-use-cases with MIT License | 4 votes |
Subscribe = ({location}) => {
// Get the lookup key for the price from the previous page redirect.
const [clientSecret] = useState(location.state.clientSecret);
const [subscriptionId] = useState(location.state.subscriptionId);
const [name, setName] = useState('Jenny Rosen');
const [messages, _setMessages] = useState('');
const [paymentIntent, setPaymentIntent] = useState();
// helper for displaying status messages.
const setMessage = (message) => {
_setMessages(`${messages}\n\n${message}`);
}
// Initialize an instance of stripe.
const stripe = useStripe();
const elements = useElements();
if (!stripe || !elements) {
// Stripe.js has not loaded yet. Make sure to disable
// form submission until Stripe.js has loaded.
return '';
}
// When the subscribe-form is submitted we do a few things:
//
// 1. Tokenize the payment method
// 2. Create the subscription
// 3. Handle any next actions like 3D Secure that are required for SCA.
const handleSubmit = async (e) => {
e.preventDefault();
// Get a reference to a mounted CardElement. Elements knows how
// to find your CardElement because there can only ever be one of
// each type of element.
const cardElement = elements.getElement(CardElement);
// Use card Element to tokenize payment details
let { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: name,
}
}
});
if(error) {
// show error and collect new card details.
setMessage(error.message);
return;
}
setPaymentIntent(paymentIntent);
}
if(paymentIntent && paymentIntent.status === 'succeeded') {
return <Redirect to={{pathname: '/account'}} />
}
return (
<>
<h1>Subscribe</h1>
<p>
Try the successful test card: <span>4242424242424242</span>.
</p>
<p>
Try the test card that requires SCA: <span>4000002500003155</span>.
</p>
<p>
Use any <i>future</i> expiry date, CVC,5 digit postal code
</p>
<hr />
<form onSubmit={handleSubmit}>
<label>
Full name
<input type="text" id="name" value={name} onChange={(e) => setName(e.target.value)} />
</label>
<CardElement />
<button>
Subscribe
</button>
<div>{messages}</div>
</form>
</>
)
}
Example #9
Source File: Stripe.js From greenbook with MIT License | 4 votes |
CheckoutForm = (props) => {
const stripe = useStripe();
const elements = useElements();
console.log("state reset for some reason");
const [success, setSuccess] = useState(false);
const [sending, setSending] = useState(0);
const [useCustomAmount, setUseCustomAmount] = useState(false);
const [fields, setFields] = useState({
amount: 50,
//name: "Dan Yo",
//email: "[email protected]",
//address: "6051 Calle Cortez",
//zip: "92886",
});
const setValue = (key, value) => {
let updateFields = { ...fields };
updateFields[key] = value;
setFields(updateFields);
console.log("fields are", fields);
};
const sendApi = (token) => {
console.log("sending fields", { token: token, ...fields });
const card = elements.getElement(CardElement);
fetch("/api/donation", {
method: "post",
body: JSON.stringify({ token: token, ...fields }),
})
.then(function (res) {
console.log("response is", res);
return res.json();
})
.then(async function (data) {
console.log("response is", data);
if (data.error) {
document.getElementById("card-errors").textContent =
data.error;
return false;
} else {
try {
const result = await stripe.confirmCardPayment(
data.intentSecret,
{
payment_method: {
card: card,
billing_details: { name: fields.name },
},
}
);
if (result.error) {
console.log(err);
document.getElementById("card-errors").textContent =
result.error.message;
return false;
} else {
setSuccess(true);
}
} catch (err) {
console.log(err);
document.getElementById("card-errors").textContent =
err.message;
return false;
}
}
setSending(0);
});
};
const handleSubmit = async (event) => {
// Block native form submission.
event.preventDefault();
if (!stripe || !elements) {
// Stripe.js has not loaded yet. Make sure to disable
// form submission until Stripe.js has loaded.
return;
}
// Get a reference to a mounted CardElement. Elements knows how
// to find your CardElement because there can only ever be one of
// each type of element.
const card = elements.getElement(CardElement);
setSending(1);
stripe.createToken(card).then(function (result) {
if (result.error) {
// Inform the customer that there was an error.
var errorElement = document.getElementById("card-errors");
errorElement.textContent = result.error.message;
setSending(0);
} else {
//setValue("token", result.token.id);
sendApi(result.token.id);
}
});
};
useEffect(() => {}, [fields, useCustomAmount]);
let amounts = [15, 25, 50, 100, 150, 200, 300, 500, 750, 1000];
console.log("render");
return (
<div className={stripe_css.stripe}>
{success ? (
<div style={{ margin: "80px 0" }}>
<h2>We have received your donation. Thank You!</h2>
<p>You should be receiving an email receipt shortly.</p>
</div>
) : (
<form
onSubmit={handleSubmit}
disabled={!sending}
action="/api/donation"
method="post"
id="payment-form"
>
<div className={stripe_css.formRow}>
<label htmlFor="card-name">Name on card</label>
<input
type="text"
name="name"
id="card-name"
placeholder="John Doe"
value={fields.name}
onChange={(e) => setValue("name", e.target.value)}
required
/>
</div>
<div className={stripe_css.formRow}>
<label htmlFor="card-address">Billing address</label>
<div className="ib middle">
<div className={stripe_css.subLabel}>
Street address
</div>
<input
type="text"
name="name"
id="card-address"
placeholder="123 Dover St."
value={fields.address}
onChange={(e) =>
setValue("address", e.target.value)
}
required
/>
</div>
<div className="ib middle">
<div className={stripe_css.subLabel}>Zip code</div>
<input
type="text"
name="name"
id="card-zip"
placeholder="92561"
pattern="^\s*\d{5}(-\d{4})?\s*$"
size="5"
value={fields.zip}
onChange={(e) =>
setValue("zip", e.target.value)
}
required
/>
</div>
</div>
<div className={stripe_css.formRow}>
<label htmlFor="card-email">Email Address</label>
<div className={stripe_css.subLabel}>
For an email receipt
</div>
<input
type="email"
name="email"
id="card-email"
value={fields.email || ""}
placeholder="[email protected]"
onChange={(e) => setValue("email", e.target.value)}
required
/>
</div>
<div className={stripe_css.formRow}>
<label htmlFor="card-amount">Donation Amount</label>
{amounts.map((n) => (
<span
key={n}
className={
stripe_css.amount_box +
" " +
(fields.amount === n
? stripe_css.amount_selected
: "")
}
onClick={(e) => {
setValue("amount", n);
setUseCustomAmount(false);
}}
>
${format(n)}
</span>
))}
<span
className={
stripe_css.amount_box +
" " +
(useCustomAmount
? stripe_css.amount_selected
: "")
}
onClick={(e) => {
if (!useCustomAmount) {
setValue("amount", 20);
setUseCustomAmount(true);
} else {
// do nothing its already custom
}
}}
>
Custom
</span>
{useCustomAmount && (
<div>
<input
type="number"
name="custom-amount"
placeholder="Amount"
value={fields.amount || ""}
onChange={(e) =>
setValue("amount", num(e.target.value))
}
/>
</div>
)}
</div>
<div className={stripe_css.formRow}>
<label htmlFor="card-element">
Credit or debit card
</label>
<div
id="card-element"
className={stripe_css.card_element}
style={{ margin: "20px 0" }}
>
<CardElement
options={{
style: {
base: {
color: "#828282",
fontFamily:
'"noto sans", "Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: "antialiased",
fontSize: "16px",
borderRadius: "5px",
"::placeholder": {
color: "#aab7c4",
},
},
invalid: {
color: "#fa755a",
iconColor: "#fa755a",
},
},
}}
/>
</div>
<div id="card-errors" role="alert"></div>
</div>
<div className="form-row">
<button id="card-button" disabled={!stripe && !sending} className={stripe_css.button} sending={sending}>
{sending ?
(<span>Processing Donation...</span>) :
(<span>Donate ${format(fields.amount)}</span>)
}
</button>
</div>
</form>
)}
</div>
);
}
Example #10
Source File: CreditCardPaymentComponent.js From rysolv with GNU Affero General Public License v3.0 | 4 votes |
CreditCardPaymentComponent = ({
amount,
handleClearAllAlerts,
handleStripeToken,
handleZipChange,
setStripeError,
setZipValue,
zipValue,
}) => {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async event => {
event.preventDefault();
if (!stripe || !elements) {
return;
}
const card = elements.getElement(CardNumberElement);
const result = await stripe.createToken(card, zipValue);
handleClearAllAlerts();
if (result.error) {
setStripeError({ message: result.error.message });
} else {
handleStripeToken({
amount,
token: result.token,
values: { depositValue: amount },
});
}
};
return (
<div>
<InputWrapper width="50%">
<InputHeader>Card Number</InputHeader>
<StyledBaseTextInput
InputProps={{
inputComponent: StripeInput,
inputProps: {
component: CardNumberElement,
},
}}
variant="outlined"
/>
</InputWrapper>
<HorizontalWrapper>
<InputWrapper width="50%">
<InputHeader>Expiration Date</InputHeader>
<StyledBaseTextInput
InputProps={{
inputComponent: StripeInput,
inputProps: {
component: CardExpiryElement,
},
}}
variant="outlined"
/>
</InputWrapper>
<InputWrapper width="35%">
<InputHeader>
CVV
<TooltipIconWrapper>
<TooltipIcon Icon={InfoIcon} Tooltip={CvvTooltip} />
</TooltipIconWrapper>
</InputHeader>
<StyledBaseTextInput
InputProps={{
inputComponent: StripeInput,
inputProps: {
component: CardCvcElement,
},
}}
variant="outlined"
/>
</InputWrapper>
</HorizontalWrapper>
<InputWrapper width="25%">
<InputHeader>Postal Code</InputHeader>
<StyledBaseTextInput
inputProps={{ maxLength: 5 }}
onChange={e => handleZipChange(e, e.target.value, setZipValue)}
value={zipValue}
variant="outlined"
/>
</InputWrapper>
<StyledPrimaryAsyncButton
disabled={amount <= 0}
label="Confirm"
onClick={handleSubmit}
/>
</div>
);
}
Example #11
Source File: CreditCardView.js From rysolv with GNU Affero General Public License v3.0 | 4 votes |
CreditCardView = ({
emailValue,
fundValue,
handleClearAlerts,
handleStripeToken,
handleZipChange,
isCreditPaymentOpen,
isPersonalInfoComplete,
setFundValue,
setStripeError,
setZipValue,
values,
zipValue,
}) => {
const stripe = useStripe();
const elements = useElements();
const fundAmount = Number(fundValue);
const feeValue = fundAmount * 0.03 + 0.3;
const totalValue = fundAmount + feeValue;
const handleSubmit = async event => {
event.preventDefault();
if (!stripe || !elements) {
return;
}
const card = elements.getElement(CardNumberElement);
const { error, token } = await stripe.createToken(card, zipValue);
handleClearAlerts();
if (error) {
setStripeError({ message: error.message });
} else {
handleStripeToken({
amount: fundValue,
email: emailValue,
token,
values,
});
setFundValue('10');
}
};
return (
<ConditionalRender
Component={
<Fragment>
<CreditCardViewContainer>
<TextWrapper>
A 3% + $0.30 standard transaction fee will be added to cover
credit card processing and the safe transfer of funds.
</TextWrapper>
<ChargeBreakdownWrapper>
<ChargeTitle>
<Title>Transaction fee</Title>
<Title isBold>Total due today</Title>
</ChargeTitle>
<ChargeValue>
<Value>{formatDollarAmount(parseFloat(feeValue, 10))}</Value>
<Value isBold>
{formatDollarAmount(parseFloat(totalValue, 10))}
</Value>
</ChargeValue>
</ChargeBreakdownWrapper>
<InputWrapper>
<StyledPaymentTextInput
adornmentComponent="Number"
InputProps={{
inputComponent: StripeInput,
inputProps: {
component: CardNumberElement,
},
}}
fontSize="1rem"
/>
<StyledPaymentTextInput
adornmentComponent="MM/YY"
InputProps={{
inputComponent: StripeInput,
inputProps: {
component: CardExpiryElement,
},
}}
fontSize="1rem"
/>
<HorizontalInputWrapper>
<StyledPaymentTextInput
adornmentComponent="CVC"
InputProps={{
inputComponent: StripeInput,
inputProps: {
component: CardCvcElement,
},
}}
fontSize="1rem"
/>
<StyledPaymentTextInput
adornmentComponent="Zip"
fontSize="1rem"
inputProps={{ maxLength: 5 }}
onChange={e =>
handleZipChange(e, e.target.value, setZipValue)
}
value={zipValue}
/>
</HorizontalInputWrapper>
</InputWrapper>
<StyledPrimaryAsyncButton
disabled={!isPersonalInfoComplete || !stripe}
label="Confirm"
onClick={handleSubmit}
/>
</CreditCardViewContainer>
</Fragment>
}
shouldRender={isCreditPaymentOpen}
/>
);
}
Example #12
Source File: CompanyPayment.jsx From rysolv with GNU Affero General Public License v3.0 | 4 votes |
CompanyPaymentModal = ({
dispatchClearAlerts,
dispatchFetchPlaidToken,
dispatchResetModalState,
dispatchSetModalAlerts,
dispatchUpdatePaymentMethod,
handleClose,
modalAlerts,
modalLoading,
paymentConfirmed,
plaidToken,
}) => {
const [selectedMethod, setSelectedMethod] = useState('Credit card');
const [zipCode, setZipCode] = useState('');
const elements = useElements();
const stripe = useStripe();
useEffect(() => {
if (!plaidToken) dispatchFetchPlaidToken();
return dispatchResetModalState;
}, []);
const handleSubmit = async () => {
if (!stripe || !elements) return;
const cardElement = elements.getElement(CardNumberElement);
const { error, token } = await stripe.createToken(cardElement, zipCode);
if (!error) {
const { id, card } = token;
dispatchUpdatePaymentMethod({
metadata: card,
provider: 'stripe',
token: id,
});
} else {
// Using standardized 'Something went wrong' errors for now
// Stripe provides more detailed errors
dispatchSetModalAlerts({ error: stripeError });
}
};
return (
<ModalContainer>
<ConditionalRender
Component={
<Fragment>
<StyledTitle>
{paymentConfirmed ? 'Update' : 'Add'} payment method
</StyledTitle>
<StyledErrorSuccessBanner
error={modalAlerts.error}
onClose={dispatchClearAlerts}
success={modalAlerts.success}
/>
<BaseRadioButtonGroup
handleRadioChange={e => setSelectedMethod(e.target.value)}
selectedValue={selectedMethod}
values={['Credit card', 'ACH']}
/>
<ConditionalRender
Component={
<CreditCard setZipCode={setZipCode} zipCode={zipCode} />
}
FallbackComponent={
<ACH
dispatchSetModalAlerts={dispatchSetModalAlerts}
dispatchUpdatePaymentMethod={dispatchUpdatePaymentMethod}
plaidToken={plaidToken}
/>
}
shouldRender={selectedMethod === 'Credit card'}
/>
<ButtonWrapper>
<StyledPrimaryButton label="Cancel" onClick={handleClose} />
<ConditionalRender
Component={
<StyledPrimaryAsyncButton
disabled={!stripe || !zipCode}
label="Save"
loading={modalLoading}
onClick={handleSubmit}
/>
}
shouldRender={selectedMethod === 'Credit card'}
/>
</ButtonWrapper>
<DisclaimerWrapper>
<Asterisk>*</Asterisk> Payment authorized with
{selectedMethod === 'Credit card' ? 'Stripe' : 'Plaid'}.
</DisclaimerWrapper>
</Fragment>
}
FallbackComponent={PaymentLoadingIndicator}
shouldRender={!modalLoading}
/>
</ModalContainer>
);
}
Example #13
Source File: checkout-form.js From muffinsplantshop with BSD Zero Clause License | 4 votes |
PaymentForm = ({ clientSecret, shippingValues, billingValues, sameAsShipping }) => {
const [succeeded, setSucceeded] = useState(false);
const [error, setError] = useState(null);
const [processing, setProcessing] = useState('');
const [disabled, setDisabled] = useState(true);
const stripe = useStripe();
const elements = useElements();
const { cartDispatch } = useCartContext();
const cardOptions = {
hidePostalCode: true,
style: {
base: {
color: `rgba(0,0,0, 0.85)`,
fontFamily: `Source Sans Pro, Helvetica, Arial, sans-serif`,
fontSize: `16px`,
fontWeight: `300`,
"::placeholder": {
color: `rgba(0,0,0, 0.85)`
}
}
}
}
const handleChange = async (event) => {
setDisabled(event.empty);
setError(event.error ? event.error.message : "");
}
const handleCheckout = async (ev) => {
ev.preventDefault();
setProcessing(true);
const billing_address = {
line1: sameAsShipping ? shippingValues.addressOne : billingValues.addressOne,
line2: sameAsShipping ? shippingValues.addressTwo : billingValues.addressTwo,
city: sameAsShipping ? shippingValues.municipality : billingValues.municipality,
country: `CA`,
postal_code: sameAsShipping ? shippingValues.postalCode : billingValues.postalCode,
state: sameAsShipping ? shippingValues.provinceTerritory : billingValues.provinceTerritory
}
const shipping_address = {
line1: shippingValues.addressOne,
line2: shippingValues.addressTwo,
city: shippingValues.municipality,
country: `CA`,
postal_code: shippingValues.postalCode,
state: shippingValues.provinceTerritory
}
const payload = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
address: billing_address
}
},
shipping: {
name: `${shippingValues.firstName} ${shippingValues.lastName}`,
address: shipping_address
},
});
if (payload.error) {
setError(`Payment failed ${payload.error.message}`);
setProcessing(false);
} else {
setError(null);
setProcessing(false);
setSucceeded(true);
cartDispatch({ type: 'CLEAR_CART' });
navigate("/success");
}
}
return (
<div style={{ padding: `0 1rem` }}>
{!succeeded &&
(<form onSubmit={handleCheckout}>
<p style={{ fontSize: `0.8rem`, paddingBottom: `1rem` }}>Test card number: 4242 4242 4242 4242 <br />
CVC: Any 3 digits <br />
Expiry: Any future date
</p>
<CardElement
id="card-element"
onChange={handleChange}
options={cardOptions} />
{error && (
<div className={styles.checkoutForm__error} role="alert">
{error}
</div>
)}
<button
className={styles.checkoutForm__submitButton}
disabled={processing || disabled || succeeded}
type="submit">
Place order
</button>
</form>)}
{succeeded && <p>Test payment succeeded!</p>}
</div>
)
}
Example #14
Source File: HomePage.js From tutorial-code with MIT License | 4 votes |
function HomePage() {
const classes = useStyles();
// State
const [email, setEmail] = useState('');
const stripe = useStripe();
const elements = useElements();
const handleSubmitPay = async (event) => {
if (!stripe || !elements) {
// Stripe.js has not yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
const res = await axios.post('http://localhost:3000/pay', {email: email});
const clientSecret = res.data['client_secret'];
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
email: email,
},
},
});
if (result.error) {
// Show error to your customer (e.g., insufficient funds)
console.log(result.error.message);
} else {
// The payment has been processed!
if (result.paymentIntent.status === 'succeeded') {
console.log('Money is in the bank!');
// Show a success message to your customer
// There's a risk of the customer closing the window before callback
// execution. Set up a webhook or plugin to listen for the
// payment_intent.succeeded event that handles any business critical
// post-payment actions.
}
}
};
const handleSubmitSub = async (event) => {
if (!stripe || !elements) {
// Stripe.js has not yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
const result = await stripe.createPaymentMethod({
type: 'card',
card: elements.getElement(CardElement),
billing_details: {
email: email,
},
});
if (result.error) {
console.log(result.error.message);
} else {
const res = await axios.post('http://localhost:3000/sub', {'payment_method': result.paymentMethod.id, 'email': email});
// eslint-disable-next-line camelcase
const {client_secret, status} = res.data;
if (status === 'requires_action') {
stripe.confirmCardPayment(client_secret).then(function(result) {
if (result.error) {
console.log('There was an issue!');
console.log(result.error);
// Display error message in your UI.
// The card was declined (i.e. insufficient funds, card has expired, etc)
} else {
console.log('You got the money!');
// Show a success message to your customer
}
});
} else {
console.log('You got the money!');
// No additional information was needed
// Show a success message to your customer
}
}
};
return (
<Card className={classes.root}>
<CardContent className={classes.content}>
<TextField
label='Email'
id='outlined-email-input'
helperText={`Email you'll recive updates and receipts on`}
margin='normal'
variant='outlined'
type='email'
required
value={email}
onChange={(e) => setEmail(e.target.value)}
fullWidth
/>
<CardInput />
<div className={classes.div}>
<Button variant="contained" color="primary" className={classes.button} onClick={handleSubmitPay}>
Pay
</Button>
<Button variant="contained" color="primary" className={classes.button} onClick={handleSubmitSub}>
Subscription
</Button>
</div>
</CardContent>
</Card>
);
}
Example #15
Source File: CheckoutForm.jsx From real-estate-site with MIT License | 4 votes |
export default function CheckoutForm(props) {
const { userReducer } = useStore().getState();
const stripe = useStripe();
const elements = useElements();
const dispatch = useDispatch();
const handleSubmit = async event => {
event.preventDefault();
if (!stripe || !elements || !state.foundUserId) {
return;
}
const result = await stripe.confirmCardPayment(state.userId, {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
name: userReducer.user.email
}
}
});
if (result.error) {
// Show error to your customer (e.g., insufficient funds)
console.log(result.error.message);
} else {
// The payment has been processed!
if (result.paymentIntent.status === "succeeded") {
const { jwt } = userReducer;
axios.defaults.headers.common["Authorization"] = `Bearer ${jwt}`;
axios
.post(`${baseUrl}/user/addcredits`, { ...result })
.then(res => {
dispatch(creditAddSuccess(res.data));
state.requested = false;
state.foundUserId = false;
})
.catch(console.error);
}
}
};
useEffect(() => {
if (userReducer) {
if (!state.amountInCents) {
state.amountInCents = +props.amountInCents;
} else if (!state.requested) {
state.requested = true;
const { jwt } = userReducer;
axios.defaults.headers.common["Authorization"] = `Bearer ${jwt}`;
axios
.get(`${baseUrl}/payment/${state.amountInCents}`)
.then(res => {
// console.log(res.data);
state.userId = res.data.client_secret;
state.foundUserId = true;
})
.catch(console.error);
}
}
});
return (
<Fragment>
<div className="alert alert-info mt-3" role="alert">
You are going to top up your account for {state.amountInCents / 100}{" "}
EUR. It is {state.amountInCents / 100} new advertisements. 1
advertisement price is 1 EUR.
</div>
<div className="row">
{userReducer ? (
<form onSubmit={handleSubmit} className="col-12">
<CardSection />
<br />
<button disabled={!stripe} className="btn btn-success">
Confirm order
</button>
</form>
) : (
<div class="alert alert-warning mt-3" role="alert">
Sorry to TopUp your account, you should login first
</div>
)}
</div>
</Fragment>
);
}
Example #16
Source File: CheckoutForm.jsx From dineforward with MIT License | 4 votes |
CheckoutForm = ({ config, cart }) => {
const stripe = useStripe();
const elements = useElements();
// Handle new PaymentIntent result
const handlePayment = paymentResponse => {
const { paymentIntent, error } = paymentResponse;
if (error) {
cart.setStatus({
class: 'error',
message: error.message,
});
} else if (paymentIntent.status === 'succeeded') {
cart.setStatus({
class: 'success',
message:
'We just sent your receipt to your email address, and your items will be on their way shortly.',
});
} else if (paymentIntent.status === 'processing') {
cart.setStatus({
class: 'success',
message:
'We’ll send your receipt and ship your items as soon as your payment is confirmed.',
});
} else {
// Payment has failed.
cart.setStatus({
class: 'error',
});
}
};
const handleFormSubmit = async event => {
// TODO REACTIFY
// Block native form submission.
event.preventDefault();
if (!stripe || !elements) {
// Stripe.js has not loaded yet. Make sure to disable
// form submission until Stripe.js has loaded.
return;
}
const form = event.target;
// Retrieve the user information from the form.
const payment = form.querySelector('input[name=payment]:checked').value;
const name = form.querySelector('input[name=name]').value;
const country = form.querySelector('select[name=country] option:checked').value;
const email = form.querySelector('input[name=email]').value;
const shipping = {
name,
address: {
line1: form.querySelector('input[name=address]').value,
city: form.querySelector('input[name=city]').value,
postal_code: form.querySelector('input[name=postal_code]').value,
state: form.querySelector('input[name=state]').value,
country,
},
};
// Disable the Pay button to prevent multiple click events.
// submitButton.disabled = true;
// submitButton.textContent = 'Processing…';
// Get a reference to a mounted CardElement. Elements knows how
// to find your CardElement because there can only ever be one of
// each type of element.
const cardElement = elements.getElement(CardElement);
if (payment === 'card') {
// Let Stripe.js handle the confirmation of the PaymentIntent with the card Element.
const response = await stripe.confirmCardPayment(cart.paymentIntent.client_secret, {
payment_method: {
card: cardElement,
billing_details: {
name,
},
},
shipping,
});
console.log({ response });
handlePayment(response);
}
};
return (
<form id="payment-form" onSubmit={handleFormSubmit}>
<BillingInformation config={config} />
<PaymentInformation />
<button className="payment-button" type="submit">
Pay {formatAmountForDisplay(cart?.total, cart?.currency)}
</button>
</form>
);
}
Example #17
Source File: JobPostForm.js From remotebond-remote-jobs with Creative Commons Zero v1.0 Universal | 4 votes |
JobPostForm = ({ paymentIntentSSR }) => {
const defaultValues = {
position: "",
category: "Software Development",
tags: "",
location: "Remote",
description: "",
minSalary: null,
maxSalary: null,
applyLink: "",
company_name: "",
company_email: "",
company_website: "",
company_twitter: "",
company_logo: "",
company_is_highlighted:
paymentIntentSSR.amount === 12500 || paymentIntentSSR.amount === 15000
? true
: false,
show_company_logo:
paymentIntentSSR.amount === 5000 || paymentIntentSSR.amount === 15000
? true
: false,
}
let tempTags = []
const stripe = useStripe()
const elements = useElements()
const [payment, setPayment] = useState({ status: "initial" })
const [checkoutError, setCheckoutError] = useState()
const [checkoutSuccess, setCheckoutSuccess] = useState()
const [logoImage, setLogoImage] = useState()
const [formLogoFile, setFormLogoFile] = useState()
const [jobPrice, setJobPrice] = useState(paymentIntentSSR?.amount)
const { handleSubmit, register, errors, watch, control, setValue } = useForm({
defaultValues,
})
const isPostHighlighted = watch("company_is_highlighted")
const modules = {
toolbar: [
[{ header: "1" }, { header: "2" }],
["bold", "italic", "underline", "strike", "blockquote"],
[{ list: "ordered" }, { list: "bullet" }],
["link"],
["clean"],
],
clipboard: {
// toggle to add extra line breaks when pasting HTML:
matchVisual: false,
},
}
/*
* Quill editor formats
* See https://quilljs.com/docs/formats/
*/
const formats = [
"header",
"bold",
"italic",
"underline",
"strike",
"blockquote",
"list",
"bullet",
"indent",
"link",
]
const onSubmit = async (values, e) => {
e.preventDefault()
setPayment({ status: "processing" })
try {
const { error, paymentIntent } = await stripe.confirmCardPayment(
paymentIntentSSR.client_secret,
{
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
email: values.company_email,
name: values.company_name,
},
},
}
)
console.log(error)
if (error) throw new Error(error.message)
if (paymentIntent.status === "succeeded") {
setPayment({ status: "succeeded" })
destroyCookie(null, "paymentIntentId")
// Build formdata object
let formData = new FormData()
formData.append("position", values.position)
formData.append("company_name", values.company_name)
formData.append("category", values.category)
formData.append("tags", values.tags)
formData.append("location", values.location)
formData.append("show_company_logo", values.show_company_logo)
formData.append("company_is_highlighted", values.company_is_highlighted)
formData.append("minSalary", values.minSalary)
formData.append("maxSalary", values.maxSalary)
formData.append("applyLink", values.applyLink)
formData.append("company_email", values.company_email)
formData.append("company_logo", formLogoFile)
formData.append("company_website", values.company_website)
formData.append("company_twitter", values.company_twitter)
formData.append("description", values.description)
const newJobResponse = await fetch(
`${window.location.origin}/api/jobs/new`,
{
method: "post",
headers: {
"rb-stripe-id": paymentIntentSSR.id,
},
body: formData,
}
)
setCheckoutSuccess(true)
}
} catch (err) {
setPayment({ status: "error" })
setCheckoutError(err.message)
}
}
const handleFileInputChange = (event) => {
setLogoImage(URL.createObjectURL(event.target.files[0]))
setFormLogoFile(event.target.files[0])
setValue("show_company_logo", true)
handleShowCompanyLogoChange()
}
const handleShowCompanyLogoChange = async (event) => {
const isChecked = event?.target?.checked
if (isChecked || watch("show_company_logo")) {
const intentResponse = await fetch(
`${window.location.origin}/api/stripe/intents?package=logo_add&token=${paymentIntentSSR.id}`
)
// Intent is OK, continue
intentResponse.status === 200 &&
setJobPrice((prevPrice) => {
if (prevPrice === 2500 || prevPrice === 12500) {
return prevPrice + 2500
} else {
return prevPrice
}
})
} else {
const intentResponse = await fetch(
`${window.location.origin}/api/stripe/intents?package=logo_remove&token=${paymentIntentSSR.id}`
)
// Intent is OK, continue
intentResponse.status === 200 &&
setJobPrice((prevPrice) => prevPrice - 2500)
setLogoImage(null)
setFormLogoFile(null)
setValue("company_logo", "")
}
}
const handleHighlightPostChange = async (event) => {
const isChecked = event?.target?.checked
if (isChecked || watch("company_is_highlighted")) {
const intentResponse = await fetch(
`${window.location.origin}/api/stripe/intents?package=highlight_add&token=${paymentIntentSSR.id}`
)
intentResponse.status === 200 &&
setJobPrice((prevPrice) => prevPrice + 10000)
} else {
const intentResponse = await fetch(
`${window.location.origin}/api/stripe/intents?package=highlight_remove&token=${paymentIntentSSR.id}`
)
intentResponse.status === 200 &&
setJobPrice((prevPrice) => prevPrice - 10000)
}
}
// Dirty hack to set tags in preview box
tempTags = !watch("tags")
? ["Add tag", "Add tag", "Add tag"]
: watch("tags").split(",")
if (checkoutSuccess)
return (
<div className="bg-white flex flex-1 justify-center items-center">
<div className="max-w-screen-xl mx-auto py-4 px-4 sm:px-6">
<div className="text-white bg-rb-green-6 rounded-full w-20 h-20 mx-auto sm:w-24 sm:h-24">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
</div>
<div className="w-full text-center py-6">
<h2 className="text-rb-green-6 text-2xl font-bold mb-2 sm:text-4xl">
Job posted
</h2>
<p className="mb-2">
Your job has been posted and will be available soon.
</p>
<p>Please check your provided email for further information.</p>
<Link href={`/`} as={`/`}>
<a className="mt-8 inline-flex items-center px-6 py-3 border border-transparent text-base leading-6 font-bold rounded-md text-white bg-rb-green-6 hover:bg-rb-green-5 hover:text-white focus:outline-none focus:border-rb-green-7 focus:shadow-outline-blue active:bg-rb-green-7 transition ease-in-out duration-150">
Return to homepage
</a>
</Link>
</div>
</div>
</div>
)
return (
<form onSubmit={handleSubmit(onSubmit)} className="pt-6 bg-rb-gray-1">
<div className="bg-rb-gray-1">
<div className="max-w-screen-xl mx-auto py-4 px-4 sm:px-6">
{Object.keys(errors).length !== 0 && (
<Alert
title={`There ${Object.keys(errors).length > 1 ? "are" : "is"} ${
Object.keys(errors).length
} ${
Object.keys(errors).length > 1 ? "errors" : "error"
} with your submission`}
message={`Please fix the marked ${
Object.keys(errors).length > 1 ? "fields" : "field"
} and try submitting your job post again`}
/>
)}
{checkoutError && (
<Alert title="Payment errors" message={checkoutError} />
)}
<div className="bg-white shadow px-4 py-5 sm:rounded-lg sm:p-6">
<div className="md:grid md:grid-cols-3 md:gap-6">
<div className="md:col-span-1">
<h3 className="text-lg font-medium leading-6 text-gray-900">
Job information
</h3>
<p className="mt-1 text-sm leading-5 text-gray-500">
Fill in the main information of your listing.
</p>
</div>
<div className="mt-5 md:mt-0 md:col-span-2">
<div className="grid grid-cols-6 gap-6">
<div className="col-span-6 sm:col-span-6">
<label
htmlFor="position"
className={`flex justify-between text-sm font-medium leading-5 ${
!errors.position ? "text-gray-700" : "text-red-500"
}`}
>
* Position{" "}
{errors.position && (
<span className="inline-block text-right">
{errors.position.message}
</span>
)}
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
id="position"
name="position"
ref={register({
required: "Job position is required",
})}
className={`${
!errors.position
? "mt-1 form-input block w-full py-2 px-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition duration-150 ease-in-out sm:text-sm sm:leading-5"
: "form-input block w-full pr-10 border-red-300 text-red-900 placeholder-red-300 focus:border-red-300 focus:shadow-outline-red sm:text-sm sm:leading-5"
}`}
/>
{errors.position && (
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg
className="h-5 w-5 text-red-500"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
</div>
)}
</div>
<p className="mt-2 text-xs text-gray-400">
Please specify as single job position like "Fullstack
developer Manager" or "Social Media manager".
</p>
</div>
<div className="col-span-6 sm:col-span-4">
<label
htmlFor="company_name"
className={`flex justify-between text-sm font-medium leading-5 ${
!errors.company_name ? "text-gray-700" : "text-red-500"
}`}
>
* Company{" "}
{errors.company_name && (
<span className="inline-block text-right">
{errors.company_name.message}
</span>
)}
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
id="company_name"
name="company_name"
ref={register({
required: "Company name is required",
})}
className={`${
!errors.company_name
? "mt-1 form-input block w-full py-2 px-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition duration-150 ease-in-out sm:text-sm sm:leading-5"
: "form-input block w-full pr-10 border-red-300 text-red-900 placeholder-red-300 focus:border-red-300 focus:shadow-outline-red sm:text-sm sm:leading-5"
}`}
/>
{errors.company_name && (
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg
className="h-5 w-5 text-red-500"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
</div>
)}
</div>
</div>
<div className="col-span-6 sm:col-span-3">
<label
htmlFor="category"
className="block text-sm font-medium leading-5 text-gray-700"
>
* Category
</label>
<select
id="category"
name="category"
ref={register}
className="mt-1 block form-select w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition duration-150 ease-in-out sm:text-sm sm:leading-5"
>
<option>Software Development</option>
<option>Customer Support</option>
<option>Marketing</option>
<option>Design</option>
<option>Non Tech</option>
</select>
</div>
<div className="col-span-6 sm:col-span-6">
<label
htmlFor="tags"
className={`flex justify-between text-sm font-medium leading-5 ${
!errors.tags ? "text-gray-700" : "text-red-500"
}`}
>
* Tags (Comma seperated)
{errors.tags && (
<span className="inline-block text-right">
{errors.tags.message}
</span>
)}
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
id="tags"
name="tags"
ref={register({
required: "Tags are required",
pattern: {
value: /([a-zA-Z]*[ ]*,[ ]*)*[a-zA-Z]*/gm,
message: "Please use comma to seperate tags",
},
})}
className={`${
!errors.tags
? "mt-1 form-input block w-full py-2 px-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition duration-150 ease-in-out sm:text-sm sm:leading-5"
: "form-input block w-full pr-10 border-red-300 text-red-900 placeholder-red-300 focus:border-red-300 focus:shadow-outline-red sm:text-sm sm:leading-5"
}`}
placeholder="Design, Marketing, Javascript, React"
/>
{errors.tags && (
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg
className="h-5 w-5 text-red-500"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
</div>
)}
</div>
<p className="mt-2 text-xs text-gray-400">
Use tags like industry and tech stack, and separate
multiple tags by comma. The first 3 tags are shown on the
site, other tags are still used for tag specific pages.
</p>
</div>
<div className="col-span-6 sm:col-span-6">
<label
htmlFor="location"
className={`flex justify-between text-sm font-medium leading-5 ${
!errors.location ? "text-gray-700" : "text-red-500"
}`}
>
* Location
{errors.location && (
<span className="inline-block text-right">
{errors.location.message}
</span>
)}
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
id="location"
name="location"
className={`${
!errors.location
? "mt-1 form-input block w-full py-2 px-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition duration-150 ease-in-out sm:text-sm sm:leading-5"
: "form-input block w-full pr-10 border-red-300 text-red-900 placeholder-red-300 focus:border-red-300 focus:shadow-outline-red sm:text-sm sm:leading-5"
}`}
defaultValue="Remote"
ref={register({
required: "Job location is required",
})}
/>
{errors.location && (
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg
className="h-5 w-5 text-red-500"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
</div>
)}
</div>
<p className="mt-2 text-xs text-gray-400">
Location for this job, leave "Remote" if it's a remote
job.
</p>
</div>
</div>
</div>
</div>
</div>
<div className="mt-6 bg-white shadow px-4 py-5 sm:rounded-lg sm:p-6">
<div className="md:grid md:grid-cols-3 md:gap-6">
<div className="md:col-span-1">
<h3 className="text-lg font-medium leading-6 text-gray-900">
<span className="text-blue-500">
<strong>Help your job stand out</strong>
</span>
</h3>
<p className="mt-1 text-sm leading-5 text-gray-500">
Choose a package to get more attention for you job post.
</p>
</div>
<div className="mt-5 md:mt-0 md:col-span-2">
<div className="mt-4">
<div className="flex items-start">
<div className="flex items-center h-5">
<input
id="showCompanyLogo"
type="checkbox"
name="show_company_logo"
ref={register}
onChange={handleShowCompanyLogoChange}
className="form-checkbox h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
/>
</div>
<div className="ml-3 text-sm leading-5">
<label
htmlFor="showCompanyLogo"
className="font-medium text-gray-700"
>
Company logo (+$25)
</label>
<p className="text-gray-500 flex flex-col md:flex-row">
<span className="mr-3 mb-1 md:mb-0">
Show your company logo beside your post.
</span>
<div>
<span className="inline-flex mr-3 flex-grow-0 items-center px-2 py-0.5 rounded text-xs font-medium leading-4 bg-yellow-100 text-yellow-800">
More views
</span>
<span className="inline-flex flex-grow-0 items-center px-2 py-0.5 rounded text-xs font-medium leading-4 bg-red-100 text-red-800">
Recommended
</span>
</div>
</p>
</div>
</div>
<div className="mt-4">
<div className="flex items-start">
<div className="flex items-center h-5">
<input
id="highlightPost"
name="company_is_highlighted"
ref={register}
type="checkbox"
onChange={handleHighlightPostChange}
className="form-checkbox h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
/>
</div>
<div className="ml-3 text-sm leading-5">
<label
htmlFor="highlightPost"
className="font-medium text-gray-700"
>
Highlight post (+$100)
</label>
<p className="text-gray-500 flex flex-col md:flex-row">
<span className="mr-3 mb-1 md:mb-0">
Highlight your post in yellow for more attention.
</span>
<div>
<span className="inline-flex flex-grow-0 items-center px-2 py-0.5 rounded text-xs font-medium leading-4 bg-yellow-100 text-yellow-800">
More views
</span>
</div>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="mt-6 bg-white shadow px-4 py-5 sm:rounded-lg sm:p-6">
<div className="md:grid md:grid-cols-3 md:gap-6">
<div className="md:col-span-1">
<h3 className="text-lg font-medium leading-6 text-gray-900">
Job details
</h3>
<p className="mt-1 text-sm leading-5 text-gray-500">
Now let's get into the details of the listing.
</p>
</div>
<div className="mt-5 md:mt-0 md:col-span-2">
<div className="grid grid-cols-6 gap-6">
<div className="col-span-6 sm:col-span-3">
<label
htmlFor="company_logo"
className="block text-sm leading-5 font-medium text-gray-700"
>
Company logo (.JPG or .PNG)
</label>
<div className="mt-2 flex items-center">
<div className="inline-block h-24 w-24 rounded-sm overflow-hidden bg-gray-100 relative">
{logoImage && (
<img
className="absolute inset-0 object-cover h-full w-full"
src={logoImage}
alt="Company logo"
/>
)}
{!logoImage && (
<div className="flex justify-center items-center h-full">
<p className="px-2 py-1 text-sm bg-blue-500 text-center rounded-sm text-white hover:bg-blue-100 hover:text-blue-400">
Upload
</p>
</div>
)}
<input
type="file"
ref={register}
id="company_logo"
name="company_logo"
accept="image/png, image/jpeg"
className="absolute inset-0 appearance-none h-full w-full opacity-0 cursor-pointer z-10"
onChange={handleFileInputChange}
/>
</div>
</div>
</div>
<div className="col-span-6 sm:col-span-6">
<label
htmlFor="job_salary_min"
className="block text-sm font-medium leading-5 text-gray-700"
>
Annual salary
</label>
<div className="grid grid-cols-6 gap-6">
<div className="col-span-6 sm:col-span-2">
<div className="mt-1 relative rounded-md shadow-sm">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm sm:leading-5">
$
</span>
</div>
<input
id="job_salary_min"
name="minSalary"
ref={register}
className="form-input block w-full pl-7 pr-12 sm:text-sm sm:leading-5"
placeholder="Min per year"
aria-describedby="currency"
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span
className="text-gray-500 sm:text-sm sm:leading-5"
id="currency"
>
USD
</span>
</div>
</div>
</div>
<div className="col-span-6 sm:col-span-1 text-center pt-2">
-
</div>
<div className="col-span-6 sm:col-span-2">
<div className="mt-1 relative rounded-md shadow-sm">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm sm:leading-5">
$
</span>
</div>
<input
id="job_salary_max"
name="maxSalary"
ref={register}
className="form-input block w-full pl-7 pr-12 sm:text-sm sm:leading-5"
placeholder="Max per year"
aria-describedby="currency"
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span
className="text-gray-500 sm:text-sm sm:leading-5"
id="currency"
>
USD
</span>
</div>
</div>
</div>
</div>
<p className="mt-2 text-xs text-gray-400">
Not required but HIGHLY recommended, because Google does
NOT index jobs without salary data! Write it preferrably
in US DOLLARS PER YEAR, like $25,000 - $75,000.
</p>
</div>
<div className="col-span-6">
<label
htmlFor="email_address"
className={`flex justify-between text-sm font-medium leading-5 ${
!errors.description ? "text-gray-700" : "text-red-500"
}`}
>
* Description
{errors.description && (
<span className="inline-block text-right">
{errors.description.message}
</span>
)}
</label>
<WysiwygEditor
control={control}
inputError={errors}
modules={modules}
formats={formats}
/>
</div>
<div className="col-span-6 sm:col-span-6">
<label
htmlFor="applyLink"
className={`flex justify-between text-sm font-medium leading-5 ${
!errors.applyLink ? "text-gray-700" : "text-red-500"
}`}
>
* Apply link or email
{errors.applyLink && (
<span className="inline-block text-right">
{errors.applyLink.message}
</span>
)}
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
id="applyLink"
name="applyLink"
className={`${
!errors.applyLink
? "mt-1 form-input block w-full py-2 px-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition duration-150 ease-in-out sm:text-sm sm:leading-5"
: "form-input block w-full pr-10 border-red-300 text-red-900 placeholder-red-300 focus:border-red-300 focus:shadow-outline-red sm:text-sm sm:leading-5"
}`}
ref={register({
required: "Apply link / email is required",
})}
/>
{errors.applyLink && (
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg
className="h-5 w-5 text-red-500"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
</div>
)}
</div>
<p className="mt-2 text-xs text-gray-400">
Provide a link or email for applicants. If you provide an
email, this email will public.
</p>
</div>
</div>
</div>
</div>
</div>
<div className="mt-6 bg-white shadow px-4 py-5 sm:rounded-lg sm:p-6">
<div className="md:grid md:grid-cols-3 md:gap-6">
<div className="md:col-span-1">
<h3 className="text-lg font-medium leading-6 text-gray-900">
Company information
</h3>
<p className="mt-1 text-sm leading-5 text-gray-500">
The information here will be used for billing, invoice and
your company profile.
</p>
</div>
<div className="mt-5 md:mt-0 md:col-span-2">
<div className="grid grid-cols-6 gap-6">
<div className="col-span-6 sm:col-span-6">
<label
htmlFor="company_email"
className={`flex justify-between text-sm font-medium leading-5 ${
!errors.company_email ? "text-gray-700" : "text-red-500"
}`}
>
* Company email{" "}
{errors.company_email && (
<span className="inline-block text-right">
{errors.company_email.message}
</span>
)}
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
id="company_email"
name="company_email"
ref={register({
required: "Company email is required",
})}
className={`${
!errors.company_email
? "mt-1 form-input block w-full py-2 px-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition duration-150 ease-in-out sm:text-sm sm:leading-5"
: "form-input block w-full pr-10 border-red-300 text-red-900 placeholder-red-300 focus:border-red-300 focus:shadow-outline-red sm:text-sm sm:leading-5"
}`}
/>
{errors.company_email && (
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<svg
className="h-5 w-5 text-red-500"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
</div>
)}
</div>
<p className="mt-2 text-xs text-gray-400">
This email stays private and is used for billing + edit
link. Make sure it's accessible by you.
</p>
</div>
<div className="col-span-6 sm:col-span-3">
<label
htmlFor="company_website"
className={`flex justify-between text-sm font-medium leading-5 text-gray-700`}
>
Company website{" "}
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
id="company_website"
name="company_website"
ref={register}
className={`${"mt-1 form-input block w-full py-2 px-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition duration-150 ease-in-out sm:text-sm sm:leading-5"}`}
/>
</div>
</div>
<div className="col-span-6 sm:col-span-3">
<label
htmlFor="company_twitter"
className={`flex justify-between text-sm font-medium leading-5 text-gray-700`}
>
Company Twitter
</label>
<div className="mt-1 relative rounded-md shadow-sm">
<input
id="company_twitter"
name="company_twitter"
ref={register}
className={`mt-1 form-input block w-full py-2 px-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition duration-150 ease-in-out sm:text-sm sm:leading-5`}
/>
</div>
<p className="mt-2 text-xs text-gray-400">
Used for mentioning you when we tweet your job
</p>
</div>
<div className="col-span-6 sm:col-span-6">
<label
className={`flex justify-between text-sm font-medium leading-5 ${
!checkoutError ? "text-gray-700" : "text-red-500"
}`}
>
* Company card
{checkoutError && (
<span className="inline-block text-right">
{checkoutError}
</span>
)}
</label>
<CardElement
className={`${
!checkoutError
? "form-input w-full mt-1 py-3 px-3 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition duration-150 ease-in-out sm:text-sm sm:leading-5"
: "form-input block mt-1 w-full pr-10 border-red-300 text-red-900 placeholder-red-300 focus:border-red-300 focus:shadow-outline-red sm:text-sm sm:leading-5"
}`}
/>
<p className="mt-2 text-xs text-gray-400">
Secure payment by Stripe over HTTPS. You are only charged
when you press "Post your job"
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div
className={`sticky bottom-0 ${
isPostHighlighted ? "bg-yellow-100" : "bg-white"
}`}
>
<div className="max-w-screen-xl mx-auto px-4 sm:px-6 py-4">
<div className="flex justify-between">
<div>
<div
className={`h-12 md:h-full w-12 rounded-sm text-center font-extrabold mr-4 pt-3 relative overflow-hidden ${
isPostHighlighted
? "bg-yellow-400 text-white"
: "bg-gray-100 text-gray-500"
}`}
>
{!logoImage ? (
<span className="uppercase">
{!watch("company_name")
? "RB"
: watch("company_name").charAt(0)}
</span>
) : (
<img
className="absolute inset-0 object-cover h-full w-full"
src={logoImage}
alt="Company logo"
/>
)}
</div>
</div>
<div className="flex-1">
<div className="flex items-center justify-between">
<div
className={`text-sm leading-5 font-medium truncate ${
isPostHighlighted ? "text-yellow-800" : "text-blue-600"
}`}
>
{!watch("position")
? "Add a job position"
: watch("position")}
</div>
<div className="ml-2 flex-shrink-0 flex">
<span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">
Preview
</span>
</div>
</div>
<div className="mt-2 sm:flex sm:justify-between">
<div className="sm:flex">
<div
className={`mr-6 flex items-center text-sm leading-5 ${
isPostHighlighted ? "text-yellow-500" : "text-rb-gray-5"
}`}
>
<svg
className={`flex-shrink-0 mr-1.5 h-5 w-5 ${
isPostHighlighted
? "text-yellow-400"
: "text-rb-gray-4"
}`}
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M4 4a2 2 0 012-2h8a2 2 0 012 2v12a1 1 0 110 2h-3a1 1 0 01-1-1v-2a1 1 0 00-1-1H9a1 1 0 00-1 1v2a1 1 0 01-1 1H4a1 1 0 110-2V4zm3 1h2v2H7V5zm2 4H7v2h2V9zm2-4h2v2h-2V5zm2 4h-2v2h2V9z"
clipRule="evenodd"
/>
</svg>
{!watch("company_name")
? "Company name"
: watch("company_name")}
</div>
<div
className={`mt-2 flex items-center text-sm leading-5 sm:mt-0 ${
isPostHighlighted ? "text-yellow-500" : "text-rb-gray-5"
}`}
>
<svg
className={`flex-shrink-0 mr-1.5 h-5 w-5 ${
isPostHighlighted
? "text-yellow-400"
: "text-rb-gray-4"
}`}
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z"
clipRule="evenodd"
/>
</svg>
{!watch("location") ? "Remote" : watch("location")}
</div>
</div>
<div className="mt-2 flex items-center text-sm leading-5 sm:mt-0">
{tempTags.length && (
<ul className="flex space-x-3">
{tempTags.map((tag, i) => {
if (i > 2) return
return (
<li
key={i}
className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium leading-4 hover:text-white ${
isPostHighlighted
? "bg-yellow-400 text-white hover:bg-yellow-300"
: "bg-gray-100 text-rb-gray-5 hover:bg-rb-gray-8"
}`}
>
<span>{tag}</span>
</li>
)
})}
</ul>
)}
</div>
</div>
</div>
</div>
</div>
<button
type="submit"
className={`flex w-full items-center justify-center px-6 py-3 border border-transparent text-xl leading-6 font-bold focus:outline-none focus:shadow-outline-green transition ease-in-out duration-150 ${
payment.status === "processing"
? "bg-rb-gray-2 text-rb-gray-5"
: "text-white bg-rb-green-6 hover:bg-rb-green-5 focus:border-rb-green-7 active:bg-rb-green-7"
}`}
disabled={
!["initial", "succeeded", "error"].includes(payment.status) ||
!stripe
}
>
{payment.status === "processing"
? "Processing payment..."
: `Post your job - $${jobPrice / 100}`}
</button>
</div>
</div>
</form>
)
}
Example #18
Source File: AddBalanceDialog.js From react-saas-template with MIT License | 4 votes |
AddBalanceDialog = withTheme(function (props) {
const { open, theme, onClose, onSuccess } = props;
const [loading, setLoading] = useState(false);
const [paymentOption, setPaymentOption] = useState("Credit Card");
const [stripeError, setStripeError] = useState("");
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [amount, setAmount] = useState(0);
const [amountError, setAmountError] = useState("");
const elements = useElements();
const stripe = useStripe();
const onAmountChange = amount => {
if (amount < 0) {
return;
}
if (amountError) {
setAmountError("");
}
setAmount(amount);
};
const getStripePaymentInfo = () => {
switch (paymentOption) {
case "Credit Card": {
return {
type: "card",
card: elements.getElement(CardElement),
billing_details: { name: name }
};
}
case "SEPA Direct Debit": {
return {
type: "sepa_debit",
sepa_debit: elements.getElement(IbanElement),
billing_details: { email: email, name: name }
};
}
default:
throw new Error("No case selected in switch statement");
}
};
const renderPaymentComponent = () => {
switch (paymentOption) {
case "Credit Card":
return (
<Fragment>
<Box mb={2}>
<StripeCardForm
stripeError={stripeError}
setStripeError={setStripeError}
setName={setName}
name={name}
amount={amount}
amountError={amountError}
onAmountChange={onAmountChange}
/>
</Box>
<HighlightedInformation>
You can check this integration using the credit card number{" "}
<b>4242 4242 4242 4242 04 / 24 24 242 42424</b>
</HighlightedInformation>
</Fragment>
);
case "SEPA Direct Debit":
return (
<Fragment>
<Box mb={2}>
<StripeIbanForm
stripeError={stripeError}
setStripeError={setStripeError}
setName={setName}
setEmail={setEmail}
name={name}
email={email}
amount={amount}
amountError={amountError}
onAmountChange={onAmountChange}
/>
</Box>
<HighlightedInformation>
You can check this integration using the IBAN
<br />
<b>DE89370400440532013000</b>
</HighlightedInformation>
</Fragment>
);
default:
throw new Error("No case selected in switch statement");
}
};
return (
<FormDialog
open={open}
onClose={onClose}
headline="Add Balance"
hideBackdrop={false}
loading={loading}
onFormSubmit={async event => {
event.preventDefault();
if (amount <= 0) {
setAmountError("Can't be zero");
return;
}
if (stripeError) {
setStripeError("");
}
setLoading(true);
const { error } = await stripe.createPaymentMethod(
getStripePaymentInfo()
);
if (error) {
setStripeError(error.message);
setLoading(false);
return;
}
onSuccess();
}}
content={
<Box pb={2}>
<Box mb={2}>
<Grid container spacing={1}>
{paymentOptions.map(option => (
<Grid item key={option}>
<ColoredButton
variant={
option === paymentOption ? "contained" : "outlined"
}
disableElevation
onClick={() => {
setStripeError("");
setPaymentOption(option);
}}
color={theme.palette.common.black}
>
{option}
</ColoredButton>
</Grid>
))}
</Grid>
</Box>
{renderPaymentComponent()}
</Box>
}
actions={
<Fragment>
<Button
fullWidth
variant="contained"
color="secondary"
type="submit"
size="large"
disabled={loading}
>
Pay with Stripe {loading && <ButtonCircularProgress />}
</Button>
</Fragment>
}
/>
);
})
Example #19
Source File: checkout.js From jamstack-ecommerce with MIT License | 4 votes |
Checkout = ({ context }) => {
const [errorMessage, setErrorMessage] = useState(null)
const [orderCompleted, setOrderCompleted] = useState(false)
const [input, setInput] = useState({
name: "",
email: "",
street: "",
city: "",
postal_code: "",
state: "",
})
const stripe = useStripe()
const elements = useElements()
const onChange = e => {
setErrorMessage(null)
setInput({ ...input, [e.target.name]: e.target.value })
}
const handleSubmit = async event => {
event.preventDefault()
const { name, email, street, city, postal_code, state } = input
const { total, clearCart } = context
if (!stripe || !elements) {
// Stripe.js has not loaded yet. Make sure to disable
// form submission until Stripe.js has loaded.
return
}
// Validate input
if (!street || !city || !postal_code || !state) {
setErrorMessage("Please fill in the form!")
return
}
// Get a reference to a mounted CardElement. Elements knows how
// to find your CardElement because there can only ever be one of
// each type of element.
const cardElement = elements.getElement(CardElement)
// Use your card Element with other Stripe.js APIs
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: "card",
card: cardElement,
billing_details: { name: name },
})
if (error) {
setErrorMessage(error.message)
return
}
const order = {
email,
amount: total,
address: state, // should this be {street, city, postal_code, state} ?
payment_method_id: paymentMethod.id,
receipt_email: "[email protected]",
id: uuid(),
}
console.log("order: ", order)
// TODO call API
setOrderCompleted(true)
clearCart()
}
const { numberOfItemsInCart, cart, total } = context
const cartEmpty = numberOfItemsInCart === Number(0)
if (orderCompleted) {
return (
<div>
<h3>Thanks! Your order has been successfully processed.</h3>
</div>
)
}
return (
<div className="flex flex-col items-center pb-10">
<div
className="
flex flex-col w-full
c_large:w-c_large
"
>
<div className="pt-10 pb-8">
<h1 className="text-5xl font-light">Checkout</h1>
<Link to="/cart">
<div className="cursor-pointer flex">
<FaLongArrowAltLeft className="mr-2 text-gray-600 mt-1" />
<p className="text-gray-600 text-sm">Edit Cart</p>
</div>
</Link>
</div>
{cartEmpty ? (
<h3>No items in cart.</h3>
) : (
<div className="flex flex-col">
<div className="">
{cart.map((item, index) => {
return (
<div className="border-b py-10" key={index}>
<div className="flex items-center">
<Image
className="w-32 m-0"
src={item.image}
alt={item.name}
/>
<p className="m-0 pl-10 text-gray-600 text-sm">
{item.name}
</p>
<div className="flex flex-1 justify-end">
<p className="m-0 pl-10 text-gray-900 tracking-tighter font-semibold">
{DENOMINATION + item.price}
</p>
</div>
</div>
</div>
)
})}
</div>
<div className="flex flex-1 flex-col md:flex-row">
<div className="flex flex-1 pt-8 flex-col">
<div className="mt-4 border-t pt-10">
<form onSubmit={handleSubmit}>
{errorMessage ? <span>{errorMessage}</span> : ""}
<Input
onChange={onChange}
value={input.name}
name="name"
placeholder="Cardholder name"
/>
<CardElement className="mt-2 shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" />
<Input
onChange={onChange}
value={input.email}
name="email"
placeholder="Email"
/>
<Input
onChange={onChange}
value={input.street}
name="street"
placeholder="Street"
/>
<Input
onChange={onChange}
value={input.city}
name="city"
placeholder="City"
/>
<Input
onChange={onChange}
value={input.state}
name="state"
placeholder="State"
/>
<Input
onChange={onChange}
value={input.postal_code}
name="postal_code"
placeholder="Postal Code"
/>
<button
type="submit"
disabled={!stripe}
onClick={handleSubmit}
className="hidden md:block bg-secondary hover:bg-black text-white font-bold py-2 px-4 mt-4 rounded focus:outline-none focus:shadow-outline"
type="button"
>
Confirm order
</button>
</form>
</div>
</div>
<div className="md:pt-20">
<div className="ml-4 pl-2 flex flex-1 justify-end pt-2 md:pt-8 pr-4">
<p className="text-sm pr-10">Subtotal</p>
<p className="tracking-tighter w-38 flex justify-end">
{DENOMINATION + total}
</p>
</div>
<div className="ml-4 pl-2 flex flex-1 justify-end pr-4">
<p className="text-sm pr-10">Shipping</p>
<p className="tracking-tighter w-38 flex justify-end">
FREE SHIPPING
</p>
</div>
<div className="md:ml-4 pl-2 flex flex-1 justify-end bg-gray-200 pr-4 pt-6">
<p className="text-sm pr-10">Total</p>
<p className="font-semibold tracking-tighter w-38 flex justify-end">
{DENOMINATION + (total + calculateShipping())}
</p>
</div>
<button
type="submit"
disabled={!stripe}
onClick={handleSubmit}
className="md:hidden bg-secondary hover:bg-black text-white font-bold py-2 px-4 mt-4 rounded focus:outline-none focus:shadow-outline"
type="button"
>
Confirm order
</button>
</div>
</div>
</div>
)}
</div>
</div>
)
}
Example #20
Source File: index.js From fireact with MIT License | 4 votes |
Plans = () => {
const title = 'Select a Plan';
const countries = countryJSON.countries;
const { userData, authUser } = useContext(AuthContext);
const stripe = useStripe();
const elements = useElements();
const mountedRef = useRef(true);
const { setBreadcrumb } = useContext(BreadcrumbContext);
const CARD_ELEMENT_OPTIONS = {
style: {
base: {
color: '#32325d',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
},
hidePostalCode: true
};
const [loading, setLoading] = useState(true);
const [processing, setProcessing] = useState(false);
const [plans, setPlans] = useState([]);
const [selectedPlan, setSelectedPlan] = useState({id: 0});
const [cardError, setCardError] = useState(null);
const [errorMessage, setErrorMessage] = useState(null);
const [country, setCountry] = useState("");
const [countryError, setCountryError] = useState(null);
const [state, setState] = useState("");
const [states, setStates] = useState([]);
const [stateError, setStateError] = useState(null);
useEffect(() => {
setBreadcrumb([
{
to: "/",
text: "Home",
active: false
},
{
to: "/account/"+userData.currentAccount.id+"/",
text: userData.currentAccount.name,
active: false
},
{
to: null,
text: title,
active: true
}
]);
setLoading(true);
const plansQuery = FirebaseAuth.firestore().collection('plans').orderBy('price', 'asc');
plansQuery.get().then(planSnapShots => {
if (!mountedRef.current) return null
let p = [];
planSnapShots.forEach(doc => {
p.push({
'id': doc.id,
'name': doc.data().name,
'price': doc.data().price,
'currency': doc.data().currency,
'paymentCycle': doc.data().paymentCycle,
'features': doc.data().features,
'stripePriceId': doc.data().stripePriceId,
'current': (userData.currentAccount.planId===doc.id)?true:false
});
});
if(p.length > 0){
const ascendingOrderPlans = p.sort((a, b) => parseFloat(a.price) - parseFloat(b.price));
setPlans(ascendingOrderPlans);
}
setLoading(false);
});
},[userData, setBreadcrumb, title]);
useEffect(() => {
return () => {
mountedRef.current = false
}
},[]);
const subscribe = async(event) => {
event.preventDefault();
setProcessing(true);
setErrorMessage(null);
let hasError = false;
let paymentMethodId = '';
if(selectedPlan.price !== 0){
if(country === ''){
setCountryError('Please select a country.');
hasError = true;
}
if(state === '' && countries[country] && countries[country].states){
setStateError('Please select a state.');
hasError = true;
}
setCardError(null);
if (!stripe || !elements) {
// Stripe.js has not loaded yet. Make sure to disable
// form submission until Stripe.js has loaded.
return;
}
// Get a reference to a mounted CardElement. Elements knows how
// to find your CardElement because there can only ever be one of
// each type of element.
const cardElement = elements.getElement(CardElement);
// Use your card Element with other Stripe.js APIs
const {error, paymentMethod} = await stripe.createPaymentMethod({
type: 'card',
card: cardElement
});
if (error) {
setCardError(error.message);
hasError = true;
} else {
paymentMethodId = paymentMethod.id;
}
}
if(!hasError){
const createSubscription = CloudFunctions.httpsCallable('createSubscription');
createSubscription({
planId: selectedPlan.id,
accountId: userData.currentAccount.id,
paymentMethodId: paymentMethodId,
billing: {
country: country,
state: state
}
}).then(res => {
// physical page load to reload the account data
if (!mountedRef.current) return null
document.location = '/account/'+userData.currentAccount.id+'/';
}).catch(err => {
if (!mountedRef.current) return null
setProcessing(false);
setErrorMessage(err.message);
});
}else{
setProcessing(false);
}
}
return (
<>
{(!loading)?(
<>{(userData.currentAccount.owner === authUser.user.uid)?(
<>{plans.length > 0 ? (
<Paper>
<Box p={3} style={{textAlign: 'center'}} >
<h2>{title}</h2>
<Grid container spacing={3}>
<>
{plans.map((plan,i) =>
<Grid container item xs={12} md={4} key={i} >
<Card style={{
width: '100%',
display: 'flex',
flexDirection: 'column',
paddingBottom: '20px',
}}>
<CardHeader title={plan.name} subheader={"$"+plan.price+"/"+plan.paymentCycle} />
<CardContent>
<Divider />
<ul style={{listStyleType: 'none', paddingLeft: '0px'}}>
{plan.features.map((feature, i) =>
<li key={i}>
<i className="fa fa-check" style={{color: "#2e7d32"}} /> {feature}
</li>
)}
</ul>
</CardContent>
<CardActions style={{
marginTop: 'auto',
justifyContent: 'center',
}}>
{plan.current?(
<Button color="success" variant="contained" disabled={true}>Current Plan</Button>
):(
<Button color="success" variant={(plan.id!==selectedPlan.id)?"outlined":"contained"} onClick={() => {
for(let i=0; i<plans.length; i++){
if(plans[i].id === plan.id){
setSelectedPlan(plan);
}
}
}}>{plan.id===selectedPlan.id && <><i className="fa fa-check" /> </>}{(plan.id!==selectedPlan.id)?"Select":"Selected"}</Button>
)}
</CardActions>
</Card>
</Grid>
)}
</>
</Grid>
{selectedPlan.id !== 0 && selectedPlan.price > 0 &&
<div style={{justifyContent: 'center', marginTop: '50px'}}>
<h2>Billing Details</h2>
<Grid container spacing={3}>
<Grid container item xs={12}>
<Card style={{
width: '100%',
paddingBottom: '20px',
}}>
<CardContent>
<Container maxWidth="sm">
<Stack spacing={3}>
{countryError !== null &&
<Alert severity="error" onClose={() => setCountryError(null)}>{countryError}</Alert>
}
<Autocomplete
value={(country !== '')?(countries.find(obj =>{
return obj.code === country
})):(null)}
options={countries}
autoHighlight
getOptionLabel={(option) => option.label}
renderOption={(props, option) => (
<Box component="li" sx={{ '& > img': { mr: 2, flexShrink: 0 } }} {...props}>
<img
loading="lazy"
width="20"
src={`https://flagcdn.com/w20/${option.code.toLowerCase()}.png`}
srcSet={`https://flagcdn.com/w40/${option.code.toLowerCase()}.png 2x`}
alt=""
/>
{option.label}
</Box>
)}
renderInput={(params) => (
<TextField
{...params}
label="Country"
inputProps={{
...params.inputProps,
autoComplete: 'new-password',
}}
/>
)}
onChange={(event, newValue) => {
if(newValue && newValue.code){
setCountry(newValue.code);
setState("");
if(newValue.states){
setStates(newValue.states);
}else{
setStates([]);
}
setCountryError(null);
}
}}
/>
{states.length > 0 &&
<>
{stateError !== null &&
<Alert severity="error" onClose={() => setStateError(null)}>{stateError}</Alert>
}
<Autocomplete
value={(state !== '')?(states.find(obj =>{
return obj.code === state
})):(null)}
options={states}
autoHighlight
getOptionLabel={(option) => option.label}
renderOption={(props, option) => (
<Box component="li" {...props}>
{option.label}
</Box>
)}
renderInput={(params) => (
<TextField
{...params}
label="State"
inputProps={{
...params.inputProps,
autoComplete: 'new-password',
}}
/>
)}
onChange={(event, newValue) => {
if(newValue && newValue.code){
setState(newValue.code);
setStateError(null);
}
}}
/>
</>
}
{cardError !== null &&
<Alert severity="error" onClose={() => setCardError(null)}>{cardError}</Alert>
}
<div style={{position: "relative", minHeight: '56px', padding: '15px'}}>
<CardElement options={CARD_ELEMENT_OPTIONS}></CardElement>
<fieldset style={{
borderColor: 'rgba(0, 0, 0, 0.23)',
borderStyle: 'solid',
borderWidth: '1px',
borderRadius: '4px',
position: 'absolute',
top: '-5px',
left: '0',
right: '0',
bottom: '0',
margin: '0',
padding: '0 8px',
overflow: 'hidden',
pointerEvents: 'none'
}}></fieldset>
</div>
</Stack>
</Container>
</CardContent>
</Card>
</Grid>
</Grid>
</div>
}
{selectedPlan.id!==0 &&
<div style={{marginTop: '50px'}}>
<Container maxWidth="sm">
<Stack spacing={3}>
{errorMessage !== null &&
<Alert severity="error" onClose={() => setErrorMessage(null)}>{errorMessage}</Alert>
}
<Button color="success" size="large" variant="contained" disabled={selectedPlan.id===0||processing?true:false} onClick={e => {
subscribe(e);
}}>{processing?(<><Loader /> Processing...</>):(<>Subscribe Now</>)}</Button>
</Stack>
</Container>
</div>
}
</Box>
</Paper>
):(
<Alert severity="warning">No plan is found.</Alert>
)}</>
):(
<Alert severity="error" >Access Denied.</Alert>
)}</>
):(
<Loader text="loading plans..." />
)}
</>
)
}
Example #21
Source File: index.js From fireact with MIT License | 4 votes |
PaymentMethod = () => {
const title = 'Update Payment Method';
const mountedRef = useRef(true);
const history = useHistory();
const { userData, authUser } = useContext(AuthContext);
const stripe = useStripe();
const elements = useElements();
const { setBreadcrumb } = useContext(BreadcrumbContext);
const CARD_ELEMENT_OPTIONS = {
style: {
base: {
color: '#32325d',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
},
hidePostalCode: true
};
const [processing, setProcessing] = useState(false);
const [success, setSuccess] = useState(false);
const [cardError, setCardError] = useState(null);
const [errorMessage, setErrorMessage] = useState(null);
const subscribe = async(event) => {
event.preventDefault();
setProcessing(true);
setErrorMessage(null);
setSuccess(false);
let hasError = false;
let paymentMethodId = '';
setCardError(null);
if (!stripe || !elements) {
// Stripe.js has not loaded yet. Make sure to disable
// form submission until Stripe.js has loaded.
return;
}
// Get a reference to a mounted CardElement. Elements knows how
// to find your CardElement because there can only ever be one of
// each type of element.
const cardElement = elements.getElement(CardElement);
// Use your card Element with other Stripe.js APIs
const {error, paymentMethod} = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
});
if (error) {
setCardError(error.message);
hasError = true;
} else {
paymentMethodId = paymentMethod.id;
}
if(!hasError){
const updatePaymentMethod = CloudFunctions.httpsCallable('updatePaymentMethod');
updatePaymentMethod({
accountId: userData.currentAccount.id,
paymentMethodId: paymentMethodId
}).then(res => {
if (!mountedRef.current) return null
setSuccess(true);
setProcessing(false);
}).catch(err => {
if (!mountedRef.current) return null
setProcessing(false);
setErrorMessage(err.message);
});
}else{
setProcessing(false);
}
}
useEffect(() => {
setBreadcrumb([
{
to: "/",
text: "Home",
active: false
},
{
to: "/account/"+userData.currentAccount.id+"/",
text: userData.currentAccount.name,
active: false
},
{
to: "/account/"+userData.currentAccount.id+"/billing",
text: 'Billing',
active: false
},
{
to: null,
text: title,
active: true
}
]);
},[userData, setBreadcrumb, title]);
useEffect(() => {
return () => {
mountedRef.current = false
}
},[]);
return (
<>
<Paper>
<Box p={2}>
{userData.currentAccount.price > 0 ? (
<Stack spacing={3}>
{(userData.currentAccount.owner === authUser.user.uid)?(
<>
{success &&
<Alert severity="success" onClose={() => setSuccess(false)}>The payment method has been successfully updated.</Alert>
}
{errorMessage !== null &&
<Alert severity="error" onClose={() => setErrorMessage(null)}>{errorMessage}</Alert>
}
{cardError !== null &&
<Alert severity="error" onClose={() => setCardError(null)}>{cardError}</Alert>
}
<div style={{position: "relative", minHeight: '56px', padding: '15px', maxWidth: '500px'}}>
<CardElement options={CARD_ELEMENT_OPTIONS}></CardElement>
<fieldset style={{
borderColor: 'rgba(0, 0, 0, 0.23)',
borderStyle: 'solid',
borderWidth: '1px',
borderRadius: '4px',
position: 'absolute',
top: '-5px',
left: '0',
right: '0',
bottom: '0',
margin: '0',
padding: '0 8px',
overflow: 'hidden',
pointerEvents: 'none'
}}></fieldset>
</div>
<Stack direction="row" spacing={1} mt={2}>
<Button variant="contained" disabled={processing} onClick={(e) => subscribe(e)}>
{processing?(
<><Loader /> Processing...</>
):(
<>Save</>
)}
</Button>
<Button variant="contained" color="secondary" disabled={processing} onClick={() => history.push("/account/"+userData.currentAccount.id+"/billing")}>Back</Button>
</Stack>
</>
):(
<Alert type="danger" message="Access Denied." dismissible={false} ></Alert>
)}
</Stack>
):(
<Alert severity="error">The account doesn't support payment methods.</Alert>
)}
</Box>
</Paper>
</>
)
}
Example #22
Source File: HomePage.js From tutorial-code with MIT License | 4 votes |
function HomePage() {
const classes = useStyles();
// State
const [email, setEmail] = useState('');
const [status, setStatus] = useState('');
const [clientSecret, setClientSecret] = useState('');
const stripe = useStripe();
const elements = useElements();
const handleSubmitPay = async (event) => {
if (!stripe || !elements) {
// Stripe.js has not yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
const res = await axios.post('http://localhost:5000/pay', {email: email});
const clientSecret = res.data['client_secret'];
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
email: email,
},
},
});
if (result.error) {
// Show error to your customer (e.g., insufficient funds)
console.log(result.error.message);
} else {
// The payment has been processed!
if (result.paymentIntent.status === 'succeeded') {
// Show a success message to your customer
// There's a risk of the customer closing the window before callback
// execution. Set up a webhook or plugin to listen for the
// payment_intent.succeeded event that handles any business critical
// post-payment actions.
console.log('You got 500$!');
}
}
};
const handleSubmitSub = async (event) => {
if (!stripe || !elements) {
// Stripe.js has not yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
if (status !== '') {
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
email: email,
},
},
});
if (result.error) {
console.log(result.error.message);
// Show error in payment form
} else {
console.log('Hell yea, you got that sub money!');
}
} else {
const result = await stripe.createPaymentMethod({
type: 'card',
card: elements.getElement(CardElement),
billing_details: {
email: email,
},
});
if (result.error) {
console.log(result.error.message);
// Show error in payment form
} else {
const payload = {
email: email,
payment_method: result.paymentMethod.id,
};
// Otherwise send paymentMethod.id to your server
const res = await axios.post('http://localhost:5000/sub', payload);
// eslint-disable-next-line camelcase
const {client_secret, status} = res.data;
if (status === 'requires_action') {
setStatus(status);
setClientSecret(client_secret);
stripe.confirmCardPayment(client_secret).then(function(result) {
if (result.error) {
// Display error message in your UI.
// The card was declined (i.e. insufficient funds, card has expired, etc)
console.log(result.error.message);
} else {
// Show a success message to your customer
console.log('Hell yea, you got that sub money!');
}
});
} else {
console.log('Hell yea, you got that sub money!');
}
}
}
};
return (
<Card className={classes.root}>
<CardContent className={classes.content}>
<TextField
label='Email'
id='outlined-email-input'
helperText={`Email you'll recive updates and receipts on`}
margin='normal'
variant='outlined'
type='email'
required
value={email}
onChange={(e) => setEmail(e.target.value)}
fullWidth
/>
<CardInput />
<div className={classes.div}>
<Button variant="contained" color="primary" className={classes.button} onClick={handleSubmitPay}>
Pay
</Button>
<Button variant="contained" color="primary" className={classes.button} onClick={handleSubmitSub}>
Subscription
</Button>
</div>
</CardContent>
</Card>
);
}