Since three days i am trying to get the Paypal Checkout to work but i always have the problem that the order is created and the money in gone from the buying account but not reaching the payee account.
So here is my setup:
The Smart Buttons integeration in JavaScript:
paypal.Buttons({
env: enviroment,
// Set up the transaction
createOrder: function() {
let formData = new FormData();
formData.append('bookingId', bookingId);
return fetch (url_createOrder, {
method: 'POST',
body: formData
}).then(response => {
console.log(response);
return response.json()
})
.then(function(res) {
console.log(res);
return res.result.id;
});
},
// Finalize the transaction
onApprove: function(data, actions) {
console.log(data);
// This function captures the funds from the transaction.
return actions.order.capture().then(function(details) {
console.log(details);
// This function shows a transaction success message to your buyer
// window.location.href = 'danke.php';
});
}
}).render('#paypal-button-container');
As you can see the createOrder starts a AJAX call to this script:
[...]
$client = new PayPalHttpClient($environment);
$request = new OrdersCreateRequest();
$request->prefer('return=representation');
$request->body = self::buildRequestBody($price);
// 3. Call PayPal to set up a transaction
$response = $client->execute($request);
echo json_encode($response, JSON_PRETTY_PRINT);
// 4. Return a successful response to the client.
return $response;
}
private static function buildRequestBody($price) {
return array(
'intent' => 'CAPTURE',
'application_context' => array(
'brand_name' => 'Example',
'cancel_url' => 'http://localhost/example/abbruch.php',
'return_url' => 'http://localhost/example/danke.php'
),
'purchase_units' => array(
0 => array(
'reference_id' => 'example_addr',
'description' => 'example Address',
'amount' => array(
'currency_code' => 'EUR',
'value' => $price
)
)
)
);
[...]
Everything works so far to this point. I get a OrderId back which i return to the AJAX call and then i am am able to insert credentials and pay the given price.
When i finish the payment the onApprove of the smart buttons in the JS file get called back and i also get the correct response of that actions.order.capture():
{create_time: "2020-08-14T19:37:59Z", update_time: "2020-08-14T19:38:20Z", id: "6FP46164U47878440", intent: "CAPTURE", status: "COMPLETED", …}
create_time: "2020-08-14T19:37:59Z"
id: "6FP46164U47878440"
intent: "CAPTURE"
links: [{…}]
payer:
address:
country_code: "DE"
__proto__: Object
email_address: "sb-ughwh2901918#personal.example.com"
name: {given_name: "John", surname: "Doe"}
payer_id: "8Z5RM2ERW6VTL"
__proto__: Object
purchase_units: [{…}]
status: "COMPLETED"
update_time: "2020-08-14T19:38:20Z"
__proto__: Object
Afterwards the money is gone from the buyer account but it says "pending", here a screenshot (but in german)
payment_is_pending.png
On the seller account i can´t select anything like "approve". I found an example of the paypal checkout api which works similar and tried to copy it into my code but yeah... same story.
Then i thought maybe the problem is the seller sandbox account, but if i try it which an sandbox account created and given by a paypal tutorial is says pending as well.
Please, help!
The payments are pending because there is no sandbox account with the email sb-2xact2876961#business.example.com confirmed, so the payments are in an unclaimed state. Pending unclaimed payments will be automatically returned after 30 days if left unclaimed.
To claim the payments, the email sb-2xact2876961#business.example.com must be confirmed on a sandbox account, via https://www.sandbox.paypal.com/businessprofile/settings/email and https://developer.paypal.com/developer/notifications/
Related
I came across Javascript's Payment API and thought of understanding it. So, I have generated a sample code for the API to check the working. But I am getting this error:
DOMException: Failed to execute 'show' on 'PaymentRequest': PaymentRequest.show() requires either transient user activation or delegated payment request capability
while using the code. No idea what is wrong with it
Here is the sample code I am using:
if (window.PaymentRequest) {
// Create a payment method object for the supported payment methods
const methodData = [{
supportedMethods: ['basic-card'],
data: {
supportedNetworks: ['visa', 'mastercard'],
supportedTypes: ['debit', 'credit']
}
}];
// Create the payment details object with the total amount
const details = {
total: {
label: 'Total amount',
amount: {
currency: 'USD',
value: '50.00'
}
}
};
// Create the payment request object
const paymentRequest = new PaymentRequest(methodData, details);
// Show the payment UI to the user and wait for their response
paymentRequest.show()
.then(response => {
console.log("Response: ", response)
})
.catch(error => {
console.error('Payment request failed: ', error);
});
} else {
console.error('Payment Request API is not supported');
}
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?
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 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.
I first created a customer with the help of API, when a customer created successfully then it returned me customerId, with the help of customerId I created a credit card.
// for creating user
gateway->customer()->create([
'firstName' => $firstName,
'lastName' => $lastName,
'company' => $company,
'email' => $email,
'phone' => $phone,
'fax' => $fax,
'website' => $website
]);
//for creating card
$result = $this->gateway->creditCard()->create([
'customerId' => $customerId,
'number' => $number,
'expirationDate' => $expirationDate,
'cvv' => $cvv
After saving card in vault successfully it gives me a token In order to retrive data of card I did this:
$result = $this->gateway->creditCard()->find($token);
and it returned me the card detail, Now I want to perform payment with this card detail or token(as I am confused). previously I successfully done payment with drop in UI but I want to use vault this time
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact
support.
Now that you have the payment method token, you can pass that value as a parameter to the Transaction Sale API Call in order to complete a transaction with the saved payment method as opposed to a payment method nonce, which represents a single use payment method.
Example
$result = $gateway->transaction()->sale(
[
'paymentMethodToken' => 'the_payment_method_token',
'amount' => '100.00'
]
);