简体   繁体   中英

Why is Paypal giving an error when I run my subscription form?

I have never been able to successfully set up recurring paypal subscription buttons on my website because Paypal always gives me the error Paypal cannot process this transaction because of a problem with the seller's website. I have tested the script locally and in the IPN simulator and all worked fine. But whenever I try to test in sandbox mode it gives me this error. Is it a problem with my code or with my Paypal account? I am trying to set this up in wordpress.

Here is my form:

if(get_option('jz_payment_environment') == 'local'){
    $form_action = site_url('gateway/paypal');
} else if(get_option('jz_payment_environment') == 'sandbox'){
    $form_action = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
} else if(get_option('jz_payment_environment') == 'production'){
    $form_action = 'https://ipnpb.paypal.com/cgi-bin/webscr';
}?>
<form action="<?php echo $form_action;?>" method="post" target="_top">
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="hosted_button_id" value="">
<input type="hidden" name="custom" value="<?php echo get_current_user_id();?>">
<input type="hidden" name="business" value="<?php echo get_option('jz_paypal_email');?>">
<?php if(get_option('jz_payment_environment') == 'local'):?>
<input type="hidden" name="t3" value="M">
<input type="hidden" name="p3" value="1">
<input type="hidden" name="src" value="1">
<input type="hidden" name="srt" value="52">
<input type="hidden" name="currency_code" value="USD">
<?php endif;?>

Here is my IPN Shortcode:

