$this->_paymentProcessor = $paymentProcessor;
}
+ /**
+ * @var GuzzleHttp\Client
+ */
+ protected $guzzleClient;
+
+ /**
+ * @return \GuzzleHttp\Client
+ */
+ public function getGuzzleClient(): \GuzzleHttp\Client {
+ return $this->guzzleClient ?? new \GuzzleHttp\Client();
+ }
+
+ /**
+ * @param \GuzzleHttp\Client $guzzleClient
+ */
+ public function setGuzzleClient(\GuzzleHttp\Client $guzzleClient) {
+ $this->guzzleClient = $guzzleClient;
+ }
+
/**
* Helper function to check which payment processor type is being used.
*
* @throws \Civi\Payment\Exception\PaymentProcessorException
*/
public function doPayment(&$params, $component = 'contribute') {
+ $this->_component = $component;
if ($this->isPayPalType($this::PAYPAL_EXPRESS) || ($this->isPayPalType($this::PAYPAL_PRO) && !empty($params['token']))) {
- $this->_component = $component;
return $this->doExpressCheckout($params);
}
- return parent::doPayment($params, $component);
+
+ $statuses = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id', 'validate');
+
+ // If we have a $0 amount, skip call to processor and set payment_status to Completed.
+ // Conceivably a processor might override this - perhaps for setting up a token - but we don't
+ // have an example of that at the mome.
+ if ($params['amount'] == 0) {
+ $result['payment_status_id'] = array_search('Completed', $statuses);
+ $result['payment_status'] = 'Completed';
+ return $result;
+ }
+
+ if ($this->_paymentProcessor['billing_mode'] == 4) {
+ $this->doPaymentRedirectToPayPal($params, $component);
+ // redirect calls CiviExit() so execution is stopped
+ }
+ else {
+ $result = $this->doPaymentPayPalButton($params, $component);
+ if (is_array($result) && !isset($result['payment_status_id'])) {
+ if (!empty($params['is_recur'])) {
+ // See comment block.
+ $result['payment_status_id'] = array_search('Pending', $statuses);
+ $result['payment_status'] = 'Pending';
+ }
+ else {
+ $result['payment_status_id'] = array_search('Completed', $statuses);
+ $result['payment_status'] = 'Completed';
+ }
+ }
+ }
+ if (is_a($result, 'CRM_Core_Error')) {
+ CRM_Core_Error::deprecatedFunctionWarning('payment processors should throw exceptions rather than return errors');
+ throw new PaymentProcessorException(CRM_Core_Error::getMessages($result));
+ }
+ return $result;
+ }
+
+ /**
+ * Temporary function to catch transition to doPaymentPayPalButton()
+ * @deprecated
+ */
+ public function doDirectPayment(&$params) {
+ CRM_Core_Error::deprecatedFunctionWarning('doPayment');
+ return $this->doPaymentPayPalButton($params);
}
/**
* the result in an nice formatted array (or an error object)
* @throws \Civi\Payment\Exception\PaymentProcessorException
*/
- public function doDirectPayment(&$params, $component = 'contribute') {
+ public function doPaymentPayPalButton(&$params, $component = 'contribute') {
$args = [];
$this->initialize($args, 'DoDirectPayment');
}
/**
- * @return null|string
- * @throws \Civi\Payment\Exception\PaymentProcessorException
+ * Get url for users to manage this recurring contribution for this processor.
+ *
+ * @param int $entityID
+ * @param null $entity
+ * @param string $action
+ *
+ * @return string|null
+ * @throws \CRM_Core_Exception
*/
- public function cancelSubscriptionURL() {
+ public function subscriptionURL($entityID = NULL, $entity = NULL, $action = 'cancel') {
if ($this->isPayPalType($this::PAYPAL_STANDARD)) {
+ if ($action !== 'cancel') {
+ return NULL;
+ }
return "{$this->_paymentProcessor['url_site']}cgi-bin/webscr?cmd=_subscr-find&alias=" . urlencode($this->_paymentProcessor['user_name']);
}
- else {
- return NULL;
- }
+ return parent::subscriptionURL($entityID, $entity, $action);
}
/**
}
$this->_component = $params['component'];
$token = $this->setExpressCheckOut($params);
+ $siteUrl = rtrim($this->_paymentProcessor['url_site'], '/');
return [
'pre_approval_parameters' => ['token' => $token],
- 'redirect_url' => $this->_paymentProcessor['url_site'] . "/cgi-bin/webscr?cmd=_express-checkout&token=$token",
+ 'redirect_url' => $siteUrl . "/cgi-bin/webscr?cmd=_express-checkout&token=$token",
];
}
+ /**
+ * Temporary function to catch transition to doPaymentRedirectToPayPal()
+ * @deprecated
+ */
+ public function doTransferCheckout(&$params, $component = 'contribute') {
+ CRM_Core_Error::deprecatedFunctionWarning('doPayment');
+ $this->doPaymentRedirectToPayPal($params);
+ }
+
/**
* @param array $params
* @param string $component
*
* @throws Exception
*/
- public function doTransferCheckout(&$params, $component = 'contribute') {
-
+ public function doPaymentRedirectToPayPal(&$params, $component = 'contribute') {
$notifyParameters = ['module' => $component];
$notifyParameterMap = [
'contactID' => 'contactID',
// Allow further manipulation of the arguments via custom hooks ..
CRM_Utils_Hook::alterPaymentProcessorParams($this, $params, $paypalParams);
+ /*
+ * PayPal urlencodes the IPN Notify URL. For sites not using Clean URLs (or
+ * using Shortcodes in WordPress) this results in "%2F" becoming "%252F" and
+ * therefore incomplete transactions. We need to prevent that.
+ * @see https://lab.civicrm.org/dev/core/-/issues/1931
+ */
+ $paypalParams['notify_url'] = rawurldecode($paypalParams['notify_url']);
+
$uri = '';
foreach ($paypalParams as $key => $value) {
if ($value === NULL) {
throw new PaymentProcessorException('curl functions NOT available.');
}
- //setting the curl parameters.
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_VERBOSE, 0);
+ $response = (string) $this->getGuzzleClient()->post($url, [
+ 'body' => $nvpreq,
+ 'curl' => [
+ CURLOPT_RETURNTRANSFER => TRUE,
+ CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'),
+ ],
+ ])->getBody();
- //turning off the server and peer verification(TrustManager Concept).
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, Civi::settings()->get('verifySSL'));
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, Civi::settings()->get('verifySSL') ? 2 : 0);
-
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_POST, 1);
-
- //setting the nvpreq as POST FIELD to curl
- curl_setopt($ch, CURLOPT_POSTFIELDS, $nvpreq);
-
- //getting response from server
- $response = curl_exec($ch);
-
- //converting NVPResponse to an Associative Array
$result = self::deformat($response);
- if (curl_errno($ch)) {
- throw new PaymentProcessorException(ts('Network error') . ' ' . curl_error($ch) . curl_errno($ch), curl_errno($ch));
- }
- curl_close($ch);
-
- $outcome = strtolower(CRM_Utils_Array::value('ack', $result));
+ $outcome = strtolower($result['ack'] ?? '');
- if ($outcome != 'success' && $outcome != 'successwithwarning') {
+ if ($outcome !== 'success' && $outcome !== 'successwithwarning') {
throw new PaymentProcessorException("{$result['l_shortmessage0']} {$result['l_longmessage0']}");
}