From 061972706c288d92ca1fdb2395face4a55759981 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Thu, 4 Oct 2018 15:59:35 +0200 Subject: [PATCH] [WIP] TwingleDonation.Submit API action --- CRM/Twingle/Profile.php | 278 ++++++++++++++++++ CRM/Twingle/Submission.php | 237 +++++++++++++++ api/v3/TwingleDonation/Submit.php | 460 ++++++++++++++++++++++++++++++ info.xml | 3 + 4 files changed, 978 insertions(+) create mode 100644 CRM/Twingle/Profile.php create mode 100644 CRM/Twingle/Submission.php create mode 100644 api/v3/TwingleDonation/Submit.php diff --git a/CRM/Twingle/Profile.php b/CRM/Twingle/Profile.php new file mode 100644 index 0000000..5d9c421 --- /dev/null +++ b/CRM/Twingle/Profile.php @@ -0,0 +1,278 @@ +name = $name; + $allowed_attributes = self::allowedAttributes(); + $this->data = $data + array_combine( + $allowed_attributes, + array_fill(0, count($allowed_attributes), NULL) + ); + } + + + /** + * Checks whether the profile's selector matches the given project ID. + * + * @param string | int $project_id + * + * @return bool + */ + public function matches($project_id) { + $selector = $this->getAttribute('selector'); + $project_ids = explode(',', $selector); + return in_array($project_id, $project_ids); + } + + /** + * Retrieves all data attributes of the profile. + * + * @return array + */ + public function getData() { + return $this->data; + } + + /** + * Retrieves the profile name. + * + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Sets the profile name. + * + * @param $name + */ + public function setName($name) { + $this->name = $name; + } + + /** + * Retrieves an attribute of the profile. + * + * @param string $attribute_name + * + * @return mixed | NULL + */ + public function getAttribute($attribute_name) { + if (isset($this->data[$attribute_name])) { + return $this->data[$attribute_name]; + } + else { + return NULL; + } + } + + /** + * Sets an attribute of the profile. + * + * @param string $attribute_name + * @param mixed $value + * + * @throws \Exception + * When the attribute name is not known. + */ + public function setAttribute($attribute_name, $value) { + if (!in_array($attribute_name, self::allowedAttributes())) { + throw new Exception(E::ts('Unknown attribute %1.', array(1 => $attribute_name))); + } + // TODO: Check if value is acceptable. + $this->data[$attribute_name] = $value; + } + + /** + * Verifies whether the profile is valid (i.e. consistent and not colliding + * with other profiles). + * + * @throws Exception + * When the profile could not be successfully validated. + */ + public function verifyProfile() { + // TODO: check + // data of this profile consistent? + // conflicts with other profiles? + } + + /** + * Persists the profile within the CiviCRM settings. + */ + public function saveProfile() { + self::$_profiles[$this->getName()] = $this; + $this->verifyProfile(); + self::storeProfiles(); + } + + /** + * Deletes the profile from the CiviCRM settings. + */ + public function deleteProfile() { + unset(self::$_profiles[$this->getName()]); + self::storeProfiles(); + } + + /** + * Returns an array of attributes allowed for a profile. + * + * @return array + */ + public static function allowedAttributes() { + // TODO: Adjust attributes for Twingle. + return array( + 'selector', + 'location_type_id', + 'financial_type_id', + 'campaign_id', + 'pi_creditcard', + 'pi_sepa', + 'pi_paypal', + 'groups', + ); + } + + /** + * Returns the default profile with "factory" defaults. + * + * @param string $name + * The profile name. Defaults to "default". + * + * @return CRM_Twingle_Profile + */ + public static function createDefaultProfile($name = 'default') { + // TODO: Adjust attributes for Twingle. + return new CRM_Twingle_Profile($name, array( + 'selector' => '', + 'location_type_id' => CRM_Twingle_Submission::LOCATION_TYPE_ID_WORK, + 'financial_type_id' => 1, // "Donation" + 'campaign_id' => '', + 'pi_creditcard' => 1, // "Credit Card" + 'pi_sepa' => 5, // "EFT" + 'pi_paypal' => 3, // "Debit" + 'groups' => '', + )); + } + + /** + * Retrieves the profile that matches the given project ID, i.e. the profile + * which is responsible for processing the project's data. + * Returns the default profile if no match was found. + * + * @param $project_id + * + * @return CRM_Twingle_Profile + */ + public static function getProfileForProject($project_id) { + $profiles = self::getProfiles(); + foreach ($profiles as $profile) { + if ($profile->matches($project_id)) { + return $profile; + } + } + + // No profile matched, return default profile. + return $profiles['default']; + } + + /** + * Retrieves the profil with the given name. + * + * @param $name + * + * @return CRM_Twingle_Profile | NULL + */ + public static function getProfile($name) { + $profiles = self::getProfiles(); + if (isset($profiles[$name])) { + return $profiles[$name]; + } + else { + return NULL; + } + } + + /** + * Retrieves the list of all profiles persisted within the current CiviCRM + * settings, including the default profile. + * + * @return CRM_Twingle_Profile[] + */ + public static function getProfiles() { + if (self::$_profiles === NULL) { + self::$_profiles = array(); + if ($profiles_data = CRM_Core_BAO_Setting::getItem('de.systopia.twingle', 'twingle_profiles')) { + foreach ($profiles_data as $profile_name => $profile_data) { + self::$_profiles[$profile_name] = new CRM_Twingle_Profile($profile_name, $profile_data); + } + } + } + + // Include the default profile if it was not overridden within the settings. + if (!isset(self::$_profiles['default'])) { + self::$_profiles['default'] = self::createDefaultProfile(); + self::storeProfiles(); + } + + return self::$_profiles; + } + + + /** + * Persists the list of profiles into the CiviCRM settings. + */ + public static function storeProfiles() { + $profile_data = array(); + foreach (self::$_profiles as $profile_name => $profile) { + $profile_data[$profile_name] = $profile->data; + } + CRM_Core_BAO_Setting::setItem((object) $profile_data, 'de.systopia.twingle', 'twingle_profiles'); + } +} diff --git a/CRM/Twingle/Submission.php b/CRM/Twingle/Submission.php new file mode 100644 index 0000000..8260d81 --- /dev/null +++ b/CRM/Twingle/Submission.php @@ -0,0 +1,237 @@ +getAttribute('pi_' . $params['payment_method'])) { + throw new CiviCRM_API3_Exception( + E::ts('Payment method could not be matched to existing payment instrument.'), + 'invalid_format' + ); + } + $params['payment_instrument_id'] = $payment_instrument_id; + + // Validate date for parameter "confirmed_at". + if (!DateTime::createFromFormat('Ymd', $params['confirmed_at'])) { + throw new CiviCRM_API3_Exception( + E::ts('Invalid date for parameter "confirmed_at".'), + 'invalid_format' + ); + } + + // Validate date for parameter "user_birthdate". + if (!empty($params['user_birthdate']) && !DateTime::createFromFormat('Ymd', $params['user_birthdate'])) { + throw new CiviCRM_API3_Exception( + E::ts('Invalid date for parameter "user_birthdate".'), + 'invalid_format' + ); + } + + // Get the gender ID defined within the profile, or return an error if none + // matches (i.e. an unknown gender was submitted). + if (!$gender_id = $profile->getAttribute('gender_' . $params['user_gender'])) { + throw new CiviCRM_API3_Exception( + E::ts('Gender could not be matched to existing gender.'), + 'invalid_format' + ); + } + $params['gender_id'] = $gender_id; + } + + /** + * Retrieves the contact matching the given contact data or creates a new + * contact. + * + * @param string $contact_type + * The contact type to look for/to create. + * @param array $contact_data + * Data to use for contact lookup/to create a contact with. + * + * @return int | NULL + * The ID of the matching/created contact, or NULL if no matching contact + * was found and no new contact could be created. + * @throws \CiviCRM_API3_Exception + * When invalid data was given. + */ + public static function getContact($contact_type, $contact_data) { + // If no parameters are given, do nothing. + if (empty($contact_data)) { + return NULL; + } + + // Prepare values: country. + if (!empty($contact_data['country'])) { + if (is_numeric($contact_data['country'])) { + // If a country ID is given, update the parameters. + $contact_data['country_id'] = $contact_data['country']; + unset($contact_data['country']); + } + else { + // Look up the country depending on the given ISO code. + $country = civicrm_api3('Country', 'get', array('iso_code' => $contact_data['country'])); + if (!empty($country['id'])) { + $contact_data['country_id'] = $country['id']; + unset($contact_data['country']); + } + else { + throw new \CiviCRM_API3_Exception( + E::ts('Unknown country %1.', array(1 => $contact_data['country'])), + 'invalid_format' + ); + } + } + } + + // Pass to XCM. + $contact_data['contact_type'] = $contact_type; + $contact = civicrm_api3('Contact', 'getorcreate', $contact_data); + if (empty($contact['id'])) { + return NULL; + } + + return $contact['id']; + } + + /** + * Shares an organisation's work address, unless the contact already has one. + * + * @param $contact_id + * The ID of the contact to share the organisation address with. + * @param $organisation_id + * The ID of the organisation whose address to share with the contact. + * @param $location_type_id + * The ID of the location type to use for address lookup. + * + * @return boolean + * Whether the organisation address has been shared with the contact. + * + * @throws \CiviCRM_API3_Exception + * When looking up or creating the shared address failed. + */ + public static function shareWorkAddress($contact_id, $organisation_id, $location_type_id = self::LOCATION_TYPE_ID_WORK) { + if (empty($organisation_id)) { + // Only if organisation exists. + return FALSE; + } + + // Check whether organisation has a WORK address. + $existing_org_addresses = civicrm_api3('Address', 'get', array( + 'contact_id' => $organisation_id, + 'location_type_id' => $location_type_id)); + if ($existing_org_addresses['count'] <= 0) { + // Organisation does not have a WORK address. + return FALSE; + } + + // Check whether contact already has a WORK address. + $existing_contact_addresses = civicrm_api3('Address', 'get', array( + 'contact_id' => $contact_id, + 'location_type_id' => $location_type_id)); + if ($existing_contact_addresses['count'] > 0) { + // Contact already has a WORK address. + return FALSE; + } + + // Create a shared address. + $address = reset($existing_org_addresses['values']); + $address['contact_id'] = $contact_id; + $address['master_id'] = $address['id']; + unset($address['id']); + civicrm_api3('Address', 'create', $address); + return TRUE; + } + + /** + * Updates or creates an employer relationship between contact and + * organisation. + * + * @param int $contact_id + * The ID of the employee contact. + * @param int $organisation_id + * The ID of the employer contact. + * + * @throws \CiviCRM_API3_Exception + */ + public static function updateEmployerRelation($contact_id, $organisation_id) { + if (empty($contact_id) || empty($organisation_id)) { + return; + } + + // see if there is already one + $existing_relationship = civicrm_api3('Relationship', 'get', array( + 'relationship_type_id' => self::EMPLOYER_RELATIONSHIP_TYPE_ID, + 'contact_id_a' => $contact_id, + 'contact_id_b' => $organisation_id, + 'is_active' => 1, + )); + + if ($existing_relationship['count'] == 0) { + // There is currently no (active) relationship between these contacts. + $new_relationship_data = array( + 'relationship_type_id' => self::EMPLOYER_RELATIONSHIP_TYPE_ID, + 'contact_id_a' => $contact_id, + 'contact_id_b' => $organisation_id, + 'is_active' => 1, + ); + + civicrm_api3('Relationship', 'create', $new_relationship_data); + } + } + +} diff --git a/api/v3/TwingleDonation/Submit.php b/api/v3/TwingleDonation/Submit.php new file mode 100644 index 0000000..4d0fb40 --- /dev/null +++ b/api/v3/TwingleDonation/Submit.php @@ -0,0 +1,460 @@ + 'project_id', + 'title' => 'Project ID', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 1, + 'description' => 'The Twingle project ID.', + ); + $params['trx_id'] = array( + 'name' => 'trx_id', + 'title' => 'Transaction ID', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 1, + 'description' => 'The unique transaction ID of the donation', + ); + $params['confirmed_at'] = array( + 'name' => 'confirmed_at', + 'title' => 'Confirmed at', + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 1, + 'description' => 'The date when the donation was issued, format: YYYYMMDD.', + ); + $params['purpose'] = array( + 'name' => 'purpose', + 'title' => 'Purpose', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The purpose of the donation.', + ); + $params['amount'] = array( + 'name' => 'amount', + 'title' => 'Amount', + 'type' => CRM_Utils_Type::T_INT, + 'api.required' => 1, + 'description' => 'The donation amount in minor currency unit.', + ); + $params['currency'] = array( + 'name' => 'currency', + 'title' => 'Currency', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 1, + 'description' => 'The ISO-4217 currency code of the donation.', + ); + $params['newsletter'] = array( + 'name' => 'newsletter', + 'title' => 'Newsletter', + 'type' => CRM_Utils_Type::T_BOOLEAN, + 'api.required' => 0, + 'description' => 'Whether to subscribe the contact to the newsletter group defined in the profile.', + ); + $params['postinfo'] = array( + 'name' => 'postinfo', + 'title' => 'Postal mailing', + 'type' => CRM_Utils_Type::T_BOOLEAN, + 'api.required' => 0, + 'description' => 'Whether to subscribe the contact to the postal mailing group defined in the profile.', + ); + $params['donation_receipt'] = array( + 'name' => 'donation_receipt', + 'title' => 'Donation receipt', + 'type' => CRM_Utils_Type::T_BOOLEAN, + 'api.required' => 0, + 'description' => 'Whether the contact requested a donation receipt.', + ); + $params['payment_method'] = array( + 'name' => 'payment_method', + 'title' => 'Payment method', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 1, + 'description' => 'The Twingle payment method used for the donation.', + ); + $params['donation_rhythm'] = array( + 'name' => 'donation_rhythm', + 'title' => 'Donation rhythm', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 1, + 'description' => 'The interval which the donation is recurring in.', + ); + $params['debit_iban'] = array( + 'name' => 'debit_iban', + 'title' => 'SEPA IBAN', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The IBAN for SEPA Direct Debit payments, conforming with ISO 13616-1:2007.', + ); + $params['debit_bic'] = array( + 'name' => 'debit_bic', + 'title' => 'SEPA BIC', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The BIC for SEPA Direct Debit payments, conforming with ISO 9362.', + ); + $params['debit_mandate_reference'] = array( + 'name' => 'debit_mandate_reference', + 'title' => 'SEPA Direct Debit Mandate reference', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The mandate for SEPA Direct Debit payments.', + ); + $params['debit_account_holder'] = array( + 'name' => 'debit_account_holder', + 'title' => 'SEPA Direct Debit Account holder', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The mandate for SEPA Direct Debit payments.', + ); + $params['is_anonymous'] = array( + 'name' => 'is_anonymous', + 'title' => 'Anonymous donation', + 'type' => CRM_Utils_Type::T_BOOLEAN, + 'api.required' => 0, + 'api.default' => 0, + 'description' => 'Whether the donation is submitted anonymously.', + ); + $params['user_gender'] = array( + 'name' => 'user_gender', + 'title' => 'Gender', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The gender of the contact.', + ); + $params['user_birthdate'] = array( + 'name' => 'user_birthdate', + 'title' => 'Date of birth', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The date of birth of the contact, format: YYYYMMDD.', + ); + $params['user_title'] = array( + 'name' => 'user_title', + 'title' => 'Formal title', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The formal title of the contact.', + ); + $params['user_email'] = array( + 'name' => 'user_email', + 'title' => 'E-mail address', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The e-mail address of the contact.', + ); + $params['user_firstname'] = array( + 'name' => 'user_firstname', + 'title' => 'First name', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The first name of the contact.', + ); + $params['user_lastname'] = array( + 'name' => 'user_lastname', + 'title' => 'Last name', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The last name of the contact.', + ); + $params['user_street'] = array( + 'name' => 'user_street', + 'title' => 'Street address', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The street address of the contact.', + ); + $params['user_postal_code'] = array( + 'name' => 'user_postal_code', + 'title' => 'Postal code', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The postal code of the contact.', + ); + $params['user_city'] = array( + 'name' => 'user_city', + 'title' => 'City', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The city of the contact.', + ); + $params['user_telephone'] = array( + 'name' => 'user_telephone', + 'title' => 'Telephone', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The telephone number of the contact.', + ); + $params['user_company'] = array( + 'name' => 'user_company', + 'title' => 'Company', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'The company of the contact.', + ); + $params['user_extrafield'] = array( + 'name' => 'user_extrafield', + 'title' => 'User extra field', + 'type' => CRM_Utils_Type::T_STRING, + 'api.required' => 0, + 'description' => 'Additional information of the contact.', + ); +} + +/** + * TwingleDonation.Submit API + * + * @param array $params + * @return array API result descriptor + * @see civicrm_api3_create_success + * @see civicrm_api3_create_error + */ +function civicrm_api3_twingle_donation_Submit($params) { + try { + // Copy submitted parameters. + $original_params = $params; + + // Get the profile defined for the given form ID, or the default profile + // if none matches. + $profile = CRM_Twingle_Profile::getProfileForProject($params['project_id']); + + // Validate submitted parameters + CRM_Twingle_Submission::validateSubmission($params, $profile); + + // Do not process an already existing contribution with the given + // transaction ID. + $contribution = civicrm_api3('Contribution', 'get', array( + 'trxn_id' => $params['trx_id'] + )); + if ($contribution['count'] > 0) { + throw new CiviCRM_API3_Exception( + E::ts('Contribution with the given transaction ID already exists.'), + 'api_error' + ); + } + + // Create contact(s). + if ($params['is_anonymous']) { + // Retrieve the ID of the contact to use for anonymous donations defined + // within the profile + $contact_id = civicrm_api3('Contact', 'getsingle', array( + 'id' => $profile->getAttribute('anonymous_contact_id'), + ))['id']; + } + else { + // Prepare parameter mapping for address. + foreach (array( + 'user_street' => 'street_address', + 'user_postal_code' => 'postal_code', + 'user_city' => 'city', + ) as $address_param => $address_component) { + if (!empty($params[$address_param])) { + $params[$address_component] = $params[$address_param]; + if ($address_param != $address_component) { + unset($params[$address_param]); + } + } + } + + // Prepare parameter mapping for organisation. + if (!empty($params['user_company'])) { + $params['organization_name'] = $params['user_company']; + unset($params['user_company']); + } + + // Remove parameter "id". + if (!empty($params['id'])) { + unset($params['id']); + } + + // Add location type to parameters. + $params['location_type_id'] = (int) $profile->getAttribute('location_type_id'); + + // Exclude address for now when retrieving/creating the individual contact + // and an organisation is given, as we are checking organisation address + // first and share it with the individual. + if (!empty($params['organization_name'])) { + $submitted_address = array(); + foreach (array( + 'street_address', + 'postal_code', + 'city', + 'country', + 'location_type_id', + ) as $address_component) { + if (!empty($params[$address_component])) { + $submitted_address[$address_component] = $params[$address_component]; + unset($params[$address_component]); + } + } + } + + // Get the ID of the contact matching the given contact data, or create a + // new contact if none exists for the given contact data. + $contact_data = array(); + foreach (array( + 'user_firstname' => 'first_name', + 'user_lastname' => 'last_name', + 'gender_id' => 'gender_id', + 'user_birthdate' => 'birth_date', + 'user_email' => 'email', + 'user_telephone' => 'phone', + ) as $contact_param => $contact_component) { + if (!empty($params[$contact_param])) { + $contact_data[$contact_component] = $params[$contact_param]; + } + } + if (!$contact_id = CRM_Twingle_Submission::getContact( + 'Individual', + $contact_data + )) { + throw new CiviCRM_API3_Exception( + E::ts('Individual contact could not be found or created.'), + 'api_error' + ); + } + + // Organisation lookup. + if (!empty($params['organization_name'])) { + $organisation_data = array( + 'organization_name' => $params['organization_name'], + ); + if (!empty($submitted_address)) { + $params += $submitted_address; + } + if (!$organisation_id = CRM_Twingle_Submission::getContact( + 'Organization', + $organisation_data + )) { + throw new CiviCRM_API3_Exception( + E::ts('Organisation contact could not be found or created.'), + 'api_error' + ); + } + } + $address_shared = ( + isset($organisation_id) + && CRM_Twingle_Submission::shareWorkAddress( + $contact_id, + $organisation_id, + $params['location_type_id'] + ) + ); + + // Address is not shared, use submitted address. + if (!$address_shared && !empty($submitted_address)) { + $submitted_address['contact_id'] = $contact_id; + civicrm_api3('Address', 'create', $submitted_address); + } + + // Create employer relationship between organization and individual. + if (isset($organisation_id)) { + CRM_Twingle_Submission::updateEmployerRelation($contact_id, $organisation_id); + } + } + + // Create contribution or SEPA mandate. + $contribution_data = array( + 'contact_id' => (isset($organisation_id) ? $organisation_id : $contact_id), + 'currency' => $params['currency'], + 'trxn_id' => $params['trx_id'], + 'financial_type_id' => $profile->getAttribute('financial_type_id'), + 'payment_instrument_id' => $params['payment_instrument_id'], + 'amount' => $params['amount'], + 'total_amount' => $params['amount'], + ); + if (!empty($params['purpose'])) { + $contribution_data['note'] = $params['purpose']; + } + + $sepa_extension = civicrm_api3('Extension', 'get', array( + 'full_name' => 'org.project60.sepa', + 'is_active' => 1, + )); + if ($sepa_extension['count'] && CRM_Sepa_Logic_Settings::isSDD($contribution_data)) { + // If CiviSEPA is installed and the financial type is a CiviSEPA-one, + // create SEPA mandate (and recurring contribution, using "createfull" API + // action). + $mandate_data = $contribution_data + array( + 'type' => ($params['donation_rhythm'] == 'one_time' ? 'OOFF' : 'RCUR'), + 'iban' => $params['debit_iban'], + 'bic' => $params['debit_bic'], + 'reference' => $params['debit_mandate_reference'], + 'date' => $params['confirmed_at'], + 'creditor_id' => $profile->getAttribute('sepa_creditor_id'), + ); + $mandate = civicrm_api3('SepaMandate', 'createfull', $mandate_data); + if ($mandate['is_error']) { + throw new CiviCRM_API3_Exception( + E::ts('Could not create SEPA mandate'), + 'api_error' + ); + } + } + else { + // Create (recurring) contribution. + if ($params['donation_rhythm'] != 'one_time') { + // Create recurring contribution first. + $contribution_recur_data = $contribution_data + array( + 'frequency_interval' => '', // TODO + 'contribution_status_id' => 'Pending', // TODO: Or "In Progress"? + 'start_date' => $params['confirmed_at'], + ); + $contribution_recur = civicrm_api3('contributionRecur', 'create', $contribution_recur_data); + if ($contribution_recur['is_error']) { + throw new CiviCRM_API3_Exception( + E::ts('Could not create recurring contribution.'), + 'api_error' + ); + } + $contribution_data['contribution_recur_id'] = $contribution_recur['id']; + } + + // Create contribution. + $contribution_data += array( + 'contribution_status_id' => 'Completed', + 'receive_date' => $params['confirmed_at'], + ); + $contribution = civicrm_api3('Contribution', 'create', $contribution_data); + if ($contribution['is_error']) { + throw new CiviCRM_API3_Exception( + E::ts('Could not create contribution'), + 'api_error' + ); + } + } + + // TODO: Assemble return data. + $result = civicrm_api3_create_success(); + } + catch (CiviCRM_API3_Exception $exception) { + $result = civicrm_api3_create_error($exception->getMessage()); + } + + return $result; +} diff --git a/info.xml b/info.xml index f606e6c..d493788 100644 --- a/info.xml +++ b/info.xml @@ -21,6 +21,9 @@ 4.7 + + de.systopia.xcm + CRM/Twingle