Facebook Payments Lite(无服务器):第一次购买有效,但第二次总是失败

Facebook Payments Lite(无服务器):第一次购买有效,但第二次总是失败,facebook,dialog,payment,facebook-canvas,Facebook,Dialog,Payment,Facebook Canvas,在Facebook上的一款以Canvas应用程序形式托管的文字游戏中,我想出售一个1年VIP消费身份,让玩家临时访问游戏中的某些区域——通过使用 我的JavaScript代码显示Pay对话框,然后传递到我的PHP脚本- 我的画布应用程序中的JavaScript代码: 我的PHP脚本/payment-lite.PHP: 在app Dashboard->Web Payments中,我添加了一个测试用户和一个产品ID为test1、价格为0.01欧元的测试产品: 最后,我以测试用户身份登录,并按下应用程

在Facebook上的一款以Canvas应用程序形式托管的文字游戏中,我想出售一个1年VIP消费身份,让玩家临时访问游戏中的某些区域——通过使用

我的JavaScript代码显示Pay对话框,然后传递到我的PHP脚本-

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

我的PHP脚本/payment-lite.PHP:

在app Dashboard->Web Payments中,我添加了一个测试用户和一个产品ID为test1、价格为0.01欧元的测试产品:

最后,我以测试用户身份登录,并按下应用程序中的一个按钮,调用buyVip方法-导致出现支付对话框:

然后在服务器日志中,我看到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
)
但是,当我稍后尝试相同的过程时,会出现Pay对话框,但在按下Buy按钮后失败,并显示错误

处理您的付款时出现问题:抱歉,我们正在处理 处理您的付款时遇到问题。你还没有为此收费 交易请再试一次

在浏览器控制台中,我看到错误代码:

{错误代码:1383001,错误消息:处理时出现问题 您的付款:抱歉…此交易收取n。请重试 再来一次。}

请问,这意味着什么?为什么第一次购买请求成功了,但随后又失败了

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

此外,在未来,我想在我的游戏中销售可消费的虚拟商品,如硬币,然后多次购买应该会成功

更新:

我已尝试通过将以下代码添加到my payment.php而不是使用所需的用户访问令牌来完成购买:

$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));
但不幸的是,我得到了一个错误:

{错误:{消息:不支持的post请求。ID为的对象 “498440660497153”不存在,无法加载,因为缺少 权限,或不支持此操作。请阅读图表 API文档位于 https://developers.facebook.com/docs/graph-api,类型:GraphMethodException,代码:100,fbtrace_id:HDusTBubydJ}


在创建具有相同产品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);
  }
);
更新:

如果您想通过服务器消费purchase,可以将access_令牌传递给php脚本

$.post("/words/facebook/payment.php", { access_token: access_token })        
要获取访问令牌,您可以使用此

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

在创建具有相同产品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);
  }
);
更新:

如果您想通过服务器消费purchase,可以将access_令牌传递给php脚本

$.post("/words/facebook/payment.php", { access_token: access_token })        
要获取访问令牌,您可以使用此

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

根据Alexey Mukhin的有益回复,我将回答自己的问题,分享通过销售可消费虚拟商品所需的完整源代码-

Facebook画布应用程序中的JavaScript代码分配给按钮ONCLICK:

web服务器上托管的payment-lite.PHP脚本中的PHP代码:

注意:如果您碰巧有一个最新的PHP版本,那么最好在上面的代码中使用,以减轻定时攻击

别忘了在应用程序中启用Payments Lite,并在其中添加test1产品:

如果按照上述说明进行操作,您将能够多次购买test1项目,并且您将在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}
最后,我将在下面分享我的非lite的webhook代码,因为这就是我最终使用的,它处理退款,并且不需要在购买后标记可消费的物品-

Facebook画布应用程序中的JavaScript代码分配给按钮ONCLICK:

web服务器上托管的payment-full.PHP脚本中的PHP代码:

别忘了在应用程序中禁用Payments Lite,并在其中添加payment-full.php webhook:

最后在web服务器上添加test1.html产品文件:

目前在网上发现的Facebook支付示例并不多


因此,如果您发现我的源代码公共域许可证很有用,请投票表决问题和答案,以帮助其他开发人员发现它。

根据Alexey Mukhin的有用回答,我正在回答我自己的问题,分享通过销售可消费虚拟商品所需的完整源代码-

Facebook画布应用程序中的JavaScript代码分配给按钮ONCLICK:

web服务器上托管的payment-lite.PHP脚本中的PHP代码:

注意:如果您碰巧有一个最新的PHP版本,那么最好在上面的代码中使用,以减轻定时攻击

别忘了在应用程序中启用Payments Lite,并在其中添加test1产品:

如果您按照上面的说明进行操作,请选择y 您将能够多次购买test1项目,并且您将在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}
最后,我将在下面分享我的非lite的webhook代码,因为这就是我最终使用的,它处理退款,并且不需要在购买后标记可消费的物品-

Facebook画布应用程序中的JavaScript代码分配给按钮ONCLICK:

web服务器上托管的payment-full.PHP脚本中的PHP代码:

别忘了在应用程序中禁用Payments Lite,并在其中添加payment-full.php webhook:

最后在web服务器上添加test1.html产品文件:

目前在网上发现的Facebook支付示例并不多


因此,如果您发现我的源代码公共域许可证很有用,请向上投票,以帮助其他开发人员发现它。

谢谢您,Alexey!在我的payment.php脚本中,是否有一种在服务器端消费购买的方法?请看我问题中的更新。我是否应该将用户访问令牌作为开发人员负载传递作为解决方法?只需将访问令牌传递给脚本即可。见我的最新答案。谢谢你,阿列克西!在我的payment.php脚本中,是否有一种在服务器端消费购买的方法?请看我问题中的更新。我是否应该将用户访问令牌作为开发人员负载传递作为解决方法?只需将访问令牌传递给脚本即可。请参阅我的答案更新。
function buyItemFull() { 
        var payDialog = {
                method:  "pay",
                action:  "purchaseitem",
                product: "https://myserver/test1.html"
        };

        FB.ui(payDialog, function(data) {
                location.reload();
        });
}
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
}
<!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>