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
Related
I am using Slim 3.1 and able to authenticate correctly i.e. able to generate the token and use it for another POST request. Now I want to parse the request header to extract the user information so I can identify which user have sent the request.
Here is my code to get the token.
$app->post('/login/token', function (Request $request, Response $response,
array $args) {
$input = $request->getParsedBody();
$now = new DateTime();
$future = new DateTime("+10 minutes");
$server = $request->getServerParams();
$jti = (new Base62)->encode(random_bytes(16));
$payload = [
"iat" => $now->getTimeStamp(),
"exp" => $future->getTimeStamp(),
"jti" => $jti,
"sub" => $input['username']
];
$sql = "SELECT * FROM user WHERE User_Name= :username";
$sth = $this->db->prepare($sql);
$sth->bindParam("username", $input['username']);
$sth->execute();
$user = $sth->fetchObject();
// verify email address.
if(!$user) {
return $this->response->withJson(['error' => true, 'message' => 'These credentials do not match our records.']);
}
// verify password.
if (!password_verify($input['password'],$user->User_Password)) {
return $this->response->withJson(['error' => true, 'message' => 'These credentials do not match our records.']);
}
$settings = $this->get('settings'); // get settings array.
//$token = JWT::encode(['User_ID' => $user->User_ID, 'username' => $user->User_Name], $settings['jwt']['secret'], "HS256");
$token = JWT::encode($payload, $settings['jwt']['secret'], "HS256");
return $this->response->withJson(['token' => $token, 'ACL' => $user->User_ACL]);
});
This returns me a token that I send in the following POST request
$app->group('/api', function(\Slim\App $app) {
$app->post('/createuser', function (Request $request, Response $response,
array $args) {
$headerValueArray = $request->getHeader('HTTP_AUTHORIZATION');
return $this->response->withJson(['success' => true, $token]);
});
});
The above POST request gives the following output
{
"success": true,
"0": ["Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MzU4Mjk0OTUsImV4cCI6MTUzNTgzNjY5NSwianRpIjoiMWc5ZFM3dUNLbzl1blRQZzBmYjU2diIsInN1YiI6InN5c2FkbWluIn0.vo3FBPhBkhfA2y7AG-afmjfeEhygIYY7lIaaVNX5i5k"]
}
I need to parse this token to extract the user information to see if its the valid user to perform this operation.In other words, how I can decode the above token.
Any help here will be much appreciated!
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.
I have a form for lead gen, which just get the user information (like name, gender, email) which can be filled out with Facebook. But the problem is, selecting to fill form with FB redirect to index page. How can i set the return page to some other view or action?
public function onAuthSuccess($cliente)
{
// TODO: fb login e retornar dados do perfil para o form
$fb = new Facebook([
'app_id' => MYAPPID,
'app_secret' => MYAPPSECRET
]);
try {
$token = $cliente->getAccessToken()->getToken();
// Returns a `Facebook\FacebookResponse` object
$usuario = $fb->get('/me?fields=email,name,gender,age_range',
$token)->getDecodedBody();
} catch(Exception $e) {
echo 'Error: ' . $e->getMessage();
exit;
}
return ; // actually doesn't matter, it always end redirecting to site/index
}
Auth config:
'auth' => ['class' => yii\authclient\AuthAction::className(),'successCallback' => [$this, 'onAuthSuccess']]
Add successUrl in Auth action config
'successUrl'=>'url'
it is an public property you can override its value in your function also
$this->action->successUrl = "url-with-data";
Note: this is for understanding purpose only, best way to generate dynamic urls would be using urlmanager
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 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