/**
  * Receives verified IPN data from PayPal and processes the donation. 
  *
  * @return  void
  * @access  public
  * @static
  * @since   1.0.0
  */
 public static function process_web_accept($data, $donation_id)
 {
     if (!isset($data['invoice'])) {
         return;
     }
     $gateway = new Charitable_Gateway_Paypal();
     $donation = new Charitable_Donation($donation_id);
     if ('paypal' != $donation->get_gateway()) {
         return;
     }
     $donation_key = $data['invoice'];
     $amount = $data['mc_gross'];
     $payment_status = strtolower($data['payment_status']);
     $currency_code = strtoupper($data['mc_currency']);
     $business_email = isset($data['business']) && is_email($data['business']) ? trim($data['business']) : trim($data['receiver_email']);
     /* Verify that the business email matches the PayPal email in the settings */
     if (strcasecmp($business_email, trim($gateway->get_value('paypal_email'))) != 0) {
         $message = sprintf('%s %s', __('Invalid Business email in the IPN response. IPN data:', 'charitable'), json_encode($data));
         Charitable_Donation::update_donation_log($donation_id, $message);
         $donation->update_status('charitable-failed');
         return;
     }
     /* Verify that the currency matches. */
     if ($currency_code != charitable_get_currency()) {
         $message = sprintf('%s %s', __('The currency in the IPN response does not match the site currency. IPN data:', 'charitable'), json_encode($data));
         Charitable_Donation::update_donation_log($donation_id, $message);
         $donation->update_status('charitable-failed');
         return;
     }
     /* Process a refunded donation. */
     if (in_array($payment_status, array('refunded', 'reversed'))) {
         /* It's a partial refund. */
         if ($amount < $donation->get_total_donation_amount()) {
             $message = sprintf('%s: #%s', __('Partial PayPal refund processed', 'charitable'), isset($data['parent_txn_id']) ? $data['parent_txn_id'] : '');
         } else {
             $message = sprintf('%s #%s %s: %s', __('PayPal Payment', 'charitable'), isset($data['parent_txn_id']) ? $data['parent_txn_id'] : '', __('refunded with reason', 'charitable'), isset($data['reason_code']) ? $data['reason_code'] : '');
         }
         $donation->process_refund($amount, $message);
         return;
     }
     /* Mark a payment as failed. */
     if (in_array($payment_status, array('declined', 'failed', 'denied', 'expired', 'voided'))) {
         $message = sprintf('%s: %s', __('The donation has failed with the following status', 'charitable'), $payment_status);
         Charitable_Donation::update_donation_log($donation_id, $message);
         $donation->update_status('charitable-failed');
         return;
     }
     /* If we have already processed this donation, stop here. */
     if ('charitable-completed' == get_post_status($donation_id)) {
         return;
     }
     /* Verify that the donation key matches the one stored for the donation. */
     if ($donation_key != $donation->get_donation_key()) {
         $message = sprintf('%s %s', __('Donation key in the IPN response does not match the donation. IPN data:', 'charitable'), json_encode($data));
         Charitable_Donation::update_donation_log($donation_id, $message);
         $donation->update_status('charitable-failed');
         return;
     }
     /* Verify that the amount in the IPN matches the amount we expected. */
     if ($amount < $donation->get_total_donation_amount()) {
         $message = sprintf('%s %s', __('The amount in the IPN response does not match the expected donation amount. IPN data:', 'charitable'), json_encode($data));
         Charitable_Donation::update_donation_log($donation_id, $message);
         $donation->update_status('charitable-failed');
         return;
     }
     /* Process a completed donation. */
     if ('completed' == $payment_status) {
         $message = sprintf('%s: %s', __('PayPal Transaction ID', 'charitable'), $data['txn_id']);
         Charitable_Donation::update_donation_log($donation_id, $message);
         $donation->update_status('charitable-completed');
         return;
     }
     /* If the donation is set to pending but has a pending_reason provided, save that to the log. */
     if ('pending' == $payment_status) {
         if (isset($data['pending_reason'])) {
             $message = $gateway->get_pending_reason_note(strtolower($data['pending_reason']));
             Charitable_Donation::update_donation_log($donation_id, $message);
         }
         $donation->update_status('charitable-pending');
     }
 }
 /**
  * Receives the IPN from PayPal after the sandbox test and attempts to verify the result.
  *
  * @return  void
  * @access  public
  * @static
  * @since   1.4.3
  */
 public static function process_sandbox_test_ipn()
 {
     $gateway = new Charitable_Gateway_Paypal();
     $data = $gateway->get_encoded_ipn_data();
     /* If any of these checks fail, we conclude that this is not a proper IPN from PayPal. */
     if (empty($data) || !is_array($data)) {
         die("empty data");
     }
     /* Compare the token with the one we generated. */
     $token = get_option('charitable_paypal_sandbox_test_token');
     if (!array_key_exists('custom', $data) || $token !== $data['custom']) {
         die("missing or mismatched custom data");
     }
     $remote_post_vars = array('method' => 'POST', 'timeout' => 45, 'redirection' => 5, 'httpversion' => '1.1', 'blocking' => true, 'headers' => array('host' => 'www.paypal.com', 'connection' => 'close', 'content-type' => 'application/x-www-form-urlencoded', 'post' => '/cgi-bin/webscr HTTP/1.1'), 'sslverify' => false, 'body' => $data);
     /* Call the PayPal API to verify the IPN. */
     $protocol = is_ssl() ? 'https://' : 'http://';
     $remote_url = $protocol . 'www.sandbox.paypal.com/cgi-bin/webscr';
     $api_response = wp_remote_post($remote_url, $remote_post_vars);
     $succeeded = !is_wp_error($api_response);
     $message = '';
     if ($succeeded) {
         $result = 'succeeded';
         $subject = __('Your PayPal integration is working', 'charitable');
         $message = __('<p>Good news! We successfuly received the Instant Payment Notification from PayPal and were able to verify it with them.</p>', 'charitable');
         $message .= __('<p>This means that your website is all set to continue receiving donations through PayPal. You should not experience any issues when PayPal upgrades its SSL certificates.</p>', 'charitable');
         $message .= __('<p>Cheers<br />Eric & Wes', 'charitable');
     } else {
         $result = 'failed';
         $subject = __('Your PayPal test failed', 'charitable');
         $message .= __('<p>We received the Instant Payment Notification from PayPal but were not able to verify its authenticity.', 'charitable');
         $message .= __('<p>Our communicaton with PayPal failed with the following errors:</p>', 'charitable');
         $message .= '<ul>';
         foreach ($api_response->get_error_messages() as $error) {
             $message .= sprintf('<li>%s</li>', $error);
         }
         $message .= '</ul>';
         $message .= __('<p>Unfortunately, this means that you are likely to face problems with your PayPal donations from October 2016 onwards. Your donors will still be able to proceed to PayPal and make their donation, but their donations will not be automatically marked as Paid in your WordPress dashboard.</p>', 'charitable');
         $message .= __('<h3>Short-term fix</h3>', 'charitable');
         $message .= __('<p><strong>Disable IPN verification</strong>. This makes your donation verification process less secure, but it will allow your donations to continue getting marked as Paid. To set this up, log into your WordPress dashboard and go to <em>Charitable</em> > <em>Settings</em> > <em>Payment Gateways</em>, select your PayPal settings and enable the "Disable IPN Verification" setting.', 'charitable');
         $message .= __('<h3>Long-term solution</h3>', 'charitable');
         $message .= __('<p><strong>Get in touch with your web host</strong>. Please refer them to <a href="https://www.paypal-knowledge.com/infocenter/index?page=content&widgetview=true&id=FAQ1766&viewlocale=en_US">the upgrade information provided by PayPal</a>. You should also provide them with the error message you received from PayPal above.</p>', 'charitable');
         $message .= __('<p>If your web host is unable to upgrade the software on your server, we strongly recommend switching to a hosting platform that provides a more modern, and secure service.</p>', 'charitable');
         $message .= __('<p>Cheers<br />Eric & Wes', 'charitable');
     }
     /* Store the result. */
     update_option('charitable_paypal_sandbox_test', $result);
     /* Clear the token. */
     delete_option('charitable_paypal_sandbox_test_token');
     /* Set a transient to display the success/failure of the test. */
     set_transient('charitable_paypal-sandbox-test_notice', 1);
     /* Remove the transient about the PayPal upgrade. */
     delete_transient('charitable_release-143-paypal_notice');
     /* Send an email to the site admin. */
     ob_start();
     charitable_template('emails/header.php', array('email' => null, 'headline' => $subject));
     echo $message;
     charitable_template('emails/footer.php');
     $message = ob_get_clean();
     $headers = "From: Charitable <*****@*****.**>\r\n";
     $headers .= "Reply-To: support@wpcharitable.com\r\n";
     $headers .= "Content-Type: text/html; charset=utf-8\r\n";
     /* Send an email to the site administrator letting them know. */
     $sent = wp_mail(get_option('admin_email'), $subject, $message, $headers);
 }