I am using Stripe's 'Payment' element for creating paymentintent and card charges, Using PHP, HTML & JS.
https://stripe.com/docs/payments/payment-element
a) As soon as i load the payment page, Stripe generates a paymentintent with status 'Incomplete'.
b) After i enter Credit Card details and hit 'pay', Stripe again issues a second paymentintent with status accordingly (say 'Succeeded')
The result is, that my Dashboard is now full of unnecessary records
This is, because i initialize the $paymentIntent = \Stripe\PaymentIntent::create as soon as the page loads.
I understand that this is Stripe default behaviour, because at that moment, no payment_method is attached yet.
My question is: How is this resolved best, to avoid such 'Incomplete' records?
Maybe attach an object and fire the paymentintent creation only when
that object is present?
Or onclick of the 'pay' button..awaiting paymentintent, confirm presence and then submit the form?
Or retrieve that 1st paymentintent, store it and then update with
form submission?
create.php
header('Content-Type: application/json');
try {
// retrieve JSON from POST body
$jsonStr = file_get_contents('php://input');
$jsonObj = json_decode($jsonStr);
// Create a PaymentIntent with amount and currency
$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => 1000,
'currency' => 'eur',
'receipt_email' => 'whatever#mail.com',
'automatic_payment_methods' => ['enabled' => true,],
'description' => 'Reservation Dimi123 / Name: John Doe',
'metadata' => ['order_id' => '12345']
]);
$output = [
'clientSecret' => $paymentIntent->client_secret,
];
echo json_encode($output);
} catch (Error $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
script.js
const stripe = Stripe("pk_test_..");
let elements;
initialize();
checkStatus();
document
.querySelector("#payment-form")
.addEventListener("submit", handleSubmit);
// Fetches a payment intent and captures the client secret
async function initialize() {
const { clientSecret } = await fetch("../create.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
}).then((r) => r.json());
elements = stripe.elements({ clientSecret });
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: "https://localhost/stripe/prebuilt-checkout-custom-flow/public/checkout.html",
},
});
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occurred.");
}
setLoading(false);
}
How to Avoid Incomplete Payment Intents? You don't.
his is how the system is designed to function since you need the client_secret from the Payment Intent (or Setup Intent) to render the Payment Element. There is no negative impact to your account.
You should feel comfortable ignoring them. You can adjust the filters in your Payments tab to not see them. They aren't a great metric of individual customers coming to your payment interface and navigating away since any user refreshing their browser would trigger a new one.
Related
I am developing an Ionic 5 app with a PHP backend. I copied the code from the Stripe docs and put it in a try/catch as follows:
\Stripe\Stripe::setApiKey('sk_test_********************');
$postedChargeObject = file_get_contents("php://input");
$cO = json_decode($postedChargeObject);
try {
$charge = \Stripe\Charge::create([
'amount' => $cO->amount,
'currency' => 'usd',
'description' => 'charge test with token 2',
'source' => $cO->token,
]);
echo "success";
} catch(\Stripe\Error\Base $e){
echo($e->getMessage());
} catch (Exception $e){
echo $e;
}
With the echo "success", I get some unexpected behavior. 1) it takes a long time to get any response from my server and 2) I get the following error in the developer console:
If I comment out the echo "success" it still takes a few seconds but the charge goes through because I can see it on my Stripe dashboard.
Ideally, I would like my PHP script to charge the card with Stripe::create and return the charge object, as defined in the Stripe charge object docs, back to the client script. So not just echo "success" but the whole Stripe charge object.
My service for calling the PHP script is:
chargeCardForOrder(chargeObj): Observable<any> {
let c = JSON.stringify(chargeObj);
console.log("stringified c from service", c);
return this.http.post(this.chargeUrl, c);
}
My .ts file that calls the service is:
this.coService.chargeCardForOrder(cO).subscribe( resp => {
console.log( "response from server charge", resp );
})
On a side note, if I take out the echo "success" from the PHP script, the charge goes thru as per my Stripe dashboard but the console reads "response from server charge null". I am quite confused as to what I am missing. Your help would be greatly appreciated.
Your client-side code is expecting a JSON object, and you're sending it just the string success which is not valid JSON. You should likely be responding with JSON in the shape that you client-side code expects, or not returning any content at all if it's not needed client-side.
I am trying to integrate Stripe in a php project and all is working fine except the fact that the payment intent client_secret is always null and I actually know why but couldn't figure how to fix it, I am using a javascript file in which there is a listener for the submit button for the payment intent creation. The problem is that the listener try to fetch the json data before its creation, the client_secret value is null because I am setting its value just after creating the payment intent, how could solve this? any advice could help, thanks.
Here is the code I wrote:
intent.php:
$intent= \Stripe\PaymentIntent::create(
array(
'amount' => $price + ($price * $tva),
'currency' => 'EUR',
'setup_future_usage' => 'off_session',
)
);
$intentcls=$intent->client_secret;
intent.js:
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
var response = fetch('/secret').then(function(response) {
return response.json();
}).then(function(responseJson) {
var clientSecret = responseJson.client_secret;
stripe.confirmCardPayment(
clientSecret,
{
payment_method: {card: card}
}
).then(function(result) {
if (result.error) {
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
var errorElement = document.getElementById('card-errors');
errorElement.textContent = 'Paiement effectué avec succées';
stripeTokenHandler(result.token);
}
});
secret.php:
echo json_encode(array('clientsecret' => $intentcls));
You'll need to rearrange your logic to ensure the PaymentIntent is created before (or during) your fetch request to the backend.
If you already know the amount, you could create the PaymentIntent on page load.
A better solution is to create it when the call to /secret is made, then return the client_secret after the creation call to the Stripe API is complete.
secret.php could be:
$intent = \Stripe\PaymentIntent::create(
array(
'amount' => $amount, // calculate amount before this block
'currency' => 'EUR',
'setup_future_usage' => 'off_session',
)
);
echo json_encode(array('client_secret' => $intent->client_secret));
// perhaps store the ID in your database here
I'm integrating Stripe Payment Intent API and it's working well where 3D secure is not required, 3D secure authorization is popping up but I think I'm missing return_url to confirm the payment.
Where do I need to mention return_url for 3D Secure in PaymentIntent?
I've tried multiple times but got stuck on 3D Secure Authorize. It returns an error in the object.
I've mentioned the code of view and controller below.
Thanks in Advance
Client Side Code:
form.addEventListener('submit', function(event) {
event.preventDefault();
$('#card-button').html('<i class="fa fa-circle-o-notch fa-spin" style="font-size:24px"></i>');
var fname = $('#firstname2').val();
var lname = $('#lastname2').val();
var cardholderName = fname + " " + lname;
var cardButton = document.getElementById('card-button');
var form_data = $("#payment-form").serialize();
cardButton.addEventListener('click', function(ev) {
stripe.createPaymentMethod('card', cardElement, {
billing_details: {name: cardholderName}
}).then(function(result) {
if (result.error) {
// Show error in payment form
} else {
console.log(result);
// Otherwise send paymentMethod.id to your server (see Step 2)
fetch('<?php echo base_url(); ?>payment/stripe_test/process_payment',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payment_method_id: result.paymentMethod.id, customer_detail: form_data})
}).then(function(result) {
// Handle server response (see Step 3)
result.json().then(function(result) {
console.log("Response" + result);
handleServerResponse(result);
});
});
}
});
});
}
function handleServerResponse(response) {
if (response.error) {
// Show error from server on payment form
} else if (response.requires_action) {
var action = response.next_action;
if (action && action.type === 'redirect_to_url') {
window.location = action.redirect_to_url.url;
}
// Use Stripe.js to handle required card action
stripe.handleCardAction(
response.payment_intent_client_secret
).then(function(result) {
if (result.error) {
// Show error in payment form
} else {
// The card action has been handled
// The PaymentIntent can be confirmed again on the server
fetch('<?php echo base_url(); ?>payment/stripe_test/process_payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payment_intent_id: result.paymentIntent.id })
}).then(function(confirmResult) {
return confirmResult.json();
}).then(handleServerResponse);
}
});
} else {
// Show success message
console.log("3D" + response);
}
}
CodeIgniter Controller:
//PaymentIntent Function
function process_payment() {
require_once (APPPATH."third_party/stripe/init.php");
$key = "STRIPE_KEY_HERE";
header('Content-Type: application/json');
# retrieve json from POST body
$json_str = file_get_contents('php://input');
$json_obj = json_decode($json_str);
$intent = null;
try {
if (isset($json_obj->payment_method_id)) {
# Create the PaymentIntent
//STRIPE PAYMENT INTENT
\Stripe\Stripe::setApiKey($key);
// Create a Customer:
$customer = \Stripe\Customer::create([
'email' => 'client#gmail.com',
]);
// Attach payment method to the customer:
$customer_detail = $json_obj->customer_detail;
$intent = \Stripe\PaymentIntent::create([
'payment_method' => $json_obj->payment_method_id,
'amount' => 1099,
'currency' => 'GBP',
'confirmation_method' => 'manual',
"customer" => $customer->id,
'confirm' => true,
]);
}
if (isset($json_obj->payment_intent_id)) {
$intent = \Stripe\PaymentIntent::retrieve(
$json_obj->payment_intent_id
);
$intent->confirm();
}
$this->generatePaymentResponse($intent);
} catch (\Stripe\Error\Base $e) {
# Display error on client
echo json_encode([
'error' => $e->getMessage()
]);
}
}
generatePaymentResponse Function:
function generatePaymentResponse($intent) {
if ($intent->status == 'requires_source_action' &&
$intent->next_action->type == 'use_stripe_sdk') {
# Tell the client to handle the action
echo json_encode([
'requires_action' => true,
'payment_intent_client_secret' => $intent->client_secret
]);
} else if ($intent->status == 'succeeded') {
# The payment didn’t need any additional actions and completed!
# Handle post-payment fulfillment
echo json_encode([
"success" => true
]);
} else {
# Invalid status
http_response_code(500);
echo json_encode(['error' => 'Invalid PaymentIntent status']);
}
}
As mentioned in comments, you don't need to specify return_url, because in your case Stripe will use Popup for 3DS confirmation, not redirecting.
You missed two things in your code:
1. In function generatePaymentResponse add condition for $intent->status === 'requires_action'.
2. On Payment Intent confirmation ($intent->confirm();) you missed to set api key (\Stripe\Stripe::setApiKey($key);)
I have tested your code and it works with mentioned modifications.
Stripe updated the API and few statuses were renamed so the major reason was that I was using requires_source_action as status so replaced it with requires_action.
Stripe Update - 2019-02-11
Some PaymentIntent statuses have been renamed
requires_source is now requires_payment_method
requires_source_action is now requires_action
All other statuses are unchanged
save_source_to_customer has been renamed to save_payment_method.
allowed_source_types has been renamed to payment_method_types.
The next_source_action property on PaymentIntent has been renamed to next_action, and the authorize_with_url within has been renamed to redirect_to_url.
You can find Stripe Api upgrades here: https://stripe.com/docs/upgrades#api-versions.
I am using Stripe elements to make an asychronous paymentRequest to charge customers. The request returns 'success' irrespective of the charge result so what's the best way to return a state so I can handle the charge state client side and report a failed charge? Do I need to use endpoints? Stripe seems to leave us hanging after the charge with no guidance.
Listener:
<script>
paymentRequest.on('token', function(ev) {
fetch('https://example.com/pub/apple.php', {
method: 'POST',
body: JSON.stringify({token: ev.token.id , email: ev.token.email}),
headers: {'content-type': 'application/json'},
})
.then(function(response) {
if (response.ok) {
// Report to the browser that the payment was successful, prompting
// it to close the browser payment interface.
ev.complete('success');
} else {
// Report to the browser that the payment failed, prompting it to
// re-show the payment interface, or show an error message and close
// the payment interface.
ev.complete('fail');
}
});
});
</script>
https://example.com/pub/apple.php:
require_once('../_stripe-php-4.9.0/init.php');
// Retrieve the request's body and parse it as JSON
$input = #file_get_contents("php://input");
$json = json_decode($input);
// get the token from the returned object
$token = $json->token;
$email = $json->email;
$skey = 'sk_test_Y********************F';
\Stripe\Stripe::setApiKey($skey);
// create the customer
$customer = \Stripe\Customer::create(array(
"source" => $token,
"description" => 'Device',
"email" => $email)
);
//charge the card
$charge = \Stripe\Charge::create(array(
"amount" => 1000,
"currency" => 'GBP',
"statement_descriptor" => 'MTL',
"description" => 'Device - '.$customer->id,
"customer" => $customer->id)
);
In order to let the user know that their charge failed, and trigger ev.complete('fail'); in the code above, you'll need response.ok inside your fetch request to return false.
Let's first look at the definition of that response.ok property,
The ok read-only property of the Response interface contains a Boolean
stating whether the response was successful (status in the range
200-299) or not.
via https://developer.mozilla.org/en-US/docs/Web/API/Response/ok
So for a successful request you want PHP to return a 2xx status (let's say 200) , and for failure a non-200 HTTP status code (lets say a 402).
In your PHP, you will attempt to make a Charge, then receive a response from Stripe. Based upon this response, you should make PHP return a status to your front-end --- either a 200 OK or a 402 error.
You can do this with the function http_response_code(), e.g. http_response_code(200) or http_response_code(402)
http://php.net/manual/en/function.http-response-code.php
Let's look at a simplified example
try {
// make a charge
$charge = \Stripe\Charge::create(array(
"amount" => 1000,
"currency" => 'GBP',
"source" => $token
));
// send a 200 ok
http_response_code(200);
print("Charge successful");
} catch(\Stripe\Error\Card $e) {
// Since it's a decline, \Stripe\Error\Card will be caught
$body = $e->getJsonBody();
$err = $body['error'];
// this charge declined, return a 402
// response.ok should then be false
http_response_code(402);
print('Error:' . $err['message']);
}
The Charge call here is wrapped in a try-catch block. If the charge succeeds, a 200 HTTP response code is sent, and response.ok will be true.
If the user provides a card that declines, that error will be caught, and 402 HTTP response code will be returned (along with an error message). In that case response.ok would be false, so ev.complete('fail'); would be called.
There are some other types of Stripe errors you probably want to catch, a full reference is here,
https://stripe.com/docs/api/errors
https://stripe.com/docs/api/errors/handling
I have this code that works great.
<input class="form-control"
type="number"
id="custom-donation-amount"
placeholder="50.00"
min="0"
value="50"
step="10.00"/>
<script src="https://checkout.stripe.com/checkout.js"></script>
<button id="customButton" class="pay-button">
<h4 class="donate-text button">Donate by
<img src="http://#/testing/credit-card.png" ></h4>
</button>
<script>
var handler = StripeCheckout.configure({
key: 'pk_test_mytestingkey',
image: '',
locale: 'auto',
token: function(token) {
// Use the token to create the charge with a server-side script.
// You can access the token ID with `token.id`
}
});
$('#customButton').on('click', function(e) {
// Open Checkout with further options
var amount = $("#custom-donation-amount").val() * 100;
handler.open({
name: 'Testing',
description: 'Testing',
amount: amount
});
e.preventDefault();
});
// Close Checkout on page navigation
$(window).on('popstate', function() {
handler.close();
});
</script>
It states on the documentation:
"On your server, grab the Stripe token in the POST parameters submitted by your form. From there, it's one simple API call to charge the card:"
I am trying to CHARGE the card information. Stripe provides the following API call to do that: I am assuming this is a charge.php file?
// Set your secret key: remember to change this to your live secret key in production
// See your keys here https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey("sk_test_mykey");
// Get the credit card details submitted by the form
$token = $_POST['stripeToken'];
// Create the charge on Stripe's servers - this will charge the user's card
try {
$charge = \Stripe\Charge::create(array(
"amount" => 1000, // amount in cents, again
"currency" => "usd",
"source" => $token,
"description" => "Example charge"
));
} catch(\Stripe\Error\Card $e) {
// The card has been declined
}
My question: how do I charge the card using the following code without there being a <form> method post, or <form> action...? It's using javascript to capture the token. But I don't know how to send that token to a charge.php file... Basically how do I grab that stripe token and initiate the API call? How do I initiate the API call....?
You should have a charge.php file on your server and use $.post to send to the token to the php.
// make sure you have the right path to charge.php
// pass the token to the php file with POST, your php is expecting $_POST['stripeToken']
token: function(token) {
// Use the token to create the charge with a server-side script.
// You can access the token ID with `token.id
$.post( "charge.php", { stripeToken: token.id})
// check if it worked
.done(function( data ) {
console.log( "Card charged: " + data );
});
}
Your charge.php file, make sure you installed the Stripe PHP library
<?
// composer require stripe/stripe-php
require 'vendor/autoload.php';
// Set your secret key: remember to change this to your live secret key in production
// See your keys here https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey("sk_test_mykey");
// Get the credit card details submitted by the form
$token = $_POST['stripeToken'];
// Create the charge on Stripe's servers - this will charge the user's card
try {
$charge = \Stripe\Charge::create(array(
"amount" => 1000, // amount in cents, again
"currency" => "usd",
"source" => $token,
"description" => "Example charge"
));
} catch(\Stripe\Error\Card $e) {
// The card has been declined
}
?>
Like you say, you need to generate a Stripe token and do a form post. Without a valid token you can not charge the card.
Here's a guide on how to create custom forms with Stripe, including how to generate a token:
https://stripe.com/docs/custom-form
An easier way is to simply embed their ready-to-use checkout:
https://stripe.com/docs/checkout/tutorial
If your are using mamp. UPDATE TO MAMP 4
stripes API no longer supports older versions of MAMP.