/** * Action payment. * * @param array $params * * @return array * API result array. * @throws CiviCRM_API3_Exception */ function civicrm_api3_payment_processor_pay($params) { $processor = Civi\Payment\System::singleton()->getById($params['payment_processor_id']); $processor->setPaymentProcessor(civicrm_api3('PaymentProcessor', 'getsingle', array('id' => $params['payment_processor_id']))); $result = $processor->doDirectPayment($params); if (is_a($result, 'CRM_Core_Error')) { throw API_Exception('failed'); } return civicrm_api3_create_success($result, $params); }
public function setUp() { parent::setUp(); $this->_paymentProcessorID = $this->paymentProcessorAuthorizeNetCreate(); $this->processor = Civi\Payment\System::singleton()->getById($this->_paymentProcessorID); $this->_financialTypeId = 1; // for some strange unknown reason, in batch mode this value gets set to null // so crude hack here to avoid an exception and hence an error $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array(); }
public function testIPNPaymentSuccess() { $pendingStatusID = CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name'); $completedStatusID = CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name'); $params = array('payment_processor_id' => $this->_paymentProcessorID, 'contact_id' => $this->_contactID, 'trxn_id' => NULL, 'invoice_id' => $this->_invoiceID, 'contribution_status_id' => $pendingStatusID); $this->_contributionID = $this->contributionCreate($params); $contribution = $this->callAPISuccess('contribution', 'get', array('id' => $this->_contributionID, 'sequential' => 1)); // assert that contribution created before handling payment via paypal standard has no transaction id set and pending status $this->assertEquals(NULL, $contribution['values'][0]['trxn_id']); $this->assertEquals($pendingStatusID, $contribution['values'][0]['contribution_status_id']); global $_REQUEST; $_REQUEST = array('q' => CRM_Utils_System::url('civicrm/payment/ipn/' . $this->_paymentProcessorID)) + $this->getPaypalTransaction(); $paymentProcesors = civicrm_api3('PaymentProcessor', 'getsingle', array('id' => $this->_paymentProcessorID)); $payment = Civi\Payment\System::singleton()->getByProcessor($paymentProcesors); $payment->handlePaymentNotification(); $contribution = $this->callAPISuccess('contribution', 'get', array('id' => $this->_contributionID, 'sequential' => 1)); // assert that contribution is completed after getting response from paypal standard which has transaction id set and completed status $this->assertEquals($_REQUEST['txn_id'], $contribution['values'][0]['trxn_id']); $this->assertEquals($completedStatusID, $contribution['values'][0]['contribution_status_id']); }
/** * Validate the payment instrument values before passing it to the payment processor * We want this to be overrideable by the payment processor, and default to using * this object's validCreditCard for credit cards (implemented as the default in the Payment class). */ public static function validatePaymentInstrument($payment_processor_id, $values, &$errors, $form) { // ignore if we don't have a payment instrument to validate (e.g. backend payments) if ($payment_processor_id > 0) { $payment = Civi\Payment\System::singleton()->getById($payment_processor_id); $payment->validatePaymentInstrument($values, $errors); } }
/** * Get all payment processors as an array of objects. * * @param string|NULL $mode * only return this mode - test|live or NULL for all * @param bool $reset * * @throws CiviCRM_API3_Exception * @return array */ public static function getAllPaymentProcessors($mode, $reset = FALSE) { $cacheKey = 'CRM_Financial_BAO_Payment_Processor_' . ($mode ? 'test' : 'all'); if (!$reset) { $processors = CRM_Utils_Cache::singleton()->get($cacheKey); if (!empty($processors)) { return $processors; } } $retrievalParameters = array('is_active' => TRUE, 'options' => array('sort' => 'is_default DESC, name'), 'api.payment_processor_type.getsingle' => 1); if ($mode == 'test') { $retrievalParameters['is_test'] = 1; } elseif ($mode == 'live') { $retrievalParameters['is_test'] = 0; } $processors = civicrm_api3('payment_processor', 'get', $retrievalParameters); foreach ($processors['values'] as $processor) { $fieldsToProvide = array('user_name', 'password', 'signature', 'subject', 'is_recur'); foreach ($fieldsToProvide as $field) { // Prevent e-notices in processor classes when not configured. if (!isset($processor[$field])) { $processor[$field] = NULL; } } $processors['values'][$processor['id']]['payment_processor_type'] = $processor['payment_processor_type'] = $processors['values'][$processor['id']]['api.payment_processor_type.getsingle']['name']; $processors['values'][$processor['id']]['object'] = Civi\Payment\System::singleton()->getByProcessor($processor); } CRM_Utils_Cache::singleton()->set($cacheKey, $processors['values']); return $processors['values']; }
/** * Retrieve payment processor id / info/ object based on component-id. * * @todo function needs revisiting. The whole 'info / obj' thing is an overload. Recommend creating new functions * that are entity specific as there is little shared code specific to obj or info * * Also, it does not accurately derive the processor - for a completed contribution the best place to look is in the * relevant financial_trxn record. For a recurring contribution it is in the contribution_recur table. * * For a membership the relevant contribution_recur should be derived & then resolved as above. The contribution page * is never a reliable place to look as there can be more than one configured. For a pending contribution there is * no way to derive the processor - but hey - what processor? it didn't go through! * * Query for membership might look something like: * SELECT fte.payment_processor_id * FROM civicrm_membership mem * INNER JOIN civicrm_line_item li ON ( mem.id = li.entity_id AND li.entity_table = 'civicrm_membership') * INNER JOIN civicrm_contribution con ON ( li.contribution_id = con.id ) * LEFT JOIN civicrm_entity_financial_trxn ft ON ft.entity_id = con.id AND ft.entity_table = * 'civicrm_contribution' * LEFT JOIN civicrm_financial_trxn fte ON fte.id = ft.financial_trxn_id * * @param int $entityID * @param string $component * Component. * @param string $type * Type of payment information to be retrieved. * * @return int|array|object */ public static function getProcessorForEntity($entityID, $component = 'contribute', $type = 'id') { $result = NULL; if (!in_array($component, array('membership', 'contribute', 'recur'))) { return $result; } //FIXME: if ($component == 'membership') { $sql = "\n SELECT cr.payment_processor_id as ppID1, cp.payment_processor as ppID2, con.is_test\n FROM civicrm_membership mem\nINNER JOIN civicrm_membership_payment mp ON ( mem.id = mp.membership_id )\nINNER JOIN civicrm_contribution con ON ( mp.contribution_id = con.id )\n LEFT JOIN civicrm_contribution_recur cr ON ( mem.contribution_recur_id = cr.id )\n LEFT JOIN civicrm_contribution_page cp ON ( con.contribution_page_id = cp.id )\n WHERE mp.membership_id = %1"; } elseif ($component == 'contribute') { $sql = "\n SELECT cr.payment_processor_id as ppID1, cp.payment_processor as ppID2, con.is_test\n FROM civicrm_contribution con\n LEFT JOIN civicrm_contribution_recur cr ON ( con.contribution_recur_id = cr.id )\n LEFT JOIN civicrm_contribution_page cp ON ( con.contribution_page_id = cp.id )\n WHERE con.id = %1"; } elseif ($component == 'recur') { $sql = "\n SELECT cr.payment_processor_id as ppID1, NULL as ppID2, cr.is_test\n FROM civicrm_contribution_recur cr\n WHERE cr.id = %1"; } //we are interesting in single record. $sql .= ' LIMIT 1'; $params = array(1 => array($entityID, 'Integer')); $dao = CRM_Core_DAO::executeQuery($sql, $params); if (!$dao->fetch()) { return $result; } $ppID = isset($dao->ppID1) && $dao->ppID1 ? $dao->ppID1 : (isset($dao->ppID2) ? $dao->ppID2 : NULL); $mode = isset($dao->is_test) && $dao->is_test ? 'test' : 'live'; if (!$ppID || $type == 'id') { $result = $ppID; } elseif ($type == 'info') { $result = self::getPayment($ppID, $mode); } elseif ($type == 'obj' && is_numeric($ppID)) { try { $paymentProcessor = civicrm_api3('PaymentProcessor', 'getsingle', array('id' => $ppID)); } catch (API_Exception $e) { // Unable to load the processor because this function uses an unreliable method to derive it. // The function looks to load the payment processor ID from the contribution page, which // can support multiple processors. } $result = Civi\Payment\System::singleton()->getByProcessor($paymentProcessor); } return $result; }
/** * Handle pre approval for processors. * * This fits with the flow where a pre-approval is done and then confirmed in the next stage when confirm is hit. * * This function is shared between contribution & event forms & this is their common class. * * However, this should be seen as an in-progress refactor, the end goal being to also align the * backoffice forms that action payments. * * @param array $params */ protected function handlePreApproval(&$params) { try { $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); $params['component'] = 'contribute'; $result = $payment->doPreApproval($params); if (empty($result)) { // This could happen, for example, when paypal looks at the button value & decides it is not paypal express. return; } } catch (\Civi\Payment\Exception\PaymentProcessorException $e) { CRM_Core_Error::displaySessionError($e->getMessage()); CRM_Utils_System::redirect($params['cancelURL']); } $this->set('pre_approval_parameters', $result['pre_approval_parameters']); if (!empty($result['redirect_url'])) { CRM_Utils_System::redirect($result['redirect_url']); } }
/** * Test the submit function of the membership form. */ public function testSubmitRecurCompleteInstant() { $form = $this->getForm(); $processor = Civi\Payment\System::singleton()->getById($this->_paymentProcessorID); $processor->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'kettles boil water')); $this->callAPISuccess('MembershipType', 'create', array('id' => $this->membershipTypeAnnualFixedID, 'duration_unit' => 'month', 'duration_interval' => 1, 'auto_renew' => TRUE)); $form->preProcess(); $this->createLoggedInUser(); $params = $this->getBaseSubmitParams(); $form->_mode = 'test'; $form->submit($params); $membership = $this->callAPISuccessGetSingle('Membership', array('contact_id' => $this->_individualId)); $this->callAPISuccessGetCount('ContributionRecur', array('contact_id' => $this->_individualId), 1); $contribution = $this->callAPISuccess('Contribution', 'get', array('contact_id' => $this->_individualId, 'is_test' => TRUE)); $this->callAPISuccessGetCount('LineItem', array('entity_id' => $membership['id'], 'entity_table' => 'civicrm_membership', 'contribution_id' => $contribution['id']), 1); }
/** * Test the submit function of the membership form. */ public function testSubmitRecurCompleteInstant() { $form = $this->getForm(); $mut = new CiviMailUtils($this, TRUE); $processor = Civi\Payment\System::singleton()->getById($this->_paymentProcessorID); $processor->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'kettles boil water', 'fee_amount' => 0.14)); $this->callAPISuccess('MembershipType', 'create', array('id' => $this->membershipTypeAnnualFixedID, 'duration_unit' => 'month', 'duration_interval' => 1, 'auto_renew' => TRUE)); $form->preProcess(); $this->createLoggedInUser(); $params = $this->getBaseSubmitParams(); $form->_mode = 'test'; $form->_contactID = $this->_individualId; $form->testSubmit($params); $membership = $this->callAPISuccessGetSingle('Membership', array('contact_id' => $this->_individualId)); $this->callAPISuccessGetCount('ContributionRecur', array('contact_id' => $this->_individualId), 1); $contribution = $this->callAPISuccess('Contribution', 'getsingle', array('contact_id' => $this->_individualId, 'is_test' => TRUE)); $this->assertEquals(0.14, $contribution['fee_amount']); $this->assertEquals('kettles boil water', $contribution['trxn_id']); $this->callAPISuccessGetCount('LineItem', array('entity_id' => $membership['id'], 'entity_table' => 'civicrm_membership', 'contribution_id' => $contribution['id']), 1); $mut->checkMailLog(array('=========================================================== Billing Name and Address =========================================================== Test 10 Test St Test, AR 90210 US', '=========================================================== Membership Information =========================================================== Membership Type: AnnualFixed Membership Start Date: ', '=========================================================== Credit Card Information =========================================================== Visa ************1111 Expires: ')); $mut->stop(); }
/** * Create array of message information - ie. return html version, txt version, to field * * @param array $input * Incoming information. * - is_recur - should this be treated as recurring (not sure why you wouldn't * just check presence of recur object but maintaining legacy approach * to be careful) * @param array $ids * IDs of related objects. * @param array $values * Any values that may have already been compiled by calling process. * This is augmented by values 'gathered' by gatherMessageValues * @param bool $recur * @param bool $returnMessageText * Distinguishes between whether to send message or return. * message text. We are working towards this function ALWAYS returning message text & calling * function doing emails / pdfs with it * * @return array * messages * @throws Exception */ public function composeMessageArray(&$input, &$ids, &$values, $recur = FALSE, $returnMessageText = TRUE) { $this->loadRelatedObjects($input, $ids); if (empty($this->_component)) { $this->_component = CRM_Utils_Array::value('component', $input); } //not really sure what params might be passed in but lets merge em into values $values = array_merge($this->_gatherMessageValues($input, $values, $ids), $values); $template = CRM_Core_Smarty::singleton(); $this->_assignMessageVariablesToTemplate($values, $input, $template, $recur, $returnMessageText); //what does recur 'mean here - to do with payment processor return functionality but // what is the importance if ($recur && !empty($this->_relatedObjects['paymentProcessor'])) { $paymentObject = Civi\Payment\System::singleton()->getByProcessor($this->_relatedObjects['paymentProcessor']); $entityID = $entity = NULL; if (isset($ids['contribution'])) { $entity = 'contribution'; $entityID = $ids['contribution']; } if (!empty($ids['membership'])) { //not sure whether is is possible for this not to be an array - load related contacts loads an array but this code was expecting a string // the addition of the casting is in case it could get here & be a string. Added in 4.6 - maybe remove later? This AuthorizeNetIPN & PaypalIPN tests hit this // line having loaded an array $ids['membership'] = (array) $ids['membership']; $entity = 'membership'; $entityID = $ids['membership'][0]; } $template->assign('cancelSubscriptionUrl', $paymentObject->subscriptionURL($entityID, $entity)); $template->assign('updateSubscriptionBillingUrl', $paymentObject->subscriptionURL($entityID, $entity, 'billing')); $template->assign('updateSubscriptionUrl', $paymentObject->subscriptionURL($entityID, $entity, 'update')); if ($this->_relatedObjects['paymentProcessor']['billing_mode'] & CRM_Core_Payment::BILLING_MODE_FORM) { //direct mode showing billing block, so use directIPN for temporary $template->assign('contributeMode', 'directIPN'); } } // todo remove strtolower - check consistency if (strtolower($this->_component) == 'event') { $eventParams = array('id' => $this->_relatedObjects['participant']->event_id); $values['event'] = array(); CRM_Event_BAO_Event::retrieve($eventParams, $values['event']); //get location details $locationParams = array('entity_id' => $this->_relatedObjects['participant']->event_id, 'entity_table' => 'civicrm_event'); $values['location'] = CRM_Core_BAO_Location::getValues($locationParams); $ufJoinParams = array('entity_table' => 'civicrm_event', 'entity_id' => $ids['event'], 'module' => 'CiviEvent'); list($custom_pre_id, $custom_post_ids) = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinParams); $values['custom_pre_id'] = $custom_pre_id; $values['custom_post_id'] = $custom_post_ids; //for tasks 'Change Participant Status' and 'Batch Update Participants Via Profile' case //and cases involving status updation through ipn // whatever that means! // total_amount appears to be the preferred input param & it is unclear why we support amount here // perhaps we should throw an e-notice if amount is set & force total_amount? if (!empty($input['amount'])) { $values['totalAmount'] = $input['amount']; } if ($values['event']['is_email_confirm']) { $values['is_email_receipt'] = 1; } return CRM_Event_BAO_Event::sendMail($ids['contact'], $values, $this->_relatedObjects['participant']->id, $this->is_test, $returnMessageText); } else { $values['contribution_id'] = $this->id; if (!empty($ids['related_contact'])) { $values['related_contact'] = $ids['related_contact']; if (isset($ids['onbehalf_dupe_alert'])) { $values['onbehalf_dupe_alert'] = $ids['onbehalf_dupe_alert']; } $entityBlock = array('contact_id' => $ids['contact'], 'location_type_id' => CRM_Core_DAO::getFieldValue('CRM_Core_DAO_LocationType', 'Home', 'id', 'name')); $address = CRM_Core_BAO_Address::getValues($entityBlock); $template->assign('onBehalfAddress', $address[$entityBlock['location_type_id']]['display']); } $isTest = FALSE; if ($this->is_test) { $isTest = TRUE; } if (!empty($this->_relatedObjects['membership'])) { foreach ($this->_relatedObjects['membership'] as $membership) { if ($membership->id) { $values['isMembership'] = TRUE; // need to set the membership values here $template->assign('membership_assign', 1); $template->assign('membership_name', CRM_Member_PseudoConstant::membershipType($membership->membership_type_id)); $template->assign('mem_start_date', $membership->start_date); $template->assign('mem_join_date', $membership->join_date); $template->assign('mem_end_date', $membership->end_date); $membership_status = CRM_Member_PseudoConstant::membershipStatus($membership->status_id, NULL, 'label'); $template->assign('mem_status', $membership_status); if ($membership_status == 'Pending' && $membership->is_pay_later == 1) { $template->assign('is_pay_later', 1); } // if separate payment there are two contributions recorded and the // admin will need to send a receipt for each of them separately. // we dont link the two in the db (but can potentially infer it if needed) $template->assign('is_separate_payment', 0); if ($recur && $paymentObject) { $url = $paymentObject->subscriptionURL($membership->id, 'membership'); $template->assign('cancelSubscriptionUrl', $url); $url = $paymentObject->subscriptionURL($membership->id, 'membership', 'billing'); $template->assign('updateSubscriptionBillingUrl', $url); $url = $paymentObject->subscriptionURL($entityID, $entity, 'update'); $template->assign('updateSubscriptionUrl', $url); } $result = CRM_Contribute_BAO_ContributionPage::sendMail($ids['contact'], $values, $isTest, $returnMessageText); return $result; // otherwise if its about sending emails, continue sending without return, as we // don't want to exit the loop. } } } else { return CRM_Contribute_BAO_ContributionPage::sendMail($ids['contact'], $values, $isTest, $returnMessageText); } } }
/** * @param $submittedValues */ public function processCreditCard($submittedValues) { $config = CRM_Core_Config::singleton(); $session = CRM_Core_Session::singleton(); $unsetParams = array('trxn_id', 'payment_instrument_id', 'contribution_status_id'); foreach ($unsetParams as $key) { if (isset($submittedValues[$key])) { unset($submittedValues[$key]); } } // Get the required fields value only. $params = $this->_params = $submittedValues; //get the payment processor id as per mode. //@todo unclear relevance of mode - seems like a lot of duplicated params here! $this->_params['payment_processor'] = $params['payment_processor_id'] = $this->_params['payment_processor_id'] = $submittedValues['payment_processor_id'] = $this->_paymentProcessor['id']; $now = date('YmdHis'); $fields = array(); // we need to retrieve email address if ($this->_context == 'standalone' && !empty($submittedValues['is_email_receipt'])) { list($this->userDisplayName, $this->userEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($this->_contactId); $this->assign('displayName', $this->userDisplayName); } //set email for primary location. $fields['email-Primary'] = 1; $params['email-Primary'] = $this->_contributorEmail; // now set the values for the billing location. foreach ($this->_fields as $name => $dontCare) { $fields[$name] = 1; } // also add location name to the array $params["address_name-{$this->_bltID}"] = CRM_Utils_Array::value('billing_first_name', $params) . ' ' . CRM_Utils_Array::value('billing_middle_name', $params) . ' ' . CRM_Utils_Array::value('billing_last_name', $params); $params["address_name-{$this->_bltID}"] = trim($params["address_name-{$this->_bltID}"]); $fields["address_name-{$this->_bltID}"] = 1; $ctype = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_contactId, 'contact_type'); $nameFields = array('first_name', 'middle_name', 'last_name'); foreach ($nameFields as $name) { $fields[$name] = 1; if (array_key_exists("billing_{$name}", $params)) { $params[$name] = $params["billing_{$name}"]; $params['preserveDBName'] = TRUE; } } if (!empty($params['source'])) { unset($params['source']); } $contactID = CRM_Contact_BAO_Contact::createProfileContact($params, $fields, $this->_contactId, NULL, NULL, $ctype); // Add all the additional payment params we need. $this->_params["state_province-{$this->_bltID}"] = $this->_params["billing_state_province-{$this->_bltID}"] = CRM_Core_PseudoConstant::stateProvinceAbbreviation($this->_params["billing_state_province_id-{$this->_bltID}"]); $this->_params["country-{$this->_bltID}"] = $this->_params["billing_country-{$this->_bltID}"] = CRM_Core_PseudoConstant::countryIsoCode($this->_params["billing_country_id-{$this->_bltID}"]); if ($this->_paymentProcessor['payment_type'] & CRM_Core_Payment::PAYMENT_TYPE_CREDIT_CARD) { $this->_params['year'] = CRM_Core_Payment_Form::getCreditCardExpirationYear($this->_params); $this->_params['month'] = CRM_Core_Payment_Form::getCreditCardExpirationMonth($this->_params); } $this->_params['ip_address'] = CRM_Utils_System::ipAddress(); $this->_params['amount'] = $this->_params['total_amount']; // @todo - stop setting amount level in this function & call the CRM_Price_BAO_PriceSet::getAmountLevel // function to get correct amount level consistently. Remove setting of the amount level in // CRM_Price_BAO_PriceSet::processAmount. Extend the unit tests in CRM_Price_BAO_PriceSetTest // to cover all variants. $this->_params['amount_level'] = 0; $this->_params['currencyID'] = CRM_Utils_Array::value('currency', $this->_params, $config->defaultCurrency); if (!empty($this->_params['trxn_date'])) { $this->_params['receive_date'] = CRM_Utils_Date::processDate($this->_params['trxn_date'], $this->_params['trxn_date_time']); } if (empty($this->_params['invoice_id'])) { $this->_params['invoiceID'] = md5(uniqid(rand(), TRUE)); } else { $this->_params['invoiceID'] = $this->_params['invoice_id']; } $this->assignBillingName($params); $this->assign('address', CRM_Utils_Address::getFormattedBillingAddressFieldsFromParameters($params, $this->_bltID)); $date = CRM_Utils_Date::format($params['credit_card_exp_date']); $date = CRM_Utils_Date::mysqlToIso($date); $this->assign('credit_card_type', CRM_Utils_Array::value('credit_card_type', $params)); $this->assign('credit_card_exp_date', $date); $this->assign('credit_card_number', CRM_Utils_System::mungeCreditCard($params['credit_card_number'])); //Add common data to formatted params CRM_Contribute_Form_AdditionalInfo::postProcessCommon($params, $this->_params, $this); // at this point we've created a contact and stored its address etc // all the payment processors expect the name and address to be in the // so we copy stuff over to first_name etc. $paymentParams = $this->_params; $paymentParams['contactID'] = $this->_contactId; CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $paymentParams, TRUE); // add some financial type details to the params list // if folks need to use it $paymentParams['contributionType_name'] = $this->_params['contributionType_name'] = $contributionType->name; $paymentParams['contributionPageID'] = NULL; if (!empty($this->_params['is_email_receipt'])) { $paymentParams['email'] = $this->_contributorEmail; $paymentParams['is_email_receipt'] = 1; } else { $paymentParams['is_email_receipt'] = 0; $this->_params['is_email_receipt'] = 0; } if (!empty($this->_params['receive_date'])) { $paymentParams['receive_date'] = $this->_params['receive_date']; } if (!empty($this->_params['receive_date'])) { $paymentParams['receive_date'] = $this->_params['receive_date']; } $result = NULL; if ($paymentParams['amount'] > 0.0) { try { // force a reget of the payment processor in case the form changed it, CRM-7179 $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); $result = $payment->doPayment($paymentParams); } catch (\Civi\Payment\Exception\PaymentProcessorException $e) { //set the contribution mode. $urlParams = "action=add&cid={$this->_contactId}&id={$this->_id}&component={$this->_component}"; if ($this->_mode) { $urlParams .= "&mode={$this->_mode}"; } CRM_Core_Error::displaySessionError($result); CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/payment/add', $urlParams)); } } if ($result) { $this->_params = array_merge($this->_params, $result); } if (empty($this->_params['receive_date'])) { $this->_params['receive_date'] = $now; } $this->set('params', $this->_params); // set source if not set if (empty($this->_params['source'])) { $userID = $session->get('userID'); $userSortName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $userID, 'sort_name'); $this->_params['source'] = ts('Submit Credit Card Payment by: %1', array(1 => $userSortName)); } // process the additional payment $participantId = NULL; if ($this->_component == 'event') { $participantId = $this->_id; } $trxnRecord = CRM_Contribute_BAO_Contribution::recordAdditionalPayment($this->_contributionId, $submittedValues, $this->_paymentType, $participantId); if ($trxnRecord->id && !empty($this->_params['is_email_receipt'])) { $sendReceipt = $this->emailReceipt($this->_params); } if ($trxnRecord->id) { $statusMsg = ts('The payment record has been processed.'); if (!empty($this->_params['is_email_receipt']) && $sendReceipt) { $statusMsg .= ' ' . ts('A receipt has been emailed to the contributor.'); } CRM_Core_Session::setStatus($statusMsg, ts('Complete'), 'success'); $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$this->_contactId}&selectedChild=participant")); } }
/** * Process payment after confirmation. * * @param CRM_Core_Form $form * Form object. * @param array $paymentParams * Array with payment related key. * value pairs * @param int $contactID * Contact id. * @param int $contributionTypeId * Financial type id. * @param int|string $component component id * @param $isTest * * @throws CRM_Core_Exception * @throws Exception * @return array * associated array * */ public static function processConfirm(&$form, &$paymentParams, $contactID, $contributionTypeId, $component = 'contribution', $isTest) { CRM_Core_Payment_Form::mapParams($form->_bltID, $form->_params, $paymentParams, TRUE); $lineItems = $form->_lineItem; $isPaymentTransaction = self::isPaymentTransaction($form); $financialType = new CRM_Financial_DAO_FinancialType(); $financialType->id = $contributionTypeId; $financialType->find(TRUE); if ($financialType->is_deductible) { $form->assign('is_deductible', TRUE); $form->set('is_deductible', TRUE); } // add some financial type details to the params list // if folks need to use it //CRM-15297 - contributionType is obsolete - pass financial type as well so people can deprecate it $paymentParams['financialType_name'] = $paymentParams['contributionType_name'] = $form->_params['contributionType_name'] = $financialType->name; //CRM-11456 $paymentParams['financialType_accounting_code'] = $paymentParams['contributionType_accounting_code'] = $form->_params['contributionType_accounting_code'] = CRM_Financial_BAO_FinancialAccount::getAccountingCode($contributionTypeId); $paymentParams['contributionPageID'] = $form->_params['contributionPageID'] = $form->_values['id']; $paymentParams['contactID'] = $form->_params['contactID'] = $contactID; //fix for CRM-16317 $form->_params['receive_date'] = date('YmdHis'); $form->assign('receive_date', CRM_Utils_Date::mysqlToIso($form->_params['receive_date'])); if ($isPaymentTransaction) { // Fix for CRM-14354. If the membership is recurring, don't create a // civicrm_contribution_recur record for the additional contribution // (i.e., the amount NOT associated with the membership). Temporarily // cache the is_recur values so we can process the additional gift as a // one-off payment. if (!empty($form->_values['is_recur'])) { if ($form->_membershipBlock['is_separate_payment'] && !empty($form->_params['auto_renew'])) { $cachedFormValue = CRM_Utils_Array::value('is_recur', $form->_values); $cachedParamValue = CRM_Utils_Array::value('is_recur', $paymentParams); unset($form->_values['is_recur']); unset($paymentParams['is_recur']); } } $contributionParams = array('contact_id' => $contactID, 'line_item' => $lineItems, 'is_test' => $isTest, 'campaign_id' => CRM_Utils_Array::value('campaign_id', $paymentParams, CRM_Utils_Array::value('campaign_id', $form->_values)), 'contribution_page_id' => $form->_id, 'source' => CRM_Utils_Array::value('source', $paymentParams, CRM_Utils_Array::value('description', $paymentParams))); $isMonetary = !empty($form->_values['is_monetary']); if ($isMonetary) { if (empty($paymentParams['is_pay_later'])) { // @todo look up payment_instrument_id on payment processor table. $contributionParams['payment_instrument_id'] = 1; } } $contribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($form, $paymentParams, NULL, $contributionParams, $financialType, TRUE, TRUE, $form->_bltID); $paymentParams['contributionTypeID'] = $contributionTypeId; $paymentParams['item_name'] = $form->_params['description']; if ($contribution && $form->_values['is_recur'] && $contribution->contribution_recur_id) { $paymentParams['contributionRecurID'] = $contribution->contribution_recur_id; } $paymentParams['qfKey'] = $form->controller->_key; if ($component == 'membership') { return array('contribution' => $contribution); } // restore cached values (part of fix for CRM-14354) if (!empty($cachedFormValue)) { $form->_values['is_recur'] = $cachedFormValue; $paymentParams['is_recur'] = $cachedParamValue; } $paymentParams['contributionID'] = $contribution->id; //CRM-15297 deprecate contributionTypeID $paymentParams['financialTypeID'] = $paymentParams['contributionTypeID'] = $contribution->financial_type_id; $paymentParams['contributionPageID'] = $contribution->contribution_page_id; if (isset($paymentParams['contribution_source'])) { $paymentParams['source'] = $paymentParams['contribution_source']; } if ($form->_values['is_recur'] && $contribution->contribution_recur_id) { $paymentParams['contributionRecurID'] = $contribution->contribution_recur_id; } if ($form->_contributeMode && $form->_amount > 0.0) { try { $payment = Civi\Payment\System::singleton()->getByProcessor($form->_paymentProcessor); if ($form->_contributeMode == 'notify') { // We want to get rid of this & make it generic - eg. by making payment processing the last thing // and always calling it first. $form->postProcessHook(); } $result = $payment->doPayment($paymentParams); $form->_params = array_merge($form->_params, $result); $form->assign('trxn_id', CRM_Utils_Array::value('trxn_id', $result)); if (!empty($result['trxn_id'])) { $contribution->trxn_id = $result['trxn_id']; } if (!empty($result['payment_status_id'])) { $contribution->payment_status_id = $result['payment_status_id']; } $result['contribution'] = $contribution; return $result; } catch (\Civi\Payment\Exception\PaymentProcessorException $e) { // Clean up DB as appropriate. if (!empty($paymentParams['contributionID'])) { CRM_Contribute_BAO_Contribution::failPayment($paymentParams['contributionID'], $paymentParams['contactID'], $e->getMessage()); } if (!empty($paymentParams['contributionRecurID'])) { CRM_Contribute_BAO_ContributionRecur::deleteRecurContribution($paymentParams['contributionRecurID']); } $result['is_payment_failure'] = TRUE; $result['error'] = $e; return $result; } } } // Only pay later or unpaid should reach this point. The theory is that paylater should get a receipt now & // processor // transaction receipts should be outcome driven. $form->set('params', $form->_params); if (isset($paymentParams['contribution_source'])) { $form->_params['source'] = $paymentParams['contribution_source']; } // get the price set values for receipt. if ($form->_priceSetId && $form->_lineItem) { $form->_values['lineItem'] = $form->_lineItem; $form->_values['priceSetID'] = $form->_priceSetId; } $form->_values['contribution_id'] = $contribution->id; $form->_values['contribution_page_id'] = $contribution->contribution_page_id; CRM_Contribute_BAO_ContributionPage::sendMail($contactID, $form->_values, $contribution->is_test); }
/** * Test submit recurring pledge. * * - we process 1 pledge with a future start date. A recur contribution and the pledge should be created with first payment date in the future. */ public function testSubmitPledgePaymentPaymentProcessorRecurFuturePayment() { $this->params['adjust_recur_start_date'] = TRUE; $this->params['is_pay_later'] = FALSE; $this->setUpContributionPage(); $this->setUpPledgeBlock(); $this->setupPaymentProcessor(); $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success')); $submitParams = array('id' => (int) $this->_ids['contribution_page'], 'amount' => 100, 'billing_first_name' => 'Billy', 'billing_middle_name' => 'Goat', 'billing_last_name' => 'Gruff', 'email' => '*****@*****.**', 'payment_processor_id' => 1, 'credit_card_number' => '4111111111111111', 'credit_card_type' => 'Visa', 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040), 'cvv2' => 123, 'pledge_frequency_interval' => 1, 'pledge_frequency_unit' => 'week', 'pledge_installments' => 3, 'is_pledge' => TRUE, 'pledge_block_id' => (int) $this->_ids['pledge_block_id']); $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL); // Check if contribution created. $contribution = $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page'], 'contribution_status_id' => 'Completed')); $this->assertEquals('create_first_success', $contribution['trxn_id']); // Check if pledge created. $pledge = $this->callAPISuccess('pledge', 'getsingle', array()); $this->assertEquals(date('Ymd', strtotime($pledge['pledge_start_date'])), date('Ymd', strtotime("+1 month"))); $this->assertEquals($pledge['pledge_amount'], 300.0); // Check if pledge payments created. $params = array('pledge_id' => $pledge['id']); $pledgePayment = $this->callAPISuccess('pledge_payment', 'get', $params); $this->assertEquals($pledgePayment['count'], 3); $this->assertEquals(date('Ymd', strtotime($pledgePayment['values'][1]['scheduled_date'])), date('Ymd', strtotime("+1 month"))); $this->assertEquals($pledgePayment['values'][1]['scheduled_amount'], 100.0); $this->assertEquals($pledgePayment['values'][1]['status_id'], 1); // Will be pending when actual payment processor is used (dummy processor does not support future payments). // Check contribution recur record. $recur = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $contribution['contribution_recur_id'])); $this->assertEquals(date('Ymd', strtotime($recur['start_date'])), date('Ymd', strtotime("+1 month"))); $this->assertEquals($recur['amount'], 100.0); $this->assertEquals($recur['contribution_status_id'], 5); // In progress status. }
/** * Handle pre approval for processors. * * This fits with the flow where a pre-approval is done and then confirmed in the next stage when confirm is hit. * * This applies to processors that * @param array $params */ protected function handlePreApproval(&$params) { try { $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); $result = $payment->doPreApproval($params); } catch (\Civi\Payment\Exception\PaymentProcessorException $e) { CRM_Core_Error::displaySessionError($e->getMessage()); CRM_Utils_System::redirect($params['cancelURL']); } $this->set('pre_approval_parameters', $result['pre_approval_parameters']); if (!empty($result['redirect_url'])) { CRM_Utils_System::redirect($result['redirect_url']); } }
/** * Where a second separate financial transaction is supported we will process it here. * * @param int $contactID * @param CRM_Contribute_Form_Contribution_Confirm $form * @param array $tempParams * @param bool $isTest * @param array $lineItems * @param $minimumFee * @param int $financialTypeID * * @throws CRM_Core_Exception * @throws Exception * @return CRM_Contribute_BAO_Contribution */ protected function processSecondaryFinancialTransaction($contactID, &$form, $tempParams, $isTest, $lineItems, $minimumFee, $financialTypeID) { $financialType = new CRM_Financial_DAO_FinancialType(); $financialType->id = $financialTypeID; $financialType->find(TRUE); $tempParams['amount'] = $minimumFee; $tempParams['invoiceID'] = md5(uniqid(rand(), TRUE)); $result = NULL; if ($form->_values['is_monetary'] && !$form->_params['is_pay_later'] && $minimumFee > 0.0) { // At the moment our tests are calling this form in a way that leaves 'object' empty. For // now we compensate here. if (empty($form->_paymentProcessor['object'])) { $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); } else { $payment = $form->_paymentProcessor['object']; } $result = $payment->doPayment($tempParams, 'contribute'); } //assign receive date when separate membership payment //and contribution amount not selected. if ($form->_amount == 0) { $now = date('YmdHis'); $form->_params['receive_date'] = $now; $receiveDate = CRM_Utils_Date::mysqlToIso($now); $form->set('params', $form->_params); $form->assign('receive_date', $receiveDate); } $form->set('membership_trx_id', $result['trxn_id']); $form->set('membership_amount', $minimumFee); $form->assign('membership_trx_id', $result['trxn_id']); $form->assign('membership_amount', $minimumFee); // we don't need to create the user twice, so lets disable cms_create_account // irrespective of the value, CRM-2888 $tempParams['cms_create_account'] = 0; //CRM-16165, scenarios are // 1) If contribution is_pay_later and if contribution amount is > 0.0 we set pending = TRUE, vice-versa FALSE // 2) If not pay later but auto-renewal membership is chosen then pending = TRUE as it later triggers // pending recurring contribution, vice-versa FALSE $pending = $form->_params['is_pay_later'] ? $minimumFee > 0.0 ? TRUE : FALSE : (!empty($form->_params['auto_renew']) ? TRUE : FALSE); //set this variable as we are not creating pledge for //separate membership payment contribution. //so for differentiating membership contribution from //main contribution. $form->_params['separate_membership_payment'] = 1; $membershipContribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($form, $tempParams, $result, $contactID, $financialType, $pending, TRUE, $isTest, $lineItems, $form->_bltID); return $membershipContribution; }
/** * Process payment after confirmation. * * @param CRM_Core_Form $form * Form object. * @param array $paymentParams * Array with payment related key. * value pairs * @param int $contactID * Contact id. * @param int $contributionTypeId * Financial type id. * @param int|string $component component id * @param bool $isTest * @param bool $isRecur * * @throws CRM_Core_Exception * @throws Exception * @return array * associated array * */ public static function processConfirm(&$form, &$paymentParams, $contactID, $contributionTypeId, $component = 'contribution', $isTest, $isRecur) { CRM_Core_Payment_Form::mapParams($form->_bltID, $form->_params, $paymentParams, TRUE); $lineItems = $form->_lineItem; $isPaymentTransaction = self::isPaymentTransaction($form); $financialType = new CRM_Financial_DAO_FinancialType(); $financialType->id = $contributionTypeId; $financialType->find(TRUE); if ($financialType->is_deductible) { $form->assign('is_deductible', TRUE); $form->set('is_deductible', TRUE); } // add some financial type details to the params list // if folks need to use it //CRM-15297 - contributionType is obsolete - pass financial type as well so people can deprecate it $paymentParams['financialType_name'] = $paymentParams['contributionType_name'] = $form->_params['contributionType_name'] = $financialType->name; //CRM-11456 $paymentParams['financialType_accounting_code'] = $paymentParams['contributionType_accounting_code'] = $form->_params['contributionType_accounting_code'] = CRM_Financial_BAO_FinancialAccount::getAccountingCode($contributionTypeId); $paymentParams['contributionPageID'] = $form->_params['contributionPageID'] = $form->_values['id']; $paymentParams['contactID'] = $form->_params['contactID'] = $contactID; //fix for CRM-16317 $form->_params['receive_date'] = date('YmdHis'); $form->assign('receive_date', CRM_Utils_Date::mysqlToIso($form->_params['receive_date'])); if ($isPaymentTransaction) { $contributionParams = array('id' => CRM_Utils_Array::value('contribution_id', $paymentParams), 'contact_id' => $contactID, 'line_item' => $lineItems, 'is_test' => $isTest, 'campaign_id' => CRM_Utils_Array::value('campaign_id', $paymentParams, CRM_Utils_Array::value('campaign_id', $form->_values)), 'contribution_page_id' => $form->_id, 'source' => CRM_Utils_Array::value('source', $paymentParams, CRM_Utils_Array::value('description', $paymentParams))); $isMonetary = !empty($form->_values['is_monetary']); if ($isMonetary) { if (empty($paymentParams['is_pay_later'])) { // @todo look up payment_instrument_id on payment processor table. $contributionParams['payment_instrument_id'] = 1; } } $contribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($form, $paymentParams, NULL, $contributionParams, $financialType, TRUE, $form->_bltID, $isRecur); $paymentParams['contributionTypeID'] = $contributionTypeId; $paymentParams['item_name'] = $form->_params['description']; $paymentParams['qfKey'] = $form->controller->_key; if ($component == 'membership') { return array('contribution' => $contribution); } $paymentParams['contributionID'] = $contribution->id; //CRM-15297 deprecate contributionTypeID $paymentParams['financialTypeID'] = $paymentParams['contributionTypeID'] = $contribution->financial_type_id; $paymentParams['contributionPageID'] = $contribution->contribution_page_id; if (isset($paymentParams['contribution_source'])) { $paymentParams['source'] = $paymentParams['contribution_source']; } if ($form->_values['is_recur'] && $contribution->contribution_recur_id) { $paymentParams['contributionRecurID'] = $contribution->contribution_recur_id; } if (isset($paymentParams['contribution_source'])) { $form->_params['source'] = $paymentParams['contribution_source']; } // get the price set values for receipt. if ($form->_priceSetId && $form->_lineItem) { $form->_values['lineItem'] = $form->_lineItem; $form->_values['priceSetID'] = $form->_priceSetId; } $form->_values['contribution_id'] = $contribution->id; $form->_values['contribution_page_id'] = $contribution->contribution_page_id; if (!empty($form->_paymentProcessor)) { try { $payment = Civi\Payment\System::singleton()->getByProcessor($form->_paymentProcessor); if ($form->_contributeMode == 'notify') { // We want to get rid of this & make it generic - eg. by making payment processing the last thing // and always calling it first. $form->postProcessHook(); } $result = $payment->doPayment($paymentParams); $form->_params = array_merge($form->_params, $result); $form->assign('trxn_id', CRM_Utils_Array::value('trxn_id', $result)); if (!empty($result['trxn_id'])) { $contribution->trxn_id = $result['trxn_id']; } if (!empty($result['payment_status_id'])) { $contribution->payment_status_id = $result['payment_status_id']; } $result['contribution'] = $contribution; if ($result['payment_status_id'] == CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'status_id', 'Pending') && $payment->isSendReceiptForPending()) { CRM_Contribute_BAO_ContributionPage::sendMail($contactID, $form->_values, $contribution->is_test); } return $result; } catch (\Civi\Payment\Exception\PaymentProcessorException $e) { // Clean up DB as appropriate. if (!empty($paymentParams['contributionID'])) { CRM_Contribute_BAO_Contribution::failPayment($paymentParams['contributionID'], $paymentParams['contactID'], $e->getMessage()); } if (!empty($paymentParams['contributionRecurID'])) { CRM_Contribute_BAO_ContributionRecur::deleteRecurContribution($paymentParams['contributionRecurID']); } $result['is_payment_failure'] = TRUE; $result['error'] = $e; return $result; } } } // Only pay later or unpaid should reach this point, although pay later likely does not & is handled via the // manual processor, so it's unclear what this set is for and whether the following send ever fires. $form->set('params', $form->_params); if ($form->_params['amount'] == 0) { // This is kind of a back-up for pay-later $0 transactions. // In other flows they pick up the manual processor & get dealt with above (I // think that might be better...). return array('payment_status_id' => 1, 'contribution' => $contribution, 'payment_processor_id' => 0); } elseif (empty($form->_values['amount'])) { // If the amount is not in _values[], set it $form->_values['amount'] = $form->_params['amount']; } CRM_Contribute_BAO_ContributionPage::sendMail($contactID, $form->_values, $contribution->is_test); }
/** * Process payment after confirmation. * * @param CRM_Core_Form $form * Form object. * @param array $paymentParams * Array with payment related key. * value pairs * @param array $premiumParams * Array with premium related key. * value pairs * @param int $contactID * Contact id. * @param int $contributionTypeId * Financial type id. * @param int|string $component component id * @param array $fieldTypes * Presumably relates to custom field types - used when building data for sendMail. * @param $isTest * @param $isPayLater * * @throws CRM_Core_Exception * @throws Exception * @return array * associated array * */ public static function processConfirm(&$form, &$paymentParams, &$premiumParams, $contactID, $contributionTypeId, $component = 'contribution', $fieldTypes = NULL, $isTest, $isPayLater) { CRM_Core_Payment_Form::mapParams($form->_bltID, $form->_params, $paymentParams, TRUE); $lineItems = $form->_lineItem; $isPaymentTransaction = self::isPaymentTransaction($form); $financialType = new CRM_Financial_DAO_FinancialType(); $financialType->id = $contributionTypeId; $financialType->find(TRUE); if ($financialType->is_deductible) { $form->assign('is_deductible', TRUE); $form->set('is_deductible', TRUE); } // add some financial type details to the params list // if folks need to use it //CRM-15297 - contributionType is obsolete - pass financial type as well so people can deprecate it $paymentParams['financialType_name'] = $paymentParams['contributionType_name'] = $form->_params['contributionType_name'] = $financialType->name; //CRM-11456 $paymentParams['financialType_accounting_code'] = $paymentParams['contributionType_accounting_code'] = $form->_params['contributionType_accounting_code'] = CRM_Financial_BAO_FinancialAccount::getAccountingCode($contributionTypeId); $paymentParams['contributionPageID'] = $form->_params['contributionPageID'] = $form->_values['id']; $payment = NULL; $paymentObjError = ts('The system did not record payment details for this payment and so could not process the transaction. Please report this error to the site administrator.'); if ($isPaymentTransaction && !empty($form->_paymentProcessor)) { // @todo - remove this line once we are sure we can just use $form->_paymentProcessor['object'] consistently. $payment = Civi\Payment\System::singleton()->getByProcessor($form->_paymentProcessor); } //fix for CRM-16317 $form->_params['receive_date'] = date('YmdHis'); $form->assign('receive_date', CRM_Utils_Date::mysqlToIso($form->_params['receive_date'])); $result = NULL; if ($form->_contributeMode == 'notify' || $isPayLater) { // this is not going to come back, i.e. we fill in the other details // when we get a callback from the payment processor // also add the contact ID and contribution ID to the params list $paymentParams['contactID'] = $form->_params['contactID'] = $contactID; $contribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($form, $paymentParams, NULL, $contactID, $financialType, TRUE, TRUE, $isTest, $lineItems, $form->_bltID); if ($contribution) { $form->_params['contributionID'] = $contribution->id; } $form->_params['contributionTypeID'] = $contributionTypeId; $form->_params['item_name'] = $form->_params['description']; if ($contribution && $form->_values['is_recur'] && $contribution->contribution_recur_id) { $form->_params['contributionRecurID'] = $contribution->contribution_recur_id; } $form->set('params', $form->_params); $form->postProcessPremium($premiumParams, $contribution); if ($isPaymentTransaction) { // add qfKey so we can send to paypal $form->_params['qfKey'] = $form->controller->_key; if ($component == 'membership') { return array('contribution' => $contribution); } else { if (!$isPayLater) { if (is_object($payment)) { // call postProcess hook before leaving $form->postProcessHook(); // this does not return $result = $payment->doTransferCheckout($form->_params, 'contribute'); } else { CRM_Core_Error::fatal($paymentObjError); } } else { // follow similar flow as IPN // send the receipt mail $form->set('params', $form->_params); if (isset($paymentParams['contribution_source'])) { $form->_params['source'] = $paymentParams['contribution_source']; } // get the price set values for receipt. if ($form->_priceSetId && $form->_lineItem) { $form->_values['lineItem'] = $form->_lineItem; $form->_values['priceSetID'] = $form->_priceSetId; } $form->_values['contribution_id'] = $contribution->id; $form->_values['contribution_page_id'] = $contribution->contribution_page_id; CRM_Contribute_BAO_ContributionPage::sendMail($contactID, $form->_values, $contribution->is_test); return; } } } } elseif ($form->_contributeMode == 'express') { if ($form->_values['is_monetary'] && $form->_amount > 0.0) { // determine if express + recurring and direct accordingly if (!empty($paymentParams['is_recur']) && $paymentParams['is_recur'] == 1) { if (is_object($payment)) { $result = $payment->createRecurringPayments($paymentParams); } else { CRM_Core_Error::fatal($paymentObjError); } } else { if (is_object($payment)) { $result = $payment->doExpressCheckout($paymentParams); } else { CRM_Core_Error::fatal($paymentObjError); } } } } elseif ($isPaymentTransaction) { if ($form->_contributeMode == 'direct') { $paymentParams['contactID'] = $contactID; // Fix for CRM-14354. If the membership is recurring, don't create a // civicrm_contribution_recur record for the additional contribution // (i.e., the amount NOT associated with the membership). Temporarily // cache the is_recur values so we can process the additional gift as a // one-off payment. if (!empty($form->_values['is_recur'])) { if ($form->_membershipBlock['is_separate_payment'] && !empty($form->_params['auto_renew'])) { $cachedFormValue = CRM_Utils_Array::value('is_recur', $form->_values); $cachedParamValue = CRM_Utils_Array::value('is_recur', $paymentParams); unset($form->_values['is_recur']); unset($paymentParams['is_recur']); } } $contribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($form, $paymentParams, NULL, $contactID, $financialType, TRUE, TRUE, $isTest, $lineItems, $form->_bltID); // restore cached values (part of fix for CRM-14354) if (!empty($cachedFormValue)) { $form->_values['is_recur'] = $cachedFormValue; $paymentParams['is_recur'] = $cachedParamValue; } $paymentParams['contributionID'] = $contribution->id; //CRM-15297 deprecate contributionTypeID $paymentParams['financialTypeID'] = $paymentParams['contributionTypeID'] = $contribution->financial_type_id; $paymentParams['contributionPageID'] = $contribution->contribution_page_id; if ($form->_values['is_recur'] && $contribution->contribution_recur_id) { $paymentParams['contributionRecurID'] = $contribution->contribution_recur_id; } } try { $result = $payment->doPayment($paymentParams); } catch (\Civi\Payment\Exception\PaymentProcessorException $e) { // Clean up DB as appropriate. if (!empty($paymentParams['contributionID'])) { CRM_Contribute_BAO_Contribution::failPayment($paymentParams['contributionID'], $paymentParams['contactID'], $e->getMessage()); } if (!empty($paymentParams['contributionRecurID'])) { CRM_Contribute_BAO_ContributionRecur::deleteRecurContribution($paymentParams['contributionRecurID']); } $result['is_payment_failure'] = TRUE; $result['error'] = $e; } } if ($result || $form->_amount == 0.0 && !$form->_params['is_pay_later']) { if ($result) { $form->_params = array_merge($form->_params, $result); } $form->set('params', $form->_params); $form->assign('trxn_id', CRM_Utils_Array::value('trxn_id', $result)); // result has all the stuff we need // lets archive it to a financial transaction if (isset($paymentParams['contribution_source'])) { $form->_params['source'] = $paymentParams['contribution_source']; } $form->postProcessPremium($premiumParams, $contribution); if (is_array($result) && !empty($result['trxn_id'])) { $contribution->trxn_id = $result['trxn_id']; } $result['contribution'] = $contribution; } //Do not send an email if Recurring contribution is done via Direct Mode //We will send email once the IPN is received. if ($form->_contributeMode == 'direct') { return $result; } // get the price set values for receipt. if ($form->_priceSetId && $form->_lineItem) { $form->_values['lineItem'] = $form->_lineItem; $form->_values['priceSetID'] = $form->_priceSetId; } // finally send an email receipt if ($contribution) { $form->_values['contribution_id'] = $contribution->id; CRM_Contribute_BAO_ContributionPage::sendMail($contactID, $form->_values, $contribution->is_test, FALSE, $fieldTypes); } }
/** * Test the submit function of the membership form. */ public function testSubmitRecurCompleteInstant() { $form = $this->getForm(); $processor = Civi\Payment\System::singleton()->getById($this->_paymentProcessorID); $processor->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'kettles boil water', 'fee_amount' => 0.29)); $this->callAPISuccess('MembershipType', 'create', array('id' => $this->membershipTypeAnnualFixedID, 'duration_unit' => 'month', 'duration_interval' => 1, 'auto_renew' => TRUE)); $this->createLoggedInUser(); $form->preProcess(); $form->_contactID = $this->_individualId; $params = $this->getBaseSubmitParams(); $form->_mode = 'test'; $form->testSubmit($params); $membership = $this->callAPISuccessGetSingle('Membership', array('contact_id' => $this->_individualId)); $contributionRecur = $this->callAPISuccessGetSingle('ContributionRecur', array('contact_id' => $this->_individualId)); $this->assertEquals($contributionRecur['id'], $membership['contribution_recur_id']); $this->assertEquals(0, $contributionRecur['is_email_receipt']); $this->assertEquals(date('Y-m-d'), date('Y-m-d', strtotime($contributionRecur['modified_date']))); $this->assertNotEmpty($contributionRecur['invoice_id']); print_r($contributionRecur); // @todo fix this part! /* $this->assertEquals(CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'In Progress'), $contributionRecur['contribution_status_id']); $this->assertNotEmpty($contributionRecur['next_sched_contribution_date']); */ $this->assertEquals(CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', 'Credit Card'), $contributionRecur['payment_instrument_id']); $contribution = $this->callAPISuccess('Contribution', 'getsingle', array('contact_id' => $this->_individualId, 'is_test' => TRUE)); $this->assertEquals(CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', 'Credit Card'), $contribution['payment_instrument_id']); $this->assertEquals('kettles boil water', $contribution['trxn_id']); $this->assertEquals(0.29, $contribution['fee_amount']); $this->assertEquals(78, $contribution['total_amount']); $this->assertEquals(77.70999999999999, $contribution['net_amount']); $this->callAPISuccessGetCount('LineItem', array('entity_id' => $membership['id'], 'entity_table' => 'civicrm_membership', 'contribution_id' => $contribution['id']), 1); }
/** * @param array $params * * @return array|void * @throws Exception */ public function make_payment(&$params) { $config = CRM_Core_Config::singleton(); if (isset($params["billing_state_province_id-{$this->_bltID}"]) && $params["billing_state_province_id-{$this->_bltID}"]) { $params["billing_state_province-{$this->_bltID}"] = CRM_Core_PseudoConstant::stateProvinceAbbreviation($params["billing_state_province_id-{$this->_bltID}"]); } if (isset($params["billing_country_id-{$this->_bltID}"]) && $params["billing_country_id-{$this->_bltID}"]) { $params["billing_country-{$this->_bltID}"] = CRM_Core_PseudoConstant::countryIsoCode($params["billing_country_id-{$this->_bltID}"]); } $params['ip_address'] = CRM_Utils_System::ipAddress(); $params['currencyID'] = $config->defaultCurrency; $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); CRM_Core_Payment_Form::mapParams($this->_bltID, $params, $params, TRUE); $params['month'] = $params['credit_card_exp_date']['M']; $params['year'] = $params['credit_card_exp_date']['Y']; try { $result = $payment->doPayment($params); } catch (\Civi\Payment\Exception\PaymentProcessorException $e) { CRM_Core_Error::displaySessionError($result); CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/event/cart_checkout', "_qf_Payment_display=1&qfKey={$this->controller->_key}", TRUE, NULL, FALSE)); } $trxnDetails = array('trxn_id' => $result['trxn_id'], 'trxn_date' => $result['now'], 'currency' => CRM_Utils_Array::value('currencyID', $result)); return $trxnDetails; }
/** * Validation. * * @param array $params * (ref.) an assoc array of name/value pairs. * * @param $files * @param int $contributionPageId * * @return bool|array * mixed true or array of errors */ public static function formRule($params, $files, $contributionPageId = NULL) { $errors = array(); if (!empty($params['member_price_set_id'])) { //check if this price set has membership type both auto-renew and non-auto-renew memberships. $bothTypes = CRM_Price_BAO_PriceSet::isMembershipPriceSetContainsMixOfRenewNonRenew($params['member_price_set_id']); //check for supporting payment processors //if both auto-renew and non-auto-renew memberships if ($bothTypes) { $paymentProcessorIds = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage', $contributionPageId, 'payment_processor'); $paymentProcessorId = explode(CRM_Core_DAO::VALUE_SEPARATOR, $paymentProcessorIds); if (!empty($paymentProcessorId)) { foreach ($paymentProcessorId as $pid) { if ($pid) { $processor = Civi\Payment\System::singleton()->getById($pid); if (!$processor->supports('MultipleConcurrentPayments')) { $errors['member_price_set_id'] = ts('The membership price set associated with this online contribution allows a user to select BOTH an auto-renew AND a non-auto-renew membership. This requires submitting multiple processor transactions, and is not supported for one or more of the payment processors enabled under the Amounts tab.'); } } } } } } if (!empty($params['member_is_active'])) { // don't allow price set w/ membership signup, CRM-5095 if ($contributionPageId && ($setID = CRM_Price_BAO_PriceSet::getFor('civicrm_contribution_page', $contributionPageId, NULL, 1))) { $extends = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $setID, 'extends'); if ($extends != CRM_Core_Component::getComponentID('CiviMember')) { $errors['member_is_active'] = ts('You cannot enable both Membership Signup and a Contribution Price Set on the same online contribution page.'); return $errors; } } if (!empty($params['member_price_set_id'])) { return $errors; } if (!isset($params['membership_type']) || !is_array($params['membership_type'])) { $errors['membership_type'] = ts('Please select at least one Membership Type to include in the Membership section of this page.'); } else { $membershipType = array_values($params['membership_type']); $isRecur = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage', $contributionPageId, 'is_recur'); if (array_sum($membershipType) == 0) { $errors['membership_type'] = ts('Please select at least one Membership Type to include in the Membership section of this page.'); } elseif (array_sum($membershipType) > CRM_Price_Form_Field::NUM_OPTION) { // for CRM-13079 $errors['membership_type'] = ts('You cannot select more than %1 choices. For more complex functionality, please use a Price Set.', array(1 => CRM_Price_Form_Field::NUM_OPTION)); } elseif ($isRecur) { if (empty($params['is_separate_payment']) && array_sum($membershipType) != 0) { $errors['is_separate_payment'] = ts('You need to enable Separate Membership Payment when online contribution page is configured for both Membership and Recurring Contribution'); } elseif (!empty($params['is_separate_payment'])) { foreach ($params['membership_type'] as $mt => $dontCare) { if (!empty($params["auto_renew_{$mt}"])) { $errors["auto_renew_{$mt}"] = ts('You cannot enable both Recurring Contributions and Auto-renew memberships on the same online contribution page'); break; } } } } } //for CRM-1302 //if Membership status is not present, then display an error message $dao = new CRM_Member_BAO_MembershipStatus(); if (!$dao->find()) { $errors['_qf_default'] = ts('Add status rules, before configuring membership'); } //give error if default is selected for an unchecked membership type if (!empty($params['membership_type_default']) && !$params['membership_type'][$params['membership_type_default']]) { $errors['membership_type_default'] = ts('Can\'t set default option for an unchecked membership type.'); } if ($contributionPageId) { $amountBlock = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage', $contributionPageId, 'amount_block_is_active'); if (!$amountBlock && !empty($params['is_separate_payment'])) { $errors['is_separate_payment'] = ts('Please enable the contribution amount section to use this option.'); } } } return empty($errors) ? TRUE : $errors; }
/** * Test submit recurring membership with delayed confirmation (Authorize.net style) * - we process 2 membership transactions against with a recurring contribution against a contribution page with a delayed * processor (Authorize.net style - denoted by NOT returning trxn_id) * - the first creates a pending membership, pending contribution, penging recurring. Check these * - complete the transaction * - create another - end date should NOT be extended */ public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurDelayed() { $this->params['is_recur'] = 1; $this->params['recur_frequency_unit'] = 'month'; $this->setUpMembershipContributionPage(); $dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 2)); $submitParams = array('price_' . $this->_ids['price_field'][0] => reset($this->_ids['price_field_value']), 'id' => (int) $this->_ids['contribution_page'], 'amount' => 10, 'billing_first_name' => 'Billy', 'billing_middle_name' => 'Goat', 'billing_last_name' => 'Gruff', 'email' => '*****@*****.**', 'selectMembership' => $this->_ids['membership_type'], 'payment_processor_id' => 1, 'credit_card_number' => '4111111111111111', 'credit_card_type' => 'Visa', 'credit_card_exp_date' => array('M' => 9, 'Y' => 2040), 'cvv2' => 123, 'is_recur' => 1, 'frequency_interval' => 1, 'frequency_unit' => 'month'); $this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL); $contribution = $this->callAPISuccess('contribution', 'getsingle', array('contribution_page_id' => $this->_ids['contribution_page'], 'contribution_status_id' => 2)); $membershipPayment = $this->callAPISuccess('membership_payment', 'getsingle', array()); $this->assertEquals($membershipPayment['contribution_id'], $contribution['id']); $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id'])); $this->assertEquals($membership['contact_id'], $contribution['contact_id']); $this->assertEquals(5, $membership['status_id']); //@todo - check with Joe about these not existing //$this->callAPISuccess('line_item', 'getsingle', array('contribution_id' => $contribution['id'], 'entity_id' => $membership['id'])); $this->callAPISuccess('contribution', 'completetransaction', array('id' => $contribution['id'], 'trxn_id' => 'ipn_called')); $membership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id'])); //renew it with processor setting completed - should extend membership $submitParams = array_merge($submitParams, array('contact_id' => $contribution['contact_id'], 'is_recur' => 1, 'frequency_interval' => 1, 'frequency_unit' => 'month')); $dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 2)); $this->callAPISuccess('contribution_page', 'submit', $submitParams); $newContribution = $this->callAPISuccess('contribution', 'getsingle', array('id' => array('NOT IN' => array($contribution['id'])), 'contribution_page_id' => $this->_ids['contribution_page'], 'contribution_status_id' => 2)); $renewedMembership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id'])); //no renewal as the date hasn't changed $this->assertEquals($membership['end_date'], $renewedMembership['end_date']); $recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $newContribution['contribution_recur_id'])); $this->assertEquals(2, $recurringContribution['contribution_status_id']); }
/** * Where a second separate financial transaction is supported we will process it here. * * @param int $contactID * @param CRM_Contribute_Form_Contribution_Confirm $form * @param array $tempParams * @param bool $isTest * @param array $lineItems * @param $minimumFee * @param int $financialTypeID * * @throws CRM_Core_Exception * @throws Exception * @return CRM_Contribute_BAO_Contribution */ protected function processSecondaryFinancialTransaction($contactID, &$form, $tempParams, $isTest, $lineItems, $minimumFee, $financialTypeID) { $financialType = new CRM_Financial_DAO_FinancialType(); $financialType->id = $financialTypeID; $financialType->find(TRUE); $tempParams['amount'] = $minimumFee; $tempParams['invoiceID'] = md5(uniqid(rand(), TRUE)); $isRecur = CRM_Utils_Array::value('is_recur', $tempParams); //assign receive date when separate membership payment //and contribution amount not selected. if ($form->_amount == 0) { $now = date('YmdHis'); $form->_params['receive_date'] = $now; $receiveDate = CRM_Utils_Date::mysqlToIso($now); $form->set('params', $form->_params); $form->assign('receive_date', $receiveDate); } $form->set('membership_amount', $minimumFee); $form->assign('membership_amount', $minimumFee); // we don't need to create the user twice, so lets disable cms_create_account // irrespective of the value, CRM-2888 $tempParams['cms_create_account'] = 0; //set this variable as we are not creating pledge for //separate membership payment contribution. //so for differentiating membership contribution from //main contribution. $form->_params['separate_membership_payment'] = 1; $contributionParams = array('contact_id' => $contactID, 'line_item' => $lineItems, 'is_test' => $isTest, 'campaign_id' => CRM_Utils_Array::value('campaign_id', $tempParams, CRM_Utils_Array::value('campaign_id', $form->_values)), 'contribution_page_id' => $form->_id, 'source' => CRM_Utils_Array::value('source', $tempParams, CRM_Utils_Array::value('description', $tempParams))); $isMonetary = !empty($form->_values['is_monetary']); if ($isMonetary) { if (empty($paymentParams['is_pay_later'])) { $contributionParams['payment_instrument_id'] = $form->_paymentProcessor['payment_instrument_id']; } } $membershipContribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($form, $tempParams, $tempParams, $contributionParams, $financialType, TRUE, $form->_bltID, $isRecur); $result = array(); if ($form->_values['is_monetary'] && !$form->_params['is_pay_later'] && $minimumFee > 0.0) { // At the moment our tests are calling this form in a way that leaves 'object' empty. For // now we compensate here. if (empty($form->_paymentProcessor['object'])) { $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); } else { $payment = $form->_paymentProcessor['object']; } $result = $payment->doPayment($tempParams, 'contribute'); $form->set('membership_trx_id', $result['trxn_id']); $form->assign('membership_trx_id', $result['trxn_id']); } return array($membershipContribution, $result); }
/** * Process credit card payment. * * @param array $submittedValues * @param array $lineItem * * @param int $contactID * Contact ID * * @return bool|\CRM_Contribute_DAO_Contribution * @throws \CiviCRM_API3_Exception * @throws \Civi\Payment\Exception\PaymentProcessorException */ protected function processCreditCard($submittedValues, $lineItem, $contactID) { $contribution = FALSE; $isTest = $this->_mode == 'test' ? 1 : 0; // CRM-12680 set $_lineItem if its not set // @todo - I don't believe this would ever BE set. I can't find anywhere in the code. // It would be better to pass line item out to functions than $this->_lineItem as // we don't know what is being changed where. if (empty($this->_lineItem) && !empty($lineItem)) { $this->_lineItem = $lineItem; } $this->_paymentObject = Civi\Payment\System::singleton()->getById($submittedValues['payment_processor_id']); $this->_paymentProcessor = $this->_paymentObject->getPaymentProcessor(); // Set source if not set if (empty($submittedValues['source'])) { $userID = CRM_Core_Session::singleton()->get('userID'); $userSortName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $userID, 'sort_name'); $submittedValues['source'] = ts('Submit Credit Card Payment by: %1', array(1 => $userSortName)); } $params = $this->_params = $submittedValues; // Mapping requiring documentation. $this->_params['payment_processor'] = $submittedValues['payment_processor_id']; $now = date('YmdHis'); $fields = array(); // we need to retrieve email address if ($this->_context == 'standalone' && !empty($submittedValues['is_email_receipt'])) { list($this->userDisplayName, $this->userEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contactID); $this->assign('displayName', $this->userDisplayName); } // Set email for primary location. $fields['email-Primary'] = 1; $params['email-Primary'] = $this->userEmail; // now set the values for the billing location. foreach (array_keys($this->_fields) as $name) { $fields[$name] = 1; } // also add location name to the array $params["address_name-{$this->_bltID}"] = CRM_Utils_Array::value('billing_first_name', $params) . ' ' . CRM_Utils_Array::value('billing_middle_name', $params) . ' ' . CRM_Utils_Array::value('billing_last_name', $params); $params["address_name-{$this->_bltID}"] = trim($params["address_name-{$this->_bltID}"]); $fields["address_name-{$this->_bltID}"] = 1; $nameFields = array('first_name', 'middle_name', 'last_name'); foreach ($nameFields as $name) { $fields[$name] = 1; if (array_key_exists("billing_{$name}", $params)) { $params[$name] = $params["billing_{$name}"]; $params['preserveDBName'] = TRUE; } } if (!empty($params['source'])) { unset($params['source']); } CRM_Contact_BAO_Contact::createProfileContact($params, $fields, $contactID, NULL, NULL, CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactID, 'contact_type')); // add all the additional payment params we need if (!empty($this->_params["billing_state_province_id-{$this->_bltID}"])) { $this->_params["state_province-{$this->_bltID}"] = $this->_params["billing_state_province-{$this->_bltID}"] = CRM_Core_PseudoConstant::stateProvinceAbbreviation($this->_params["billing_state_province_id-{$this->_bltID}"]); } if (!empty($this->_params["billing_country_id-{$this->_bltID}"])) { $this->_params["country-{$this->_bltID}"] = $this->_params["billing_country-{$this->_bltID}"] = CRM_Core_PseudoConstant::countryIsoCode($this->_params["billing_country_id-{$this->_bltID}"]); } if (in_array('credit_card_exp_date', array_keys($this->_paymentFields))) { $this->_params['year'] = CRM_Core_Payment_Form::getCreditCardExpirationYear($this->_params); $this->_params['month'] = CRM_Core_Payment_Form::getCreditCardExpirationMonth($this->_params); } $this->_params['ip_address'] = CRM_Utils_System::ipAddress(); $this->_params['amount'] = $this->_params['total_amount']; $this->_params['amount_level'] = 0; $this->_params['description'] = ts('Office Credit Card contribution'); $this->_params['currencyID'] = CRM_Utils_Array::value('currency', $this->_params, CRM_Core_Config::singleton()->defaultCurrency); if (!empty($this->_params['receive_date'])) { $this->_params['receive_date'] = CRM_Utils_Date::processDate($this->_params['receive_date'], $this->_params['receive_date_time']); } if (!empty($params['soft_credit_to'])) { $this->_params['soft_credit_to'] = $params['soft_credit_to']; $this->_params['pcp_made_through_id'] = $params['pcp_made_through_id']; } $this->_params['pcp_display_in_roll'] = CRM_Utils_Array::value('pcp_display_in_roll', $params); $this->_params['pcp_roll_nickname'] = CRM_Utils_Array::value('pcp_roll_nickname', $params); $this->_params['pcp_personal_note'] = CRM_Utils_Array::value('pcp_personal_note', $params); //Add common data to formatted params CRM_Contribute_Form_AdditionalInfo::postProcessCommon($params, $this->_params, $this); if (empty($this->_params['invoice_id'])) { $this->_params['invoiceID'] = md5(uniqid(rand(), TRUE)); } else { $this->_params['invoiceID'] = $this->_params['invoice_id']; } // At this point we've created a contact and stored its address etc // all the payment processors expect the name and address to be in the // so we copy stuff over to first_name etc. $paymentParams = $this->_params; $paymentParams['contactID'] = $contactID; CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $paymentParams, TRUE); $financialType = new CRM_Financial_DAO_FinancialType(); $financialType->id = $params['financial_type_id']; // Add some financial type details to the params list // if folks need to use it. $paymentParams['contributionType_name'] = $this->_params['contributionType_name'] = $financialType->name; $paymentParams['contributionPageID'] = NULL; if (!empty($this->_params['is_email_receipt'])) { $paymentParams['email'] = $this->userEmail; $paymentParams['is_email_receipt'] = 1; } else { $paymentParams['is_email_receipt'] = 0; $this->_params['is_email_receipt'] = 0; } if (!empty($this->_params['receive_date'])) { $paymentParams['receive_date'] = $this->_params['receive_date']; } $this->_params['receive_date'] = $now; if (!empty($this->_params['is_email_receipt'])) { $this->_params['receipt_date'] = $now; } else { $this->_params['receipt_date'] = CRM_Utils_Date::processDate($this->_params['receipt_date'], $params['receipt_date_time'], TRUE); } $this->set('params', $this->_params); $this->assign('receive_date', $this->_params['receive_date']); // Result has all the stuff we need // lets archive it to a financial transaction if ($financialType->is_deductible) { $this->assign('is_deductible', TRUE); $this->set('is_deductible', TRUE); } $contribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($this, $this->_params, NULL, $contactID, $financialType, TRUE, FALSE, $isTest, $lineItem, $this->_bltID); $paymentParams['contributionID'] = $contribution->id; $paymentParams['contributionTypeID'] = $contribution->financial_type_id; $paymentParams['contributionPageID'] = $contribution->contribution_page_id; $paymentParams['contributionRecurID'] = $contribution->contribution_recur_id; if ($paymentParams['amount'] > 0.0) { // force a re-get of the payment processor in case the form changed it, CRM-7179 // NOTE - I expect this is not obsolete. $payment = CRM_Core_Payment::singleton($this->_mode, $this->_paymentProcessor, $this, TRUE); try { $statuses = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id'); $result = $payment->doPayment($paymentParams, 'contribute'); $this->assign('trxn_id', $result['trxn_id']); $contribution->trxn_id = $result['trxn_id']; /* Our scenarios here are * 1) the payment failed & an Exception should have been thrown * 2) the payment succeeded but the payment is not immediate (for example a recurring payment * with a delayed start) * 3) the payment succeeded with an immediate payment. * * The doPayment function ensures that contribution_status_id is always set * as historically we have had to guess from the context - ie doDirectPayment * = error or success, unless it is a recurring contribution in which case it is pending. */ if (!isset($result['contribution_status_id']) || $result['contribution_status_id'] == array_search('Completed', $statuses)) { civicrm_api3('contribution', 'completetransaction', array('id' => $contribution->id, 'trxn_id' => $result['trxn_id'])); } else { // Save the trxn_id. $contribution->save(); } } catch (PaymentProcessorException $e) { CRM_Contribute_BAO_Contribution::failPayment($contribution->id, $paymentParams['contactID'], $e->getMessage()); throw new PaymentProcessorException($e->getMessage()); } } // Send receipt mail. if ($contribution->id && !empty($this->_params['is_email_receipt'])) { $this->_params['trxn_id'] = CRM_Utils_Array::value('trxn_id', $result); $this->_params['contact_id'] = $contactID; $this->_params['contribution_id'] = $contribution->id; if (CRM_Contribute_Form_AdditionalInfo::emailReceipt($this, $this->_params, TRUE)) { $this->statusMessage[] = ts('A receipt has been emailed to the contributor.'); } } return $contribution; }
/** * Handle pre approval for processors. * * This fits with the flow where a pre-approval is done and then confirmed in the next stage when confirm is hit. * * This function is shared between contribution & event forms & this is their common class. * * However, this should be seen as an in-progress refactor, the end goal being to also align the * backoffice forms that action payments. * * @param array $params */ protected function handlePreApproval(&$params) { try { $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); $params['component'] = 'contribute'; $result = $payment->doPreApproval($params); if (empty($result)) { // This could happen, for example, when paypal looks at the button value & decides it is not paypal express. return; } } catch (\Civi\Payment\Exception\PaymentProcessorException $e) { CRM_Core_Error::statusBounce(ts('Payment approval failed with message :') . $e->getMessage(), $payment->getCancelUrl($params['qfKey'], CRM_Utils_Array::value('participant_id', $params))); } $this->set('pre_approval_parameters', $result['pre_approval_parameters']); if (!empty($result['redirect_url'])) { CRM_Utils_System::redirect($result['redirect_url']); } }
/** * Get a contribution form object for testing. * * @return \CRM_Contribute_Form_Contribution_Main */ protected function getContributionForm() { $form = new CRM_Contribute_Form_Contribution_Main(); $form->_values['is_monetary'] = 1; $form->_values['is_pay_later'] = 0; $form->_priceSetId = $this->callAPISuccessGetValue('PriceSet', array('name' => 'default_membership_type_amount', 'return' => 'id')); $priceFields = $this->callAPISuccess('PriceField', 'get', array('id' => $form->_priceSetId)); $form->_priceSet['fields'] = $priceFields['values']; $paymentProcessorID = $this->paymentProcessorCreate(array('payment_processor_type_id' => 'Dummy')); $form->_paymentProcessor = array('billing_mode' => CRM_Core_Payment::BILLING_MODE_FORM, 'object' => Civi\Payment\System::singleton()->getById($paymentProcessorID), 'is_recur' => TRUE); $form->_values = array('title' => "Test Contribution Page", 'financial_type_id' => 1, 'currency' => 'NZD', 'goal_amount' => 6000, 'is_pay_later' => 1, 'is_monetary' => TRUE, 'pay_later_text' => 'Front up', 'pay_later_receipt' => 'Ta'); return $form; }
/** * Process credit card payment. * * @param array $submittedValues * @param array $lineItem * * @param int $contactID * Contact ID * * @return bool|\CRM_Contribute_DAO_Contribution * @throws \CRM_Core_Exception * @throws \Civi\Payment\Exception\PaymentProcessorException */ protected function processCreditCard($submittedValues, $lineItem, $contactID) { $isTest = $this->_mode == 'test' ? 1 : 0; // CRM-12680 set $_lineItem if its not set // @todo - I don't believe this would ever BE set. I can't find anywhere in the code. // It would be better to pass line item out to functions than $this->_lineItem as // we don't know what is being changed where. if (empty($this->_lineItem) && !empty($lineItem)) { $this->_lineItem = $lineItem; } $this->_paymentObject = Civi\Payment\System::singleton()->getById($submittedValues['payment_processor_id']); $this->_paymentProcessor = $this->_paymentObject->getPaymentProcessor(); // Set source if not set if (empty($submittedValues['source'])) { $userID = CRM_Core_Session::singleton()->get('userID'); $userSortName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $userID, 'sort_name'); $submittedValues['source'] = ts('Submit Credit Card Payment by: %1', array(1 => $userSortName)); } $params = $submittedValues; $this->_params = array_merge($this->_params, $submittedValues); // Mapping requiring documentation. $this->_params['payment_processor'] = $submittedValues['payment_processor_id']; $now = date('YmdHis'); // we need to retrieve email address if ($this->_context == 'standalone' && !empty($submittedValues['is_email_receipt'])) { list($this->userDisplayName, $this->userEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contactID); $this->assign('displayName', $this->userDisplayName); } $this->_contributorEmail = $this->userEmail; $this->_contributorContactID = $contactID; $this->processBillingAddress(); if (!empty($params['source'])) { unset($params['source']); } $this->_params['amount'] = $this->_params['total_amount']; // @todo - stop setting amount level in this function & call the CRM_Price_BAO_PriceSet::getAmountLevel // function to get correct amount level consistently. Remove setting of the amount level in // CRM_Price_BAO_PriceSet::processAmount. Extend the unit tests in CRM_Price_BAO_PriceSetTest // to cover all variants. $this->_params['amount_level'] = 0; $this->_params['description'] = ts("Contribution submitted by a staff person using contributor's credit card"); $this->_params['currencyID'] = CRM_Utils_Array::value('currency', $this->_params, CRM_Core_Config::singleton()->defaultCurrency); if (!empty($this->_params['receive_date'])) { $this->_params['receive_date'] = CRM_Utils_Date::processDate($this->_params['receive_date'], $this->_params['receive_date_time']); } $this->_params['pcp_display_in_roll'] = CRM_Utils_Array::value('pcp_display_in_roll', $params); $this->_params['pcp_roll_nickname'] = CRM_Utils_Array::value('pcp_roll_nickname', $params); $this->_params['pcp_personal_note'] = CRM_Utils_Array::value('pcp_personal_note', $params); //Add common data to formatted params CRM_Contribute_Form_AdditionalInfo::postProcessCommon($params, $this->_params, $this); if (empty($this->_params['invoice_id'])) { $this->_params['invoiceID'] = md5(uniqid(rand(), TRUE)); } else { $this->_params['invoiceID'] = $this->_params['invoice_id']; } // At this point we've created a contact and stored its address etc // all the payment processors expect the name and address to be in the // so we copy stuff over to first_name etc. $paymentParams = $this->_params; $paymentParams['contactID'] = $contactID; CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $paymentParams, TRUE); $financialType = new CRM_Financial_DAO_FinancialType(); $financialType->id = $params['financial_type_id']; $financialType->find(TRUE); // Add some financial type details to the params list // if folks need to use it. $paymentParams['contributionType_name'] = $this->_params['contributionType_name'] = $financialType->name; $paymentParams['contributionPageID'] = NULL; if (!empty($this->_params['is_email_receipt'])) { $paymentParams['email'] = $this->userEmail; $paymentParams['is_email_receipt'] = 1; } else { $paymentParams['is_email_receipt'] = 0; $this->_params['is_email_receipt'] = 0; } if (!empty($this->_params['receive_date'])) { $paymentParams['receive_date'] = $this->_params['receive_date']; } $this->_params['receive_date'] = $now; if (!empty($this->_params['is_email_receipt'])) { $this->_params['receipt_date'] = $now; } else { $this->_params['receipt_date'] = CRM_Utils_Date::processDate($this->_params['receipt_date'], $params['receipt_date_time'], TRUE); } $this->set('params', $this->_params); $this->assign('receive_date', $this->_params['receive_date']); // Result has all the stuff we need // lets archive it to a financial transaction if ($financialType->is_deductible) { $this->assign('is_deductible', TRUE); $this->set('is_deductible', TRUE); } $contributionParams = array('contact_id' => $contactID, 'line_item' => $lineItem, 'is_test' => $isTest, 'campaign_id' => CRM_Utils_Array::value('campaign_id', $this->_params), 'contribution_page_id' => CRM_Utils_Array::value('contribution_page_id', $this->_params), 'source' => CRM_Utils_Array::value('source', $paymentParams, CRM_Utils_Array::value('description', $paymentParams)), 'thankyou_date' => CRM_Utils_Array::value('thankyou_date', $this->_params)); if (empty($paymentParams['is_pay_later'])) { // @todo look up payment_instrument_id on payment processor table. $contributionParams['payment_instrument_id'] = 1; } $contribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($this, $this->_params, NULL, $contributionParams, $financialType, FALSE, $this->_bltID, CRM_Utils_Array::value('is_recur', $this->_params)); $paymentParams['contributionID'] = $contribution->id; $paymentParams['contributionTypeID'] = $contribution->financial_type_id; $paymentParams['contributionPageID'] = $contribution->contribution_page_id; $paymentParams['contributionRecurID'] = $contribution->contribution_recur_id; if ($paymentParams['amount'] > 0.0) { // force a re-get of the payment processor in case the form changed it, CRM-7179 // NOTE - I expect this is obsolete. $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); try { $statuses = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id'); $result = $payment->doPayment($paymentParams, 'contribute'); $this->assign('trxn_id', $result['trxn_id']); $contribution->trxn_id = $result['trxn_id']; /* Our scenarios here are * 1) the payment failed & an Exception should have been thrown * 2) the payment succeeded but the payment is not immediate (for example a recurring payment * with a delayed start) * 3) the payment succeeded with an immediate payment. * * The doPayment function ensures that payment_status_id is always set * as historically we have had to guess from the context - ie doDirectPayment * = error or success, unless it is a recurring contribution in which case it is pending. */ if ($result['payment_status_id'] == array_search('Completed', $statuses)) { try { civicrm_api3('contribution', 'completetransaction', array('id' => $contribution->id, 'trxn_id' => $result['trxn_id'], 'payment_processor_id' => $this->_paymentProcessor['id'], 'is_transactional' => FALSE, 'fee_amount' => CRM_Utils_Array::value('fee_amount', $result))); // This has now been set to 1 in the DB - declare it here also $contribution->contribution_status_id = 1; } catch (CiviCRM_API3_Exception $e) { if ($e->getErrorCode() != 'contribution_completed') { throw new CRM_Core_Exception('Failed to update contribution in database'); } } } else { // Save the trxn_id. $contribution->save(); } } catch (PaymentProcessorException $e) { CRM_Contribute_BAO_Contribution::failPayment($contribution->id, $paymentParams['contactID'], $e->getMessage()); throw new PaymentProcessorException($e->getMessage()); } } // Send receipt mail. array_unshift($this->statusMessage, ts('The contribution record has been saved.')); if ($contribution->id && !empty($this->_params['is_email_receipt'])) { $this->_params['trxn_id'] = CRM_Utils_Array::value('trxn_id', $result); $this->_params['contact_id'] = $contactID; $this->_params['contribution_id'] = $contribution->id; if (CRM_Contribute_Form_AdditionalInfo::emailReceipt($this, $this->_params, TRUE)) { $this->statusMessage[] = ts('A receipt has been emailed to the contributor.'); } } return $contribution; }
/** * Run hooks in the payment processor class. * Load requested payment processor and call the method specified. * * @param CRM_Extension_Info $info * @param string $method * The method to call in the payment processor class. */ private function _runPaymentHook(CRM_Extension_Info $info, $method) { // Not concerned about performance at this stage, as these are seldom performed tasks // (payment processor enable/disable/install/uninstall). May wish to implement some // kind of registry/caching system if more hooks are added. try { $paymentClass = $this->mapper->keyToClass($info->key, 'payment'); $file = $this->mapper->classToPath($paymentClass); if (!file_exists($file)) { CRM_Core_Session::setStatus(ts('Failed to load file (%3) for payment processor (%1) while running "%2"', array(1 => $info->key, 2 => $method, 3 => $file)), '', 'error'); return; } else { require_once $file; } } catch (CRM_Extension_Exception $e) { CRM_Core_Session::setStatus(ts('Failed to determine file path for payment processor (%1) while running "%2"', array(1 => $info->key, 2 => $method)), '', 'error'); return; } $processorDAO = CRM_Core_DAO::executeQuery(" SELECT pp.id, ppt.class_name\n FROM civicrm_extension ext\n INNER JOIN civicrm_payment_processor_type ppt\n ON ext.name = ppt.name\n LEFT JOIN civicrm_payment_processor pp\n ON ppt.id = pp.payment_processor_type_id\n WHERE ext.type = 'payment'\n AND ext.full_name = %1\n ", array(1 => array($info->key, 'String'))); while ($processorDAO->fetch()) { $class_name = $processorDAO->class_name; $processor_id = $processorDAO->id; } if (empty($class_name)) { CRM_Core_Error::fatal("Unable to find payment processor in " . __CLASS__ . '::' . __METHOD__); } // In the case of uninstall, check for instances of PP first. // Don't run hook if any are found. if ($method == 'uninstall' && $processor_id > 0) { return; } switch ($method) { case 'install': case 'uninstall': case 'enable': case 'disable': // Instantiate PP - the getClass function allows us to do this when no payment processor instances exist. $processorInstance = Civi\Payment\System::singleton()->getByClass($class_name); // Does PP implement this method, and can we call it? if (method_exists($processorInstance, $method) && is_callable(array($processorInstance, $method))) { // If so, call it ... $processorInstance->{$method}(); } break; default: CRM_Core_Session::setStatus(ts("Unrecognized payment hook (%1) in %2::%3", array(1 => $method, 2 => __CLASS__, 3 => __METHOD__)), '', 'error'); } }
/** * Validate the payment instrument values before passing it to the payment processor. * * We want this to be able to be overridden by the payment processor, and default to using * this object's validCreditCard for credit cards (implemented as the default in the Payment class). * * @param int $payment_processor_id * @param array $values * @param array $errors * @param int $billing_profile_id */ public static function validatePaymentInstrument($payment_processor_id, $values, &$errors, $billing_profile_id) { $payment = Civi\Payment\System::singleton()->getById($payment_processor_id); $payment->setBillingProfile($billing_profile_id); $payment->validatePaymentInstrument($values, $errors); }
/** * Retrieve payment processor id / info/ object based on component-id. * * @param int $entityID * @param string $component * Component. * @param string $type * Type of payment information to be retrieved. * * @return int|array|object */ public static function getProcessorForEntity($entityID, $component = 'contribute', $type = 'id') { $result = NULL; if (!in_array($component, array('membership', 'contribute', 'recur'))) { return $result; } //FIXME: if ($component == 'membership') { $sql = "\n SELECT cr.payment_processor_id as ppID1, cp.payment_processor as ppID2, con.is_test\n FROM civicrm_membership mem\nINNER JOIN civicrm_membership_payment mp ON ( mem.id = mp.membership_id )\nINNER JOIN civicrm_contribution con ON ( mp.contribution_id = con.id )\n LEFT JOIN civicrm_contribution_recur cr ON ( mem.contribution_recur_id = cr.id )\n LEFT JOIN civicrm_contribution_page cp ON ( con.contribution_page_id = cp.id )\n WHERE mp.membership_id = %1"; } elseif ($component == 'contribute') { $sql = "\n SELECT cr.payment_processor_id as ppID1, cp.payment_processor as ppID2, con.is_test\n FROM civicrm_contribution con\n LEFT JOIN civicrm_contribution_recur cr ON ( con.contribution_recur_id = cr.id )\n LEFT JOIN civicrm_contribution_page cp ON ( con.contribution_page_id = cp.id )\n WHERE con.id = %1"; } elseif ($component == 'recur') { $sql = "\n SELECT cr.payment_processor_id as ppID1, NULL as ppID2, cr.is_test\n FROM civicrm_contribution_recur cr\n WHERE cr.id = %1"; } //we are interesting in single record. $sql .= ' LIMIT 1'; $params = array(1 => array($entityID, 'Integer')); $dao = CRM_Core_DAO::executeQuery($sql, $params); if (!$dao->fetch()) { return $result; } $ppID = isset($dao->ppID1) && $dao->ppID1 ? $dao->ppID1 : (isset($dao->ppID2) ? $dao->ppID2 : NULL); $mode = isset($dao->is_test) && $dao->is_test ? 'test' : 'live'; if (!$ppID || $type == 'id') { $result = $ppID; } elseif ($type == 'info') { $result = self::getPayment($ppID, $mode); } elseif ($type == 'obj') { $payment = self::getPayment($ppID, $mode); $result = Civi\Payment\System::singleton()->getByProcessor($payment); } return $result; }