stripe - can not use same payment-method for new paymentIntents - javascript

I have the issue that saved 3d-secure cards can not be re-used again.
it says, card number invalid. below is the paymentmethod created. (sorry, it is in german).
in this picture, you see, it says "For future usage" and shows the paymentintent id.
if I want to use the saved card for new payment this way:
it says, card is invalid.
I have some more codes, so dont know which part is relevant. i will paste js part already here:
stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: card,
billing_details: {},
},
return_url: 'https://localhost:8000/'
}).then(function(data) {
console.log(data);
if (data.error) {
showError(data.error.message);
} else if (data.paymentIntent.status === "succeeded") {
orderComplete(clientSecret);
}
});

Related

Stripe Payment element show saved card

I am using laravel with stripe payment element. I am trying to show the saved cards for the customers that we already have. I have followed the stripe docs and found how I can show it on checkout. But the problem is that I am not getting the saved cards for the customer. And instead I am facing an error on my console as:
When authenticating with an ephemeral key, you must set the Stripe-Version header to an explicit API version, such as 2020-08-27
I have checked and changed lot of versions from here:
$ephemeralKey = \Stripe\EphemeralKey::create(
['customer' => "$user->stripe_customer_id"],
['stripe_version' => '2019-11-05']
);
I changed the version to different version that I can see on my stripe dashboard:
This is my Js Initialize function:
// Fetches a payment intent and captures the client secret
async function initialize() {
// Customize the appearance of Elements using the Appearance API.
const appearance = { /* ... */ };
// Enable the skeleton loader UI for the optimal loading experience.
const loader = 'auto';
const { clientSecret, customerOptions } = await fetch("{{ route("user-create-stripe-element-payment") }}", {
method: "POST",
headers: {
"Content-Type" : "application/json",
"accept" : "application/json",
'X-CSRF-TOKEN': "{{ csrf_token() }}",
'stripe_version':"2019-11-05"
},
body: JSON.stringify({ totalCharge:total }),
}).then((r) => r.json());
elements = stripe.elements({
clientSecret,
appearance,
loader,
customerOptions
});
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
}
And I am also using the betas which is given in the documentation:
const stripe = Stripe("{{env('STRIPE_KEY')}}", {
betas: ['elements_customers_beta_1'],
});
But this error is not going away. And its not even populating the Payment element.
Please help me debug this or if someone has any suggestion to check what is going on here.
Thanks in advance.
You are not providing an API version in your JS here
const stripe = Stripe("{{env('STRIPE_KEY')}}", {
betas: ['elements_customers_beta_1'],
});
change the above code to
const stripe = Stripe("{{env('STRIPE_KEY')}}", {
betas: ['elements_customers_beta_1'],
apiVersion: 'Your Version Here'
});
In your case, it should be something like this
const stripe = Stripe("{{env('STRIPE_KEY')}}", {
betas: ['elements_customers_beta_1'],
apiVersion: '2019-11-05'
});
You can read more here. https://stripe.com/docs/api/versioning?lang=node
It is for nodejs but the API version override will work in the same way.

Having Trouble Accessing Variables & Setting Up Stripe Connect (Flask)

