2017-07-30 93 views
0

在一个文字游戏在Facebook上作为Canvas应用程序托管我想卖一个消耗品“1年VIP身份”,让玩家临时访问某些区域游戏 - 通过使用Facebook Payments Lite (serverless)付款精简版(无服务器):第一次购买的作品,但第二次总是失败

我的JavaScript代码显示收费对话框然后通过signed_request到我的PHP脚本 -

在我的画布应用程序的JavaScript代码:

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(); 
       }); 
     }); 
} 

我的PHP脚本/支付,精简版.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, '-_', '+/')); 
} 

在应用仪表盘 - >网络付款我添加了一个测试用户和测试产品,“产品ID” test1和欧元0.01的价格:

dashboard

最后我登录为测试用户和按在应用程序调用buyVip方法的按钮 - 导致收费对话框出现:

pay dialog

然后在服务器日志的I看到payment.php脚本成功地叫:

[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 
) 

然而,当我尝试后,同样的程序,收费对话框出现,但随后的错误

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.

购买按钮后失败

error message

并在浏览器控制台我看1383001 Unknown错误代码:

{error_code: 1383001, error_message: "There Was a Problem Processing Your Payment: Sorry…n charged for this transaction. Please try again."}

请问这是什么意思,为什么先买请求成功了,但后来失败了?

在我的应用程序中,我当然会在成功购买后隐藏“购买VIP状态”按钮一年,但我仍然想知道,这里发生了什么。

同样在未来,我想在我的游戏中销售像“硬币”这样的消费品虚拟商品,然后多次购买应该成功。

UPDATE:

我试图consume购买加入下面的代码我payment.php(使用APP_ID|APP_SECRET而不是要求用户访问令牌)到:

$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)); 

但不幸的是获得错误:

{"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"}}

回答

1

您应该使用previ在创建具有相同product_id的新用户之前为该用户购买。这样做是为了防止用户为非消耗品购买同一物品的次数超过一次。

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:

如果你想通过你可以通过ACCESS_TOKEN到PHP脚本,服务器消耗的购买。

$.post("/words/facebook/payment.php", { access_token: access_token })   

要获得access_token,您可以使用它。

var access_token = ''; 
FB.getLoginStatus(function(response) { 
    if (response.status === 'connected') { 
    access_token = response.authResponse.accessToken; 
    } 
}); 
+0

谢谢阿列克谢!有没有办法在我的payment.php脚本中使用服务器端**上的购买**?请参阅我的问题中的UPDATE。作为解决方法,我是否应该将用户访问令牌作为'developer_payload'传递给应用程序? –

+1

只需将access_token传递给脚本即可。查看我的答案更新。 –

1

我回答我的问题,分享通过Facebook Payments Lite耗材虚拟物品所需要的完整的源代码的基础上,阿列克谢·穆欣乐于助人的答复 - 在你的Facebook画布应用

JavaScript代码(分配到一个按钮ONCLICK):在支付-lite.php SCR

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代码IPT托管在您的Web服务器:

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, '-_', '+/')); 
} 

注:如果你碰巧有最新的PHP版本,在上面的代码,然后更好地利用hash_equals,以减轻攻击时机

不要忘记启用付款精简版在您的应用程序的Facebook Dashboard,并添加一个“测试1”产品有:

dashboard

如果你按照上面的指示,你将能够购买在“测试1”项目进行了多次,你会在PHP日志中获取的输出如下:

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} 

最后,我将分享我下面的瓦特ebhook代码非精简版Facebook Payments,因为这是我实际上已经结束了使用(它处理退款,不需要购买后,标记项目耗材) -

中的JavaScript功能的Facebook画布的应用程序代码(分配到一个按钮ONCLICK):

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 
} 
0:在你的Web服务器托管

function buyItemFull() { 
     var payDialog = { 
       method: "pay", 
       action: "purchaseitem", 
       product: "https://myserver/test1.html" 
     }; 

     FB.ui(payDialog, function(data) { 
       location.reload(); 
     }); 
} 

PHP代码中支付,full.php脚本

不要忘记禁用付款精简版在您的应用程序的Facebook Dashboard,并添加了“付款full.php”网络挂接有:

dashboard

最后添加了“test1.html”在你的web服务器产品文件:

<!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> 

目前还没有很多的Facebook付款的例子在网络上被发现。

因此,如果您发现自己的源代码(公共领域许可证)有用,可以帮助其他开发人员发现问题和答案。

相关问题