How can I set amount field to PayPal SDK - php

I am working on PayPal express and want to send the amount field value to the server file (payment.php)
from the client-side script and also want to gets its value to the server-side file.
Here is client-side script
<script src="https://www.paypal.com/sdk/js?client-id=<client-id>"></script>
<script>
paypal.Buttons({
createOrder: function() {
return fetch('payment.php', {
method: 'post',
headers: {
'content-type': 'application/json'
},
}).then(function(res) {
return res.json();
}).then(function(data) {
return data.id;
});
},
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.
window.location = "paypal-transaction-complete.php?&orderID="+data.orderID;
});
}
}).render('#paypal-button-container');
</script>
<div id="paypal-button-container"></div>
Here is the payment.php where I want to get the amount field value
<?php
namespace Sample\CaptureIntentExamples;
require __DIR__ . '/vendor/autoload.php';
//1. Import the PayPal SDK client that was created in `Set up Server-Side SDK`.
use Sample\PayPalClient;
use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
require 'paypal-client.php';
class CreateOrder
{
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);
echo json_encode($response->result, JSON_PRETTY_PRINT);
return $response;
}
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' => '20.00'
)
)
)
);
}
}
if (!count(debug_backtrace()))
{
CreateOrder::createOrder(true);
}
?>
Please let me know how can I do that? Thanks!

