I've been using Stripe to successfully process payments. The payment intent is created in PHP on the server and there is client side scripting in javascript to set up and call the payment intent script and handle the results.
If something like a card declined happens, then this is handled fine. However if there is a different kind of problem then although I can successfully trap this error server side, I've been unable to work out how to handle it client side and display a useful error message to the user.
This is my client side code:
var stripe = "***HIDDEN***";
var stripesetup = "/stripecreate.php";
var subscriptionid = document.getElementById("subscriptionid").value;
var email = document.getElementById('email').value;
// The items the customer wants to buy
var purchase = {
items: [{ id: subscriptionid }]
};
// Disable the button until we have Stripe set up on the page
document.getElementById("btn-checkout").disabled = true;
fetch(stripesetup, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(purchase)
}).then(function(result) {
/* This is where the error should be trapped, however result isn't the value from the server side script */
return result.json();
}).catch( (message) => {
/* Have also been trying to handle 'message' in here but without success */
}).then(function(data) {
var elements = stripe.elements();
var style = {
base: {
color: "#32325d",
fontFamily: 'Arial, sans-serif',
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#32325d"
}
},
invalid: {
fontFamily: 'Arial, sans-serif',
color: "#fa755a",
iconColor: "#fa755a"
}
};
// Stripe injects an iframe into the DOM
var card = elements.create("card", { style: style });
card.mount("#card-element");
/*
var cardNumberElement = elements.create('cardNumber');
cardNumberElement.mount("#card-element");
var cardExpiryElement = elements.create('cardExpiry');
cardExpiryElement.mount("#cardexpiry-element");
*/
card.on("change", function (event) {
// Disable the Pay button if there are no card details in the Element
document.getElementById("btn-checkout").disabled = event.empty;
document.querySelector("#shopvalidation").textContent = event.error ? event.error.message : "";
});
var form = document.getElementById("shopform5");
form.addEventListener("submit", function(event) {
event.preventDefault();
// Complete payment when the submit button is clicked
payWithCard(stripe, card, data.clientSecret);
});
});
// Calls stripe.confirmCardPayment
// If the card requires authentication Stripe shows a pop-up modal to
// prompt the user to enter authentication details without leaving your page.
var payWithCard = function(stripe, card, clientSecret) {
loading(true);
stripe
.confirmCardPayment(clientSecret, {
receipt_email: email,
payment_method: {
card: card
}
})
.then(function(result) {
if (result.error) {
// Show error to your customer
showError(result.error.message);
} else {
// The payment succeeded!
orderComplete(result.paymentIntent.id);
}
});
};
/* ------- UI helpers ------- */
// Shows a success message when the payment is complete
var orderComplete = function(paymentIntentId) {
loading(false);
document.getElementById("btn-checkout").disabled = true;
location.href="/thank-you";
};
// Show the customer the error from Stripe if their card fails to charge
var showError = function(errorMsgText) {
loading(false);
var errorMsg = document.querySelector("#shopvalidation");
errorMsg.textContent = "Unfortunately your payment has been unsuccessful due to "+errorMsgText+" Please contact us for more information";
setTimeout(function() {
errorMsg.textContent = "";
}, 10000);
};
// Show a spinner on payment submission
var loading = function(isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.getElementById("btn-checkout").disabled = true;
document.querySelector("#lds-ring").classList.remove("invisible");
document.querySelector("#button-text").classList.add("invisible");
} else {
document.getElementById("btn-checkout").disabled = false;
document.querySelector("#lds-ring").classList.add("invisible");
document.querySelector("#button-text").classList.remove("invisible");
}
};
On the server, the payment intent is being created with this:
try {
$customer = \Stripe\Customer::create([
'name' => $fullname,
'email' => $email,
'phone' => $telephone,
"shipping" => [
'name' => $fullname,
"address" => [
"line1" => $address1,
"line2" => $address2,
"city" => $town,
"state" => $county,
"country" => $countrycode
],
],
"address" => [
"line1" => $billingaddress1,
"line2" => $billingaddress2,
"city" => $billingtown,
"state" => $billingcounty,
"country" => $billingcountrycode
],
'description' => $userid
]);
$stripeid = $customer->id;
} catch(\Stripe\Exception\CardException $e) {
// Since it's a decline, \Stripe\Exception\CardException will be caught
echo 'Status is:' . $e->getHttpStatus() . '\n';
echo 'Type is:' . $e->getError()->type . '\n';
echo 'Code is:' . $e->getError()->code . '\n';
echo 'Param is:' . $e->getError()->param . '\n';
echo 'Message is:' . $e->getError()->message . '\n';
die();
} catch (\Stripe\Exception\RateLimitException $e) {
// Too many requests made to the API too quickly
echo json_encode(['error' => $e->getMessage()]);
die();
} catch (\Stripe\Exception\InvalidRequestException $e) {
// Invalid parameters were supplied to Stripe's API
echo json_encode(['error' => $e->getMessage()]);
die();
} catch (\Stripe\Exception\AuthenticationException $e) {
// Authentication with Stripe's API failed
// (maybe you changed API keys recently)
echo json_encode(['error' => $e->getMessage()]);
die();
} catch (\Stripe\Exception\ApiConnectionException $e) {
// Network communication with Stripe failed
echo json_encode(['error' => $e->getMessage()]);
die();
} catch (\Stripe\Exception\ApiErrorException $e) {
// Display a very generic error to the user, and maybe send
// yourself an email
echo json_encode(['error' => $e->getMessage()]);
die();
} catch (Exception $e) {
// Something else happened, completely unrelated to Stripe
echo json_encode(['error' => $e->getMessage()]);
die();
}
}
An "\Stripe\Exception\InvalidRequestException" is being thrown in my testing when I delibrately put in an invalid phone number. This is being returned by my stripecreate.php script:
{"error":"Invalid string: 0123456789...0123456789; must be at most 20 characters"}
I would expect this to be handled in the first part of the promise indicated in my client side script above, however the result is returning this when echoed to the console:
Response { type: "basic", url: "https://www.HIDDEN*****/stripecreate.php", redirected: false, status: 200, ok: true, statusText: "OK", headers: Headers(11), body: ReadableStream, bodyUsed: false }
body: ReadableStream { locked: true }
bodyUsed: true
headers: Headers(11) { "cache-control" → "no-store, no-cache, must-revalidate", "content-length" → "82", "content-type" → "application/json", … }
ok: true
redirected: false
status: 200
statusText: "OK"
type: "basic"
url: "https://www.HIDDEN*****/stripecreate.php"
<prototype>: ResponsePrototype { clone: clone(), arrayBuffer: arrayBuffer(), blob: blob(), … }
I expect this is me not correctly understanding how the promise structure is working and how I can get the response text from the server so I can check for an error and display it.
The next .then in the promise carries on execution even though I want it to stop at that point.
Been scratching my head over this one all day. Any help gratefully received.
Seems like your server is responding with JSON & 200 status code, so the first .then() callback only parses the response and moves on.
The status of the response being 200, it is unlikely that it'll be caught by the .catch() function too.
So you'd likely want to handle this response in the following .then() call.
Have you tried responding with a different response code (4xx) or (5xx) to see if that triggers .catch() callback?
Related
I'm stuck at integrating stripe's prebuild payment gateway to my project.
I'm coding very simple Eshop (without login/registration and shopping cart) just a simple project with a form and stripe checkout as a way to pay...
I was following official stripe documentation but I struggle to find an answer how to implement their "simple prebuilt checkout page" to procedural PHP.
Currently I'm stuck on getting this error...the provided code is what I have used from their official documentation "still getting error ReferenceError: sessionId is not defined in the console in devtools ://
Also IDK how to configure endpoint on my server when coding it all without PHP framework such as Slim/Laravel...al examples provided by stripe use Slim framework when configuring endpoints....any ideas?
<?php
//config PHP
require_once("vendor/autoload.php");
// === SET UP STRIPE PAYMENT GATEWAY ===
$stripe = [
"secret_key" => "sk_test_4eC39HqLyjWDarjtT1zdp7dc",
"publishable_key" => "pk_test_TYooMQauvdEDq54NiTphI7jx",
];
\Stripe\Stripe::setApiKey($stripe['secret_key']);
?>
<?php
//create-checkout-session.php
require_once '_includes/config.php';
// ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
$checkout_session = \Stripe\Checkout\Session::create([
'success_url' => 'http://localhost:8888/Avanza---Eshop/success.php?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => 'http://localhost:8888/Avanza---Eshop/canceled.php',
'payment_method_types' => ['card'], //, 'alipay'
'mode' => 'payment',
'line_items' => [[
'amount' => 2000,
'currency' => 'usd',
'name' => 'mikina',
'quantity' => 1,
]]
]);
header('Content-type: application/json');
echo json_encode(['sessionId' => $checkout_session['id']]);
<!--order.php actual page that will be displayed to users-->
<button style="width: 100px; height: 100px" id="checkout-button"></button>
<script type="text/javascript">
// Create an instance of the Stripe object with your publishable API key
var stripe = Stripe('pk_test_51HjoRfIaBaXJG6udQspXdLRNwMesCriMwZoR7nGCF0hZtu2Zp9FUxCFWwVpwwU4BZs7fTxJtYorVTuoK1vqXp2Uw002r6qvmO7'); // removed for Stackoverflow post
var checkoutButton = document.getElementById('checkout-button');
checkoutButton.addEventListener('click', function() {
// Create a new Checkout Session using the server-side endpoint you
// created in step 3.
fetch('create-checkout-session.php', {
method: 'POST',
})
.then(function(response) {
return response.json();
})
.then(function(session) {
return stripe.redirectToCheckout({ sessionId: sessionId});
})
.then(function(result) {
// If `redirectToCheckout` fails due to a browser or network
// error, you should display the localized error message to your
// customer using `error.message`.
if (result.error) {
alert(result.error.message);
}
})
.catch(function(error) {
console.error('Error:', error);
});
});
</script>
I think you need to replace return stripe.redirectToCheckout({ sessionId: sessionId}); with return stripe.redirectToCheckout({ sessionId: session.sessionId});
It's worked for me. If you see more errors or face any problems, loot at the browser console Network tab.
$(function() {
var stripe = Stripe('<?= Config::STRIPE_PUB_KEY ?>'); // here write: pk_test_5...
$(document).on('click', '.buy_now_btn', function(e) {
let id = $(this).attr('id');
$(this).text('Please wait...');
$.ajax({
url: 'action.php',
method: 'post',
data: {
id: id,
stripe_payment_process: 1
},
dataType: 'json',
success: function(response) {
console.log(response);
return stripe.redirectToCheckout({
sessionId: response.id
});
},
})
})
});
I am working on a django website and have come across an error which I am not able to solve and Its been a couple of days but I wasnt able to solve the error. I am trying to integrate a payment gateway to my site. I have a payment button which on click is supposed to post the data to the database as well as go to the payment site. But it is storing the data but not going to payment site.
Here is the javascript for my button:
document.getElementById('payment-info').addEventListener('click', function (e) {
submitFormData()
})
function submitFormData() {
console.log('Payment Button Clicked')
var userFormData = {
'name': null,
}
var shippingInfo = {
'address': null,
}
shippingInfo.address = form.address.value
userFormData.name=form.name.value
var url = "/process_order/"
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken,
},
body:JSON.stringify({'form': userFormData, 'shipping': shippingInfo }),
})
.then((response) => response.json())
.then((data) => {
console.log('Success:', data);
alert('Transaction Completed')
window.location.href = "{% url 'index' %}"
})
}
</script>
This is my views.py:
def processOrder(request):
transaction_id = datetime.datetime.now().timestamp()
data = json.loads(request.body)
if request.user.is_authenticated:
customer=request.user.customer
order, created=Order.objects.get_or_create(customer=customer, complete=False)
total=float(data['form']['total'])
order.transaction_id=transaction_id
if total == order.get_cart_total:
order.complete = True
order.save()
ShippingAddress.objects.create(
customer=customer,
order=order,
address=data['shipping']['address'],
name=data['form']['name'],
)
//Code for integration below
param_dict = {
'MID': 'DIY12386817555501617',
'ORDER_ID': str(order.id),
'TXN_AMOUNT': '4',
'CUST_ID': 'j',
'INDUSTRY_TYPE_ID': 'Retail',
'WEBSITE': 'WEBSTAGING',
'CHANNEL_ID': 'WEB',
'CALLBACK_URL':'http://127.0.0.1:8000/handlerequest/',
}
param_dict['CHECKSUMHASH'] = Checksum.generate_checksum(param_dict, MERCHANT_KEY)
return render(request, 'paytm.html', {'param_dict': param_dict})
return JsonResponse('Done',safe=False)
#csrf_exempt
def handlerequest(request):
# paytm will send you post request here
form = request.POST
response_dict = {}
for i in form.keys():
response_dict[i] = form[i]
if i == 'CHECKSUMHASH':
checksum = form[i]
verify = Checksum.verify_checksum(response_dict, MERCHANT_KEY, checksum)
if verify:
if response_dict['RESPCODE'] == '01':
print('order successful')
else:
print('order was not successful because' + response_dict['RESPMSG'])
return HttpResponse('doneee')
And this is my urls.py:
path("process_order/",views.processOrder,name="process_order"),
path("handlerequest/",views.handlerequest,name="handlerequest"),
This code is not working . After clicking the payment button I am getting this error:
Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
This error is referring to the submitformdata() in my javascript code.
I want to workout a way so that once a person clicks on the payment button and his transaction is completed then his data is submitted to the database otherwise not for which I have written the code in handlerequest.
Please help me out.
I've been trying to figure out this problem for 2 days already..
I want to implement Smart Payment Buttons from PayPal, literally followed every step of the explanation closely but still getting following error:
Error: JSON.parse: unexpected character at line 1 column 1 of the JSON data
My javascript for Button rendering:
paypal.Buttons({
createOrder: function() {
return fetch('vendor/paypal/paypal-checkout-sdk/samples/CaptureIntentExamples/CreateOrder.php', {
method: 'post',
headers: {
'content-type': 'application/json'
}
}).then(function(res) {
return res.json();
}).then(function(data) {
return data.orderID; // Use the same key name for order ID on the client and server
});
},
onApprove: function(data, actions) {
// This function captures the funds from the transaction.
return actions.order.capture().then(function(details) {
// This function shows a transaction success message to your buyer.
alert('Transaction completed by ' + details.payer.name.given_name);
});
},
onError: function(err) {
alert(err);
}
}).render('#paypal-button-container');
My CreateOrder.php:
namespace Sample\CaptureIntentExamples;
require __DIR__ . '/../../../../autoload.php';
use Sample\PayPalClient;
use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
class CreateOrder
{
/**
* Setting up the JSON request body for creating the Order. The Intent in the
* request body should be set as "CAPTURE" for capture intent flow.
*
*/
private static function buildRequestBody()
{
return array(
'intent' => 'CAPTURE',
'application_context' =>
array(
'return_url' => 'https://example.com/return',
'cancel_url' => 'https://example.com/cancel'
),
'purchase_units' =>
array(
0 =>
array(
'amount' =>
array(
'currency_code' => 'USD',
'value' => '220.00'
)
)
)
);
}
/**
* This is the sample function which can be sued to create an order. It uses the
* JSON body returned by buildRequestBody() to create an new Order.
*/
public static function createOrder($debug=false)
{
$request = new OrdersCreateRequest();
$request->headers["prefer"] = "return=representation";
$request->body = self::buildRequestBody();
$client = PayPalClient::client();
$response = $client->execute($request);
if ($debug)
{
print "Status Code: {$response->statusCode}\n";
print "Status: {$response->result->status}\n";
print "Order ID: {$response->result->id}\n";
print "Intent: {$response->result->intent}\n";
print "Links:\n";
foreach($response->result->links as $link)
{
print "\t{$link->rel}: {$link->href}\tCall Type: {$link->method}\n";
}
// To toggle printing the whole response body comment/uncomment below line
echo json_encode($response->result, JSON_PRETTY_PRINT), "\n";
}
return $response;
}
}
if (!count(debug_backtrace()))
{
CreateOrder::createOrder(true);
}
It's basicly all copied from the PayPal walkthough.
If I visit the CreateOrder.php directly it is creating an order and I can see the response without errors.
Status Code: 201 Status: CREATED [...]
What I did was deleting the part of the code which was printing out the response in txt format. This is why you were getting JSON syntax error.
public static function createOrder($debug=false)
{
$request = new OrdersCreateRequest();
$request->prefer('return=representation');
$request->body = self::buildRequestBody();
// 3. Call PayPal to set up a transaction
$client = PayPalClient::client();
$response = $client->execute($request);
// To print the whole response body, uncomment the following line
echo json_encode($response->result, JSON_PRETTY_PRINT);
// 4. Return a successful response to the client.
return $response;
}
By the way, this answer is very helpful: https://stackoverflow.com/a/63019280/12208549
Stripe are soon to roll out their use of Strong Customer Authentication for payments with their platform. There's a fairly substantial section in their documentation about it.
https://stripe.com/docs/payments/payment-intents/quickstart#manual-confirmation-flow
The process has the following flow:
The vanilla PHP implementation is like so:
<?php
# vendor using composer
require_once('vendor/autoload.php');
\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
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
$intent = \Stripe\PaymentIntent::create([
'payment_method' => $json_obj->payment_method_id,
'amount' => 1099,
'currency' => 'gbp',
'confirmation_method' => 'manual',
'confirm' => true,
]);
}
if (isset($json_obj->payment_intent_id)) {
$intent = \Stripe\PaymentIntent::retrieve(
$json_obj->payment_intent_id
);
$intent->confirm();
}
generatePaymentResponse($intent);
} catch (\Stripe\Error\Base $e) {
# Display error on client
echo json_encode([
'error' => $e->getMessage()
]);
}
function generatePaymentResponse($intent) {
# Note that if your API version is before 2019-02-11, 'requires_action'
# appears as 'requires_source_action'.
if ($intent->status == 'requires_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']);
}
}
?>
The necessary JavaScript for its use with Stripe Elements looks like this:
var cardholderName = document.getElementById('cardholder-name');
var cardButton = document.getElementById('card-button');
cardButton.addEventListener('click', function(ev) {
stripe.createPaymentMethod('card', cardElement, {
billing_details: {name: cardholderName.value}
}).then(function(result) {
if (result.error) {
// Show error in payment form
} else {
// Otherwise send paymentMethod.id to your server (see Step 2)
fetch('/ajax/confirm_payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payment_method_id: result.paymentMethod.id })
}).then(function(result) {
// Handle server response (see Step 3)
result.json().then(function(json) {
handleServerResponse(json);
})
});
}
});
});
function handleServerResponse(response) {
if (response.error) {
// Show error from server on payment form
} else if (response.requires_action) {
// 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('/ajax/confirm_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
}
}
In my own project I'm using Laravel which is entirely based on the MVC architecture and it fairly nice to you when it comes to most things.
I have tried to refactor a little but I have a question.
Why would you use this line $json_str = file_get_contents('php://input'); over just trying to grab the posted variables from the Request object used in Laravel?
I also read the following article from the PHP Manual:
https://www.php.net/manual/en/wrappers.php.php
To be perfectly honest I've been away from procedural PHP so this has confused me to no end.
Why use 'php://input' over the POST superglobal - Stripe SCA example
The body is encoded as JSON. You can tell because the next line explicitly decodes it.
PHP doesn't understand application/json requests. It will only populate $_POST if the data is encoding using the application/x-www-form-urlencoded or multipart/form-data formats.
Why would you use this line $json_str = file_get_contents('php://input'); over just trying to grab the posted variables from the Request object used in Laravel?
If you were using Laravel, there's no reason to do that.
Since there is no sign of anything Laravel in the example you gave, it is presumably not written with the intention of introducing a dependency on Laravel.
I have a jtable that has a listAction that calls an action in the controller to return the data for the table. If the user refreshes the page while the action is in progress its alerts:
Here is the jtable:
$('#cuserTable').jtable({
title: 'Users',
paging: true,
pageSize: 15,
sorting: true,
ajaxSettings: {
contentType: 'application/json'
},
actions: {
listAction: '#Url.Action("LoadUserTable")'
},
.
.
How and where can I add an error handler to display a custom alert (or not even display an alert) if the user refreshes during a table load/reload?
you can handle the error messages for the dialog:
Imagine you are performing an update and you want to return to the dialog message the error occurred:
try{
//UPDATING
[...]
Your update code goes here
[...]
$jTableResult = array();
$jTableResult['Result'] = "OK";
echo json_encoded($jTableResult);
}catch(Exception $ex){
$jTableResult = array();
$jTableResult['Result'] = "NOK";
$jTableResult['Message'] = "Error while updating the record XYZ";
echo json_encoded($jTableResult);
}
The response should have
data = {
Message : "error message",
Result : "ERROR"
};
$dfd.resolve(data);
In jquery jtable, you have to do $dfd.resolve(data)