简体   繁体   中英

How to protect sensitive pages for paypal payment processing?

I'm using the paypal express checkout, which follows this flow: 在此处输入图片说明

When they submit on the paypal website it follows a return url on my site which shows the order review with a confirm button, and two GET variables are passed back from paypal: token and payerId . The token gives me permission to request shipping info & later finalize the payment.

The first problem is I can access the 'checkout with paypal' page directly by typing in the URL into the address bar and it will submit the request to paypal, if the $_SESSION['Payment_Amount'] variable is not set it processes with the payment amount as 0 and throws an error.

SetExpressCheckout API call failed. Detailed Error Message: This transaction cannot be processed. The amount to be charged is zero.

I know I can set another session variable on the cart page to make sure they visit the cart first, and then clear the variable after checking for it, but another problem remains that the user only needs to visit the cart page once and the variable will be set to allow them to visit the sensitive page which sends a token request to paypal.

The next problem is that after going through all the steps and the user pressing the 'confirm order' button, the request is sent to paypal to process the order/money for that token . The user can press the 'BACK' button on the page and see the order-review again, then the user can press confirm order again and an error will show that an order was already processed for that token.

GetExpressCheckoutDetails API call failed. Detailed Error Message: A successful transaction has already been completed for this token.

That's clearly a good thing but what should I implement to prevent the user from accessing sensitive pages? Will I need to track certain keys in my back-end database?

At the moment i'm working on localhost with paypal's sandbox.

You have to create a process somehow that guarantees that the user follows the needed steps in the right order and prevents him from jumping out of this order.

Tracking the steps in the users session seems like the natural thing to do. If the session does not allow the step requested, redirect him elsewhere instead of asking paypal.

The deluxe version would be you implemented a state machine for easier improvements later on. State machines have the disadvantage of looking like huge overhead at first, and are too much hassle to implement later if you initially took a different approach. That's why it is important to think about using one from the start.

What if you want to add another payment provider later? A state machine could be easily extended for this - anything else might be a mess then.

Edit: Actually, the only thing paypal expects you to send to them after the user is back on your site is the amount you want to charge. This info can be passed by putting it into the return url you send to paypal. Try adding some checksum there to prevent data errors and easy tampering (Paypal lets the process fail if the amount is incorrect nevertheless), and you are basically done. No session at all needed.

Edit2: Here is an excerpt of my code that defines the nvp parameters for paypals first step. You need the necessary auth stuff inside, too.

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 ...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM