I'm using the below script to take payments via Stripe checkout V3. I was hoping this would make my payments SCA Ready, however i'm being told my payments are still not SCA ready. Am i missing some code from the below?
var stripe = Stripe('XXXX');
var elements = stripe.elements();
var card = elements.create('card', {
style: style
});
// Add an instance of the card Element into the `card-element` <div>
card.mount('#card-element');
// Handle real-time validation errors from the card Element.
card.addEventListener('change', function (event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
// Handle form submission
var form = document.getElementById('payment-form');
form.addEventListener('submit', function (event) {
event.preventDefault();
stripe.createToken(card).then(function (result) {
if (result.error) {
// Inform the user if there was an error
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
stripeTokenHandler(result.token);
}
});
});
// Send Stripe Token to Server
function stripeTokenHandler(token) {
// Insert the token ID into the form so it gets submitted to the server
var form = document.getElementById('payment-form');
// Add Stripe Token to hidden input
var hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
form.appendChild(hiddenInput);
// Submit form
form.submit();
}
Here is my charge code ...
\Stripe\Stripe::setApiKey('XXXX');
$error = '';
$success = '';
try {
if (!isset($_POST['stripeToken']))
throw new Exception("The Stripe Token was not generated correctly");
$charge = \Stripe\Charge::create(
array(
'amount' => $_POST['stripeAmount'],
'currency' => 'gbp',
'source' => $_POST['stripeToken'],
"receipt_email" => $_SESSION["email"],
'description' => 'Booking - ' . $_SESSION['description'],
)
);
To be SCA compliant you need to be using the paymentIntents API
Your current code suggests you are using the charges-api.
There is a full migration guide here.
Basically you need to replace calls from the client:
stripe.createToken(card)
with:
stripe.createPaymentMethod(
'card',
cardElement
)
and calls in the server from:
$charge = \Stripe\Charge::create([
'source' => $json_obj->token_id,
'amount' => 1099,
'currency' => 'eur',
]);
to
$intent = \Stripe\PaymentIntent::create([
'payment_method' => $json_obj->payment_method_id,
'amount' => 1099,
'currency' => 'eur',
'confirmation_method' => 'manual',
'confirm' => true,
]);
After that you need to handle possible additional actions as explained here
Related
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.
What do I have to change in my code to immigrate from legacy stripe checkout to the new checkout?? I am confused with their wording. And most examples I find are old (2015-1016...and are the "old way")
Stripe wants me to upgrade to new checkout because of SCA
This is my working stripe checkout, I have a button that opens the checkout box
<script>
var handler = StripeCheckout.configure({
key: '<? echo $stripe_p_key;?>',
image: 'https://stripe.com/img/documentation/checkout/marketplace.png',
locale: 'auto',
token: function(token) {
var $form = $('#f2');
var token = token.id;
showloader('loaderid');
$form.prepend($('<input type="hidden" style="display:none" name="stripeToken">').val(token));
$form.prepend($('<input type="hidden" style="display:none" name="cc_currency">').val('<? echo $dialog_waehrung_kreditkarte;?>'));
$form.get(0).submit();
}
});
document.getElementById('customButton').addEventListener('click', function(e) {
// Open Checkout with further options:
handler.open({
name: '',
description: '<? echo $dialog_titel;?>',
zipCode: true,
currency: '<? echo $dialog_waehrung_kreditkarte;?>',
email: '<? echo $dialog_email_kreditkarte;?>',
amount: <? echo $dialog_preis_kreditkarte;?>
});
e.preventDefault();
});
// Close Checkout on page navigation:
window.addEventListener('popstate', function() {
handler.close();
});
</script>
then I charge the card in the next step
Stripe::setApiKey($params['private_live_key']);
$pubkey = $params['public_live_key'];
try {
$charge = Stripe_Charge::create(array(
"amount" => $amount_cents,
"currency" => $_SESSION['cc_currency'],
"source" => $_SESSION['stripeToken'],
"description" => $description,
"expand" =>array("balance_transaction")
)
);
If no error is thrown I forward the customer to his download page.
I want a very simple way, I do not need customers, bills, recruing payments or whatever..just single payments. I do not want customers address or such things. Payment and goodbye...
Stripe says I have to change this process.
But their example is confusing for me:
https://stripe.com/docs/payments/checkout/migration#api-products
(I did never create a customer for exampley...why should I?)
Can someone tell me what I have to do to migrate to the new checkout version?
Basic setup (you can build it up from here)
Back-end:
Update your Stripe PHP Library.
Change from \Stripe\Charge to \Stripe\PaymentIntent following this format:
$charge = \Stripe\Charge::create([
'source' => $token_id,
'amount' => $amount,
'currency' => 'usd',
]);
$intent = \Stripe\PaymentIntent::create([
'payment_method_data' => [
'type' => 'card',
'card' => ['token' => $token_id],
],
'amount' => $amount,
'currency' => 'usd',
'confirmation_method' => 'manual',
'confirm' => true,
]);
Front-end:
Update your Stripe JS to use v3.
<script src='https://js.stripe.com/v3/' type='text/javascript'></script>
Update JS code that handles your payment form:
document.addEventListener("DOMContentLoaded", function(event) {
var stripe = Stripe('xxxxxxxxxx'); // test publishable API key
var elements = stripe.elements();
var card = elements.create('card');
// Add an instance of the card UI component into the `card-element` <div>
card.mount('#card-element');
// Handle events and errors
card.addEventListener('change', function(event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
function stripeTokenHandler(token) {
// Insert the token ID into the form so it gets submitted to the server
var form = document.getElementById('payment-form');
var hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
form.appendChild(hiddenInput);
// Submit the form
form.submit();
}
function createToken() {
stripe.createToken(card).then(function(result) {
if (result.error) {
// Inform the user if there was an error
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Send the token to your server
stripeTokenHandler(result.token);
}
});
};
// Create a token when the form is submitted.
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(e) {
e.preventDefault();
createToken();
});
});
Edit your HTML form:
<form action="process_payment.php" method="post" id="payment-form">
<div class="form-row">
<label for="card-element">
Credit or debit card
</label>
<div id="card-element"><!-- Your form goes here --></div>
</div>
<!-- Used to display form errors -->
<div id="card-errors" role="alert"></div>
</div>
<button type="submit">Pay</button>
</form>
With the new SCA regulations coming in as you mentioned in your comment, you need to use Payment methods and Payment intents now.
Basically from taking a look at your sample code you will need to rewrite pretty much everything (if you haven't already)
Their current docs are here -> https://stripe.com/docs/payments/checkout
The reason for Payment Intents and Payment methods is due to SCA - https://stripe.com/docs/strong-customer-authentication
They have a migration guide too which can be found on the side bar.
However from their examples this is how you would create your new payment intent
$intent = \Stripe\PaymentIntent::create([
'amount' => 1099,
'currency' => 'gbp',
]);
Here is also their guide for migrating from the charges API - it has a tab for stripe.js V2 if you've been using it https://stripe.com/docs/payments/payment-intents/migration
I agree. We have been using Stripe Checkout for about a year. The original implementation was dead easy. Trying to migrate to SCA compliant code is just a mess. Their online Chat is useless, and from the response in Chat, they bascially don't care if you stay with them or go.
We're going to revert to PayPal which we used before and look for an alternative payment processor.
I have a multistep form, in the step 3 there is a button "Pay" that when is clicked it shows a Stripe modal using the jQuery below:
<form action="{{ route('registration.charge') }}" method="post" id="paymentForm">
{{csrf_field()}}
<input type="hidden" name="stripeToken" id="stripeToken"/>
<input type="submit" href="" id="payment" class="btn btn-primary float-right"
value="Pay"/>
</form>
Charge method to handle the Stripe charge:
public function charge(Request $request)
{
Stripe::setApiKey(config('services.stripe.secret'));
$source = $request->stripeToken;
Charge::create([
'currency' => 'eur',
'description' => 'Example charge',
'amount' => 2500,
'source' => $source,
]);
}
Route:
Route::post('/charge', [
'uses' => 'RegistrationController#charge',
'as' => 'registration.charge'
]);
When the user clicks in pay the stripe modal appears the user fills the form and click in Pay button the Stripe validates and send the token and the user is redirected to another page (http://proj.test/charge) because of the charge().
Do you know how to instead of redirecting the user to (http://proj.test/charge) change Stripe code to use Ajax so the user remains on the same page? So that is possible to show in that some page a success message, for example, informing that the payment was completed.
Stripe code:
let stripe = StripeCheckout.configure({
key: "{{config('services.stripe.key')}}",
image: "",
locale: "auto",
token: (token) => {
document.getElementById('stripeToken').value = token.id;
document.getElementById('paymentForm').submit();
}
});
document.getElementById('payment').addEventListener('click', function(e){
stripe.open({
name: 'test',
description: 'test',
amount: 1000
});
e.preventDefault();
});
Like this is not working, it appears the " console.log("Ajax Error!");" and then the user is redirected to "http://proj.test/charge".
let stripe = StripeCheckout.configure({
key: "{{config('services.stripe.key')}}",
image: "",
locale: "auto",
token: (token) => {
document.querySelector('#stripeToken').value = token.id;
document.querySelector('#paymentForm').submit();
$.ajax({
type: "POST",
url: '{{route('conferences.charge')}}',
data: {tokenid: token.id, email: token.email},
success: function(data) {
if (data == 'success') {
console.log("success");
}
else {
console.log("error");
console.log("Ajax Error!");
}
},
error: function(data) {
console.log(data);
}
});
}
});
document.getElementById('payment').addEventListener('click', function(e){
stripe.open({
name: 'test',
description: 'test',
amount: '{{session('total')}}'
});
e.preventDefault();
});
RegistrationController returning code 200:
public function charge(Request $request)
{
Stripe::setApiKey(config('services.stripe.secret'));
$source = $request->stripeToken;
$selectedRtypes = Session::get('selectedRtypes');
$amount = (collect($selectedRtypes)->first()['total']) * 100;
try{
Charge::create([
'currency' => 'eur',
'description' => 'Example charge',
'amount' => $amount,
'source' => $source,
]);
}
catch(\Exception $e){
return response()->json(['status' => $e->getMessage()], 422);
}
return response()->json([
'success' => true,
'message' => 'success',
], 200);
}
So this could probably be achieved in different ways.
Here is a solution from a VUE script that uses jquery to get the form
send: function () {
Stripe.setPublishableKey("stripekey")
const $form = $('#payment-form')
Stripe.card.createToken($form, (status, response) => {
if (response.error) {
return
}
this.payment_token = response.id
this.post('<your controller charge>', this.getFormData())
})
},
post: function (url, data) {
axios.post(url, data).then(response => {
// handle success here
}).catch(error => {
// handle error here
})
},
getFormData: function () {
return {
'payment_token': this.payment_token
}
},
But what I think you are looking for is
send: function () {
Stripe.setPublishableKey("stripekey")
const $form = $('#payment-form')
Stripe.card.createToken($form, (status, response) => {
if (response.error) {
return
}
let stripeToken = response.id
})
}
This uses the stripe javascript sdk
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
Here is my situation, i'm implementing Custom Stripe Checkout with Stripe PHP Api.
I have requested a post Method using jquery like this one >
var handler = StripeCheckout.configure({
key: 'pk_test_yGQM97VuEUdttuOOFQcyaPHW',
image: 'https://stripe.com/img/documentation/checkout/marketplace.png',
locale: 'auto',
token: function (token) {
// You can access the token ID with `token.id`.
// Get the token ID to your server-side code for use.
$.post(
'charge.php',
{
sT: token.id,
sE: token.email
}, function (data) {
console.log(data);
}
);
}
});
var thePayment = document.getElementById('pay-amount');
if (thePayment) {
thePayment.addEventListener('click', function (e) {
var desc = $('#pay-amount').attr('data-desc');
var amount = Number($('#pay-amount').attr('data-amount'));
var email = $('#pay-amount').attr('data-email');
// Open Checkout with further options:
handler.open({
name: 'Test',
description: desc,
amount: amount,
email: email,
allowRememberMe: false
});
e.preventDefault();
});
}
// Close Checkout on page navigation:
window.addEventListener('popstate', function () {
handler.close();
});
And the PHP side is just like this one >
require_once('html/includes/vendor/autoload.php');
$stripe = array(
"secret_key" => "sk_test_nJxSc9Yw716tLBWTa9HHMxhj",
"publishable_key" => "pk_test_yGQM97VuEUdttuOOFQcyaPHW"
);
$charge_reply = array();
\Stripe\Stripe::setApiKey($stripe['secret_key']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$token = $_POST['sT'];
$email = $_POST['sE'];
$customer = \Stripe\Customer::create(array(
'email' => $email,
'source' => $token
));
$charge = \Stripe\Charge::create(array(
"amount" => 1000,
"currency" => "usd",
"source" => $customer->id,
"email" => $email,
"description" => "Example charge"
));
$charge_reply[] = [
'token' => $token,
'email' => $email
];
sendJson($charge_reply);
return;
}
I have also enabled the curl,json,mbstring in php. But the function that accepts after requesting a post method to the charge.php, prints POST http://example.com/charge.php 500 (Internal Server Error) in the console log.
So is there any way i can fix this?
500 (Internal Server Error) is something wrong in your code which means their is a fatal error.
To find the error you should use below code in your top of the page.
ini_set('display_errors',1);
error_reporting(E_ALL);
It will return the exact error so can fix it.
Note: Do not use it in production environment this for your local development.