I'm building a Flask marketplace app (using Stripe Collect payments then pay out) where users can choose how much they want to pay (think fundraiser).
I am having trouble moving necessary data around appropriately throughout the checkout process and could really use some help.
Once a user enters how much they'd like to donate, the donation amount and the owner of the campaign they'd like to donate to are sent to the below /pay route where they see a form to enter in their card details with a "Submit Payment" button.
#app.route('/pay', methods=['GET', 'POST'])
def pay():
campaign = Campaign.query.get_or_404(request.args["campaign_id"])
owner = User.query.get_or_404(campaign.user_id) #owner of the campaign
donation_amount = request.args['amount_entered'] # EX: "1000"
return render_template('payment_form.html', campaign=campaign, owner=owner, amount=donation_amount)
The file, payment_form.html, has a simple Stripe form like this:
<form method="post" id="payment-form" class="sr-payment-form">
<div class="sr-form-row">
<label for="card-element">Credit or debit card</label>
<div style="width: 30em" id="card-element"></div>
</div>
<div>
<span style="width: 30em; height: 2em; letter-spacing: 0em" id="card-errors" role="alert"></span>
</div>
<button id="card-button" style="width: 33em;">Submit Payment</button>
</form>
And whenever someone enters their card info and submits the payment, I have a JavaScript file that listens for it and processes the payment (this does not work yet).
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(ev) {
ev.preventDefault();
fetch("/pay_now", {
method: "GET",
headers: {
"Content-Type": "application/json"
},
})
.then(response => response.json())
.then(data => {
// not sure what to do here
});
stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: card,
billing_details: {
name: 'Jenny Rosen' //placeholder (would like to replace)
}
}
}).then(function(result) {
if (result.error) {
console.log(result.error.message);
} else {
if (result.paymentIntent.status === 'succeeded') {
}
});
});
This script fetches the below Flask API /pay_now and should return the clientSecret variable as well as other necessary data to complete the transaction.
#app.route('/pay_now', methods=['GET','POST'])
def create_payment():
intent = stripe.PaymentIntent.create(
payment_method_types=['card'],
amount="1000", #need to pass dollar amount here calculated in /pay route
currency="usd",
transfer_data={'destination': owner.stripe_id}, #need to pass owner from /pay route
application_fee_amount="100")
)
client_secret = intent.client_secret
return jsonify({"client_secret": client_secret})
So basically, my dilemma is that I have the amount of the donation and the campaign owner as variables in the /pay route. But I need to access them when I create the stripe.PaymentIntent object when I call the /pay_now API from my JavaScript and then I need to pass the clientSecret variable back to my JavaScript file for confirmCardPayment() to actually complete the payment.
I'm also open to different approaches if mine doesn't make sense.
I am new to Stripe & new to APIs in Flask. Any help or explanation here would be extremely helpful. Thanks in advance!
You'd want to only POST to your /pay_now route, in the body of that POST you should include the amount your user intends to donate. Then it's a simple case of including that amount when creating the PaymentIntent and returning the client secret to be confirmed on the client.
You might want to first do some checks on both the client and server whether the amount makes sense (e.g. if someone enters -1 it correctly gets rejected).
The stripe.confirmCardPayment code should go in the then resolver of your fetch request, after the response has been parsed to JSON:
fetch("/pay_now", {
method: "POST",
body: JSON.stringify({
amount: amount, // get this from the form
}),
headers: {
"Content-Type": "application/json"
},
})
.then(response => response.json())
.then(data => {
stripe.confirmCardPayment(data.clientSecret, {
payment_method: {
card: card,
billing_details: {
name: name, // get this from the form, like you did with the amount
}
}
})
.then(function(result) {
if (result.error) {
console.log(result.error.message);
} else {
if (result.paymentIntent.status === 'succeeded') {
// display success message
}
}
});

how to properly use Stripe api for saved card on client side

I am currently working with Stripe API. I was able to use one time payment properly, and save credit card correctly as well as process;
However, I was stuck on the 3D secure(2nd auth) for saved card on client side. I had search out the website with no success. And the official doc https://stripe.com/docs/payments/save-and-reuse#web-create-payment-intent-off-session seems with little information about using saved card on client side. Or probably I just don't understand it. Hopefully someone can guide me out with correct practice.
Below it's the critical part of codes, it currently works with one time payment and 2nd auth stripe pop out.
NOTE: I was able to create a charge based on the information from each individual card from $saved_cards in my server, however it would not trig the 3d secure hence it will always failed with cards that requires 2nd authentication
backend.php
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET'));
$intent = \Stripe\PaymentIntent::create([
'amount' => $payment * 100,
'currency' => 'usd',
]);
if (!empty(Auth::user()->stripe_id)) { //all saved card info
$saved_cards = \Stripe\PaymentMethod::all([
'customer' => Auth::user()->stripe_id,
'type' => 'card',
]);
}
return view('cart.preview', $items)->with('saved_cards', $saved_cards)->with('client_secret', $intent->client_secret);
Client.js
// this is for one time payment
var payment_method = {
card: card,
billing_details: {
name: "name"
}
};
stripe.confirmCardPayment( "{{ $client_secret }}", {
payment_method: payment_method, // <= I believe this is the place I can use the saved card?
}).then(function (result) {
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.
}
}
});
/*****************************************************************************/
Update for solution based on answer suggestion:
Disclaimer: this might be a bad practice since the secret switch happens on the frontend side. But you get the idea.
In order to use your payment_method id, you also have to attach your customer id, which happens in the backend side. So for my case I created another savedCard_intent in my backend and pass it to frontend for handle the saved card specifically.
$savedCard_intent = \Stripe\PaymentIntent::create([
'customer' => Auth::user()->stripe_id,
'amount' => $payment * 100,
'currency' => 'usd',
]);
pass it to frontend with('saved_secret' ,$savedCard_intent->client_secret);
Combine all of them together I have code like the following:
newBackend.php
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET'));
$new_intent = \Stripe\PaymentIntent::create([
'amount' => $payment * 100,
'currency' => 'usd',
]);
$savedCard_intent = \Stripe\PaymentIntent::create([
'customer' => Auth::user()->stripe_id,
'amount' => $payment * 100,
'currency' => 'usd',
]);
if (!empty(Auth::user()->stripe_id)) { //all saved card info
$saved_cards = \Stripe\PaymentMethod::all([
'customer' => Auth::user()->stripe_id,
'type' => 'card',
]);
}
return view('cart.preview', $items)->with('saved_cards', $saved_cards)->with('new_secret', $new_intent->client_secret)->with('saved_secret', $savedCard_intent->client_secret);
newClient.js
// this is for one time payment
var payment_method = {
card: card,
billing_details: {
name: "name"
}
};
var secret = (Some conditions here) ? "{{$new_secret}}" : "{{$saved_secret}}";
stripe.confirmCardPayment( secret, {
payment_method: (Some conditions here) ? payment_method : saved_payment_method.id,
}).then(function (result) {
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
}
}
});
You are correct. You would use the PaymentMethod's id (pm_******) like so:
stripe.confirmCardPayment( "{{ $client_secret }}", {
payment_method: payment_method.id,
}). then( ... )
Documented here: https://stripe.com/docs/js/payment_intents/confirm_card_payment#stripe_confirm_card_payment-data-payment_method
You can also pass a PaymentMethod object if you're generating one on the client side, but that's not likely the approach you're looking for.

