Calling external API from Laravel Controller - php

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

Related

How can I set amount field to PayPal SDK

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

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.

OAuth2 - my access token is a server-side secret, yes, so how do I use it with AJAX?

I'm using Brent Shaffer's OAuth2 for PHP (https://github.com/bshaffer/oauth2-server-php)
I call my internal APIs like this:
$data = $fc->callAPI($_SERVER['HTTP_HOST'] . '/api/v1/test-member-search.php',
$fc->internalAccessTokenForAPI(), null);
callAPI takes url, token, data and as you can see, I have a method for getting my own token. callAPI is really just a cURL wrapper:
public function callAPI($fullURL, $token, $data ) {
$headers = [
'Accept: */*',
'Content-Type: application/x-www-form-urlencoded',
'Authorization: Bearer ' . $token
];
if (!$data) { $data = ["data" => "none"]; }
// open connection
$ch = curl_init();
// set curl options
$options = [
CURLOPT_URL => $fullURL,
CURLOPT_POST => count($data),
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
];
curl_setopt_array($ch, $options);
// execute
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
The OAuth2 server can take the Authorization header or if not good, it checks for access_token in the POST.
So that's how I access the API with PHP but what about an AJAX call like below:
<script type="text/javascript">
$(document).ready(function() {
var request;
$("#searchform").submit(function (event) {
event.preventDefault();
var $form = $(this);
var serializedData = $form.serialize();
request = $.ajax({
url: "/api/v1/test-member-search.php",
type: "post",
data: serializedData
});
...
... other stuff
This is where my knowledge ends. I can "make it work" by simply using PHP to echo the token from the PHP session as a hidden input called access_token, or I could include it in the AJAX request data, but this makes the access token (which has a 1 hour life) visible to the browser client.
Knowing the access token gives the ability to make API calls so how do I call the API via AJAX and keep the token hidden?
I understand that you want your client (user agent = browser) to make direct calls to an API, which is perfectly fine. In that case, the token is indeed "visible" in the browser tab which runs your JS.
In that case you need to add the header "Authorization: Bearer " to your ajax request.
For instance:
request = $.ajax({
url: "/api/v1/test-member-search.php",
type: "post",
headers: {
'Authorization': `Bearer ${accessToken}`
},
data: serializedData
});
This must be done exclusively on TLS (https) otherwise your token may be seen by an attacker.

Get Request work in postman but doesn't work in browser

I have web application that uses Angular7 in client side and CakePHP in Api
I was using Basic Authentication then I replace it with Token Authentication all request work fine, except one which works fine only in Postman but in browser it's weird because it seems that there is issue with authentication but couldn't know the reason.
The request should return a blob to download the file, so I use FPDI in CakePHP to return pdf
Here is request in postman
Postman Header
Date →Wed, 15 May 2019 12:02:44 GMT
Server →Apache/2.4.33 (Win32) OpenSSL/1.0.2n PHP/5.6.35
X-Powered-By →PHP/5.6.35
Content-Disposition →inline; filename="doc.pdf"
Cache-Control →private, max-age=0, must-revalidate
Pragma →public
Access-Control-Allow-Origin →*
Keep-Alive →timeout=5, max=100
Connection →Keep-Alive
Transfer-Encoding →chunked
Content-Type →application/pdf
Postman Body
Request on Chrome
Working request using Basic Auth
using FireFox
Call request
getWorkListPdf(id: number, cem_id?: number) {
let uri = `${this.workSessionsUrl}workList/${id}`;
let params = new HttpParams();
if (cem_id) {
params = params.set('cemetery_id', cem_id.toString());
}
const Auth = localStorage.getItem("AuthenticationToken");
let header = new HttpHeaders();
header = header.append("AuthenticationToken", Auth);
return this.dataService.get(uri,{ headers: header, params: params, responseType: 'arraybuffer' })
.pipe(
map(res => {
return res;
}
)
);
}
Any help is much appreciated!
The issue was that Api can't identify that the request which contains the required header info for Authentication, I added Authentication array in AppController to allow using both Basic Auth and Token Auth when receiving json request, this work fine for all requests except for this one ( Download pdf )
I've tried to add
$this->Auth->authenticate['Token'] = array(
'fields' => array(
),
// 'parameter' => '_token',
'header' => 'AuthenticationToken',
'scope' => array(
'User.permission_api_login' => true
)
);
again in Controller and it works fine!!. it seems that it can identify the Token now and return a valid file!
If I fully understand you want to authenticate with a bearer token instead of basic authorization. In case you want basic, you can use the following headers in your request:
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa('username:password')
})
};
In case of bearer authorization, you can do the following:
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + JSON.parse(your token)
})
};
Hope it helps!
Add withCredentials true in your request.
const httpOptions = {
headers: new HttpHeaders({
'Accept': "application/json",
'Content-Type': "text/plain;charset=UTF-8",
'Authorization': 'Bearer ' + JSON.parse(your token)
}), withCredentials: true
};

AJAX POST request to external domain using jQuery

I'm trying to do an AJAX POST request to an external domain from a post metabox in WordPress.
I know that it's not possible to do it directly because of cross domain access errors. As far as I know it's also not possible to do it simply by using PHP code like
if ( isset ( $_POST( 'button_name' ) ) ) { .. }
since all the metaboxes are inside the wordpress post form.
So this is what I came up with:
I use the wp_localize_script() function to make the php variables available in the js-file (this works). The localized variable is ajax_request_args.
The javascript file looks like this:
jQuery(document).ready(function($) {
$('#button').click( function() {
$.ajax({
type: "POST",
url: php_post.php,
data: ajax_request_args,
success: function(data) {
alert(data["success"] + " " + data["code"]);
},
dataType: 'json'
});
});
});
The php_post.php looks similar to this:
if (is_ajax()) {
...
if (isset($_POST["arg_1"]) && !empty($_POST["arg_1"])) {
$response = wp_remote_post( $external_url, array(
'method' => 'POST',
'body' => $args,
'headers' => array( 'Content-Type' => 'application/x-www-form-urlencoded' ),
'timeout' => 90,
'blocking' => true
) );
$return = $_POST;
$return['success_message'] = $response['success_message'];
$return['code'] = $response['code'];
$return["json"] = json_encode($return);
echo json_encode($return);
}
}
The AJAX request works without the POST request in the php file, for example when I set
$response['success_message'] = 'success';
$response['code'] = '1234';
and delete the wp_remote_post()part.
The POST request to the external domain also works when I call it directly using a button click like
<?php
if (isset( $_POST['test-maintenance-btn'])){
...
$response = wp_remote_post( $external_url, array(
'method' => 'POST',
'body' => $args,
'headers' => array( 'Content-Type' => 'application/x-www-form-urlencoded' ),
'timeout' => 90,
'blocking' => true
) );
}
?>
<form method="post">
<input type="submit" id="test-btn" name="test-btn" value="<?php _e('Execute', 'textdomain'); ?>">
</form>
The weird thing is that it doesn't work in connection. The Javascript should post to the php file which posts to the external server and returns the result to the ajax post request.
When I try to do the wp_remote_post (even if I don't use the result) I always get "NetworkError: 500 Internal Server Error" in the console.
Could you help me find the problem and help me with the correct approach? Thanks in advance for your help.

Categories