Commit | Line | Data |
---|---|---|
31a99426 PN |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
a30c801b | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
31a99426 | 5 | | | |
a30c801b TO |
6 | | This work is published under the GNU AGPLv3 license with some | |
7 | | permitted exceptions and without any warranty. For full license | | |
8 | | and copyright information, see https://civicrm.org/licensing | | |
31a99426 PN |
9 | +--------------------------------------------------------------------+ |
10 | */ | |
11 | ||
12 | /** | |
7cad285e JM |
13 | * This api exposes CiviCRM Order objects, an abstract entity |
14 | * comprised of contributions and related line items. | |
31a99426 PN |
15 | * |
16 | * @package CiviCRM_APIv3 | |
17 | */ | |
18 | ||
88060045 EM |
19 | use Civi\Api4\Membership; |
20 | ||
31a99426 PN |
21 | /** |
22 | * Retrieve a set of Order. | |
23 | * | |
24 | * @param array $params | |
25 | * Input parameters. | |
26 | * | |
27 | * @return array | |
28 | * Array of Order, if error an array with an error id and error message | |
ee95478a | 29 | * @throws \CiviCRM_API3_Exception |
31a99426 | 30 | */ |
ee95478a | 31 | function civicrm_api3_order_get(array $params): array { |
cf8f0fff CW |
32 | $contributions = []; |
33 | $params['api.line_item.get'] = ['qty' => ['<>' => 0]]; | |
31a99426 | 34 | $isSequential = FALSE; |
de6c59ca | 35 | if (!empty($params['sequential'])) { |
31a99426 PN |
36 | $params['sequential'] = 0; |
37 | $isSequential = TRUE; | |
38 | } | |
39 | $result = civicrm_api3('Contribution', 'get', $params); | |
40 | if (!empty($result['values'])) { | |
41 | foreach ($result['values'] as $key => $contribution) { | |
42 | $contributions[$key] = $contribution; | |
43 | $contributions[$key]['line_items'] = $contribution['api.line_item.get']['values']; | |
44 | unset($contributions[$key]['api.line_item.get']); | |
45 | } | |
46 | } | |
47 | $params['sequential'] = $isSequential; | |
48 | return civicrm_api3_create_success($contributions, $params, 'Order', 'get'); | |
49 | } | |
b8644ae3 | 50 | |
785f03e2 | 51 | /** |
52 | * Adjust Metadata for Get action. | |
53 | * | |
54 | * The metadata is used for setting defaults, documentation & validation. | |
55 | * | |
56 | * @param array $params | |
57 | * Array of parameters determined by getfields. | |
58 | */ | |
ee95478a | 59 | function _civicrm_api3_order_get_spec(array &$params) { |
785f03e2 | 60 | $params['id']['api.aliases'] = ['order_id']; |
61 | $params['id']['title'] = ts('Contribution / Order ID'); | |
62 | } | |
63 | ||
b8644ae3 PN |
64 | /** |
65 | * Add or update a Order. | |
66 | * | |
67 | * @param array $params | |
68 | * Input parameters. | |
69 | * | |
b8644ae3 PN |
70 | * @return array |
71 | * Api result array | |
9c5edcd4 | 72 | * |
73 | * @throws \CiviCRM_API3_Exception | |
74 | * @throws API_Exception | |
b8644ae3 | 75 | */ |
ee95478a | 76 | function civicrm_api3_order_create(array $params): array { |
9c5edcd4 | 77 | civicrm_api3_verify_one_mandatory($params, NULL, ['line_items', 'total_amount']); |
ca1b238b EM |
78 | if (empty($params['skipCleanMoney'])) { |
79 | // We have to do this for v3 api - sadly. For v4 it will be no more. | |
80 | foreach (['total_amount', 'net_amount', 'fee_amount', 'non_deductible_amount'] as $field) { | |
81 | if (isset($params[$field])) { | |
82 | $params[$field] = CRM_Utils_Rule::cleanMoney($params[$field]); | |
83 | } | |
84 | } | |
85 | $params['skipCleanMoney'] = TRUE; | |
86 | } | |
5ad21509 | 87 | $params['contribution_status_id'] = 'Pending'; |
ca44bb7e EM |
88 | $order = new CRM_Financial_BAO_Order(); |
89 | $order->setDefaultFinancialTypeID($params['financial_type_id'] ?? NULL); | |
e2887a3c | 90 | |
de6c59ca | 91 | if (!empty($params['line_items']) && is_array($params['line_items'])) { |
ca44bb7e | 92 | foreach ($params['line_items'] as $index => $lineItems) { |
ca1b238b EM |
93 | if (!empty($lineItems['params'])) { |
94 | $order->setEntityParameters($lineItems['params'], $index); | |
95 | } | |
ca44bb7e EM |
96 | foreach ($lineItems['line_item'] as $innerIndex => $lineItem) { |
97 | $lineIndex = $index . '+' . $innerIndex; | |
98 | $order->setLineItem($lineItem, $lineIndex); | |
ca1b238b | 99 | $order->addLineItemToEntityParameters($lineIndex, $index); |
b8644ae3 | 100 | } |
b8644ae3 PN |
101 | } |
102 | } | |
ca44bb7e EM |
103 | else { |
104 | $order->setPriceSetToDefault('contribution'); | |
68a5e5fa EM |
105 | $order->setOverrideTotalAmount((float) $params['total_amount']); |
106 | $order->setLineItem([], 0); | |
ca44bb7e | 107 | } |
ca1b238b EM |
108 | // Only check the amount if line items are set because that is what we have historically |
109 | // done and total amount is historically only inclusive of tax_amount IF | |
110 | // tax amount is also passed in it seems | |
111 | if (isset($params['total_amount']) && !empty($params['line_items'])) { | |
112 | $currency = $params['currency'] ?? CRM_Core_Config::singleton()->defaultCurrency; | |
113 | if (!CRM_Utils_Money::equals($params['total_amount'], $order->getTotalAmount(), $currency)) { | |
114 | throw new CRM_Contribute_Exception_CheckLineItemsException(); | |
115 | } | |
116 | } | |
117 | $params['total_amount'] = $order->getTotalAmount(); | |
68a5e5fa EM |
118 | if (!isset($params['tax_amount'])) { |
119 | // @todo always calculate tax amount - left for now | |
120 | // for webform | |
121 | $params['tax_amount'] = $order->getTotalTaxAmount(); | |
122 | } | |
ca1b238b EM |
123 | |
124 | foreach ($order->getEntitiesToCreate() as $entityParams) { | |
125 | if ($entityParams['entity'] === 'participant') { | |
126 | if (isset($entityParams['participant_status_id']) | |
127 | && (!CRM_Event_BAO_ParticipantStatusType::getIsValidStatusForClass($entityParams['participant_status_id'], 'Pending'))) { | |
128 | throw new CiviCRM_API3_Exception('Creating a participant via the Order API with a non "pending" status is not supported'); | |
129 | } | |
130 | $entityParams['participant_status_id'] = $entityParams['participant_status_id'] ?? 'Pending from incomplete transaction'; | |
131 | $entityParams['status_id'] = $entityParams['participant_status_id']; | |
132 | $entityParams['skipLineItem'] = TRUE; | |
133 | $entityResult = civicrm_api3('Participant', 'create', $entityParams); | |
134 | // @todo - once membership is cleaned up & financial validation tests are extended | |
135 | // we can look at removing this - some weird handling in removeFinancialAccounts | |
136 | $params['contribution_mode'] = 'participant'; | |
137 | $params['participant_id'] = $entityResult['id']; | |
138 | foreach ($entityParams['line_references'] as $lineIndex) { | |
139 | $order->setLineItemValue('entity_id', $entityResult['id'], $lineIndex); | |
140 | } | |
141 | } | |
142 | ||
143 | if ($entityParams['entity'] === 'membership') { | |
49f03de9 | 144 | if (empty($entityParams['id'])) { |
88060045 | 145 | $entityParams['status_id:name'] = 'Pending'; |
49f03de9 | 146 | } |
ca1b238b EM |
147 | if (!empty($params['contribution_recur_id'])) { |
148 | $entityParams['contribution_recur_id'] = $params['contribution_recur_id']; | |
149 | } | |
88060045 EM |
150 | // At this stage we need to get this passed through. |
151 | $entityParams['version'] = 4; | |
152 | _order_create_wrangle_membership_params($entityParams); | |
153 | ||
154 | $membershipID = Membership::save($params['check_permissions'] ?? FALSE)->setRecords([$entityParams])->execute()->first()['id']; | |
ca1b238b | 155 | foreach ($entityParams['line_references'] as $lineIndex) { |
88060045 | 156 | $order->setLineItemValue('entity_id', $membershipID, $lineIndex); |
ca1b238b EM |
157 | } |
158 | } | |
159 | } | |
160 | ||
161 | $params['line_item'][$order->getPriceSetID()] = $order->getLineItems(); | |
ca44bb7e | 162 | |
e2887a3c | 163 | $contributionParams = $params; |
0dea0c7c | 164 | // If this is nested we need to set sequential to 0 as sequential handling is done |
165 | // in create_success & id will be miscalculated... | |
166 | $contributionParams['sequential'] = 0; | |
e2887a3c | 167 | foreach ($contributionParams as $key => $value) { |
168 | // Unset chained keys so the code does not attempt to do this chaining twice. | |
169 | // e.g if calling 'api.Payment.create' We want to finish creating the order first. | |
170 | // it would probably be better to have a full whitelist of contributionParams | |
171 | if (substr($key, 0, 3) === 'api') { | |
172 | unset($contributionParams[$key]); | |
173 | } | |
174 | } | |
175 | ||
176 | $contribution = civicrm_api3('Contribution', 'create', $contributionParams); | |
26076348 | 177 | $contribution['values'][$contribution['id']]['line_item'] = array_values($order->getLineItems()); |
e376b300 | 178 | |
dffc9c5f | 179 | return civicrm_api3_create_success($contribution['values'] ?? [], $params, 'Order', 'create'); |
b8644ae3 PN |
180 | } |
181 | ||
5f44f698 PN |
182 | /** |
183 | * Delete a Order. | |
184 | * | |
185 | * @param array $params | |
186 | * Input parameters. | |
ee95478a | 187 | * |
5f44f698 | 188 | * @return array |
8089541a SL |
189 | * @throws API_Exception |
190 | * @throws CiviCRM_API3_Exception | |
5f44f698 | 191 | */ |
ee95478a | 192 | function civicrm_api3_order_delete(array $params): array { |
cf8f0fff CW |
193 | $contribution = civicrm_api3('Contribution', 'get', [ |
194 | 'return' => ['is_test'], | |
95c4e89e | 195 | 'id' => $params['id'], |
cf8f0fff | 196 | ]); |
5f44f698 PN |
197 | if ($contribution['id'] && $contribution['values'][$contribution['id']]['is_test'] == TRUE) { |
198 | $result = civicrm_api3('Contribution', 'delete', $params); | |
199 | } | |
200 | else { | |
95c4e89e | 201 | throw new API_Exception('Only test orders can be deleted.'); |
5f44f698 | 202 | } |
95c4e89e | 203 | return civicrm_api3_create_success($result['values'], $params, 'Order', 'delete'); |
5f44f698 PN |
204 | } |
205 | ||
65f7f9f6 | 206 | /** |
9d8e81c8 | 207 | * Cancel an Order. |
65f7f9f6 PN |
208 | * |
209 | * @param array $params | |
210 | * Input parameters. | |
211 | * | |
212 | * @return array | |
ee95478a | 213 | * @throws \CiviCRM_API3_Exception |
65f7f9f6 | 214 | */ |
ee95478a | 215 | function civicrm_api3_order_cancel(array $params) { |
65f7f9f6 | 216 | $contributionStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); |
a5c7fd46 | 217 | $params['contribution_status_id'] = array_search('Cancelled', $contributionStatuses); |
65f7f9f6 | 218 | $result = civicrm_api3('Contribution', 'create', $params); |
9d8e81c8 | 219 | return civicrm_api3_create_success($result['values'], $params, 'Order', 'cancel'); |
65f7f9f6 PN |
220 | } |
221 | ||
222 | /** | |
223 | * Adjust Metadata for Cancel action. | |
224 | * | |
225 | * The metadata is used for setting defaults, documentation & validation. | |
226 | * | |
227 | * @param array $params | |
228 | * Array of parameters determined by getfields. | |
229 | */ | |
ee95478a | 230 | function _civicrm_api3_order_cancel_spec(array &$params) { |
cf8f0fff | 231 | $params['contribution_id'] = [ |
7c31ae57 | 232 | 'api.required' => 1, |
65f7f9f6 PN |
233 | 'title' => 'Contribution ID', |
234 | 'type' => CRM_Utils_Type::T_INT, | |
cf8f0fff | 235 | ]; |
65f7f9f6 PN |
236 | } |
237 | ||
b8644ae3 PN |
238 | /** |
239 | * Adjust Metadata for Create action. | |
240 | * | |
241 | * The metadata is used for setting defaults, documentation & validation. | |
242 | * | |
243 | * @param array $params | |
244 | * Array of parameters determined by getfields. | |
245 | */ | |
ee95478a | 246 | function _civicrm_api3_order_create_spec(array &$params) { |
cf8f0fff | 247 | $params['contact_id'] = [ |
b8644ae3 PN |
248 | 'name' => 'contact_id', |
249 | 'title' => 'Contact ID', | |
250 | 'type' => CRM_Utils_Type::T_INT, | |
251 | 'api.required' => TRUE, | |
cf8f0fff CW |
252 | ]; |
253 | $params['total_amount'] = [ | |
b8644ae3 PN |
254 | 'name' => 'total_amount', |
255 | 'title' => 'Total Amount', | |
cf8f0fff | 256 | ]; |
2c35902f | 257 | $params['skipCleanMoney'] = [ |
258 | 'api.default' => TRUE, | |
259 | 'title' => 'Do not attempt to convert money values', | |
260 | 'type' => CRM_Utils_Type::T_BOOLEAN, | |
261 | ]; | |
785f03e2 | 262 | $params['financial_type_id'] = [ |
b8644ae3 PN |
263 | 'name' => 'financial_type_id', |
264 | 'title' => 'Financial Type', | |
265 | 'type' => CRM_Utils_Type::T_INT, | |
266 | 'api.required' => TRUE, | |
785f03e2 | 267 | 'table_name' => 'civicrm_contribution', |
268 | 'entity' => 'Contribution', | |
269 | 'bao' => 'CRM_Contribute_BAO_Contribution', | |
270 | 'pseudoconstant' => [ | |
271 | 'table' => 'civicrm_financial_type', | |
272 | 'keyColumn' => 'id', | |
273 | 'labelColumn' => 'name', | |
274 | ], | |
275 | ]; | |
b8644ae3 | 276 | } |
5f44f698 PN |
277 | |
278 | /** | |
279 | * Adjust Metadata for Delete action. | |
280 | * | |
281 | * The metadata is used for setting defaults, documentation & validation. | |
282 | * | |
283 | * @param array $params | |
284 | * Array of parameters determined by getfields. | |
285 | */ | |
ee95478a | 286 | function _civicrm_api3_order_delete_spec(array &$params) { |
cf8f0fff | 287 | $params['contribution_id'] = [ |
5f44f698 PN |
288 | 'api.required' => TRUE, |
289 | 'title' => 'Contribution ID', | |
290 | 'type' => CRM_Utils_Type::T_INT, | |
cf8f0fff CW |
291 | ]; |
292 | $params['id']['api.aliases'] = ['contribution_id']; | |
5f44f698 | 293 | } |
88060045 EM |
294 | |
295 | /** | |
296 | * Handle possibility of v3 style params. | |
297 | * | |
298 | * We used to call v3 Membership.create. Now we call v4. | |
299 | * This converts membership input parameters. | |
300 | * | |
301 | * @param array $membershipParams | |
302 | * | |
303 | * @throws \API_Exception | |
304 | */ | |
305 | function _order_create_wrangle_membership_params(array &$membershipParams) { | |
306 | $fields = Membership::getFields(FALSE)->execute()->indexBy('name'); | |
bdbf2591 EM |
307 | // Ensure this legacy parameter is not true. |
308 | $membershipParams['skipStatusCal'] = FALSE; | |
88060045 EM |
309 | foreach ($fields as $fieldName => $field) { |
310 | $customFieldName = 'custom_' . ($field['custom_field_id'] ?? NULL); | |
311 | if ($field['type'] === ['Custom'] && isset($membershipParams[$customFieldName])) { | |
312 | $membershipParams[$field['custom_group'] . '.' . $field['custom_field']] = $membershipParams[$customFieldName]; | |
313 | unset($membershipParams[$customFieldName]); | |
314 | } | |
315 | ||
316 | if (!empty($membershipParams[$fieldName]) && $field['data_type'] === 'Integer' && !is_numeric($membershipParams[$fieldName])) { | |
317 | $membershipParams[$field['name'] . ':name'] = $membershipParams[$fieldName]; | |
318 | unset($membershipParams[$field['name']]); | |
319 | } | |
320 | } | |
321 | } |