Facebook payment platform 64bit order_id on 32bit server - php

I'm currently running into a massive wall due to a problem i cannot seem to solve.
The problem is that, when you issue a payment through the Facebook payment platform (facebook javascript sdk), it sends data to your callback page, which should handle the payment on the background.
This all works decent, but there is 1 problem: The order ID that facebook uses is a 64bit ID, and my server is a 32bits server, thus it loses precision on the ID when it gets saved to a variable in the callback page. This ultimately results in not being able to get a proper order_ID in the end, because it cannot save the ID.
The issue has been described on several pages on this forum, for example:
facebook credit callback, order_id changes format changes after moving to a new server
and
PHP: Converting a 64bit integer to string
Yet, on both pages there is no solution to the problem, and i cannot seem to fix this myself.
I have tried to convert the json data that facebook sends to my callback page into string data instead of an array with integers (this happens in the basic code provided by facebook), but i just cannot get this to work.
Seeing that others have overcome this problem (without having to migrate everything to a 64bits server), i am wondering how.
Is anyone able to shine a light on this subject?
Edit:
I have tried converting to string, the standard facebook code that gets called to decode the json data (code provided by facebook):
$request = parse_signed_request($_POST['signed_request'], $app_secret);
This calls the function parse_signed_request, which does:
function parse_signed_request($signed_request, $secret) {
list($encoded_sig, $payload) = explode('.', $signed_request, 2);
$sig = base64_url_decode($encoded_sig);
$data = json_decode(base64_url_decode($payload), true);
if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') {
error_log('Unknown algorithm. Expected HMAC-SHA256');
return null;
}
// check sig
$expected_sig = hash_hmac('sha256', $payload, $secret, $raw = true);
if ($sig !== $expected_sig) {
error_log('Bad Signed JSON signature!');
return null;
}
return $data;
}
This function decodes the encrypted json data from facebook (using the app secret) and should decode the json data to a PHP array.
That function uses the following function (the exact:
function base64_url_decode($input) {
return base64_decode(strtr($input, '-_', '+/'));
}
Now, the above code results in the order ID not being saved properly, and it loses its precision, resulting in an id like: 4.8567130814993E+14
I have tried to use the following function to somehow decode the json data into a string (so the 64bit integer ID does not lose its precision), but to no avail:
function largeint($rawjson) {
$rawjson = substr($rawjson, 1, -1);
$rawjson = explode(',' , $rawjson);
array_walk($rawjson, 'strfun');
$rawjson = implode(',', $rawjson);
$rawjson = '{' . $rawjson . '}';
$json = json_decode($rawjson);
return $json;
}
function strfun(&$entry, $key) {
$data = explode(':', $entry);
if (FALSE === strpos($data[1], '"')) {
$data[1] = '"' . $data[1] . '"';
$entry = implode(':', $data);
}
}
Edit (Eugenes answer):
If i were to try modifying the JSON data before i use json_decode to make it a php variable, i should be using the preg_replace function?
Below is an example of the initial JSON data that gets sent to the callback page to initiate the payment process.
Here you can already see what the problem is (this is after using json_decode, the id and other data lose their precision). The ID's are modified to not reflect any real data.
If you compare the buyer ID on the top and user id on the bottom, you can see precision is lost.
Array
(
[algorithm] => HMAC-SHA256
[credits] => Array
(
[buyer] => 1.0055555551318E+14
[receiver] => 1.0055555551318E+14
[order_id] => 5.2555555501665E+14
[order_info] => {"item_id":"77"}
[test_mode] => 1
)
[expires] => 1358456400
[issued_at] => 1358452270
[oauth_token] => AAAH4s2ZCCEMkBAPiGSNsmj98HNdTandalotmoredata
[user] => Array
(
[country] => nl
[locale] => nl_NL
[age] => Array
(
[min] => 21
)
)
[user_id] => 100555555513181
)
Edit #3:
I have tried the following to make all the integers in the JSON data seen as strings, but that results in an error from the facebook platform. It does however change the integers to a string, so i do not lose precision (too bad nothing else works now xD)
preg_replace('/([^\\\])":([0-9]{10,})(,|})/', '$1":"$2"$3', $a)

Which version of PHP are you running?
If you are running a version of PHP that supports the JSON "JSON_BIGINT_AS_STRING" option, that may be your answer. You may have to modify their library wherever json_decode is being used to add that option. See http://php.net/manual/en/function.json-decode.php
If your PHP version does not support JSON_BIGINT_AS_STRING, then your options are limited to:
The hacky option: Do some kind of regex operation on the JSON string as it comes back from the FB API and wrap that big ints in double-quotes, so that they decode as a string and not a big int.
The ideal option: Bite the bullet and migrate to a 64 bit environment. It will save you from a lot of headaches in the long run.

Related

Passing a variable to S3 get_object_url, unexpected character encoding

Not an experienced developer and using CodeIgniter for the first time. I'm trying to grab a signed URL for a given MP3 filename stored in S3. This is currently working with the exception of files that contain brackets.
Relevant controller code:
function index ($streamfile) {
// Load S3 client
$this->load->spark('amazon-sdk');
$s3 = $this->awslib->get_s3();
// Define request parameters
$s3bucket = $userdata['s3bucket']; // defined elsewhere
$streamfiletest = ($string)'Crazy_(Remix).mp3';
// Request signed URL
$url = $s3->get_object_url($s3bucket, ***EITHER $streamfiletest or $streamfile***, '5 minutes');
// Fetch status code
$http = new CFRequest($url);
$http->add_header('Content-Type', '');
$http->send_request(true);
$code = $http->get_response_code();
$headers = $http->get_response_header();
// Load the view
$data['filename'] = $url;
$data['debug'] = array(
'file1' => $streamfile,
'file2' => $streamfiletest,
'signed_url' => $url,
'code' => $code,
'headers' => $headers
);
$this->load->view('play', $data);
Relevant view code:
<?php if (isset($debug)) {
echo "DEBUGS:";
echo '<pre>' . print_r($debug, TRUE) . '</pre>';
} ?>
As you can see I either pass $streamfile or $streamfiletest. In the debug I can confirm that both variables are the same string.
When passing $streamfile to the URL request, the URL in the response is incorrect:
DEBUGS:
[file1] => Crazy_(Remix).mp3
[file2] => Crazy_(Remix).mp3
[signed_url] => http://s3-...(removed)/Crazy_%26%2340%3BRemix%26%2341%3B.mp3?AWSAccessKey...
[code] => 404
You can see that the brackets have been strangely encoded %26%2340%3B and therefore I can't find the file in S3.
When passing $streamfiletest however, the response is fine:
DEBUGS:
[file1] => Crazy_(Remix).mp3
[file2] => Crazy_(Remix).mp3
[signed_url] => http://s3-...(removed)/Crazy_%28Remix%29.mp3?AWSAccessKey...
[code] => 200
The brackets are encoded correctly in the signed URL an I get a HTTP 200 from S3.
Any ideas what could be causing this?
In the debug I can confirm that both variables are the same string
Actually, not quite.
If you look closely, it becomes apparent what the url escaped values must mean:
%26%2340%3B %26%2341%3B
& # 40 ; & # 41 ;
Those are numeric html character codes that the browser will display as ( and ) but it does not in fact mean that the two strings have identical content. They only appear to.
The solution, of course, depends on how they are getting transformed that way, and either not doing that, or decoding the numeric character codes.
Try doing the following to decode the url encoded brackets
$data['filename'] = urldecode($url);
This should return the string to its expected format ie with brackets

Problems manipulating JSON data in PHP

I'm not that handy with JSON so here goes. I'm receiving Amazon SNS notifications for bouncing email addresses to a listener (in PHP 5.5) which does:
$post = #file_get_contents("php://input");
$object = json_decode($post, true);
This gives me:
Type => Notification
MessageId => #####
TopicArn => #####
Message => {
"notificationType":"Bounce",
"bounce": {
"bounceSubType":"General",
"bounceType":"Permanent",
"bouncedRecipients":[{"status":"5.3.0","action":"failed","diagnosticCode":"smtp; 554 delivery error: dd This user doesn't have a yahoo.com account (testuser#yahoo.com) [0] - mta1217.mail.bf1.yahoo.com","emailAddress":"testuser#yahoo.com"}],
"reportingMTA":"dsn; ######",
"timestamp":"2014-10-27T16:37:42.136Z",
"feedbackId":"######"
},
"mail": {
"timestamp":"2014-10-27T16:37:40.000Z",
"source":"myemail#mydomain.com",
"messageId":"######",
"destination":["testuser#yahoo.com"]
}
}
I was expecting an associative array all the way down but instead it's an array only at the top level and with JSON strings inside. I've tried everything I can think of, including json_decoding further parts of the array, but I'm struggling to access the data in a simple way. What I need is the "destination" email address which should be in $object['Message']['mail']['destination'][0].
Can anyone point out what I'm doing wrong here? Thanks.
It looks like $object['Message'] is also json encoded. Perhaps because it's using some generic container format for service call results. Try this
$post = #file_get_contents("php://input");
$object = json_decode($post, true);
//Message contains a json string
$object['Message'] = json_decode($object['Message'], true);
//Then access the structure using array notation
echo $object['Message']['mail']['destination'][0];

Getting currency conversion data from Yahooapis now that iGoogle is gone

Up until yesterday I had a perfectly working budget organizer site/app working with iGoogle.
Through PHP, using the following little line
file_get_contents('http://www.google.com/ig/calculator?hl=en&q=1usd=?eur');
and similar I was able to get all I needed.
As of today, this is no longer working. When I looked into the issue, what has happened is that Google has retired iGoogle. Bummer!
Anyway, I was looking around elsewhere but I can't find anything that fits my needs. I would REALLY love to just fix it and get it running again by just switching this one line of code (i.e. changing the Google address with the address of some other currency API available) but it seems like none does.
The API from rate-exchange.appspot.com seems like it could be a iGoogle analog but, alas, it never works. I keep getting an "Over Quota" message.
(Here comes an initial question: anybody out there know of a simple, reliable, iGoogle-sort API?)
So I guess the natural thing would be to the Yahoo YQL feature (at least I suppose it is as reliable).
Yahoo's queries look like this:
http://query.yahooapis.com/v1/public/yql?q=select * from yahoo.finance.xchange where pair in ("USDEUR", "USDJPY", "USDBGN")&env=store://datatables.org/alltableswithkeys
What I really can't figure out is how to parse this data. It outputs an XML.
What I used to have is this:
function exchange($inputAmount,$inputCurrency,$outputCurrency) {
$exchange = file_get_contents('http://www.google.com/ig/calculator?hl=en&q='.$inputAmount.$inputCurrency.'=?'.$outputCurrency);
$exchange = explode('"', $exchange);
$exchange = explode('.', $exchange['3']);
$exchange[0] = str_replace(" ", "",preg_replace('/\D/', '', $exchange[0]));
if(isset($exchange[1])){
$exchange[1] = str_replace(" ", "",preg_replace('/\D/', '', $exchange[1]));
$exchange = $exchange[0].".".$exchange[1];
} else{
$exchange = $exchange[0];
}
return $exchange;
}
So the user was able to get the exchange rate from an input currency such as "USD" and an output currency such as "EUR" on a specific amount of money. As I said, this was working swimmingly up until yesterday night.
Any ideas?
Never mind! Solved it!
For anyone interested, here's what I did to get my code to work (with the least chnges possible) with the Yahoo YQL:
// ** GET EXCHANGE INFO FROM YAHOO YQL ** //
$url = 'http://query.yahooapis.com/v1/public/yql?q=select * from yahoo.finance.xchange where pair in ("USDEUR", "EURUSD")&env=store://datatables.org/alltableswithkeys'; //<-- Get the YQL info from Yahoo (here I'm only interested in converting from USD to EUR and vice-versa; you should add all conversion pairs you need).
$xml = simplexml_load_file($url) or die("Exchange feed not loading!"); //<-- Load the XML file into PHP variable.
$exchange = array(); //<-- Build an array to hold the data we need.
for($i=0; $i<2; $i++): //<-- For loop to get data specific to each exchange pair (you should change 2 to the actual amount of pairs you're querying for).
$name = (string)$xml->results->rate[$i]->Name; //<-- Get the name of the pair and turn it into a string (this looks like this: "USD to EUR").
$rate = (string)$xml->results->rate[$i]->Rate; //<-- Do the same for the actual rate resulting from the conversion.
$exchange[$name] = $rate; //<-- Put the data pairs into the array.
endfor; //<-- End for loop. :)
// ** WORK WITH EXCHANGE INFO ** //
$toeur = array( //<-- Create new array specific for conversion to one of the units needed.
'usd' => $exchange['USD to EUR'], //<-- Create an array key for each unit used. In this case, in order to get the conversion of USD to EUR I ask for it from my $exchange array with the pair Name.
'eur' => 1); //<-- The way I coded the app, I found it more practical to also create a conversion for the unit into itself and simply use a 1, which translates into "do not convert"
$tousd = array(
'eur' => $exchange['EUR to USD'],
'usd' => 1);
This is basically all you need to get all the exchange info you want. After that, you use it all something like this:
amount*$toxxx['coin'];
So, say I wanted to know how many Euro is 100 USD right now:
100*$toeur['usd'];
Piece of cake!
Still a very useful solution by QuestionerNo27. Since early 2015, however, Yahoo YQL apparently slightly changed the XML output of their api. 'Name' now no longer translates into a string like 'USD to EUR', but to 'USD/EUR' and should in the code above be referenced this way:
$toeur = array(
'usd' => $exchange['USD/EUR']
instead of
$toeur = array(
'usd' => $exchange['USD to EUR']
and in a similar fashion for other currency conversions.
I created a routine to convert the currency based on #QuestionerNo27 http://jamhubsoftware.com/geoip/currencyconvertor.php?fromcur=USD&tocur=EUR&amount=1 you can consume this
<?php
$fromcur = $_GET['fromcur'];
$tocur = $_GET['tocur'];
$amt = $_GET['amount'];
// ** GET EXCHANGE INFO FROM YAHOO YQL ** //
$url = 'http://query.yahooapis.com/v1/public/yql?q=select * from yahoo.finance.xchange where pair in ("'.$fromcur.$tocur.'")&env=store://datatables.org/alltableswithkeys'; //<-- Get the YQL info from Yahoo (here I'm only interested in converting from USD to EUR and vice-versa; you should add all conversion pairs you need).
$xml = simplexml_load_file($url) or die("Exchange feed not loading!"); //<-- Load the XML file into PHP variable.
$exchange = array(); //<-- Build an array to hold the data we need.
for($i=0; $i<2; $i++): //<-- For loop to get data specific to each exchange pair (you should change 2 to the actual amount of pairs you're querying for).
$name = (string)$xml->results->rate[$i]->Name; //<-- Get the name of the pair and turn it into a string (this looks like this: "USD to EUR").
$rate = (string)$xml->results->rate[$i]->Rate; //<-- Do the same for the actual rate resulting from the conversion.
$exchange[$name] = $rate; //<-- Put the data pairs into the array.
endfor; //<-- End for loop. :)
// ** WORK WITH EXCHANGE INFO ** //
$conv = $fromcur . '/' . $tocur;
$toeur = array( //<-- Create new array specific for conversion to one of the units needed.
$tocur => $amt*$exchange[$conv], //<-- Create an array key for each unit used. In this case, in order to get the conversion of USD to EUR I ask for it from my $exchange array with the pair Name.
$fromcur => $amt,
"ex_amt" =>$amt*$exchange[$conv]); //<-- The way I coded the app, I found it more practical to also create a conversion for the unit into itself and simply use a 1, which translates into "do not convert"
echo json_encode($toeur);
?>

Read long value from webservice using SoapClient

I'm writing a soap consumer in PHP for a ws written in Java (Jax ws). The webservice exports a function listRooms() that returns an array of the complex data type Room which contains an id (64 bit long) and a description (string). Now whenever I consume the webservice using SoapClient, the id is converted to float (as there are no 64 bit integers in PHP) and I want to avoid it. As I will need the room id to consume other web services I would rather avoid this implicit conversion to float, keeping it in a string.
Does anyone know how to solve this problem?
This might help:
The long overflows because ext/soap maps it to an int, and you're on a 32bit arch. You can easily fix that problem by using a custom type mapper to override the internal handling of {http://www.w3.org/2001/XMLSchema }long:
function to_long_xml($longVal) {
return '<long>' . $longVal . '</long>';
}
function from_long_xml($xmlFragmentString) {
return (string)strip_tags($xmlFragmentString);
}
$client = new SoapClient('http://acme.com/products.wsdl', array(
'typemap' => array(
array(
'type_ns' => 'http://www.w3.org/2001/XMLSchema',
'type_name' => 'long',
'to_xml' => 'to_long_xml',
'from_xml' => 'from_long_xml',
),
),
));
Also check to see exactly what you get back from the SOAP call, as per the manual add 'trace' and use getLastRequest:
<?php
$client = SoapClient("some.wsdl", array('trace' => 1));
$result = $client->SomeFunction();
echo "REQUEST:\n" . $client->__getLastRequest() . "\n";
?>
Other way to do it, is just using the float() function before the data to sent as a long type.
In the example below I'm use a stdclass object to sent as a parameter:
<?php
if ($index == "Your_longtype_Field"){
$a->$index = (float) $value;
} else {
$a->$index = $value;
}
?>

OpenID check_authentication not working

I'm trying to get a check_authentication response working, but so far, all consumers reject it and say that my server denied check_authentication.
This is the GET and POST data that my server file receives:
$_GET:
Array
(
[mode] => profile
[username] => hachque
[domain] => roket-enterprises.com
)
$_POST:
Array
(
[openid_assoc_handle] => {HMAC-SHA1}{4b00d7b2}{vo1FEQ==}
[openid_identity] => http://www.roket-enterprises.com/openaccount/openid:hachque
[openid_mode] => check_authentication
[openid_response_nonce] => 2009-11-16T04:40:18Zrrz8R4
[openid_return_to] => http://openiddirectory.com:80/openidauth/id/c/finish_auth.php?nonce=adCevd6T
[openid_sig] => SgFE5iT9IGd5EftkrZ72mgCHiLk=
[openid_signed] => assoc_handle,identity,mode,response_nonce,return_to,signed,sreg.email,sreg.fullname,sreg.nickname
[openid_sreg_email] => jrhodes#roket-enterprises.com
[openid_sreg_fullname] => James Rhodes
[openid_sreg_nickname] => jrhodes
)
This is the header reponse that I am outputting (contains POST data as it was explained to me on IRC that sending the key-values as headers shouldn't be done to the consumer server EDIT: Come to think of it, it doesn't make much sense RESPONDING with POST data. Maybe some here can explain the whole process of check_authentication clearly).
Content-Type: text/plain;
Content-Length: 675;
openid.mode=id_res&openid.assoc_handle=%7BHMAC-SHA1%7D%7B4b00d7b2%7D%7Bvo1FEQ%3D%3D%7D&openid.identity=http%3A%2F%2Fwww.roket-enterprises.com%2Fopenaccount%2Fopenid%3Ahachque&openid.response_nonce=2009-11-16T04%3A40%3A18Zrrz8R4&openid.return_to=http%3A%2F%2Fopeniddirectory.com%3A80%2Fopenidauth%2Fid%2Fc%2Ffinish_auth.php%3Fnonce%3DadCevd6T&openid.signed=assoc_handle%2Cidentity%2Cmode%2Cresponse_nonce%2Creturn_to%2Csigned%2Csreg.email%2Csreg.fullname%2Csreg.nickname&openid.sreg_email=jrhodes%40roket-enterprises.com&openid.sreg_fullname=James+Rhodes&openid.sreg_nickname=jrhodes&openid.sig=MGVhMmQ1Mzg4ZWFlMWY1OWVlYjlmZmY0Njc3OTc5YWIzMjM3NGFjMQ%3D%3D&openid.is_valid=true;
This is the PHP code that my file is using to handle check_authentication (remember that PHP turns all . characters into _ for $_GET and $_POST variables since they aren't valid character in PHP array keys):
// Retrieve the OpenID information from the $_REQUEST data
// I'm not sure whether it's possible that this data might
// come in on the $_GET parameter instead of $_POST, so that's
// what it uses $_REQUEST.
$assoc_handle = $_REQUEST['openid_assoc_handle'];
$sig = $_REQUEST['openid_sig'];
$signed = $_REQUEST['openid_signed'];
// The method for returning data is via the headers outputted
// by the webserver. Create an array that stores the headers
// to be returned.
$keys = array(
'openid.mode' => 'id_res',
'openid.assoc_handle' => $_REQUEST['openid_assoc_handle'],
'openid.identity' => $_REQUEST['openid_identity'],
'openid.response_nonce' => $_REQUEST['openid_response_nonce'],
'openid.return_to' => $_REQUEST['openid_return_to'],
'openid.signed' => $_REQUEST['openid_signed'],
'openid.sreg_email' => $_REQUEST['openid_sreg_email'],
'openid.sreg_fullname' => $_REQUEST['openid_sreg_fullname'],
'openid.sreg_nickname' => $_REQUEST['openid_sreg_nickname']
//'openid_mode' => 'id_res'
);
// The server may request that we invalidate the user's session
// via $_REQUEST['openid_invalidate_handle']. In this case we
// will clear the session data (you may need to change this
// depending on how you implement the session). After doing so
// we continue and tell the server we did via a variable
if (strlen($_REQUEST['openid_invalidate_handle']) > 0)
{
// Reset the session
session_unset();
session_name('openid_server');
session_start();
// Set the header we need to return
$keys['openid.invalidate_handle'] = $_REQUEST['openid_invalidate_handle'];
}
// We need to validate the signature now. This constructs a token_contents
// for signing the data. The signing key is returned as openid.sig
// and is generated with base64(HMAC(secret(assoc_handle), token_contents)
$token_contents = '';
foreach (explode(',', $signed) as $param) {
$post = preg_replace('/\./', '_', $param);
$token_contents .= sprintf("%s:%s\n", $param, $_REQUEST['openid_' . $post]);
}
// Generate our openid.sig and add it to the list of keys to
// return.
$keys['openid.sig'] = base64_encode(hash_hmac('sha1',$token_contents,$assoc_handle));
// Add the data that we are sharing (via SReg) to the headers.
// For now this is fixed data (see action_authorization.php).
//$keys["sreg.fullname"] = 'James Rhodes';
//$keys["sreg.nickname"] = 'jrhodes';
//$keys["sreg.email"] = 'jrhodes#roket-enterprises.com';
// Just accept the request for now..
// phpMyID does some kind of secret-shared-key thing
// here to determine whether it is valid. I'm not
// quite sure how that process works yet, so we are just
// going to say go ahead.
$keys["openid.is_valid"] = "true";
// We need to format the $keys array into POST format
$keys_post = "";
$keys_post_first = true;
foreach ($keys as $name => $value)
{
if ($keys_post_first)
$keys_post_first = false;
else
$keys_post .= "&";
$keys_post .= urlencode($name) . "=" . urlencode($value);
}
// Now output the POST data
header('Content-Type: application/x-www-form-urlencoded');
header('Content-Length: ' . strlen($keys_post));
header($keys_post);
Can anyone help me with my problem? I've been trying to get this working for months and I can't get a straight answer on how this stage of OpenID authentication is meant to work.
First of all, although PHP transforms periods to underscores in parameter names, be sure you're sending periods and not underscores.
Secondly, your check_authentication response should only have three parameters, but you have six. Check the spec and fix up your response and see if that helps.
Andrew Arnott,you're wrong!
documentation from openid.net:
11.4.2.1. Request Parameters
openid.mode
Value: "check_authentication"
Exact copies of all fields from the authentication response, except for "openid.mode".
may be more than three fields!
I had a similar issue. In my case, the client (relying party) failed to resolve the name of the OpenId provider to the correct ip. Although this is unlikely to be the case, please check name resolution on your relying server.

Categories