PayPal Checkout Buttons - client/server communication - paypal

I am integrating the smart checkout of Paypal. I do not understand what the javascript portion expects back from the server. This is what I have got in JS
...
...
createOrder: function(data, actions) {
return fetch('/paypal/sandbox/createOrder', {
method: 'post',
headers: {
'content-type': 'application/json'
}
}).then(function(res) {
return res.json();
}).then(function(data) {
return data.orderID;
});
},
...
...
And this is what the server side does when /paypal/sandbox/createOrder' is called …
$request = new OrdersCreateRequest();
$request->prefer('return=representation');
$request->body = $this->buildRequestBody();
$response = $this->PayPalClient->execute($request);
What I can not find anywhere: What am I supposed to do with the response? I tried to echo it plain, echo it as JSON, but I always get errors like "Expected order id"
What do I need to respond to the client request?

The structure of the communication between your createOrder frontend JS and your backend server route (which in turn calls the PayPal API) is something you define. It can be is simple or as complex as you need it to be. You can send as many parameters back and forth as you want. You can use JSON, XML, Morse code, RFC 1149, or whatever you want for this transmission between your client and server.
The only required bit of information is the Order ID, which your server-side code in turn gets from the PayPal API v2/checkout/orders call (as id in the response), and which your createOrder function must then propagate back to its caller once obtained, which in the sample code happens here:
...
return data.orderID;
One very simple implementation would be to echo the whole v2/checkout/orders API response , not just id, and change the above client-side code to be return data.id so it reads that key name

Related

php8 and Paypal IPN setup: Where does db INSERT upon successful handshake go?

Of the three files here- https://github.com/paypal/ipn-code-samples/tree/master/php
I have my Webhook URL set to the stock github version of- PaypalIPN.php (this validates successfully 100% of the time, if I use example_usage.php... Doesn't work. If I use both as Webhooks... Doesn't work).
From the Paypal button side of things I'm able to post my website's active user (call him $MrUser) with this:
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
"custom_id":"<?php echo $MrUser; ?>",
"description":"One hundred Webdollars",
"amount":
{
"currency_code":"USD",
"value":1.99
}
}]
});
},
Here's the SQL I need to run upon successful validation (I change $MrUser to $kitty for clarity's sake):
require 'sqlconfig.php';
$dsn = "mysql:host=$host;dbname=$db;charset=UTF8";
try {
$pdo = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
echo $e->getMessage();
}
$hashedIP = $_SERVER['REMOTE_ADDR'];
$kitty = $_POST['custom']; // Not sure this is working yet, but this should give me $mrUser;
$sql = "INSERT INTO `Insert_upon_Paypal_success` (`date`,`hashedIP`,`username`,`webdollarAMT`) VALUES (now(),:hashedIP,:kitty,'100')";
$statement = $pdo->prepare($sql);
$statement->bindValue(':hashedIP', $hashedIP);
$statement->bindValue(':kitty', $kitty);
$inserted = $statement->execute();
I'm popping this into the PaypalIPN.php file upon validation, but, it doesn't work. Here's how I have it in there:
// Check if PayPal verifies the IPN data, and if so, return true.
if ($res == self::VALID) {
return true;
// i.e. putting all of the SQL above right here.
} else {
return false;
}
I'm guessing I need to put the SQL in a specific place that I'm missing, as per the layout of the PaypalIPN.php file... Please help!!
There is no reason to use IPN with current PayPal Checkout integrations. It is very old technology (20+ years) and should be deprecated soon.
Webhooks are a successor to IPN. However, even they are unnecessary for normal payment processing -- better used only if you need automated notifications of post-checkout exceptions such as refunds or disputes.
For normal PayPal payments, do not use either.
Instead, use the v2/checkout/orders API and make two routes (url paths) on your server, one for 'Create Order' and one for 'Capture Order'. You could use the (recently deprecated) Checkout-PHP-SDK for the routes' API calls to PayPal, or your own HTTPS implementation of first getting an access token and then doing the call with PHP's curl or similar. Both of these routes should return/output only JSON data (no HTML or text). Inside the 2nd route, when the capture API is successful you should verify the amount was correct and store its resulting payment details in your database (particularly purchase_units[0].payments.captures[0].id, which is the PayPal transaction ID) and perform any necessary business logic (such as reserving product or sending an email) immediately before forwarding return JSON to the frontend caller. In the event of an error forward the JSON details of it as well, since the frontend must handle such cases.
Pair those 2 routes with this frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server . (If you need to send any additional data from the client to the server, such as an items array or selected options, add a body parameter to the fetch with a value that is a JSON string or object)