public static function paypal_ipn(){
        global $wpdb;
        ob_start();
        if($_SERVER['REQUEST_METHOD'] == 'POST'){
            $ipn = new PaypalIPN();
            if(get_option('jz_payment_environment') == 'sandbox'){
                $ipn->useSandbox();
                $verified = $ipn->verifyIPN();
            } else if(get_option('jz_payment_environment') == 'local'){
                $verified = true;
            } else if(get_option('jz_payment_environment') == 'production'){
                $ipn->usePHPCerts();
                $verified = $ipn->verifyIPN();
            }
            if($verified){

               $payment = array(
                   'post_author' => $_POST['custom'],
                   'post_type' => 'payment',
                   'post_status' => 'publish',
                   'meta_input' => array(
                       'payment_amount' => $_POST['a3'],
                       'payment_expiration' => ($_POST['t3'] == 'M') ? time() + (60 * 60 * 24 * 30 * $_POST['p3']) : time() + (60 * 60 * 24 * 365),
                       'payment_currency' => $_POST['currency_code'],
                       'payment_status' => 'pending',
                       'payment_tpt_no' => (!empty($_POST['txn_id'])) ? $_POST['txn_id'] : 'N/A'
                   )
               );
               $payment_post = wp_insert_post($payment);
               if(!is_wp_error($payment_post)){
                   $subscriber = get_userdata($_POST['custom']);
                   if($subscriber){
                       update_user_meta($subscriber, 'subscription_expiration', $payment['meta_input']['payment_expiration']);
                       $plan = $wpdb->get_row("
                           SELECT p.ID, pm1.meta_value as plan_monthly, pm2.meta_value as plan_quarterly, pm3.meta_value as plan_yearly
                           FROM {$wpdb->prefix}posts AS p
                           LEFT JOIN {$wpdb->prefix}postmeta AS pm1 ON p.ID = pm1.post_id AND pm1.meta_key = 'plan_monthly'
                           LEFT JOIN {$wpdb->prefix}postmeta AS pm2 ON p.ID = pm2.post_id AND pm2.meta_key = 'plan_quarterly'
                           LEFT JOIN {$wpdb->prefix}postmeta AS pm3 ON p.ID = pm3.post_id AND pm3.meta_key = 'plan_yearly'
                           WHERE post_type = 'plan'
                               AND pm1.meta_value = '{$_POST['a3']}'
                               OR pm2.meta_value = '{$_POST['a3']}'
                               OR pm3.meta_value = {$_POST['a3']}
                           LIMIT 1
                           ");
                       update_user_meta($subscriber, 'subscription_plan', $plan->ID);
                   }
               }
            }
        }
        header("HTTP/1.1 200 OK");
        $out = ob_get_contents();
        ob_end_clean();
        return $out;
    }

And here is the Paypal IPN Class that I am using from the Paypal developer site:

<?php
class PaypalIPN
{
    /** @var bool Indicates if the sandbox endpoint is used. */
    private $use_sandbox = false;
    /** @var bool Indicates if the local certificates are used. */
    private $use_local_certs = true;
    /** Production Postback URL */
    const VERIFY_URI = 'https://www.paypal.com/cgi-bin/webscr';
    /** Sandbox Postback URL */
    const SANDBOX_VERIFY_URI = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
    /** Response from PayPal indicating validation was successful */
    const VALID = 'VERIFIED';
    /** Response from PayPal indicating validation failed */
    const INVALID = 'INVALID';
    const RESULT = '';
    /**
     * Sets the IPN verification to sandbox mode (for use when testing,
     * should not be enabled in production).
     * @return void
     */
    public function useSandbox()
    {
        $this->use_sandbox = true;
    }
    /**
     * Sets curl to use php curl's built in certs (may be required in some
     * environments).
     * @return void
     */
    public function usePHPCerts()
    {
        $this->use_local_certs = false;
    }
    /**
     * Determine endpoint to post the verification data to.
     *
     * @return string
     */

    public function getResult(){
        return self::RESULT;
    }
    public function getPaypalUri()
    {
        if ($this->use_sandbox) {
            return self::SANDBOX_VERIFY_URI;
        } else {
            return self::VERIFY_URI;
        }
    }
    /**
     * Verification Function
     * Sends the incoming post data back to PayPal using the cURL library.
     *
     * @return bool
     * @throws Exception
     */
    public function verifyIPN()
    {
        if ( ! count($_POST)) {
            throw new Exception("Missing POST Data");
        }
        $raw_post_data = file_get_contents('php://input');
        $raw_post_array = explode('&', $raw_post_data);
        $myPost = array();
        foreach ($raw_post_array as $keyval) {
            $keyval = explode('=', $keyval);
            if (count($keyval) == 2) {
                // Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it.
                if ($keyval[0] === 'payment_date') {
                    if (substr_count($keyval[1], '+') === 1) {
                        $keyval[1] = str_replace('+', '%2B', $keyval[1]);
                    }
                }
                $myPost[$keyval[0]] = urldecode($keyval[1]);
            }
        }
        // Build the body of the verification post request, adding the _notify-validate command.
        $req = 'cmd=_notify-validate';
        $get_magic_quotes_exists = false;
        if (function_exists('get_magic_quotes_gpc')) {
            $get_magic_quotes_exists = true;
        }
        foreach ($myPost as $key => $value) {
            if ($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) {
                $value = urlencode(stripslashes($value));
            } else {
                $value = urlencode($value);
            }
            $req .= "&$key=$value";
        }
        // Post the data back to PayPal, using curl. Throw exceptions if errors occur.
        $ch = curl_init($this->getPaypalUri());
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
        curl_setopt($ch, CURLOPT_SSLVERSION, 6);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        // This is often required if the server is missing a global cert bundle, or is using an outdated one.
        if ($this->use_local_certs) {
            curl_setopt($ch, CURLOPT_CAINFO, 'W:/laragon/etc/ssl/cacert.pem');
        }
        curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'User-Agent: PHP-IPN-Verification-Script',
            'Connection: Close',
        ));
        if ( ! ($res = curl_exec($ch))) {
            $errno = curl_errno($ch);
            $errstr = curl_error($ch);
            curl_close($ch);
            throw new Exception("cURL error: [$errno] $errstr");
        }
        $info = curl_getinfo($ch);
        $http_code = $info['http_code'];
        if ($http_code != 200) {
            throw new Exception("PayPal responded with http code $http_code");
        }
        curl_close($ch);
        // Check if PayPal verifies the IPN data, and if so, return true.
        if ($res == self::VALID) {
            return true;
        } else {
            return false;
        }
    }
}

The hosted_button_id hidden input is changed with JS when a user clicks on a radio button. If you want to test my form, click here . What am I doing wrong?

As I can see, your hosted_button_id parameter is empty. Consider removing it?

If it's filled up later, however: PayPal explicitly states that you can not change transaction amounts on hosted buttons:

You should not write HTML button code for saved buttons. Always use the code that PayPal generates. However, you can enhance the generated code for saved buttons by adding hidden HTML variables that do not affect the transaction amount. For example, you can enhance saved buttons with automatic fill-out variables.

Source: https://developer.paypal.com/docs/classic/paypal-payments-standard/integration-guide/formbasics/#using-html-variables-with-saved-payment-buttons

Don't use a hosted button if you are going to be changing values. Use an unhosted button. Create a new sample button on PayPal, in Step 2 uncheck the option to "Save" the button, and once the code is generated click to remove the code protection. Then you'll have something you can alter with dynamic code.

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