diff --git a/changelog.md b/changelog.md index 83244dc..5c46aad 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Change log +## [[1.3.0]](https://github.com/lightspeeddevelopment/lsx-give-payfast-gateway/releases/tag/1.3.0) - 2024-07-30 + +### Added +- Integrate the Payfast common class for modules v1.1.0. +- Update PayFast -> Payfast. +- General testing to ensure compatibility with latest Give - Donation Plugin version (3.13.0). +- General testing to ensure compatibility with PHP 8.0+. + ## [[1.2.8]](https://github.com/lightspeeddevelopment/lsx-give-payfast-gateway/releases/tag/1.2.8) - 2023-08-09 ### Security @@ -93,7 +101,7 @@ - Added support for passphrase. ### Fixed -- Corrected the format of the email address when sent to PayFast. +- Corrected the format of the email address when sent to Payfast. ## [[1.0.0]]() diff --git a/classes/PayfastCommon.php b/classes/PayfastCommon.php new file mode 100755 index 0000000..44a26d7 --- /dev/null +++ b/classes/PayfastCommon.php @@ -0,0 +1,509 @@ +debugMode = $debugMode; + } + + /** + * pfValidData + * + * @param $moduleInfo array pfSoftwareName, pfSoftwareVer, pfSoftwareModuleName, pfModuleVer + * @param $pfHost String Hostname to use + * @param $pfParamString String + * + * @return bool + */ + public function pfValidData( + array $moduleInfo, + string $pfHost = 'www.payfast.co.za', + string $pfParamString = '' + ): bool { + $pfFeatures = 'PHP ' . phpversion() . ';'; + $pfCurl = false; + + // - cURL + if (in_array('curl', get_loaded_extensions())) { + $pfCurl = true; + $pfVersion = curl_version(); + $pfFeatures .= ' curl ' . $pfVersion['version'] . ';'; + } else { + $pfFeatures .= ' nocurl;'; + } + + $pfUserAgent = $moduleInfo["pfSoftwareName"] . '/' . $moduleInfo['pfSoftwareVer'] . + ' (' . trim( + $pfFeatures + ) . ') ' . $moduleInfo["pfSoftwareModuleName"] . '/' . $moduleInfo["pfModuleVer"]; + + $this->pflog('Host = ' . $pfHost); + $this->pflog('Params = ' . $pfParamString); + + // Use cURL (if available) + if ($pfCurl) { + // Variable initialization + $url = 'https://' . $pfHost . '/eng/query/validate'; + + // Create default cURL object + $ch = curl_init(); + + // Set cURL options - Use curl_setopt for greater PHP compatibility + // Base settings + curl_setopt($ch, CURLOPT_USERAGENT, $pfUserAgent); // Set user agent + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return output as string rather than outputting it + curl_setopt($ch, CURLOPT_HEADER, false); // Don't include header in output + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + + // Standard settings + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $pfParamString); + curl_setopt($ch, CURLOPT_TIMEOUT, self::PF_TIMEOUT); + + // Execute CURL + $response = curl_exec($ch); + curl_close($ch); + } else { // Use fsockopen + // Variable initialization + $header = ''; + $response = ''; + $headerDone = false; + + // Construct Header + $header = "POST /eng/query/validate HTTP/1.0\n"; + $header .= "Host: " . $pfHost . "\n"; + $header .= "User-Agent: " . $pfUserAgent . "\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\n"; + $header .= "Content-Length: " . strlen($pfParamString) . "\n\n"; + + // Connect to server + $socket = fsockopen('ssl://' . $pfHost, 443, $errno, $errstr, self::PF_TIMEOUT); + + // Send command to server + fputs($socket, $header . $pfParamString); + + // Read the response from the server + while (!feof($socket)) { + $line = fgets($socket, 1024); + + // Check if we are finished reading the header yet + if (strcmp($line, "\n") == 0) { + // read the header + $headerDone = true; + } elseif ($headerDone) { // If header has been processed + // Read the main response + $response .= $line; + } + } + } + + $this->pflog("Response:\n" . print_r($response, true)); + + // Interpret Response + $lines = explode("\n", $response); + $verifyResult = trim($lines[0]); + + if (strcasecmp($verifyResult, 'VALID') == 0) { + return true; + } else { + return false; + } + } + + /** + * Log public static function for logging output. + * + * @param $msg String Message to log + * @param $close Boolean Whether to close the log file or not + */ + public function pflog(string $msg = '', bool $close = false): void + { + static $fh = 0; + + // Only log if debugging is enabled + if ($this->debugMode) { + if ($close) { + fclose($fh); + } else { + // If file doesn't exist, create it + if (!$fh) { + $pathInfo = pathinfo(__FILE__); + $fh = fopen($pathInfo['dirname'] . '/payfast.log', 'a+'); + } + + // If file was successfully created + if ($fh) { + $line = date('Y-m-d H:i:s') . ' : ' . $msg . "\n"; + + try { + fwrite($fh, $line); + } catch (\Exception $e) { + error_log($e); + } + } + } + } + } + + /** + * pfGetData + * + */ + public static function pfGetData(): bool|array + { + // Posted variables from ITN + $pfData = $_POST; + + // Strip any slashes in data + foreach ($pfData as $key => $val) { + $pfData[$key] = stripslashes($val); + } + + + // Return "false" if no data was received + if (empty($pfData)) { + return false; + } else { + return $pfData; + } + } + + /** + * pfValidSignature + * + */ + public function pfValidSignature($pfData = null, &$pfParamString = null, $pfPassphrase = null): bool + { + // Dump the submitted variables and calculate security signature + foreach ($pfData as $key => $val) { + if ($key != 'signature' && $key != 'option' && $key != 'Itemid') { + $pfParamString .= $key . '=' . urlencode($val) . '&'; + } + } + + $pfParamString = substr($pfParamString, 0, -1); + + if (!empty($pfPassphrase)) { + $pfParamStringWithPassphrase = $pfParamString . "&passphrase=" . urlencode($pfPassphrase); + $signature = md5($pfParamStringWithPassphrase); + } else { + $signature = md5($pfParamString); + } + + $result = ($pfData['signature'] == $signature); + + $this->pflog('Signature = ' . ($result ? 'valid' : 'invalid')); + + return $result; + } + + + /** + * pfValidIP + * + * @param $sourceIP String Source IP address + */ + public function pfValidIP(string $sourceIP): bool + { + // Variable initialization + $validHosts = array( + 'www.payfast.co.za', + 'sandbox.payfast.co.za', + 'w1w.payfast.co.za', + 'w2w.payfast.co.za', + ); + + $validIps = array(); + + foreach ($validHosts as $pfHostname) { + $ips = gethostbynamel($pfHostname); + + if ($ips !== false) { + $validIps = array_merge($validIps, $ips); + } + } + + // Remove duplicates + $validIps = array_unique($validIps); + + $this->pflog("Valid IPs:\n" . print_r($validIps, true)); + + if (in_array($sourceIP, $validIps)) { + return true; + } else { + return false; + } + } + + /** + * pfAmountsEqual + * + * Checks to see whether the given amounts are equal using a proper floating + * point comparison with an Epsilon which ensures that insignificant decimal + * places are ignored in the comparison. + * + * e.g. 100.00 is equal to 100.0001 + * + * @param $amount1 Float 1st amount for comparison + * @param $amount2 Float 2nd amount for comparison + */ + public function pfAmountsEqual(float $amount1, float $amount2): bool + { + if (abs(floatval($amount1) - floatval($amount2)) > self::PF_EPSILON) { + return false; + } else { + return true; + } + } + + /** + * Generate signature for API + * + * @param array $pfData (all the header, body and query string param values to be sent to the API) + * @param null $passPhrase + * + * @return string + */ + public static function generateApiSignature(array $pfData, $passPhrase = null): string + { + if ($passPhrase !== null) { + $pfData['passphrase'] = $passPhrase; + } + + // Sort the array by key, alphabetically + ksort($pfData); + + //create parameter string + $pfParamString = http_build_query($pfData); + + return md5($pfParamString); + } + + /** + * The Subscription Payments API gives Merchants the ability to interact with subscriptions on their accounts. + * + * @param $merchantID + * @param $token + * @param $action + * @param array $data + * @param $passphrase + * @param bool $testMode + * + * @return string + */ + public static function subscriptionAction( + $merchantID, + $token, + $action, + array $data = [], + $passphrase = null, + bool $testMode = false + ): string { + $url = "https://api.payfast.co.za/subscriptions/$token/$action"; + + if ($testMode) { + $url .= "?testing=true"; + } + + $method = match ($action) { + "fetch" => "GET", + "pause", "unpause", "cancel" => "PUT", + "update" => "PATCH", + "adhoc" => "POST", + default => null, + }; + + return self::placeRequest($url, $merchantID, $passphrase, $data, $method); + } + + /** + * The Refunds API provides Merchants with the ability to process refunds to their buyers. + * + * @param $merchantID + * @param $passphrase + * @param $paymentID + * @param $action + * @param array $data + * + * @return string + */ + public static function refundAction($merchantID, $passphrase, $paymentID, $action, array $data = []): string + { + $url = "https://api.payfast.co.za/refunds/"; + $method = "GET"; + + if ($action === "query") { + $url .= "query/"; + } elseif ($action === "create") { + $method = "POST"; + } + + $url .= "$paymentID?testing=true"; + + return self::placeRequest($url, $merchantID, $passphrase, $data, $method); + } + + /** + * Test API + * + * @param $merchantID + * @param null $passphrase + * + * @return string + */ + public static function pingPayfast($merchantID, $passphrase = null): string + { + $url = "https://api.payfast.co.za/ping?testing=true"; + + return self::placeRequest($url, $merchantID, $passphrase); + } + + /** + * Reusable Curl Request + * + * @param $url + * @param $merchantID + * @param null $passphrase + * @param array $body + * @param null $method + * + * @return string + */ + public static function placeRequest($url, $merchantID, $passphrase = null, array $body = [], $method = null): string + { + $date = date("Y-m-d"); + $time = date("H:i:s"); + $timeStamp = $date . "T" . $time; + $pfData = [ + "merchant-id" => $merchantID, + "timestamp" => $timeStamp, + "version" => "v1", + ]; + + $pfData = array_merge($pfData, $body); + + $signature = self::generateApiSignature($pfData, $passphrase); + + $headers = [ + "merchant-id: $merchantID", + "version: v1", + "timestamp: $timeStamp", + "signature: $signature", + ]; + + $ch = curl_init(); + $curlConfig = array( + CURLOPT_URL => $url, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_RETURNTRANSFER => true, + ); + + if (!empty($body)) { + $curlConfig[CURLOPT_POST] = 1; + $curlConfig[CURLOPT_POSTFIELDS] = http_build_query($body); + } + + if ($method === "PUT" || $method === "PATCH") { + $curlConfig[CURLOPT_CUSTOMREQUEST] = $method; + } + + curl_setopt_array($ch, $curlConfig); + $response = curl_exec($ch); + + if (curl_errno($ch)) { + return curl_error($ch); + } + + return $response; + } + + /** + * Build a checkout form and receive payments securely from our payment platform. + * This process can be used for both one-time and recurring payments. + * + * @param $payArray + * @param null $passphrase + * @param bool $testMode + * + * @return void + */ + public static function createTransaction($payArray, $passphrase = null, bool $testMode = false): void + { + $url = $testMode ? 'https://sandbox.payfast.co.za/eng/process' : 'https://www.payfast.co.za/eng/process'; + + $secureString = ''; + foreach ($payArray as $k => $v) { + $secureString .= $k . '=' . urlencode(trim($v)) . '&'; + } + + if (!empty($passphrase)) { + $secureString .= 'passphrase=' . urlencode($passphrase); + } else { + $secureString = substr($secureString, 0, -1); + } + + $securityHash = md5($secureString); + + $payArray['signature'] = $securityHash; + $inputs = ''; + foreach ($payArray as $k => $v) { + $inputs .= ''; + } + + echo << + +
+ $inputs +
+ + +EOT; + } +} diff --git a/classes/class-give-recurring-payfast.php b/classes/class-give-recurring-payfast.php index 51a0af6..6ac9270 100644 --- a/classes/class-give-recurring-payfast.php +++ b/classes/class-give-recurring-payfast.php @@ -6,7 +6,7 @@ * @author LightSpeed * @license GPL-3.0+ * @link - * @copyright 2018 LightSpeed Team + * @copyright 2024 LightSpeed Team */ // Exit if accessed directly @@ -21,9 +21,9 @@ global $give_recurring_payfast; /** - * Class Give_Recurring_PayFast + * Class Give_Recurring_Payfast */ -class Give_Recurring_PayFast extends Give_Recurring_Gateway { +class Give_Recurring_Payfast extends Give_Recurring_Gateway { /** * Setup gateway ID and possibly load API libraries. @@ -59,7 +59,7 @@ public function create_payment_profiles() { } /** - * Validate PayFast Recurring Donation Period + * Validate Payfast Recurring Donation Period * * @description: Additional server side validation for Standard recurring * @@ -70,8 +70,8 @@ public function create_payment_profiles() { function validate_recurring_period( $form_id = 0 ) { global $post; - $recurring_option = isset( $_REQUEST['_give_recurring'] ) ? $_REQUEST['_give_recurring'] : 'no'; - $set_or_multi = isset( $_REQUEST['_give_price_option'] ) ? $_REQUEST['_give_price_option'] : ''; + $recurring_option = $_REQUEST['_give_recurring'] ?? 'no'; + $set_or_multi = $_REQUEST['_give_price_option'] ?? ''; // Sanity Checks. if ( ! class_exists( 'Give_Recurring' ) ) { @@ -98,30 +98,9 @@ function validate_recurring_period( $form_id = 0 ) { return $form_id; } - $message = __( 'PayFast Only allows for Monthly and Yearly recurring donations. Please revise your selection.', 'give-recurring' ); + $message = __( 'Payfast Only allows for Monthly and Yearly recurring donations. Please revise your selection.', 'give-recurring' ); - if ( 'yes_admin' == $set_or_multi && 'multi' == $recurring_option ) { - - $prices = isset( $_REQUEST['_give_donation_levels'] ) ? $_REQUEST['_give_donation_levels'] : array( '' ); - foreach ( $prices as $price_id => $price ) { - $period = isset( $price['_give_period'] ) ? $price['_give_period'] : 0; - - if ( in_array( $period, array( 'day', 'week' ) ) ) { - wp_die( esc_html( $message ), esc_html__( 'Error', 'give-recurring' ), array( - 'response' => 400, - ) ); - } - } - } elseif ( Give_Recurring()->is_recurring( $form_id ) ) { - - $period = isset( $_REQUEST['_give_period'] ) ? $_REQUEST['_give_period'] : 0; - - if ( in_array( $period, array( 'day', 'week' ) ) ) { - wp_die( esc_html( $message ) , esc_html__( 'Error', 'give-recurring' ), array( - 'response' => 400, - ) ); - } - } + $this->setOrMulti( $set_or_multi, $recurring_option, $message, $form_id ); return $form_id; @@ -163,7 +142,7 @@ public function cancel( $subscription_id, $subscription ) { // array of the data that will be sent to the API for use in the signature generation // amount, item_name, & item_description must be added here when performing an update call. $hash_array = array( - 'merchant-id' => '10003644', + 'merchant-id' => $give_options['payfast_customer_id'], 'version' => 'v1', 'timestamp' => date( 'Y-m-d' ) . 'T' . date( 'H:i:s' ), ); @@ -197,7 +176,7 @@ public function cancel( $subscription_id, $subscription ) { $signature = md5( $pf_param_string ); // payload array - required for update call (body values are amount, frequency, date). - $payload = []; // used for CURLOPT_POSTFIELDS. + $payload = array(); // used for CURLOPT_POSTFIELDS. // set up the url. $url = 'https://api.payfast.co.za/subscriptions/' . $subscription->profile_id . '/cancel'; @@ -205,40 +184,44 @@ public function cancel( $subscription_id, $subscription ) { $url .= '?testing=true'; } - // set up cURL. - $ch = curl_init( $url ); // add "?testing=true" to the end when testing. - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); - curl_setopt( $ch, CURLOPT_HEADER, false ); - curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true ); - curl_setopt( $ch, CURLOPT_TIMEOUT, 60 ); - curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'PUT' ); - curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query( $payload ) ); // for the body values such as amount, frequency, & date. - curl_setopt( $ch, CURLOPT_VERBOSE, true ); - curl_setopt( - $ch, CURLOPT_HTTPHEADER, array( - 'version: v1', - 'merchant-id: 10003644', - 'signature: ' . $signature, - 'timestamp: ' . $hash_array['timestamp'], - ) + // Set up the headers. + $headers = array( + 'version' => 'v1', + 'merchant-id' => $give_options['payfast_customer_id'], + 'signature' => $signature, + 'timestamp' => $hash_array['timestamp'], ); - // execute and close cURL. - $data = curl_exec( $ch ); - curl_close( $ch ); + // Set up the arguments. + $args = array( + 'method' => 'PUT', + 'timeout' => 60, + 'headers' => $headers, + 'body' => http_build_query( $payload ), + 'sslverify' => true, + ); + + // Make the request. + $response = wp_remote_request( $url, $args ); + + // Check for errors. + if ( is_wp_error( $response ) ) { + return false; + } - $data = json_decode( $data ); + // Decode the response body. + $data = json_decode( wp_remote_retrieve_body( $response ) ); - if ( '200' === isset( $data->code ) && $data->code ) { + // Check the response code. + if ( isset( $data->code ) && $data->code === '200' ) { return true; } else { return false; } - } /** - * Creates payment and redirects to PayFast + * Creates payment and redirects to Payfast * * @access public * @since 1.0 @@ -248,4 +231,43 @@ public function complete_signup() { $subscription = new Give_Subscription( $this->subscriptions['profile_id'], true ); payfast_process_payment( $this->purchase_data, $subscription ); } + + /** + * @param $set_or_multi + * @param $recurring_option + * @param $message + * @param int $form_id + * + * @return void + */ + public function setOrMulti( $set_or_multi, $recurring_option, $message, int $form_id ): void { + if ( $set_or_multi == 'yes_admin' && $recurring_option == 'multi' ) { + $prices = $_REQUEST['_give_donation_levels'] ?? array( '' ); + foreach ( $prices as $price_id => $price ) { + $period = $price['_give_period'] ?? 0; + + if ( in_array( $period, array( 'day', 'week' ) ) ) { + wp_die( + esc_html( $message ), + esc_html__( 'Error', 'give-recurring' ), + array( + 'response' => 400, + ) + ); + } + } + } elseif ( Give_Recurring()->is_recurring( $form_id ) ) { + $period = $_REQUEST['_give_period'] ?? 0; + + if ( in_array( $period, array( 'day', 'week' ) ) ) { + wp_die( + esc_html( $message ), + esc_html__( 'Error', 'give-recurring' ), + array( + 'response' => 400, + ) + ); + } + } + } } diff --git a/composer.json b/composer.json index f79425a..e8e2799 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "lightspeeddevelopment/lsx-give-payfast-gateway", - "description": "LSX PayFast Payment Gateway for Give.", + "description": "LSX Payfast Payment Gateway for Give.", "type": "wordpress-plugin", "require": { "php": ">=7.2", diff --git a/give-payfast.php b/give-payfast.php index 8538ab4..122c713 100644 --- a/give-payfast.php +++ b/give-payfast.php @@ -1,10 +1,10 @@ LSX Payfast Gateway for Give requires Give - Donation plugin to work normally. Please activate it or install it from here.

