Stripe refresh data in useElement hook - reactjs

In form payment user has option to change duration of premium account (e.g from 1 month to 3 months). When user change it, front sent to api query to again create paymentIntents:
return await this.stripe.paymentIntents.create({
amount: 2000 * months,
currency: 'pln',
payment_method_types: ['p24', 'card'],
receipt_email: user.email,
description: 'premium',
});
this.stripe.paymentIntents return data:
amount: 4000
client_secret: "pi_3KTDMNDbx0KEJcOR289fojfP_secret_Mh5jPfAKjvxpeZCuXmRcjilFL"
id: "pi_3KTDMNDbx0KEJcOR289fojfP"
...
The problem is with hook useElements from #stripe/react-stripe-js library. Inside this hook still I have old id and when I click "Pay" I have old amount it's 2000, should be 6000.
How I can refresh data in useElements when user change count of months?
PS: I'm using this docs. Select 'Custom payment flow' and in backend I have Node (Nest.js), in front I have React.
Update:
Stripe method for payment:
import {
PaymentElement,
useStripe,
useElements,
} from '#stripe/react-stripe-js'
const Stripe = () => {
const stripe = useStripe()
const elements = useElements()
const sendPaymentDetails = async data => {
if (!stripe || !elements) return
setIsLoading(true)
const { name, email } = data
await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${APPLICATION_URL}premium`,
payment_method_data: {
billing_details: {
name,
email,
},
},
},
})
}
return (
<form onSubmit={sendPaymentDetails}>
<PaymentElement />
</form>
)
}
and this is method to create paymentIntent:
const [stripeOptions, setStripeOptions] = useState(null)
const paymentIntent = async months => {
const { data } = await createPaymentIntent(months);
setStripeOptions(data);
};
when I create new paymentIntent, should update too useElement data

I found resolve.I should update paymentIntents, not create new paymentIntents. So I add method:
return await this.stripe.paymentIntents.update(id, {
amount: this.amount * months,
});
from this docs
Now inside useElements hook I have correctly data

Related

The PaymentIntent requires a payment method — React, Django Rest

I have a React app and a Django Rest API.
My goal is to get the PaymentRequestButtonElement working.
In my Stripe dashboard (test mode) I get the following logs:
200 OK
POST
/v1/payment_intents
12:22:55 PM
200 OK
POST
/v1/payment_methods
12:22:54 PM
200 OK
POST
/v1/tokens
12:22:53 PM
But in the Payments tab, I get the following:
The PaymentIntent requires a payment method
Here is my React component:
import React, { useState, useEffect } from 'react';
// import { useNavigate } from 'react-router-dom';
// import { useShoppingCart } from 'use-shopping-cart';
import {
PaymentRequestButtonElement,
useStripe,
} from '#stripe/react-stripe-js';
const PaymentRequest = () => {
// const history = useNavigate();
// const { totalPrice, cartDetails, cartCount } = useShoppingCart();
const stripe = useStripe();
const [paymentRequest, setPaymentRequest] = useState(null);
const price = 350;
const handleButtonClicked = (event) => {
// if (!cartCount) {
// event.preventDefault();
// alert('Cart is empty!');
// return;
// }
paymentRequest.on('paymentmethod', handlePaymentMethodReceived);
paymentRequest.on('cancel', () => {
paymentRequest.off('paymentmethod');
});
return;
};
const handlePaymentMethodReceived = async (event) => {
// Send the cart details and payment details to our function.
const paymentDetails = {
payment_method: event.paymentMethod.id,
shipping: {
name: event.shippingAddress.recipient,
phone: event.shippingAddress.phone,
address: {
line1: event.shippingAddress.addressLine[0],
city: event.shippingAddress.city,
postal_code: event.shippingAddress.postalCode,
state: event.shippingAddress.region,
country: event.shippingAddress.country,
},
},
};
const response = await fetch('https://my-api/create-payment-intent/', {
method: 'post',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
// cartDetails,
paymentDetails,
amount: price,
currency: 'usd',
payment_method: 'card'
// automatic_payment_methods: true,
}),
}).then((res) => {
return res.json();
});
if (response.error) {
// Report to the browser that the payment failed.
console.log(response.error);
event.complete('fail');
} else {
// Report to the browser that the confirmation was successful, prompting
// it to close the browser payment method collection interface.
event.complete('success');
// Let Stripe.js handle the rest of the payment flow, including 3D Secure if needed.
const { error, paymentIntent } = await stripe.confirmCardPayment(
response.paymentIntent.client_secret
);
if (error) {
console.log(error);
return;
}
if (paymentIntent.status === 'succeeded') {
console.log('Payment succeeded!');
} else {
console.warn(
`Unexpected status: ${paymentIntent.status} for ${paymentIntent}`
);
}
}
};
useEffect(() => {
if (stripe && paymentRequest === null) {
const pr = stripe.paymentRequest({
country: 'US',
currency: 'usd',
total: {
label: 'Demo total',
//
amount: price,
pending: true,
},
requestPayerName: true,
requestPayerEmail: true,
requestShipping: true,
shippingOptions: [
{
id: 'standard-global',
label: 'Global shipping',
detail: 'Handling and delivery fee',
amount: 350,
},
],
});
// Check the availability of the Payment Request API first.
pr.canMakePayment().then((result) => {
if (result) {
setPaymentRequest(pr);
}
});
}
}, [stripe,
paymentRequest,
// totalPrice
]);
useEffect(() => {
if (paymentRequest) {
paymentRequest.update({
total: {
label: 'Demo total',
amount: 350,
pending: false,
},
});
}
}, [
// totalPrice,
paymentRequest
]);
if (paymentRequest) {
return (
<div className="payment-request-button">
<PaymentRequestButtonElement
options={{ paymentRequest }}
onClick={handleButtonClicked}
/>
--- OR ---
</div>
);
}
return '';
};
export default PaymentRequest;
and here is my Django REST View
class PaymentIntentView(APIView):
def post(self, request, *args, **kwargs):
amount = request.data.get('amount')
currency = request.data.get('currency')
# automatic_payment_methods = request.data.get('automatic_payment_methods')
try:
intent = stripe.PaymentIntent.create(
amount=amount,
currency=currency,
# automatic_payment_methods={
# 'enabled': True,
# },
# You can also add other options like capture_method, setup_future_usage, etc.
)
return Response({'client_secret': intent.client_secret, 'id': intent.id})
except Exception as e:
return Response({'error': str(e)})
I've tried variations of passing automatic_payments as true and passing the payment_method as 'card', no joy
There's a couple of options that you can do in order to fix the problem here.
Option 1: Pass the PM in the backend
When you call fetch on https://my-api/create-payment-intent/ you are passing the paymentDetails that you're not using in your stripe.PaymentIntent.create method. For this to work, you need to first deserialize your request to get access to this information since it's nested (e.g. this guide). Then you need to pass payment_method to the stripe.PaymentIntent.create method. In this option you don't have to change anything in your frontend code.
Option 2: Pass the PM in the frontend
When you call stripe.confirmCardPayment you can pass in the payment_method as explained here. In this option you don't have to change anything in your backend code but you can remove the paymentDetails from the request to your backend.

An error occurred: IntegrationError: Missing value for stripe.confirmCardPayment intent secret: value should be a client_secret string

I have a React app on the frontend, and a django rest API on the back.
I'm trying to get Payment Request Button to work with Apple and Google Pay.
When I test it on the frontend, I get the following message in the browser console:
An error occurred: IntegrationError: Missing value for stripe.confirmCardPayment intent secret: value should be a client_secret string.
I believe there is an issue in my views.py which contains the following:
views.py
class PaymentIntentView(APIView):
def post(self, request, *args, **kwargs):
amount = request.data.get('amount')
currency = request.data.get('currency')
automatic_payment_methods = request.data.get('automatic_payment_methods')
try:
intent = stripe.PaymentIntent.create(
amount=amount,
currency=currency,
automatic_payment_methods={
'enabled': True,
},
# You can also add other options like capture_method, setup_future_usage, etc.
)
return Response({'client_secret': intent.client_secret, 'id': intent.id})
except Exception as e:
return Response({'error': str(e)})
Checkout.js
import React, { useEffect, useState } from 'react';
import {
PaymentRequestButtonElement,
useStripe,
} from '#stripe/react-stripe-js';
import axios from 'axios';
const Checkout = (props) => {
const stripe = useStripe();
const [paymentRequest, setPaymentRequest] = useState(null);
const price = props.price;
const dollar_price = price * 100;
useEffect(() => {
if (stripe) {
const pr = stripe.paymentRequest({
country: 'US',
currency: 'usd',
total: {
label: 'Purchase',
amount: dollar_price,
},
requestPayerName: true,
requestPayerEmail: true,
applePay: true,
googlePay: true,
});
pr.canMakePayment().then(result => {
if (result) {
setPaymentRequest(pr);
}
});
}
}, [stripe]);
useEffect(() => {
if (paymentRequest) {
paymentRequest.on('paymentmethod', async event => {
const paymentMethod = event.paymentMethod;
try {
const response = await axios.post(
'/my/django/end-point/create-payment-intent',
{
paymentMethodId: paymentMethod.id,
amount: price,
automatic_payment_methods: {
'enabled': true,
},
currency: 'usd',
}
);
const pi = await stripe.confirmCardPayment(response.data.client_secret, {
payment_method: paymentMethod.id
});
console.log(pi);
if (pi.status === 'succeeded') {
event.complete('success');
console.log('Payment succeeded!');
window.location.href = '/success';
} else if (pi.status === 'requires_action' || pi.status === 'requires_confirmation') {
event.complete('success');
console.log('Additional steps required!');
// Prompt user to complete additional steps
} else if (pi.status === 'requires_payment_method') {
event.complete('fail');
console.log('Payment method required!');
// Prompt user to add a new payment method
} else if (pi.status === 'processing') {
event.complete('success');
console.log('Payment is being processed!');
// Show a message to the user that the payment is being processed
} else if (pi.status === 'canceled') {
event.complete('fail');
console.log('Payment canceled!');
// Show a message to the user that the payment was canceled
} else if (pi.status === 'failed') {
event.complete('fail');
console.log('Payment failed!');
// Show a message to the user that the payment failed
}
} catch (error) {
event.complete('fail');
console.log('An error occurred:', error);
// Show an error message to the user
}
});
}
}, [paymentRequest, stripe]);
if (paymentRequest) {
return <PaymentRequestButtonElement options={{ paymentRequest }} />;
}
return 'Insert your form or button component here.';
};
export default Checkout;
Your code appears to be following the recommended approach for the Payment Request Button using React specified here. The first step in troubleshooting this would be to add some logging to your Django back-end to validate the intent is being created and returned as you expect. You might even want to assign the Response to a variable and log that prior to returning it.
Then I would add a console.log() statement to check the response that is being returned by your axios.post() call. You might try assigning the secret so you can log just that (e.g. const secret = request.data.client_secret).
The last thing I would check is whether the case for the variable is preserved between your front-end and back-end. I'm not an expert on JS Frameworks but I've noticed I will use snake case for property names in my back-end (e.g. nifty_stuff) only to have that transformed to camel case in the front-end (e.g. niftyStuff). Logging the request.data in your front-end should reveal if that is the case.

How can I extract billing details from card element during button onClick?

We have the following function that runs when our stripe Start Subscription button is clicked:
const handleSubmit = async (ev) => {
setIsProcessing(true);
const cardElement = elements.getElement('card');
try {
// Grab Stripe Elements + Variables needed to post payment subscription
cardElement.update({ disabled: true });
const apiBaseUrl = config.url.API_URL;
const id = userData.user ? userData.user._id : null;
// Grab Billing (zip only) & Create Payment Method
const billingDetails = {
address: {
postal_code: ev.target.zip.value
}
};
const paymentMethodReq = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
billing_details: billingDetails
});
// Handle Creating Payment Subscription
if (paymentMethodReq.error) { ... return } // handle bad payment
const paymentObject = { id, paymentMethod: paymentMethodReq.paymentMethod.id }; // address
const response = await Axios.post(`${apiBaseUrl}/stripe/payment_subscriptions`, paymentObject);
setIsProcessing(false);
} catch (error) {
... handle error
}
};
and our subscribe button that calls the handleSubmit function
<button
className='stripe-button'
onClick={handleSubmit}
disabled={isProcessing || !stripe}
>
Start Subscription
</button>
From our understanding of the stripe docs - https://stripe.com/docs/api/payment_methods/object?lang=node - we need to pass billing_details into the createPaymentMethod function. However, handleSubmit throws an error because ev.target.zip is undefined. (I believe) since we are submitting via a button onClick (rather than a form onSubmit), that the ev.target does not have what we want. Here is the console.log for ev, ev.target and for our cardElement:
How can the billing details be extracted from the cardElement, from within the handleSubmit function, so that they can be passed to the createPaymentMethod function?

React - PayPal Button fires without checking conditions

I'm using react-paypal-express-checkout
I've to options: Cash and PayPal.
Cash working fine and checks all conditions.
But bcs PayPal is a seperate component in my CartScreen component it opens and don't check a single if conditions and opens the PayPal window
The CashButton comes with function "cashTranSuccess" it's the same function as "TranSuccess"
just without the paymentID bcs it's only needed for react-paypal-express-checkout
So what I'm looking for is, to check all TranSuccess() conditions before open the PayPal window.
PayPalButton.js
import React from 'react';
import PaypalExpressBtn from 'react-paypal-express-checkout';
export default class PayPalButton extends React.Component {
render() {
const onSuccess = (payment) => {
// Congratulation, it came here means everything's fine!
console.log('The payment was succeeded!', payment);
// You can bind the "payment" object's value to your state or props or whatever here, please see below for sample returned data
this.props.tranSuccess(payment);
};
const onCancel = (data) => {
// User pressed "cancel" or close Paypal's popup!
console.log('The payment was cancelled!', data);
// You can bind the "data" object's value to your state or props or whatever here, please see below for sample returned data
};
const onError = (err) => {
// The main Paypal's script cannot be loaded or somethings block the loading of that script!
console.log('Error!', err);
// Because the Paypal's main script is loaded asynchronously from "https://www.paypalobjects.com/api/checkout.js"
// => sometimes it may take about 0.5 second for everything to get set, or for the button to appear
};
let env = 'sandbox'; // you can set here to 'production' for production
let currency = 'EUR'; // or you can set this value from your props or state
let carttotal = this.props.carttotal; // same a s above, this is the total amount (based on currency) to be paid by using Paypal express checkout
// Document on Paypal's currency code: https://developer.paypal.com/docs/classic/api/currency_codes/
const client = {
sandbox:
'',
production: 'YOUR-PRODUCTION-APP-ID',
};
// In order to get production's app-ID, you will have to send your app to Paypal for approval first
// For sandbox app-ID (after logging into your developer account, please locate the "REST API apps" section, click "Create App"):
// => https://developer.paypal.com/docs/classic/lifecycle/sb_credentials/
// For production app-ID:
// => https://developer.paypal.com/docs/classic/lifecycle/goingLive/
// NB. You can also have many Paypal express checkout buttons on page, just pass in the correct amount and they will work!
// Style Options: https://developer.paypal.com/docs/checkout/standard/customize/buttons-style-guide/ ; https://wise.com/gb/blog/custom-paypal-button
let style = {
size: 'medium',
color: 'gold',
label: 'pay',
tagline: false,
};
return (
<PaypalExpressBtn
env={env}
client={client}
currency={currency}
total={carttotal}
onError={onError}
shipping={1}
onSuccess={onSuccess}
onCancel={onCancel}
style={style}
/>
);
}
}
CartScreen
const tranSuccess = async (payment) => {
const { paymentID } = payment;
// Check time, min amoint, for delivery add delivery fees
if (timeValidation === true) {
if (sliderDeliveryValue === 'delivery') {
if (carttotal > settings[0]?.minDeliveryAmount) {
await axios.post(
'/api/payment',
{ cartItems, paymentID, time, sliderDeliveryValue, carttotal },
{
headers: { Authorization: token },
}
);
cartItems.map((remove) => {
dispatch(deleteFromCart(remove));
});
//console.log(cartItems.length);
toast.success(
'Order successful',
{
position: toast.POSITION.TOP_RIGHT,
}
);
} else {
toast.error(
`Min amount${settings[0]?.minDeliveryAmount}€`,
{
position: toast.POSITION.TOP_RIGHT,
}
);
}
} else if (sliderDeliveryValue === 'pickup') {
if (carttotal > 2) {
await axios.post(
'/api/payment',
{ cartItems, paymentID, time, sliderDeliveryValue, carttotal },
{
headers: { Authorization: token },
}
);
cartItems.map((remove) => {
dispatch(deleteFromCart(remove));
});
//console.log(cartItems.length);
toast.success(
'Order successful',
{
position: toast.POSITION.TOP_RIGHT,
}
);
} else {
toast.error(`Min amount 2.00€`, {
position: toast.POSITION.TOP_RIGHT,
});
}
} else {
toast.error('Choose delivery method', {
position: toast.POSITION.TOP_RIGHT,
});
}
} else {
toast.error('closed', {
position: toast.POSITION.TOP_RIGHT,
});
}
};
<PayPalButton
carttotal={carttotal}
tranSuccess={tranSuccess}
/>
<div onClick={cashTranSuccess}>
<CashButton />
</div>
Consider using the official #paypal/react-paypal-js
An example of validation using onInit and onClick functions and the actions.enable/disable callbacks or returning a promise (actions.resolve/reject) can be found in the developer documentation. Adapt this to check whatever condition you need.

React Firebase async return of IDs

I have a project named booking app for companies and I'm trying to add "services" in firebase.
After adding the services I want to retrieve their ID's and add them to "companies" as an array returned b first function if adding the "services".
const addedServicesIDs = await addServices(arrayOfServices);
await addCompany(newCompany, addedServicesIDs);
The services are added succesfully but I cannot retreive their ID's which I store in the addServices function and returning them as array.
The console.log is working properly.
async function addServices(props) {
const arrayOfServices = props;
const arrayOfServicesID = [];
arrayOfServices.forEach(async (service, index) => {
console.log(service);
await db
.collection("services")
.add({
serviceName: service.serviceDetails.serviceName,
description: service.serviceDetails.description,
duration: service.serviceDetails.duration,
price: service.serviceDetails.price,
capacity: service.serviceDetails.capacity,
workingDays: service.serviceDayWorking,
})
.then((docRef) => {
arrayOfServicesID[index] = docRef.id;
console.log("Written Service with ID of ", docRef.id);
});
});
return arrayOfServicesID;
}
Maybe I'm not understading that well async functions,
I will be very thankful for your help!
Finally I have found a solution.
I used const instead of var ,that's why my variable was not updating.
var AddedServicesIDs = [];
I have refactored my code
export async function addServices(props) {
const doc_ref = await db.collection("services").add({
serviceName: props.serviceDetails.serviceName,
description: props.serviceDetails.description,
duration: props.serviceDetails.duration,
price: props.serviceDetails.price,
capacity: props.serviceDetails.capacity,
workingDays: props.serviceDayWorking,
});
return doc_ref.id;
}
for (const service of arrayOfServices) {
const extractedID = await addServices(service);
AddedServicesIDs.push(extractedID);
}

Resources