PayPal return URL with transaction or payment ID?

I'm using the following script to display a PP button on my website which works fine. You'll see that I have a return URL which also pulls in data (eg orderid) from my page:
<script>
paypal.Buttons({
// Sets up the transaction when a payment button is clicked
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '2.00' // Can reference variables or functions. Example: `value: document.getElementById('...').value`
}
}]
});
},
// Finalize the transaction after payer approval
onApprove: function(data, actions) {
return actions.order.capture().then(function(orderData) {
// Successful capture! For dev/demo purposes:
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
var transaction = orderData.purchase_units[0].payments.captures[0];
window.location.href = 'https://mywebsite.co.uk/signup-payment-confirmation.asp?fee=<%=fee%>&ct=<%=orderid%>';;
});
}
}).render('#paypal-button-container');
</script>
This works fine as when a transaction is made the user is simply redirected to the return URL and I record the data in my database. However, what I really need is for my return URL to also show the transaction ID from the PayPal transaction. I can then tally records in my database to those in the PayPal admin area.
actions.order.create / .capture are for simple use cases. If you intend to do anything with a database, do not use these client side functions. Any number of problems could prevent your system from recording a transaction after the fact.
Instead, use the actual v2/checkout/orders API and make two routes (url paths) on your server, one for 'Create Order' and one for 'Capture Order'. You could use one of the (recently deprecated) Checkout-*-SDKs for the routes' API calls to PayPal, or your own HTTPS implementation of first getting an access token and then doing the call. Both of these routes should return/output only JSON data (no HTML or text). Inside the 2nd route, when the capture API is successful you should verify the amount was correct and store its resulting payment details in your database (particularly purchase_units[0].payments.captures[0].id, which is the PayPal transaction ID) and perform any necessary business logic (such as reserving product or sending an email) immediately before forwarding return JSON to the frontend caller. In the event of an error forward the JSON details of it as well, since the frontend must handle such cases.
Pair those 2 routes with this frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server . (If you need to send any additional data from the client to the server, such as an items array or selected options, add a body parameter to the fetch with a value that is a JSON string or object)

Chrome DevTools Protocol Fetch Domain - getResponseBody - apparently fails with HTTP redirects

I am wishing to collect the body of an HTTP request, including when the page redirects to elsewhere. Clearly, I can use non-Fetch domain mechanisms such as Network.getResponseBody. That works fine for the "final" page in a chain of redirections, but cannot be used for the intermediate pages because Chrome appears to dump the content when going to the next redirection target.
So, I implemented Fetch.enable( { patterns: [ { requestStage: Response } ] } ) (using PHP, but the details of that are irrelevant, as you will see). No error is returned from this method call. After then doing a Page.navigate, I wait for a Fetch.requestPaused event which contains members requestId, responseStatusCode and responseHeaders and then send a Fetch.getResponseBody (using the requestId from the Fetch.requestPaused) and the response I get depends on what the actual response to the page itself was. So, for a 200, I get a response body (hurray), but for a 30x (301, 302 etc), I always get error code -32000 with the message "Can only get response body on requests captured after headers received". Now, issuing that error message is inconsistent (in my view) with the Fetch.requestPaused event data, even if Chrome DevTools Protocol (CDP) was not intended to capture the bodies of HTTP redirected pages. By the way, pages with content triggered redirection (via a META element or JavaScript) are captured okay, I assume because they return a 200 status code.
So, is the issue in the sequence of calls I'm following or in the error message returned by Fetch.getResponseBody and am I correctly assuming CDP was not intended to capture the bodies of documents in a redirection chain (apart from the last one, obviously)?
You need to continue the request on a 301/302 and let the browser follow it (there is no body in a redirect):
if (
params.responseStatusCode === 301 || params.responseStatusCode === 302
) {
await this.#client.send('Fetch.continueRequest', {
requestId,
});
} else {
// get body here
const responseCdp = await this.#client.send('Fetch.getResponseBody', {
requestId,
});
await this.#client.send('Fetch.fulfillRequest', {
requestId,
responseCode: params.responseStatusCode,
responseHeaders: params.responseHeaders,
body: responseCdp.body,
});
}

