0

我正在使用遵循以下流程的贝宝快速结帐: 在此处输入图像描述

当他们在 paypal 网站上提交时,它会跟随我网站上的返回 url,该 URL 显示带有确认按钮的订单审查,并且两个 GET 变量从 paypal 传回:tokenpayerId. 该令牌允许我请求运输信息并稍后完成付款。

第一个问题是我可以通过在地址栏中输入 URL 直接访问“使用 paypal 结帐”页面,它会将请求提交给 paypal,如果$_SESSION['Payment_Amount']未设置变量,它会处理付款金额为 0 并抛出一个错误。

SetExpressCheckout API 调用失败。详细错误消息:无法处理此交易。要收取的金额为零。

我知道我可以在购物车页面上设置另一个会话变量,以确保他们首先访问购物车,然后在检查后清除该变量,但另一个问题仍然是用户只需要访问购物车页面一次,该变量将设置为允许他们访问向贝宝发送令牌请求的敏感页面。

下一个问题是,在完成所有步骤并且用户按下“确认订单”按钮后,请求被发送到贝宝以处理该订单/资金token。用户可以按页面上的“返回”按钮并再次查看订单审查,然后用户可以再次按确认订单,错误将显示该令牌已处理订单。

GetExpressCheckoutDetails API 调用失败。详细的错误消息:此令牌的成功交易已经完成。

这显然是一件好事,但我应该实施什么来防止用户访问敏感页面?我需要跟踪后端数据库中的某些键吗?

目前我正在使用贝宝的沙箱在本地主机上工作。

4

1 回答 1

1

您必须以某种方式创建一个流程,以确保用户按照正确的顺序执行所需的步骤,并防止他跳出这个顺序。

跟踪用户会话中的步骤似乎是很自然的事情。如果会话不允许请求的步骤,请将他重定向到其他地方而不是询问贝宝。

豪华版是您实现了一个状态机,以便以后更容易改进。状态机的缺点是一开始看起来开销很大,如果您最初采用不同的方法,以后实现起来会很麻烦。这就是为什么从一开始就考虑使用一个很重要的原因。

如果您想稍后添加另一个支付提供商怎么办?为此可以轻松扩展状态机 - 其他任何事情都可能是一团糟。

编辑:实际上,贝宝希望您在用户回到您的网站后发送给他们的唯一内容是您要收取的金额。此信息可以通过将其放入您发送到贝宝的返回 url 中来传递。尝试在那里添加一些校验和以防止数据错误和容易被篡改(如果金额不正确,Paypal 会让过程失败),你基本上就完成了。根本不需要会话。

Edit2:这是我的代码的摘录,它定义了贝宝第一步的 nvp 参数。您也需要内部必要的身份验证内容。

public function preparePayment(...) {
        $nvp = array(

        'METHOD'    => 'SetExpressCheckout',
        'VERSION'   => '52.0',

        'RETURNURL'     => 'https://'.$request->server['HTTP_HOST'].'/'.$request->getLanguage().'/paypal/success/'.$this->hashAmount($amount),
        'CANCELURL'     => 'https://'.$request->server['HTTP_HOST'].'/'.$request->getLanguage().'/paypal/cancel',

            'CURRENCYCODE'  => $amount->getCurrency(),
            'AMT'           => number_format($amount->getAmount(), 2, '.', ''),
            'ITEMAMT'       => number_format($amount->getNettoAmount(), 2, '.', ''),
            'TAXAMT'        => number_format($amount->getVatAmount(), 2, '.', ''),
            'PAYMENTACTION' => 'Sale',
            'LOCALECODE'    => strtoupper($request->getLanguage())
        );
}
protected function hashAmount(Currency_Class $amount) {
    return urlencode(
        sprintf(
            '%s-%s-%s-%u',
            number_format($amount->getNettoAmount(), 2, '', ''),
            number_format($amount->getVatAmount(), 2, '', ''),
            strtoupper($amount->getCurrency()),
            $this->makeChecksumString(number_format($amount->getNettoAmount(), 2, '', ''), strtoupper($amount->getCurrency()))
        )
    );
}

protected function makeChecksumString($amount, $currency) {
    return crc32(sprintf('%sSaltValue%s', $amount, $currency));
}

protected function dehashAmount($string) {
    $parts = array();
    $found = preg_match('/^(\d+)\-(\d+)\-([A-Z]+)\-(\d+)$/', $string, $parts);
    if ($found) {
        $check = sprintf('%u', $this->makeChecksumString($parts[1], $parts[3]));
        if ($check == $parts[4]) {
            $netto  = floatval(substr($parts[1], 0, -2) .'.'. substr($parts[1], -2));
            $vat    = floatval(substr($parts[2], 0, -2) .'.'. substr($parts[2], -2));
        }
    }
    return ...
}
于 2012-10-03T22:05:33.917 回答