With PHP the simplest way is going to be to use a GET string.
return fetch('payment.php?amount=' + document.getElementById('amount').value , {
^ You might want to validate or sanitize that input or URL encode the value, but for the most part it should work.
array(
'currency_code' => 'USD',
'value' => $_GET['amount']
)
It's not modern but that's PHP for you, parsing JSON is probably overkill.
You shouldn't be using actions.order.capture() when creating on the server, I'd be surprised if that even works. Implement a capture order route on your server, and use this JS sample that has proper error handling: https://developer.paypal.com/demo/checkout/#/pattern/server

Related

Stripe avoid paymentintent with status 'Incomplete'

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.

Calling external API from Laravel Controller

I am trying to call a certain service provider's API and it has public and secret API keys.
However, I am currently using Laravel, but there are only JavaScript, NodeJS, Python implementation on how to send the post request.
My issue is that, How do I send the post request on Laravel/PHP to avoid publicizing the API keys?
They have this specific format that should be followed:
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Basic some_base64_encrypted_key'
},
body: JSON.stringify({
data: {
attributes: {
amount: 10000,
redirect: {success: 'https://www.test1.com/', failed: 'https://www.test2.com/'},
type: 'some_paymenth_method',
currency: 'SOME_CURRENCY'
}
}
})
};
fetch('https://api.serviceprovider.com/v1/sources', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
Something like this should help you:
$response = Http::withBasicAuth('keys', 'secret')
->withHeaders([
'Content-Type' => 'application/json',
'Authorization' => 'Basic some_base64_encrypted_key'
])
->post('https://api.serviceprovider.com/v1/sources', [
'amount' => '1000',
'type' => 'some_paymenth_method',
'currency' => 'SOME_CURRENCY'
]);
if( $response->successful() ){
//do some logic
//redirect https://www.test1.com/
}elseif( $response->failed() ){
//do some logic
//redirect https://www.test2.com/
}
You can play around with it, try the documentation.
https://laravel.com/docs/8.x/http-client#authentication
Using HTTPS, your authorization header is ALSO encrypted. So that if anyone intercept the message, they cannot read the actual content of the token. However, the header is still visible to both client and server. In your case, you are the client side to the service provider.
With that said, as previous anwser explained well, you can use Laravel HTTP client. This solution is available on laravel 7+. You need to install Guzzle package like this:
composer require guzzlehttp/guzzle
and make the request like this with Http facade:
use Illuminate\Support\Facades\Http;
$response = Http::withToken('place api key')->withHeaders([
'Content-Type' => 'application/json',
])->post('https://api.serviceprovider.com/v1/sources', [
// your data array
]);
// Determine if the status code is >= 200 and < 300...
if ($response->successful()) {
// todo i.e get the response body save the date etc.
} else {
// todo i.e schedule to try again later etc.
}
if your are running older version of laravel, after installing guzzlehttp/guzzle package with composer, you can make the request like this:
$headers = [
'Authorization' => 'place api key',
'Accept' => 'application/json',
];
$body = [
// your data array
];
$client = new \GuzzleHttp\Client();
$url = "https://api.serviceprovider.com/v1/sources";
$response = $client->request('POST', $url, [
'headers'=> $headers,
'body'=> $body
]);
// check response here
// don't forget error handling

How do I integrate PayPal Smart Button in Laravel 8?

I am trying to integrate PayPal Smart buttons in my Laravel 8 Website. These are the documentation I am using:
https://developer.paypal.com/docs/checkout/integrate/
https://developer.paypal.com/docs/checkout/reference/server-integration/set-up-transaction/
https://github.com/paypal/Checkout-PHP-SDK
Issue: I get two errors:
This error underlines the fetch in my front-end.
cart:278 POST http://127.0.0.1:8000/createOrder 500 (Internal Server Error)
{err: "Error: Expected an order id to be passed↵
Here is my route file:
use App\Http\Controllers\PayPalController;
Route::post('/createOrder', [PayPalController::class, 'createOrder']);
Here is my PayPalController Method:
<?php
namespace App\Http\Controllers;
// To capture payment for paypal
namespace Sample\CaptureIntentExamples;
// Grabs the environment and request to be used for paypal
use Sample\PayPalClient;
use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
// Request/Response
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class PayPalController extends Controller
{
public function createOrder() {
$request = new OrdersCreateRequest();
$request->prefer('return=representation');
$request->body = [
"intent" => "CAPTURE",
"purchase_units" => [[
"reference_id" => "test_ref_id1",
"amount" => [
"value" => "100.00",
"currency_code" => "USD"
]
]],
"application_context" => [
"cancel_url" => "https://127.0.0.1:8000/cart",
"return_url" => "https://127.0.0.1:8000/"
]
];
// 3. Call PayPal to set up a transaction
$client = PayPalClient::client();
$response = $client->execute($request);
// 4. Return a successful response to the client.
return $response;
}
}
Here is my front-end .blade.view file:
<script src="https://www.paypal.com/sdk/js?client-id=*******************"></script>
<form action="/createOrder" method="POST">
#csrf
<div id="paypal-button-container"></div>
<script>
paypal.Buttons({
createOrder: function() {
return fetch('/createOrder', {
method: 'POST',
headers: {
'content-type': 'application/json',
'Accept': 'application/json',
'url': '/createOrder',
"X-CSRF-Token": document.querySelector('input[name=_token]').value
},
}).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
});
}
}).render('#paypal-button-container');
</script>
</form>
I have edited the PayPalClient.php file with my credentials.
A 500 error within your /createOrder route is something you'll need to diagnose/debug internally. Try loading the URL in a browser and see if you get any more error output from Laravel. If you don't, edit the corresponding PHP to output more useful information about what's going on.
Once you sort that out and are returning an actual id as part of a JSON object, you need to change your front end code to read the key of that id. It looks like you are reading the key orderID, which won't be set unless you specifically set it.
The following front-end demo code is better, and has error handling: https://developer.paypal.com/demo/checkout/#/pattern/server
This part of your front-end code:
<form action="/createOrder" method="POST">
Makes absolutely no sense, unless it's just for testing purposes, but still really doesn't make sense.
All you should have is a container <div> with the id paypal-button-container, for the JS to render its own button in. That button does not in any way use or work with an HTML form.

How to fix the 3D secure confirm Payment on client side or on server side in Stripe Payment Intent?

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.

Stripe Checkout PHP API getting 500 Internal Server Error

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.

Categories