Stripe subscriptions: how to pass customer's name and email to PaymentIntent?

To set up subscriptions, I am following step-by-step the instructions at https://stripe.com/docs/payments/save-after-payment
All is good until step 5 (Save the card): I want to save the customer's name and email there, and stripe.confirmCardPayment doesn't seem to accept it (unlike the sample as https://github.com/stripe-samples/payment-form-modal/blob/master/cards-only/client/elementsModal.js)
Even when passing such data, the Dashboard shows the customer doesn't have name and email.
stripe.confirmCardPayment(paymentIntent.client_secret, {
payment_method: {
card: card,
billing_details: { name: content.customerName } // << WHAT I WANT
}
})
.then(function(result) {
if (result.error) {
var displayError = document.getElementById("card-errors");
displayError.textContent = result.error.message;
} else {
stripePaymentHandler();
}
});
How to pass the customer's name and email with stripe.confirmCardPayment?

reset the star-rating component, VUE JS

I am using star-rating plugin in vue js, and I am using v-model to show the ratings from db. Everything works fine as when user is not logged in and he/she tries to rate it shows an error "login to rate", but the stars dont reset to db value instead it shows the rating of not logged in user. Currently after the error msg I am refreshing the whole page. Is there a simple way to reset the stars instead of refreshing the whole page?
:show-rating="false" #rating-selected="setRating" v-model="rating"
v-bind:star-size="20"
above is the start rating and while clicking it calls a function where I am checking if user is logged in or not with an api call. Thanks in advance.
setRating: function (rating) {
axios.get('/checkuser').then(response => {
this.user = response.data;
if (this.user === "Logout") {
toastr.error('Please login to rate', 'Error', {
positionClass: 'toast-bottom-right'
});
window.location = "/menu/" + this.menu_id;
} else {
// save in to db
}
}).catch(error => {
// TODO: Handle error
});
},
You will have to reset rating object if it's not logged in.
setRating: function (rating) {
axios.get('/checkuser').then(response => {
...
if (this.user === "Logout") {
...
this.rating = 0; <=== reset rating here (in data, not the rating parameter)
}
...
})
...
},
managed to fix it by using "this.load()" after the api call, which refreshes all components. :)

Categories

Resources