@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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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&nbsp;
              {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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
  );
}