Unable to make HTTP Post from custom Karma reporter

I need to publish my Karma test results to a custom REST API. To handle this automatically, I've written a custom Karma reporter. I'm trying to use the run_complete event so that the POST happens after all browsers finish. However, no HTTP call is being made.
I'm using Axios 0.19.2 to do the actual HTTP call, but the same thing happens with node-fetch. The tests are being run by the Angular cli via ng test. My Karma config is lengthy but other than having a million different reporters and possible browser configs, is pretty much standard.
This is my onRunComplete method:
self.onRunComplete = function () {
var report = ... ; // logic to generate a JSON object, not relevant
var url = '...'; // the endpoint for the request
try {
console.log('Sending report to ' + url);
axios.post(url, report, {headers: {'Content-Type': 'application/json'}})
.then(function(response) {
console.log('Success!');
console.log(response);
})
.catch(function(error) {
console.log('Failure!');
console.log(error);
});
} catch (err) {
console.log('Error!');
console.log(err);
}
}
At the end of the test run, it writes to console the 'Sending report to...' message, and then immediately ends. The server does not receive the request at all.
I also tried adding explicit blocking using a 'inProgress' boolean flag and while-loop, but that pretty much just leaves the entire test run hanging since it never completes. (Since the request is never made, the 'inProgress' flag is always true and we never hit the then/catch promise handlers or the catch block.)
I have verified that the Axios POST request works by taking the entire contents of the onRunComplete as shown here, putting it in its own JS file, and calling it directly. The report logs as expected. It's only when I call from inside of Karma that it's somehow blocked.
Since Karma's documentation pretty much boils down to "go read how other people did similar things!" I'm having trouble figuring out how to get this to work. Is there a trick to getting an HTTP request to happen inside of a custom reporter? Why does my implementation not work?
Looks like the post request is made asynchronously - that is the request is made and control resumes almost immediately to the method which completes... try instead:
self.onRunComplete = function () {
var report = ... ; // logic to generate a JSON object, not relevant
var url = '...'; // the endpoint for the request
try {
console.log('Sending report to ' + url);
await axios.post(url, report, {headers: {'Content-Type': 'application/json'}})
...
}
}

Can we add Json object to RequestHeader of HTTP GET method

I am developing a new REST-full webservice for our application,I wanted to send the reqest data in requestHeader instead of sending as query param, as my request data is large.
I have My jquery code like below to add json to the request header and call the REST service GET method.
$.ajax({
beforeSend: function(req) {
req.setRequestHeader("test", "{name:mouli, id:918}");},
type : "GET",
data :'',
dataType : "jsonp",
url : 'http://localhost:29801/RestFulJSONExample/rest/jobdesc?callback=?',
success : function(data) {
alert("invoked");
}
});
});
And my GET method in my REST service is like
#GET
#Produces("application/javascript")
public JSONWithPadding getJobDescription(#Context HttpHeaders headers) {
List<String> requestHeader = headers.getRequestHeader("test");
//some logic here.
}
i could able to get the JSON object from the request header which i have added in the jquery request.
My question is ..
Can i follow this approach? is it secure and safe?
If not please tell me the other way?
What appears at the right side of the ":" in a header is mostly free. You have to take into account character set restriction in HTTP, and possible carriage returns in the JSON value (you know, headers of more than one line have a specific syntax). If your JSON examples are relatively simple, then I see no problem in that. It is just another way of organizing the actual value of a header line.