In a word game hosted as Canvas app at Facebook I would like to sell a consumable "1-year VIP status" giving players temporary access to certain areas in the game - by using the Facebook Payments Lite (serverless).
My JavaScript code displays Pay Dialog and then passes signed_request to my PHP-script -
JavaScript code at my Canvas app:
function buyVip() {
var obj = {
method: "pay",
action: "purchaseiap",
product_id: "test1"
};
FB.ui(obj, function(data) {
$.post("/payment-lite.php",
{ signed_request: data.signed_request })
.done(function(data) {
location.reload();
});
});
}
My PHP script /payment-lite.php:
const APP_SECRET = 'XXXXXXX';
$request = parse_signed_request($_POST['signed_request'], APP_SECRET);
error_log(print_r($request, TRUE));
// TODO validate $request and set the user VIP status in the game database
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;
}
$expected_sig = hash_hmac('sha256', $payload, $secret, $raw = TRUE);
if ($sig !== $expected_sig) {
error_log('Bad Signed JSON signature!');
return NULL;
}
return $data;
}
function base64_url_decode($input) {
return base64_decode(strtr($input, '-_', '+/'));
}
In the app Dashboard -> Web Payments I have added a test user and a test product with "Product ID" test1 and the price of EUR 0.01:
Finally I login as test user and press a button in the app calling the buyVip method - causing the Pay Dialog to appear:
Then in the server logs I see the payment.php script being called successfully:
[30-Jul-2017 14:34:20 Europe/Berlin] Array
(
[algorithm] => HMAC-SHA256
[amount] => 0.01
[app_id] => 376218039240910
[currency] => EUR
[issued_at] => 1501418059
[payment_id] => 1084810821649513
[product_id] => test1
[purchase_time] => 1501418057
[purchase_token] => 498440660497153
[quantity] => 1
[status] => completed
)
However when I try the same procedure later, the Pay Dialog appears, but then fails after pressing the Buy button with the error
There Was a Problem Processing Your Payment: Sorry, but we're having
trouble processing your payment. You have not been charged for this
transaction. Please try again.
And in the browser console I see the 1383001 Unknown error code:
{error_code: 1383001, error_message: "There Was a Problem Processing
Your Payment: Sorry…n charged for this transaction. Please try
again."}
What does it mean please, why do first buy requests succeed, but the subsequent fail?
In my app I am of course going to hide the "buy VIP status" button for a year after successful purchase, but still I would like to know, what is happening here.
Also in the future I would like to sell consumable virtual goods like "coins" in my game and then multiple purchases should succeed.
UPDATE:
I have tried to consume the purchase by adding the following code to my payment.php (using APP_ID|APP_SECRET instead of the required user access token):
$post = [
'access_token' => APP_ID . '|' . APP_SECRET,
];
$ch = curl_init('https://graph.facebook.com/498440660497153/consume');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
$response = curl_exec($ch);
curl_close($ch);
error_log(print_r($response, TRUE));
But unfortunately get the error:
{"error":{"message":"Unsupported post request. Object with ID
'498440660497153' does not exist, cannot be loaded due to missing
permissions, or does not support this operation. Please read the Graph
API documentation at
https://developers.facebook.com/docs/graph-api","type":"GraphMethodException","code":100,"fbtrace_id":"HDusTBubydJ"}}
You should consume previous purchase for that user before creating new one with same product_id. This is done to prevent users from buying the same item more than once for non consumable item.
FB.api(
'/' + PURCHASE_TOKEN + '/consume', // Replace the PURCHASE_TOKEN
'post',
{access_token: access_token}, // Replace with a user access token
result => {
console.log('consuming product', productId, 'with purchase token', purchaseToken);
console.log('Result:');
console.log(result);
}
);
https://developers.facebook.com/docs/games_payments/payments_lite#consuming
UPDATE:
If you want to consume purchase via server you can pass access_token to your php script.
$.post("/words/facebook/payment.php", { access_token: access_token })
To get access_token you can use this.
var access_token = '';
FB.getLoginStatus(function(response) {
if (response.status === 'connected') {
access_token = response.authResponse.accessToken;
}
});
I am answering my own question to share the complete source code needed to sell consumable virtual goods via Facebook Payments Lite, based on Alexey Mukhin's helpful reply -
JavaScript code in your Facebook Canvas app (assign to a button-ONCLICK):
function buyItemLite() {
var payDialog = {
method: "pay",
action: "purchaseiap",
product_id: "test1"
};
FB.ui(payDialog, function(payResponse) {
FB.getLoginStatus(function(loginResponse) {
if (loginResponse.status === "connected") {
$.post("/payment-lite.php", {
signed_request: payResponse.signed_request,
access_token: loginResponse.authResponse.accessToken
})
.done(function(consumeResponse) {
location.reload();
});
}
});
});
}
PHP code in the payment-lite.php script hosted at your web server:
const APP_ID = 'replace by your app id';
const APP_SECRET = 'replace by your app secret';
const SIGNED_REQUEST = 'signed_request';
const STATUS = 'status';
const COMPLETED = 'completed';
const PRODUCT_ID = 'product_id';
const PURCHASE_TOKEN = 'purchase_token';
const ACCESS_TOKEN = 'access_token';
const CONSUME_URL = 'https://graph.facebook.com/%d/consume';
$request = parse_signed_request($_REQUEST[SIGNED_REQUEST], APP_SECRET);
error_log('pay dialog request: ' . print_r($request, TRUE));
if ($request[STATUS] === COMPLETED && $request[PRODUCT_ID] === 'test1') {
# perform POST request to consume the purchase_token
$url = sprintf(CONSUME_URL, $request[PURCHASE_TOKEN]);
$fields = array(ACCESS_TOKEN => $_REQUEST[ACCESS_TOKEN]);
$client = curl_init($url);
curl_setopt($client, CURLOPT_RETURNTRANSFER, true);
curl_setopt($client, CURLOPT_POSTFIELDS, $fields);
$response = curl_exec($client);
curl_close($client);
error_log('consume response: ' . print_r($response, TRUE));
# TODO give the player the newly purchased consumable "test1" product
}
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;
}
$expected_sig = hash_hmac('sha256', $payload, $secret, $raw = TRUE);
if ($sig !== $expected_sig) { // or better use hash_equals
error_log('Bad Signed JSON signature!');
return NULL;
}
return $data;
}
function base64_url_decode($input) {
return base64_decode(strtr($input, '-_', '+/'));
}
NOTE: If you happen to have a recent PHP version, then better use hash_equals in the above code, to mitigate timing attacks.
Do not forget to enable Payments Lite in the Facebook Dashboard of your app and also add a "test1" product there:
If you follow the above instructions you will be able to purchase the "test1" item multiple times and the output you will get in the PHP logs will look like:
pay dialog request: Array
(
[algorithm] => HMAC-SHA256
[amount] => 0.01
[app_id] => 376218039240910
[currency] => EUR
[issued_at] => 1501674845
[payment_id] => 1041009052696057
[product_id] => test1
[purchase_time] => 1501674843
[purchase_token] => 499658830375336
[quantity] => 1
[status] => completed
)
consume response: {"success":true}
Finally, I will share below my webhook code for non-lite Facebook Payments, because that is what I actually have ended up using (it handles chargebacks and does not need to mark items consumable after purchases) -
JavaScript code in your Facebook Canvas app (assign to a button-ONCLICK):
function buyItemFull() {
var payDialog = {
method: "pay",
action: "purchaseitem",
product: "https://myserver/test1.html"
};
FB.ui(payDialog, function(data) {
location.reload();
});
}
PHP code in the payment-full.php script hosted at your web server:
const APP_ID = 'replace by your app id';
const APP_SECRET = 'replace by your app secret';
const HUB_MODE = 'hub_mode';
const HUB_CHALLENGE = 'hub_challenge';
const HUB_VERIFY_TOKEN = 'hub_verify_token';
const SUBSCRIBE = 'subscribe';
const ENTRY = 'entry';
const CHANGED_FIELDS = 'changed_fields';
const ID = 'id';
const USER = 'user';
const ACTIONS = 'actions';
const ITEMS = 'items';
const PRODUCT = 'product';
const AMOUNT = 'amount';
# payment status can be initiated, failed, completed
const STATUS = 'status';
const COMPLETED = 'completed';
# possible payment event types are listed below
const TYPE = 'type';
const CHARGE = 'charge';
const CHARGEBACK_REVERSAL = 'chargeback_reversal';
const REFUND = 'refund';
const CHARGEBACK = 'chargeback';
const DECLINE = 'decline';
const GRAPH = 'https://graph.facebook.com/v2.10/%d?access_token=%s|%s&fields=user,actions,items';
const TEST1 = 'https://myserver/test1.html';
# called by Facebook Dashboard when "Test Callback URL" button is pressed
if (isset($_GET[HUB_MODE]) && $_GET[HUB_MODE] === SUBSCRIBE) {
print($_GET[HUB_CHALLENGE]);
exit(0);
}
# called when there is an update on a payment (NOTE: better use hash_equals)
$body = file_get_contents('php://input');
if ('sha1=' . hash_hmac('sha1', $body, APP_SECRET) != $_SERVER['HTTP_X_HUB_SIGNATURE']) {
error_log('payment sig=' . $_SERVER['HTTP_X_HUB_SIGNATURE'] . ' does not match body=' . $body);
exit(1);
}
# find the updated payment id and what has changed: actions or disputes
$update = json_decode($body, TRUE);
error_log('payment update=' . print_r($update, TRUE));
$entry = array_shift($update[ENTRY]);
$payment_id = $entry[ID];
$changed_fields = $entry[CHANGED_FIELDS];
if (!in_array(ACTIONS, $changed_fields)) {
error_log('payment actions has not changed');
exit(0);
}
# fetch the updated payment details: user, actions, items
$graph = sprintf(GRAPH, $payment_id, APP_ID, APP_SECRET);
$payment = json_decode(file_get_contents($graph), TRUE);
error_log('payment details=' . print_r($payment, TRUE));
# find the user id who has paid
$uid = $payment[USER][ID];
# find the last action and its status and type
$actions = $payment[ACTIONS];
$action = array_pop($actions);
$status = $action[STATUS];
$type = $action[TYPE];
$price = $action[AMOUNT];
# find which product was purchased
$items = $payment[ITEMS];
$item = array_pop($items);
$product = $item[PRODUCT];
error_log("payment uid=$uid status=$status type=$type product=$product price=$price");
if ($status != COMPLETED) {
error_log('payment status is not completed');
exit(0);
}
# money has been received, update the player record in the database
if ($type === CHARGE || $type === CHARGEBACK_REVERSAL) {
if ($product === TEST1) {
# TODO give the player the purchased "test1" product
}
} else if ($type === REFUND || $type === CHARGEBACK || $type === DECLINE) {
# TODO take away from the player the "test1" product
}
Do not forget to disable Payments Lite in the Facebook Dashboard of your app and also add the "payment-full.php" webhook there:
Finally add the "test1.html" product file at your web server:
<!DOCTYPE html><html>
<head prefix=
"og: http://ogp.me/ns#
fb: http://ogp.me/ns/fb#
product: http://ogp.me/ns/product#">
<meta property="og:type" content="og:product" />
<meta property="og:title" content="Test1" />
<meta property="og:image" content="https://myserver/icon-50x50.png" />
<meta property="og:description" content="Test1" />
<meta property="og:url" content="https://myserver/test1.html" />
<meta property="product:price:amount" content="0.01"/>
<meta property="product:price:currency" content="EUR"/>
</head>
</html>
There are currently not many Facebook Payment examples to be discovered on the web.
So upvote the question and the answer, if you have found my source code (public domain license) useful, to help other developers to discover it.
Related
I'm trying to set up a purchase with credits on my facebook app.
The FB.ui() function works fine, the Payments Callback URL and the Company info is already set. My php script for the payments callback is just as the facebook developers example.
But the pay dialog does not display the confirmation dialog and the FB.ui() callback returns the error 1383046 and the error_message "Sorry, but we're having trouble processing your payment. You have not been charged for this transaction."
It happens under the payments_get_items method, so I can't test the reminder methods.
I've read a lot of posts about the same error code, but don't know if my problem could be origined in other detail that I'm not attending
This is my php script:
<?php
$app_secret = '***********************';
error_log($_POST['signed_request']);
// Validate request is from Facebook and parse contents for use.
$request = parse_signed_request($_POST['signed_request'], $app_secret);
// Get request type.
$request_type = $_POST['method'];
error_log($request_type, 0);
// Setup response.
$response = '';
if ($request_type == 'payments_get_items') {
// Get order info from Pay Dialog's order_info.
// Assumes order_info is a JSON encoded string.
$order_info = json_decode($request['credits']['order_info'], true);
// Get item id.
$item_id = $order_info['item_id'];
// Simulutates item lookup based on Pay Dialog's order_info.
if ($item_id == 'real_item_id') {
$item = array(
'title' => 'Title of my item',
'description' => 'Description if my item',
// Price must be denominated in credits.
'price' => 5,
'image_url' => 'my image url',
'product_url' => 'my image url',
);
// Construct response.
$response = array(
'content' => array (
0 => $item,
),
'method' => $request_type
);
// Response must be JSON encoded.
$response = json_encode($response);
error_log($response);
}
} else if ($request_type == "payments_status_update") {
// Get order details.
$order_details = json_decode($request['credits']['order_details'], true);
// Determine if this is an earned currency order.
$item_data = json_decode($order_details['items'][0]['data'], true);
$earned_currency_order = (isset($item_data['modified'])) ?
$item_data['modified'] : null;
// Get order status.
$current_order_status = $order_details['status'];
if ($current_order_status == 'placed') {
// Fulfill order based on $order_details unless...
if ($earned_currency_order) {
// Fulfill order based on the information below...
// URL to the application's currency webpage.
$product = $earned_currency_order['product'];
// Title of the application currency webpage.
$product_title = $earned_currency_order['product_title'];
// Amount of application currency to deposit.
$product_amount = $earned_currency_order['product_amount'];
// If the order is settled, the developer will receive this
// amount of credits as payment.
$credits_amount = $earned_currency_order['credits_amount'];
}
$next_order_status = 'settled';
// Construct response.
$response = array(
'content' => array(
'status' => $next_order_status,
'order_id' => $order_details['order_id']
),
'method' => $request_type
);
// Response must be JSON encoded.
$response = json_encode($response);
} else if ($current_order_status == 'disputed') {
// 1. Track disputed item orders.
// 2. Investigate user's dispute and resolve by settling or refunding the order.
// 3. Update the order status asychronously using Graph API.
} else if ($current_order_status == 'refunded') {
// Track refunded item orders initiated by Facebook. No need to respond.
} else if ($current_order_status == 'settled') {
// Verify that the order ID corresponds to a purchase you've fulfilled, then…
// Get order details.
$order_details = json_decode($request['credits']['order_details'], true);
// Construct response.
$response = array(
'content' => array(
'status' => 'settled',
'order_id' => $order_details['order_id']
),
'method' => $request_type
);
// Response must be JSON encoded.
$response = json_encode($response);
} else {
// Track other order statuses.
}
}
// Send response.
echo $response;
// These methods are documented here:
// https://developers.facebook.com/docs/authentication/signed_request/
function parse_signed_request($signed_request, $secret) {
list($encoded_sig, $payload) = explode('.', $signed_request, 2);
// decode the data
$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;
}
error_log( print_r($data, true) );
return $data;
}
function base64_url_decode($input) {
return base64_decode(strtr($input, '-_', '+/'));
}
?>
Looking to the error_log doc, is a fact that callback is called twice when FB.ui() function is invoked (as happens if the pay dialog doesn't receive response). But at every single call, the json object sent as var $response is correct.
My app is hosted on Heroku. The Payments Callback was first on a paid server from amazon. After that I created a free web hosting account, uploaded the script there, and directioned the payment callback url to it, but the error persists.
Is there any other aspect I need to consider???
Thanks in advance!
Note: I ommited the url's and id's from this post, but the callback script has the real information ;)
I'm having a problem with the function of tagging the facebook API, I'll do a summary of the application that I am creating for you to have a better idea of what I need.
I am creating an application where the user selects his friends and the software produces an image with all his friends names plus each ones tags.
I succeed in all of the functions: listing friends, selecting them, generating the image and posting to the timeline. But I can not tag the people, which is an important function to the viral effect of the app.
A strange thing has happened, if I enable Sandbox Mode and select only the ADMINS or TESTERS, the code works. But if I turn off Sandbox mode and select any other friends, nothing happens with the tags.
Below I leave the code I've used.
I used the array_push function to add the IDs to the Facebook 'tags' array.
Facebook Conection:
// INSERE O HEADER
include('includes/header.php');
// CONNECT // CONFIGURAÇÕES
require('facebook_sdk/src/facebook.php');
// CONEXÃO COM O FACEBOOK
$facebook = new Facebook(array(
'appId' => 'XXXXXXXXX,
'secret' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
));
// PEGA SEU ID
$user = $facebook->getUser();
// FUNÇÕES QUE PEGA AS INFOS DO FACEBOOK
if ($user) {
try {
// Proceed knowing you have a logged in user who's authenticated.
$user_profile = $facebook->api('/me?fields=id,name,email,friends,photos,friends.fields(photos)');
$input = $facebook->api('/me?fields=id,name,email,friends,photos,friends.fields(photos)');
} catch (FacebookApiException $e) {
error_log($e);
$user = null;
}
}
// LINK PARA LOGOUT E REDIRECT SE FOR USUARIO
if ($user) {
$logoutUrl = $facebook->getLogoutUrl();
header('Location: passo1.php');
}
?> <div class="bt-face"><img name="bt_face" id="bt_face" onMouseOver="formbaixo_ON('face');" onMouseOut="formbaixo_OFF('face');" src="images/bt-face.png" alt="Conectar no Facebook" /></div>
</div>
<!---------------- PHP / JAVA BUTTON FACEBOOK (SCOPE = PERMISSÕES) ----------------->
<div id="fb-root"></div>
<script type="text/javascript">
function fbLogin() {
FB.login(function(response) {
if (response.session) {
//user is logged in, reload page
window.location.reload(true);
} else {
// user is not logged in
}
}, { perms:'email,manage_friendlists,publish_stream,user_photos,friends_photos'} );
}
</script>
Tagging code:
if ($user) {
try {
// Proceed knowing you have a logged in user who's authenticated.
$access_token = $facebook->getAccessToken();
$timeline = $facebook->api('/me?link');
$timeline_link = $timeline['link'];
$input = $facebook->api('/me?fields=id,name,email,friends,photos,friends.fields(photos)');
//At the time of writing it is necessary to enable upload support in the Facebook SDK, you do this with the line:
$facebook->setFileUploadSupport(true);
$album = $facebook->api(array(
'query' => "SELECT object_id FROM album WHERE owner=me() AND name='Album name' AND can_upload=1",
'method' => 'fql.query',
));
if (isset($album[0]['object_id'])) $album_uid = $album[0]['object_id'];
else
{
foreach ($albums[data] as $album) {
//Test if the current album name is already in facebook
if($album[name] == 'Album name'){
$album_uid = $album[id];
}
}
if(!$album_uid){
}
}
//Upload a photo to album of ID...
$photo_details = array(
'message'=> 'Eu criei um time...'
);
$file = $url_image; //'images/teste2.gif'; //Example image file
$args_tags = array();
// RETIRA BARRAS DOS ARRAYS
$parte = explode("//", $convocados);
$conta = count($parte);
// EXIBE
$espacamento_linha = 20;
$x_convocados = 0;
$y_convocados = 0;
$array_facebook_tag = '';
for($i=0; $i < ($conta-1); $i++) {
$explode_parte = explode("-", $parte[$i]);
$nome = str_replace("_"," ", $explode_parte[0]);
$id = $explode_parte[1];
$x_jogador = $x_convocados;
$y_jogador = $y_convocados + ($i * $espacamento_linha);
array_push($args_tags, array('tag_uid' => $explode_parte[1], 'x' => $x_jogador, 'y' => $y_jogador));
}
$args = array(
'message' => 'Esse é o meu time dos sonhos. Crie o seu time ',
'image' => '#' . realpath($file),
'tags' => $args_tags,
);
$upload_photo = $facebook->api('/'.$album_uid.'/photos', 'post', $args);
} catch (FacebookApiException $e) {
error_log($e);
$user = null;
}
}
Any help will be welcome, Thanks!
I'm trying to open a Pay Dialog and it's not going well. When I set the action to buy_credits or ear_credits it works. But when I set action to buy_item it returns an error of"
1383046 - Sorry, but we're having trouble processing your payment. You have not been charged for this transaction. Please try again.
My Java Code:
<script type="text/javascript">
function buy() {
FB.ui(
{
method: 'pay',
action: 'buy_item',
order_info: { 'item_id': '1a'},
dev_purchase_params: {'oscif': true}
},
js_callback);
}
var js_callback = function (data) {
if (data['order_id']) {
write_callback_data(
"<br><b>Transaction Completed!</b> </br></br>"
+ "Data returned from Facebook: </br>"
+ "Order ID: " + data['order_id'] + "</br>"
+ "Status: " + data['status']);
} else if (data['error_code']) {
// Appropriately alert the user.
write_callback_data(
"<br><b>Transaction Failed!</b> </br></br>"
+ "Error message returned from Facebook:</br>"
+ data['error_code'] + " - "
+ data['error_message']);
} else {
// Appropriately alert the user.
write_callback_data("<br><b>Transaction failed!</b>");
}
};
function write_callback_data(str) {
document.getElementById('fb-ui-return-data').innerHTML = str;
}
</script>
My PHP callback code:
error_reporting(E_ALL);
$app_secret = '********************************';
// Validate request is from Facebook and parse contents for use.
$request = parse_signed_request($_POST['signed_request'], $app_secret);
// Get request type.
// Two types:
// 1. payments_get_items.
// 2. payments_status_update.
$request_type = $_POST['method'];
error_log($request_type, 0);
// Setup response.
$response = '';
if ($request_type == 'payments_get_items') {
// Get order info from Pay Dialog's order_info.
// Assumes order_info is a JSON encoded string.
$order_info = json_decode($request['credits']['order_info'], true);
// Get item id.
$item_id = $order_info['item_id'];
// Simulutates item lookup based on Pay Dialog's order_info.
if ($item_id == '1a') {
$item = array(
'title' => '100 Gold',
'description' => 'Premium Currency.',
// Price must be denominated in credits.
'price' => 5,
'image_url' => '1a.png'
);
// Construct response.
$response = array(
'content' => array(
0 => $item,
),
'method' => "payments_get_items",
);
// Response must be JSON encoded.
$response = json_encode($response);
}
} else if ($request_type == "payments_status_update") {
// Get order details.
$order_details = json_decode($request['credits']['order_details'], true);
// Determine if this is an earned currency order.
$item_data = json_decode($order_details['items'][0]['data'], true);
$earned_currency_order = (isset($item_data['modified'])) ?
$item_data['modified'] : null;
// Get order status.
$current_order_status = $order_details['status'];
if ($current_order_status == 'placed') {
// Fulfill order based on $order_details unless...
if ($earned_currency_order) {
// Fulfill order based on the information below...
// URL to the application's currency webpage.
$product = $earned_currency_order['product'];
// Title of the application currency webpage.
$product_title = $earned_currency_order['product_title'];
// Amount of application currency to deposit.
$product_amount = $earned_currency_order['product_amount'];
// If the order is settled, the developer will receive this
// amount of credits as payment.
$credits_amount = $earned_currency_order['credits_amount'];
}
$next_order_status = 'settled';
// Construct response.
$response = array(
'content' => array(
'status' => $next_order_status,
'order_id' => $order_details['order_id'],
),
'method' => $request_type,
);
// Response must be JSON encoded.
$response = json_encode($response);
} else if ($current_order_status == 'disputed') {
// 1. Track disputed item orders.
// 2. Investigate user's dispute and resolve by settling or refunding the order.
// 3. Update the order status asychronously using Graph API.
} else if ($current_order_status == 'refunded') {
// Track refunded item orders initiated by Facebook. No need to respond.
} else {
// Track other order statuses.
}
}
// Send response.
error_log(print_r($response, true));
echo $response;
// These methods are documented here:
// https://developers.facebook.com/docs/authentication/signed_request/
function parse_signed_request($signed_request, $secret) {
list($encoded_sig, $payload) = explode('.', $signed_request, 2);
// decode the data
$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;
}
function base64_url_decode($input) {
return base64_decode(strtr($input, '-_', '+/'));
}
I have error_log calls in the script and it seems when I press the buy button it calls the service twice. Calling the same method: payments_get_items. The response data looks like it is properly formatted. Here is the output of the log:
payments_get_items
{"content":[{"title":"100 Gold","description":"Premium Currency.","price":5,"image_url":"1a.png"}],"method":"payments_get_items"}
payments_get_items
{"content":[{"title":"100 Gold","description":"Premium Currency.","price":5,"image_url":"1a.png"}],"method":"payments_get_items"}
This is almost certainly a problem with the response your callback URL is returning to Facebook.
The reason you see two entries is that Facebook will try twice to get a (valid) response before failing, and giving the user the 'Sorry, but we're having trouble processing your payment...' error message.
Check the HTTP error codes and the body of the response you're giving to Facebook and make sure it's valid
I am testing in app purchase with MKStoreKit.
I'm getting response's status 21002 and wonder why.
Do I need to set up a certificate or something to talk to apple server?
Below is the php code that MKStoreKit uses
<?php
$devmode = TRUE; // change this to FALSE after testing in sandbox
$receiptdata = $_POST['receiptdata'];
$udid = $_POST['udid'];
if($devmode)
{
$appleURL = "https://sandbox.itunes.apple.com/verifyReceipt";
}
else
{
$appleURL = "https://buy.itunes.apple.com/verifyReceipt";
}
$receipt = json_encode(array("receipt-data" => $receiptdata));
$response_json = do_post_request($appleURL, $receipt);
$response = json_decode($response_json);
file_put_contents('php://stderr', print_r($response->{'status'}, true));
file_put_contents('php://stderr', print_r($udid, true));
if($response->{'status'} == 0)
{
file_put_contents('php://stderr', print_r("yes", true));
error_log('udid: %s', $udid);
error_log('quantity: %d', $response->{'receipt'}->quantity);
echo ('YES');
}
else
{
echo ('NO');
}
function do_post_request($url, $data, $optional_headers = null)
{
$params = array('http' => array(
'method' => 'POST',
'content' => $data
));
if ($optional_headers !== null) {
$params['http']['header'] = $optional_headers;
}
$ctx = stream_context_create($params);
$fp = #fopen($url, 'rb', false, $ctx);
if (!$fp) {
throw new Exception("Problem with $url, $php_errormsg");
}
$response = #stream_get_contents($fp);
if ($response === false) {
throw new Exception("Problem reading data from $url, $php_errormsg");
}
return $response;
}
?>
Please check Verify Purchase
MKStore Kit has a bug with sending receiptdata to server
You should base64 encode receiptData not asciiStringEncoding.
Used the following link's code to base64 and I get status 0.
Verify receipt for in App purchase
I have the following script which works, i.e. it goes to the facebook login page if the user is not already logged in, and asks them if they are ok with the app to post messages on their wall:
<?php
require 'facebook.php';
$facebook = new Facebook(array(
'appId' => 'removed for security reasons',
'secret' => 'removed for security reasons',
'cookie' => true,
));
$session = $facebook->getSession();
if ($session) {
if (isset($_GET[id])) {
$post = $facebook->api("/" . $_GET['id'] . "/feed", "POST", array('message' => 'Hello!'));
echo 'A message has been posted on your friends wall';
} else {
$friends = $facebook->api('/me/friends');
foreach ($friends as $key=>$value) {
echo 'You have ' . count($value) . ' friends<br />';
foreach ($value as $fkey=>$fvalue) {
echo 'friend id = ' . $fvalue[id] . ' - friend name = ' . $fvalue[name] . ' - post message<br />';
}
}
}
} else {
$loginUrl = $facebook->getLoginUrl(array(
'req_perms' => 'publish_stream',
'next' => 'http://'.$_SERVER['SERVER_NAME'].'/stage1.php',
'cancel_url' => 'http://'.$_SERVER['SERVER_NAME'].'/cancel.php',
));
header('Location: '.$loginUrl);
}
?>
How can this be improved so it does not ask for extended permissions in the start. It should only ask for basic permissions to display the friends list, and only ask for extended permissions if the user clicks on the friend to post a message.
Here's a rewrite of your code, with what I think are best practices:
<?php
require 'facebook.php';
$facebook = new Facebook(array(
'appId' => 'removed for security reasons',
'secret' => 'removed for security reasons',
'cookie' => true,
));
$session = $facebook->getSession();
// Prepare the login url with the right permission
$loginUrl = $facebook->getLoginUrl(array(
'req_perms' => 'publish_stream',
'next' => 'http://'.$_SERVER['SERVER_NAME'].'/stage1.php',
'cancel_url' => 'http://'.$_SERVER['SERVER_NAME'].'/cancel.php',
));
if ($session) {
try {
// Before processing the request
// check if we got the right permission
$perms = $facebook->api(array(
"method" => "fql.query",
"query" => "SELECT publish_stream FROM permissions WHERE uid=me()"
));
if($perms[0]['publish_stream']==='1') {
// We have the right permission
if (isset($_GET['id'])) {
// A small security measure
$id = (int) $_GET['id'];
$post = $facebook->api("/$id/feed", "POST", array('message' => 'Hello!'));
echo 'A message has been posted on your friends wall';
} else {
$friends = $facebook->api(array(
"method" => "fql.query",
"query" => "SELECT uid,name FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1=me())"
));
foreach($friends as $friend)
echo "friend id = {$friend['uid']} - friend name = {$friend['name']} - post message<br />";
}
} else {
// We don't have the right permission
header('Location: '.$loginUrl);
}
} catch (FacebookApiException $e) {
error_log($e);
}
} else {
header('Location: '.$loginUrl);
}
?>
How to check for a permission is explained here. Also I've added comments to save writing an explanation.
Quickly, there is something I want to point out regarding the following block of code:
foreach ($friends as $key=>$value) {
echo 'You have ' . count($value) . ' friends<br />';
foreach ($value as $fkey=>$fvalue) {
echo 'friend id = ' . $fvalue[id] . ' - friend name = ' . $fvalue[name] . ' - post message<br />';
}
}
Your 1st foreach loop is really misleading and not good practice at all. The Graph API isn't overly consistent in how it presents data, but the reason you are doing the foreach is to deal with the data key in the JSON object that is returned. This is generally a bad idea, because that data key is typically present along with other keys (like paging). Instead, I would check to see that $friends['data'] is not empty, and then re-assign the $friends array like so: $friends = $friends['data'];.
Example:
if (!empty($friends['data']))
{
$friends = $friends['data'];
}
else
{
$friends = array();
}
now, for your question.
You mentioned that you don't want to over-ask for permissions. That's a great thing to want, but the problem with it is that Facebook doesn't make it exceedingly easy to check for which permissions you do have or do not have. There is an FQL table that allows you check if your user has a certain set of permissions, but this table doesn't get updated with any kind of urgency. If you obtain extra permissions from a user (or if a user retracts permissions) and you then check this FQL table for the status of the permission, it can (and probably will) read the incorrect value and you will get a false positive.
You have three options to deal with this, that I can think of right off the top of my head.
Continue on your stage1.php code, as you are - there's nothing wrong with the way you're obtaining the installation and the session for the user there. You change page 2 to redirect your user through the OAuth endpoint requesting the publish-stream permission every time the user loads the page. The OAuth endpoint will not re-prompt the user to install, and will send them on their way.
The cons with this approach is, every request to post to a friends' wall turns into 3 requests.
The initial page load
The OAuth redirect / load
The redirect from OAuth back to your application
This approach also requires that you add a flag to your next key in your loginURL, which you can look for to make sure the user went through the OAuth endpoint, otherwise you're going to get an infinite redirect error.
Utilize the FB Javascript SDK to check for your users' current set of permissions. To do this, you'll utilize the FB.getLoginStatus method.
Example:
<div id="fb-root"></div>
<script src="http://code.jquery.com/jquery-1.5.2.min.js"
type="text/javascript" charset="utf-8">
</script>
<script src="http://connect.facebook.net/en_US/all.js"
type="text/javascript" charset="utf-8">
</script>
<script type="text/javascript">
(function($)
{
FB.init({
appId: '<?= FB_APP_ID; ?>',
cookie: true,
status: true,
xfbml: true
});
$('a').click(function(event)
{
var self = this;
event.preventDefault();
FB.getLoginStatus(function(session)
{
if (session.perms.match(/\"publish_stream\"/))
{
/* This user has publish stream, so we don't need
* to ask again
**/
window.location = $(self).attr('href');
}
else
{
/* This user does not have publish stream, so we need
* to ask.
**/
FB.login(function(response)
{
if (response && response.perms.match(/publish_stream/))
{
/* We now have publish stream access! */
window.location = $(self).attr('href');
}
}, {
perms: 'publish_stream'
});
}
})
return false;
})
})(jQuery);
Don't utilize any extended permissions, use the Javascript SDK (again) and give the user a publish-dialog for each user they would like to publish on the wall of. This is a relatively easy thing to do, also.
Example:
given your links for users:
Friend 1
Friend 2
Friend 3
You can do something like this:
<div id="fb-root"></div>
<script src="http://code.jquery.com/jquery-1.5.2.min.js"
type="text/javascript" charset="utf-8">
</script>
<script src="http://connect.facebook.net/en_US/all.js"
type="text/javascript" charset="utf-8">
</script>
<script type="text/javascript">
(function($)
{
$('a').click(function(event)
{
var user_id = $(this).data('id');
FB.ui({
method: 'feed',
message: 'Hello!',
to: user_id
}, function(response)
{
//this gets called whether it was successful, or not.
})
});
})(jQuery);