Back to the WordPress Plugins page." ); +} + +use Payfast\PayfastCommon\PayfastCommon; add_action( 'give_gateway_payfast', 'payfast_process_payment' ); @@ -29,15 +39,15 @@ function give_payfast_register_gateway( $gateways ) { if ( class_exists( 'Give_Recurring' ) ) { include_once plugin_dir_path( __FILE__ ) . 'classes/class-give-recurring-payfast.php'; - $give_recurring_payfast = new Give_Recurring_PayFast(); - $gateways['payfast'] = 'Give_Recurring_PayFast'; + $give_recurring_payfast = new Give_Recurring_Payfast(); + $gateways['payfast'] = 'Give_Recurring_Payfast'; } return $gateways; } add_action( 'give_recurring_available_gateways', 'give_payfast_register_gateway' ); /** - * PayFast does not need a CC form, so remove it. + * Payfast does not need a CC form, so remove it. */ add_action( 'give_payfast_cc_form', '__return_false' ); @@ -54,15 +64,15 @@ function give_payfast_load_textdomain() { */ function payfast_register_gateway( $gateways ) { $gateways['payfast'] = array( - 'admin_label' => 'PayFast', - 'checkout_label' => __( 'PayFast', 'payfast_give' ), + 'admin_label' => 'Payfast', + 'checkout_label' => __( 'Payfast', 'payfast_give' ), ); return $gateways; } add_filter( 'give_payment_gateways', 'payfast_register_gateway' ); /** - * Processes the order and redirect to the PayFast Merchant page + * Processes the order and redirect to the Payfast Merchant page */ function payfast_process_payment( $purchase_data, $recurring = false ) { $give_options = give_get_settings(); @@ -122,76 +132,78 @@ function payfast_process_payment( $purchase_data, $recurring = false ) { $seckey = $give_options['payfast_customer_id'] . $give_options['payfast_key'] . $total; $seckey = md5( $seckey ); - if ( give_is_test_mode() ) { - // test mode. - $payfast_url = 'https://sandbox.payfast.co.za/eng/process'; - } else { - // live mode. - $payfast_url = 'https://www.payfast.co.za/eng/process'; - } - - $redirect = get_permalink( $give_options['success_page'] ); - $query_string = null; - - $cancelurl = give_get_failed_transaction_uri(); + $payfast_url = give_is_test_mode() ? 'https://sandbox.payfast.co.za/eng/process' : 'https://www.payfast.co.za/eng/process'; + payfast_process_payment_stepB( $redirect, $give_options ); + payfast_process_payment_stepC( $give_options, $purchase_data, $payment, $total, $seckey, $recurring, $payfast_url ); + } +} - if ( give_is_test_mode() ) { - give_insert_payment_note( $payment, $cancelurl ); - } +function payfast_process_payment_stepB( $redirect, $give_options ) { + $redirect = get_permalink( $give_options['success_page'] ); + $query_string = null; - $payfast_args = 'merchant_id=' . $give_options['payfast_customer_id']; - $payfast_args .= '&merchant_key=' . $give_options['payfast_key']; - $payfast_args .= '&return_url=' . urlencode( apply_filters( 'give_success_page_redirect', $redirect, 'payfast', $query_string ) ); - $payfast_args .= '&cancel_url=' . urlencode( $cancelurl ); - $payfast_args .= '¬ify_url=' . urlencode( trailingslashit( home_url() ) ); - $payfast_args .= '&name_first=' . urlencode( $purchase_data['post_data']['give_first'] ); - $payfast_args .= '&name_last=' . urlencode( $purchase_data['post_data']['give_last'] ); - $payfast_args .= '&email_address=' . urlencode( $purchase_data['post_data']['give_email'] ); - $payfast_args .= '&m_payment_id=' . $payment; - $payfast_args .= '&amount=' . $total; - $payfast_args .= '&item_name=' . urlencode( $purchase_data['post_data']['give-form-title'] ); - $payfast_args .= '&custom_int1=' . $payment; - $payfast_args .= '&custom_str1=' . $seckey; - - if ( false !== $recurring ) { - $payfast_args .= '&custom_str2=' . $recurring->profile_id; - $payfast_args .= '&subscription_type=1'; - switch ( $purchase_data['period'] ) { - case 'month': - $frequency = 3; - break; - case 'year': - $frequency = 6; - break; - } - $payfast_args .= '&frequency=' . $frequency; - $payfast_args .= '&cycles=' . $purchase_data['times']; + $cancelurl = give_get_failed_transaction_uri(); - } + if ( give_is_test_mode() ) { + give_insert_payment_note( $payment, $cancelurl ); + } +} - if ( isset( $give_options['payfast_pass_phrase'] ) ) { - $pass_phrase = trim( $give_options['payfast_pass_phrase'] ); - } - $signature_str = $payfast_args; - if ( ! empty( $pass_phrase ) ) { - $signature_str .= '&passphrase=' . urlencode( $pass_phrase ); +function payfast_process_payment_stepC( $give_options, $purchase_data, $payment, $total, $seckey, $recurring, $payfast_url ) { + $payfast_args = 'merchant_id=' . $give_options['payfast_customer_id']; + $payfast_args .= '&merchant_key=' . $give_options['payfast_key']; + $payfast_args .= '&return_url=' . urlencode( give_get_success_page_uri() ); + $payfast_args .= '&cancel_url=' . urlencode( give_get_failed_transaction_uri() ); + $payfast_args .= '¬ify_url=' . urlencode( trailingslashit(home_url() ) ); + $payfast_args .= '&name_first=' . urlencode( $purchase_data['post_data']['give_first'] ); + $payfast_args .= '&name_last=' . urlencode( $purchase_data['post_data']['give_last'] ); + $payfast_args .= '&email_address=' . urlencode( $purchase_data['post_data']['give_email'] ); + $payfast_args .= '&m_payment_id=' . $payment; + $payfast_args .= '&amount=' . $total; + $payfast_args .= '&item_name=' . urlencode( $purchase_data['post_data']['give-form-title'] ) . $payment; + $payfast_args .= '&custom_int1=' . give_is_test_mode() ? 1 : 0; + $payfast_args .= '&custom_str1=' . $seckey; + + if ( false !== $recurring ) { + $payfast_args .= '&custom_str2=' . $recurring->profile_id; + $payfast_args .= '&subscription_type=1'; + switch ( $purchase_data['period'] ) { + case 'month': + $frequency = 3; + break; + case 'year': + $frequency = 6; + break; + default: + break; } + $payfast_args .= '&frequency=' . $frequency; + $payfast_args .= '&cycles=' . $purchase_data['times']; - $payfast_args .= '&signature=' . md5( $signature_str ); + } - if ( give_is_test_mode() && function_exists( 'give_record_log' ) ) { - give_record_log( 'Payfast - #' . $payment, $payfast_args, 0, 'api_requests' ); - give_insert_payment_note( $payment, $payfast_args ); - } + if ( isset( $give_options['payfast_pass_phrase'] ) ) { + $pass_phrase = trim( $give_options['payfast_pass_phrase'] ); + } + $signature_str = $payfast_args; + if ( ! empty( $pass_phrase ) ) { + $signature_str .= '&passphrase=' . urlencode( $pass_phrase ); + } - wp_redirect( $payfast_url . '?' . $payfast_args ); - exit(); + $payfast_args .= '&signature=' . md5( $signature_str ); + if ( give_is_test_mode() && function_exists( 'give_record_log' ) ) { + give_record_log( 'Payfast - #' . $payment, $payfast_args, 0, 'api_requests' ); + give_insert_payment_note( $payment, $payfast_args ); } + + wp_redirect( $payfast_url . '?' . $payfast_args ); + exit(); + } /** - * Processes the order and redirect to the PayFast Merchant page + * Processes the order and redirect to the Payfast Merchant page */ function payfast_get_realip() { @@ -211,178 +223,132 @@ function payfast_get_realip() { } /** - * An action that handles the call from PayFast to tell Give the order was Completed + * An action that handles the call from Payfast to tell Give the order was Completed */ function payfast_ipn() { + $give_options = give_get_settings(); + $payfastCommon = new PayfastCommon( 'yes' === $give_options['payfast_debug_log'] ); + $payfastCommon->pflog( 'ITN request received. Starting to process...' ); if ( function_exists( 'give_get_settings' ) ) { - $give_options = give_get_settings(); - if ( isset( $_REQUEST['m_payment_id'] ) ) { - - if ( give_is_test_mode() ) { - $pf_host = 'https://sandbox.payfast.co.za/eng/query/validate'; - give_insert_payment_note( $_REQUEST['m_payment_id'], 'ITN callback has been triggered.' ); - } else { - $pf_host = 'https://www.payfast.co.za/eng/query/validate'; - } - - $pf_error = false; - $pf_param_string = ''; - $validate_string = ''; - - if ( ! $pf_error ) { - // Strip any slashes in data. - foreach ( $_POST as $key => $val ) { - $_POST[ $key ] = stripslashes( $val ); - } - foreach ( $_POST as $key => $val ) { - if ( 'signature' != $key ) { - $pf_param_string .= $key . '=' . urlencode( $val ) . '&'; - } - } - $pf_param_string = substr( $pf_param_string, 0, - 1 ); - $validate_string = $pf_param_string; - if ( isset( $give_options['payfast_pass_phrase'] ) ) { - $pass_phrase = trim( $give_options['payfast_pass_phrase'] ); - if ( ! empty( $pass_phrase ) ) { - $pf_param_string .= '&passphrase=' . urlencode( $pass_phrase ); - } + give_insert_payment_note( $_REQUEST['m_payment_id'], 'ITN callback has been triggered.' ); + + $pfError = false; + $pf_param_string = ''; + $pfDone = false; + $pfData = $payfastCommon->pfGetData(); + $pfErrMsg = ''; + $payfastCommon->pflog( 'Payfast ITN call received' ); + + $payfastCommon->pflog( 'Payfast ITN call received' ); + + if ( ! $pfError && ! $pfDone ) { + // Notify Payfast that information has been received + header( 'HTTP/1.0 200 OK' ); + flush(); + // Get data sent by Payfast + $payfastCommon->pflog( 'Get posted data' ); + // Posted variables from ITN + $payfastCommon->pflog( 'Payfast Data: ' . print_r( $pfData, true ) ); + + if ( false === $pfData ) { + $pfError = true; + $pfErrMsg = $payfastCommon->PF_ERR_BAD_ACCESS; } } - $signature = md5( $pf_param_string ); - if ( give_is_test_mode() ) { - // translators: - give_insert_payment_note( $_REQUEST['m_payment_id'], sprintf( __( 'Signature Returned %1$s. Generated Signature %2$s.', 'payfast_give' ), $_POST['signature'], $signature ) ); - } + // Verify security signature + if ( ! $pfError && ! $pfDone ) { + $payfastCommon->pflog( 'Verify security signature' ); + give_insert_payment_note( $_REQUEST['m_payment_id'], 'Verify security signature' ); - if ( $signature != $_POST['signature'] ) { - $pf_error = 'SIGNATURE'; - $error = array( - 'oursig' => $signature, - 'vars' => $_POST, - ); - } + $passPhrase = $give_options['payfast_pass_phrase']; + $pfPassPhrase = empty( $passPhrase ) ? null : $passPhrase; + give_insert_payment_note( $_REQUEST['m_payment_id'], 'Signature Verified' ); - if ( ! $pf_error ) { - $valid_hosts = array( - 'www.payfast.co.za', - 'sandbox.payfast.co.za', - 'w1w.payfast.co.za', - 'w2w.payfast.co.za', - ); - - $valid_ips = array(); - $sender_ip = payfast_get_realip(); - foreach ( $valid_hosts as $pf_hostname ) { - $ips = gethostbynamel( $pf_hostname ); - - if ( false !== $ips ) { - $valid_ips = array_merge( $valid_ips, $ips ); - } + // If signature different, log for debugging + if ( ! $payfastCommon->pfValidSignature( $pfData, $pfParamString, $pfPassPhrase ) ) { + $pfError = true; + $pfErrMsg = $payfastCommon->PF_ERR_INVALID_SIGNATURE; + give_insert_payment_note( $_REQUEST['m_payment_id'], 'Signature verification failed' . $pfErrMsg ); } + } - $valid_ips = array_unique( $valid_ips ); + // Verify data received + verifyDataReceived( $pfError, $payfastCommon, $pfParamString ); - if ( ! in_array( $sender_ip, $valid_ips ) ) { - $pf_error = array( - 'FROM' => $sender_ip, - 'VALID' => $valid_ips, - ); - } - } + // Update donationa status + updateDonationStatus( $pfData['payment_status'] ); + } + } +} - /* - * If it fails for any reason, add that to the order. - */ - if ( false !== $pf_error ) { - // translators: - give_insert_payment_note( $_POST['m_payment_id'], sprintf( __( 'Payment Failed. The error is %s.', 'payfast_give' ), print_r( $pf_error, true ) ) ); - } else { - - $response = wp_remote_post( - $pf_host, array( - 'method' => 'POST', - 'timeout' => 60, - 'redirection' => 5, - 'httpversion' => '1.0', - 'blocking' => true, - 'headers' => array(), - 'body' => $validate_string, - 'cookies' => array(), - ) - ); - - if ( give_is_test_mode() ) { - give_insert_payment_note( - $_POST['m_payment_id'], sprintf( - // translators: - __( 'PayFast ITN Params - %1$s %2$s.', 'payfast_give' ), $pf_host, print_r( - array( - 'method' => 'POST', - 'timeout' => 60, - 'redirection' => 5, - 'httpversion' => '1.0', - 'blocking' => true, - 'headers' => array(), - 'body' => $validate_string, - 'cookies' => array(), - ), true - ) - ) - ); - // translators: - give_insert_payment_note( $_POST['m_payment_id'], sprintf( __( 'PayFast ITN Response. %s.', 'payfast_give' ), print_r( $response['body'], true ) ) ); - } +/** + * @param bool $pfError + * @param PayfastCommon $payfastCommon + * @param $pfParamString + * + * @return void + */ +function verifyDataReceived( bool $pfError, PayfastCommon $payfastCommon, $pfParamString ): void { + if ( ! $pfError ) { + give_insert_payment_note( $_REQUEST['m_payment_id'], 'Verify data received' ); - if ( ! is_wp_error( $response ) && ( $response['response']['code'] >= 200 || $response['response']['code'] < 300 ) ) { - $res = $response['body']; - if ( false == $res ) { - $pf_error = $response; + $pfHost = 'www.payfast.co.za'; - } - } - } + if ( give_is_test_mode() == 1 ) { + $pfHost = 'sandbox.payfast.co.za'; + } - if ( ! $pf_error ) { - $lines = explode( "\n", $res ); - } + $moduleInfo = [ + "pfSoftwareName" => 'Give - Donation', + "pfSoftwareVer" => '3.12.0', + "pfSoftwareModuleName" => 'Payfast-Give', + "pfModuleVer" => '1.3.0', + ]; - if ( ! $pf_error ) { - $result = trim( $lines[0] ); - - if ( strcmp( $result, 'VALID' ) === 0 ) { - if ( 'COMPLETE' == $_POST['payment_status'] ) { - - if ( ! empty( $_POST['custom_str2'] ) ) { - $subscription = new Give_Subscription( $_POST['custom_str2'], true ); - // Retrieve pending subscription from database and update it's status to active and set proper profile ID. - $subscription->update( - array( - 'profile_id' => $_POST['token'], - 'status' => 'active', - ) - ); - } - give_set_payment_transaction_id( $_POST['m_payment_id'], $_POST['pf_payment_id'] ); - // translators: - give_insert_payment_note( $_POST['m_payment_id'], sprintf( __( 'PayFast Payment Completed. The Transaction Id is %s.', 'payfast_give' ), $_POST['pf_payment_id'] ) ); - give_update_payment_status( $_POST['m_payment_id'], 'publish' ); - - } else { - // translators: - give_insert_payment_note( $_POST['m_payment_id'], sprintf( __( 'PayFast Payment Failed. The Response is %s.', 'payfast_give' ), print_r( $response['body'], true ) ) ); - } - } - } + $pfValid = $payfastCommon->pfValidData( $moduleInfo, $pfHost, $pfParamString ); + if ( $pfValid ) { + give_insert_payment_note( $_REQUEST['m_payment_id'], 'ITN message successfully verified by Payfast' ); + } else { + $pfError = true; + $pfErrMsg = $payfastCommon->PF_ERR_BAD_ACCESS; + give_insert_payment_note( $_REQUEST['m_payment_id'], 'Verify data failed' . $pfErrMsg ); } } +} + +/** + * @param $payment_status + * + * @return void + */ +function updateDonationStatus( $payment_status ): void { + if ( 'COMPLETE' == $payment_status ) { + if ( ! empty( $_POST['custom_str2'] ) ) { + $subscription = new Give_Subscription( $_POST['custom_str2'], true ); + // Retrieve pending subscription from database and update it's status to active and set proper profile ID. + $subscription->update( + array( + 'profile_id' => $_POST['token'], + 'status' => 'active', + ) + ); + } + give_set_payment_transaction_id( $_POST['m_payment_id'], $_POST['pf_payment_id'] ); + // translators: + give_insert_payment_note( $_POST['m_payment_id'], sprintf( __( 'Payfast Payment Completed. The Transaction Id is %s.', 'payfast_give' ), $_POST['pf_payment_id'] ) ); + give_update_payment_status( $_POST['m_payment_id'], 'publish' ); + } else { + // translators: + give_insert_payment_note( $_POST['m_payment_id'], sprintf( __( 'Payfast Payment Failed. The Response is %s.', 'payfast_give' ), print_r( $payment_status, true ) ) ); + } } add_action( 'wp_head', 'payfast_ipn' ); /** - * Registers our PayFast setting with Give. + * Registers our Payfast setting with Give. * * @param $settings * @return array @@ -393,30 +359,38 @@ function payfast_add_settings( $settings ) { array( 'id' => 'payfast_settings', - 'name' => __( 'PayFast Settings', 'payfast_give' ), + 'name' => __( 'Payfast Settings', 'payfast_give' ), 'type' => 'give_title', ), array( 'id' => 'payfast_customer_id', - 'name' => __( 'PayFast Merchant ID', 'payfast_give' ), - 'desc' => __( 'Please enter your PayFast Merchant Id; this is needed in order to take payment.', 'payfast_give' ), + 'name' => __( 'Payfast Merchant ID', 'payfast_give' ), + 'desc' => __( 'Please enter your Payfast Merchant Id; this is needed in order to take payment.', 'payfast_give' ), 'type' => 'text', 'size' => 'regular', ), array( 'id' => 'payfast_key', - 'name' => __( 'PayFast Key', 'payfast_give' ), - 'desc' => __( 'Please enter your PayFast Key; this is needed in order to take payment.', 'payfast_give' ), + 'name' => __( 'Payfast Key', 'payfast_give' ), + 'desc' => __( 'Please enter your Payfast Key; this is needed in order to take payment.', 'payfast_give' ), 'type' => 'text', 'size' => 'regular', ), array( 'id' => 'payfast_pass_phrase', 'name' => __( 'Account Passphrase', 'payfast_give' ), - 'desc' => __( 'This is set by yourself in the "Settings" section of the logged in area of the PayFast Dashboard.', 'payfast_give' ), + 'desc' => __( 'This is set by yourself in the "Settings" section of the logged in area of the Payfast Dashboard.', 'payfast_give' ), 'type' => 'text', 'size' => 'regular', ), + array( + 'id' => 'payfast_debug_log', + 'name' => __( 'Debug to log server-to-server communication:', 'payfast_give' ), + 'desc' => __( 'Enable Debug to log the server-to-server communication.', 'payfast_give' ), + 'type' => 'radio', // Change type to 'radio' + 'options' => array( 'yes' => __( 'Enable', 'payfast_give' ), 'no' => __( 'Disable', 'payfast_give' ) ), + 'default' => 'no', // Set default option to 'no' (disabled) + ), ); return array_merge( $settings, $payfast_settings ); diff --git a/languages/payfast_give.pot b/languages/payfast_give.pot index ca58e3d..f3f7789 100644 --- a/languages/payfast_give.pot +++ b/languages/payfast_give.pot @@ -19,7 +19,7 @@ msgid "South African Rand (R)" msgstr "" #: ../give-payfast.php:61 -msgid "PayFast" +msgid "Payfast" msgstr "" #: ../give-payfast.php:88 @@ -32,11 +32,11 @@ msgstr "" #: ../give-payfast.php:260 #, php-format -msgid "PayFast Payment Completed. The Transaction Id is %s." +msgid "Payfast Payment Completed. The Transaction Id is %s." msgstr "" #: ../give-payfast.php:277 -msgid "PayFast Settings" +msgid "Payfast Settings" msgstr "" #: ../give-payfast.php:278 @@ -44,19 +44,19 @@ msgid "Configure the gateway settings" msgstr "" #: ../give-payfast.php:283 -msgid "PayFast Merchant Id" +msgid "Payfast Merchant Id" msgstr "" #: ../give-payfast.php:284 msgid "" -"Please enter your PayFast Merchant Id; this is needed in order to take " +"Please enter your Payfast Merchant Id; this is needed in order to take " "payment." msgstr "" #: ../give-payfast.php:290 -msgid "PayFast Key" +msgid "Payfast Key" msgstr "" #: ../give-payfast.php:291 -msgid "Please enter your PayFast Key; this is needed in order to take payment." +msgid "Please enter your Payfast Key; this is needed in order to take payment." msgstr "" diff --git a/readme.md b/readme.md index 228fceb..da550a8 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ -

LSX PayFast Gateway for Give Extension +

LSX Payfast Gateway for Give Extension

-

LSX PayFast Gateway for Give

+

LSX Payfast Gateway for Give

license @@ -14,10 +14,10 @@ If you previously bought the add-on, the following information is very important. There are a few steps you need to follow when moving to the new version. * Deactivate and delete the previous plugin completely. -* Then install the new version. Either:Search for the plugin, "LSX PayFast Gateway for Give", via the plugins screen inside your WordPress dashboard +* Then install the new version. Either:Search for the plugin, "LSX Payfast Gateway for Give", via the plugins screen inside your WordPress dashboard * Or download the plugin from WordPress.org and upload it via your plugins screen -Unfortunately you may need to reconfigure payments via PayFast in Give. The plugin changed in a way that could not avoid this. +Unfortunately you may need to reconfigure payments via Payfast in Give. The plugin changed in a way that could not avoid this. ## Documentation & Support Whether you are an existing or new user of this plugin, detailed setup documentation can be found [here](https://www.lsdev.biz/lsx/documentation/lsx-extensions/lsx-payfast-payment-gateway-for-give/) @@ -26,16 +26,16 @@ The plugin is free, but **we do not offer free support**. Please consult our doc * Ensure that WordPress, your theme and plugins are fully up to date. * Activate a default WordPress theme, like twenty sixteen or twenty eighteen -* Disable all plugins but Give and the PayFast Extension +* Disable all plugins but Give and the Payfast Extension * Test your payments again * If it works, it means there is a problem with your theme or one of your other plugins that isn't covered by our support policy. Or, there is a problem with your server environment. * If it still does not work, create a user with administrative access and email that to us at [support@lsdev.biz](mailto:support@lsdev.biz), please include detailed steps on how to reproduce the error, reference any relevant links and include screenshots. We also need to know what operating system and browser you are using. If you need assistance setting up the plugin, a customization, or it needs to be made compatible with your theme and plugins, please contact us for a quote. -## Contributing to the LSX PayFast Gateway for Give Plugin +## Contributing to the LSX Payfast Gateway for Give Plugin -If you're a developer who's spotted a bug issue and have a fix, or simply have functionality you think would extend our core theme, we are always happy to accept your contribution! Visit the [LSX PayFast Gateway for Give Plugin on Github](https://github.com/lightspeeddevelopment/lsx-give-payfast-gateway) and submit a Pull Request with your updates. +If you're a developer who's spotted a bug issue and have a fix, or simply have functionality you think would extend our core theme, we are always happy to accept your contribution! Visit the [LSX Payfast Gateway for Give Plugin on Github](https://github.com/lightspeeddevelopment/lsx-give-payfast-gateway) and submit a Pull Request with your updates. ## Like what you see? Work with us at LightSpeed diff --git a/readme.txt b/readme.txt index d64c9c9..b287303 100644 --- a/readme.txt +++ b/readme.txt @@ -1,34 +1,34 @@ -=== LSX PayFast Gateway for Give === +=== LSX Payfast Gateway for Give === Tags: lsx, payment gateway, payfast, givewp, donations Requires at least: 5.3 -Tested up to: 6.3 -Stable tag: 1.2.8 -Contributors: feedmymedia +Tested up to: 6.6 +Stable tag: 1.3.0 +Contributors: feedmymedia, appinlet License: GPL3+ License URI: http://www.gnu.org/licenses/gpl-3.0.html -PayFast payment gateway for Give. +Payfast payment gateway for Give. == Description == -PayFast is one of the most popular gateways in South Africa. It’s an off-site gateway that allows donors to give securely and then be returned to your website and displayed a donation receipt. +Payfast is one of the most popular gateways in South Africa. It’s an off-site gateway that allows donors to give securely and then be returned to your website and displayed a donation receipt. -= Getting Started with PayFast = += Getting Started with Payfast = -In order to accept payments with PayFast using Give you will need to have an active PayFast account, the PayFast Give add-on, and the Give Core plugin installed and activated. +In order to accept payments with Payfast using Give you will need to have an active Payfast account, the Payfast Give add-on, and the Give Core plugin installed and activated. Click here for instructions on installing and activating Give add-ons. Note: You can always access your add-on purchase receipts, downloads, and licenses from your Give Account dashboard. -After the PayFast add-on is activated, go to “Donations > Settings” and click on the Payment Gateways tab. There you will see the default Payment Gateways (PayPal Standard, Test Payment, and Offline Donations). You should also see PayFast as an option as well. Click on the PayFast checkbox to enable it as an active payment gateway for your website. +After the Payfast add-on is activated, go to “Donations > Settings” and click on the Payment Gateways tab. There you will see the default Payment Gateways (PayPal Standard, Test Payment, and Offline Donations). You should also see Payfast as an option as well. Click on the Payfast checkbox to enable it as an active payment gateway for your website. = It's Free, and always ill be = -We’re firm believers in open source – that’s why we’re releasing the LSX PayFast Gateway for Give plugin for free, forever. +We’re firm believers in open source – that’s why we’re releasing the LSX Payfast Gateway for Give plugin for free, forever. = Contributing = -If you're a developer who's spotted a bug issue and have a fix, or simply have functionality you think would extend our core theme, we are always happy to accept your contribution! Visit the [LSX PayFast Gateway for Give on Github](https://github.com/lightspeeddevelopment/lsx-give-payfast-gateway/) and submit a Pull Request with your updates. +If you're a developer who's spotted a bug issue and have a fix, or simply have functionality you think would extend our core theme, we are always happy to accept your contribution! Visit the [LSX Payfast Gateway for Give on Github](https://github.com/lightspeeddevelopment/lsx-give-payfast-gateway/) and submit a Pull Request with your updates. == Installation == @@ -41,23 +41,23 @@ If you're a developer who's spotted a bug issue and have a fix, or simply have f == Frequently Asked Questions == = What does this plugin do? = -Enables PayFast Payment Gateway +Enables Payfast Payment Gateway = Where can I get support? = For help with add-ons from LightSpeed, use our support package plan. = Where can I report bugs or contribute to the project? = -Bugs can be reported either in our support account or preferably on the LSX PayFast Gateway for Give [GitHub repository](https://github.com/lightspeeddevelopment/lsx-give-payfast-gateway). +Bugs can be reported either in our support account or preferably on the LSX Payfast Gateway for Give [GitHub repository](https://github.com/lightspeeddevelopment/lsx-give-payfast-gateway). -= The LSX PayFast Gateway for Give plugin is awesome! Can I contribute? = += The LSX Payfast Gateway for Give plugin is awesome! Can I contribute? = Yes you can! Join in on our [GitHub repository](https://github.com/lightspeeddevelopment/lsx-give-payfast-gateway) = I need custom functionality for this plugin. Can you build it? = Yes. Just send us a message via [contact form](https://www.lsdev.biz/contact/) with precise information about what you require. -== PayFast payment gateway for Give Help & Support == +== Payfast payment gateway for Give Help & Support == We offer premium support for this plugin. Premium support that can be purchased [via our website](https://www.lsdev.biz/services/support/). -If you are experiencing issues with the LSX Importer for PayFast payment gateway for Give Plugin & have experience with Github, please log any bug issues you are having on the [PayFast payment gateway for Give Github Issues](https://github.com/lightspeeddevelopment/lsx-give-payfast-gateway/issues/) page. +If you are experiencing issues with the LSX Importer for Payfast payment gateway for Give Plugin & have experience with Github, please log any bug issues you are having on the [Payfast payment gateway for Give Github Issues](https://github.com/lightspeeddevelopment/lsx-give-payfast-gateway/issues/) page.