Odyssey
hcuTransfer.i
1 <?php
2 /**
3  * @package hcuTransfer.i
4  * @uses This file contains the shared functions needed for transfers. The difference
5  * between this and hcuTransferScheduled.i is that file has routines specific to
6  * scheduled transfers and may need to reference this file.
7  *
8  * Created: 10/10/17
9  *
10  * Primary entry points provided:
11  * PerformTransfer - The core logic of figuring out what kind of transfer operation that can be called by
12  * the web-based .data entry, or by AppFeed. This will call any of the other entry points.
13  * ValidateTransfer - Validate the transfer parameters. NOTE that values are returned in reply with
14  * derived secondary values.
15  * SubmitTransfer - Submit the transfer. This will only record the tranfer in the transhdr/transdtl tables.
16  * ApproveTransfer - Mark the transfer as approved in the transhdr table.
17  * ProcessTransfer - Process the transfer by submitting to core or as secure message. Don't call for ACH.
18  *
19  * NOTES:
20  * 1. This file also has the core code to enter the transfer into the database and to update the
21  * database to signify the transfer has been approved.
22  */
23 
24 // Function to see if transfernotify email is set.
25 // * this is done by checking the email role 'transfernotify'
26 function _HasTransferNotify( $pDbh, $pCu ) {
27  // ** Verify the feature is enabled for the Credit Union.
28  // * this is done by check the role 'transfernotify'
29  $sqlSelect = "SELECT email
30  FROM cuadmnotify
31  WHERE cu = '{$pCu}' AND role = 'transfernotify'";
32 
33  $sqlSelectRs = db_query( $sqlSelect, $pDbh );
34 
35  $sqlEmail = db_fetch_array( $sqlSelectRs, 0 );
36  db_free_result( $sqlSelectRs );
37 
38  $hasEmail = strlen( trim( $sqlEmail["email"] ) ) > 0;
39 
40  return $hasEmail;
41 } // end _HasTransferNotifyEmail
42 
43 /**
44  * PerformTransfer:
45  * @uses Make sure transfers are allowed and check permissions; called from the regular transfer screen
46  * but can handle an immediate or scheduled transfer.
47  *
48  * NOTE: Only the validation can return multple errors. Any other call will fail on tne first error. However,
49  * some error messages are added on to existing ones so need to be returned with the return structure,
50  * not thrown.
51  *
52  * @param $pHBEnv - HB_ENV values, $pTransferValues
53  * ["SYSENV"]["logger"]
54  * ['Cu']
55  * (lower calls may need more values)
56  * @param $pInputVars - all the inputs into the transfer
57  * @param $pMC - dictionary
58  *
59  * @return status array with errors or data and info
60  *
61  * If there are no errors then the return info can be in:
62  * return["data"]["posted"] - message if submitted for approval
63  * return["data"]["txn"] - results from submit (may still need approval)
64  * return["data"]["repeat"] - scheduled transfer result - might be a success message or an error message
65  */
66 function PerformTransfer( $pHBEnv, $pInputVars, $pMC ) {
67  $retStatusAry = Array(
68  'status' => Array('code'=>'000', 'errors' => Array()),
69  'data' => '',
70  'info' => ''
71  );
72 
73  try {
74  // set up some environment info for easier access
75  $dbh = $pHBEnv["dbh"];
76 
77  // these are needed in a variety of places so get them now
78  $sourceParts = isset( $pInputVars['txFromSuffix'] ) ? explode( "|", $pInputVars['txFromSuffix'] ) : array(); // eg "D|1103|10|0
79  $destParts = isset( $pInputVars["txToSuffix"] ) ? explode( "|", $pInputVars["txToSuffix"] ) : array();
80 
81  // check if feature is regular, external, or M2M transfer
82  if ( (isset( $sourceParts[0] ) && $sourceParts[0] === "X") ||
83  (isset( $destParts[0] ) && $destParts[0] === "X") ) {
84  $transferFeatureCode = FEATURE_EXTERNAL_TRANSFERS;
85  } else if ( isset( $destParts[0] ) && $destParts[0] === "M" ) {
86  $transferFeatureCode = FEATURE_M2M_TRANSFERS;
87  } else {
88  $transferFeatureCode = FEATURE_TRANSFERS;
89  }
90 
91  // NOTE: This level can return multiple errors, if the called function returns multiple errors
92  $errorMessage = array();
93 
94  // ** FOR Transfer, also verify the email if MailTxn is checked
95  if ( $pHBEnv['Fset'] & GetFlagsetValue("CU_MAILTXNS") ) {
96  // ** Verify the feature is enabled for the Credit Union.
97  $hasTransferNotify = _HasTransferNotify( $dbh, $pHBEnv["Cu"] );
98 
99  if ( !$hasTransferNotify ) {
100  throw new Exception ( $pMC->msg('Option not set', HCU_DISPLAY_AS_RAW) );
101  }
102  }
103 
104  // get some allowed amounts for client-side validation
105  $permissionInputs = array( "feature" => $transferFeatureCode );
106  $limits = Perm_GetValidationLimits( $dbh, $pHBEnv, $permissionInputs );
107 
108  if ( $limits === false ) {
109  // error occurred - assume count of zero
110  $allowedAmount = 0;
111  } else {
112  $allowedAmount = floatval( $limits["amount_per_transaction"] );
113  }
114 
115  // ** DEFAULT - if frequency is empty set to onetime
116  if (!HCU_array_key_exists('txFrequency', $pInputVars) || $pInputVars['txFrequency'] == "") {
117  $pInputVars['txFrequency'] = "OneTime";
118  }
119 
120  // ** DEFAULT - since the dropdown to select immediate/future
121  // was removed, we must set it based on current date and
122  // selected interval.
123  if (!HCU_array_key_exists('txDateStart', $pInputVars) || $pInputVars['txDateStart'] === null) {
124  // when transferring to member that is not in our db
125  // the start date is disabled the value will come in null.
126  $pInputVars['txDateStart'] = date("m/d/Y");
127  }
128 
129  // if repeating transfers is turned off, txDateEnd will not exist
130  // and throw an undefined index notice in hcuTransfer.i
131  // this key has no use in this case just set to today.
132  if (!HCU_array_key_exists('txDateEnd', $pInputVars) || $pInputVars['txDateEnd'] === null) {
133  // when transferring to member that is not in our db
134  // the start date is disabled the value will come in null.
135  $pInputVars['txDateEnd'] = date("m/d/Y");
136  }
137 
138  if ( (date("Ymd", strtotime($pInputVars['txDateStart'])) <= date("Ymd")) &&
139  ($pInputVars['txFrequency'] == "OneTime") ) {
140  // ** if one time, set to immediate
141  $pInputVars['txOption'] = "Immediate";
142  } else {
143  $pInputVars['txOption'] = "Future";
144  }
145 
146  // ** CHECK
147  // ** - scheduled transfers are allowed
148  $txScheduledAllowed = (($pHBEnv['flagset2'] & GetFlagsetValue("CU2_PROCRECUR")) === GetFlagsetValue("CU2_PROCRECUR"));
149  // ** - is payment or transfer
150 
151  // ** CHECK
152  // ** - Immediate
153  // ** - scheduled not allowed
154  if (!$txScheduledAllowed || $pInputVars['txOption'] === "Immediate") {
155 
156  // ** VALIDATE - data was sanitized already
157  $txValidateTransfer = ValidateTransfer( $pHBEnv, $dbh, $pMC, $pInputVars );
158 
159  if ( $txValidateTransfer['status']['code'] !== "000" ) {
160 
161  // validate has extra info that isn't used any more in the error handling
162  for ( $i = 0; $i < count( $txValidateTransfer["status"]["errors"] ); $i++ ) {
163  $errorMessage[] = $txValidateTransfer["status"]["errors"][$i]["message"];
164  }
165 
166  // pass back any messages as a return value because cannot throw multiple messages
167  $retStatusAry["status"]["code"] = "999";
168  $retStatusAry["status"]["errors"] = $errorMessage;
169 
170  return $retStatusAry;
171  }
172 
173  $htmlTransferAmount = $pInputVars['txAmount'];
174 
175  if ( $htmlTransferAmount > $allowedAmount ) {
176  // error occurred - cannot allow operation - just say there was a limit error
177  throw new Exception ( $pMC->msg('Perm Limit - Request over authorized limit', HCU_DISPLAY_AS_HTML) );
178  }
179 
180  // get some allowed amounts for validation
181  $permissionInputs = array( "feature" => $transferFeatureCode );
182  $permissionInputs["amount"] = $htmlTransferAmount;
183 
184  // Because EXT transfers could be Remote to Local / Local to Remote
185  // we must check for the account number/type in different places.
186 
187  // Remote to Local: Look in destParts
188  // Local to Remote: Look in sourceparts
189 
190  // Also for Member to Member transfers
191  // the local account will always be in sourceParts
192  // because we only allow outgoing Member to Member transfers.
193  if ($transferFeatureCode == FEATURE_EXTERNAL_TRANSFERS) {
194 
195  if ($sourceParts[0] == "X") { // Local account is destination
196  $permissionInputs["account"] = $destParts[1];
197  $permissionInputs["accounttype"] = $destParts[2];
198  } else { // Local account is source
199  $permissionInputs["account"] = $sourceParts[1];
200  $permissionInputs["accounttype"] = $sourceParts[2];
201  }
202  } else {
203  $permissionInputs["account"] = $sourceParts[1];
204  $permissionInputs["accounttype"] = $sourceParts[2];
205  }
206 
207  $return = Perm_CheckLimits( $dbh, $pHBEnv, $permissionInputs );
208  if ( !$return || ($return["status"]["code"] !== "000") ) {
209  // error occurred - cannot allow operation - just say there was a limit error
210  $errorMessage = Perm_GetLimitErrDesc($pMC, $return["status"]["code"]);
211  throw new Exception ( $errorMessage );
212  }
213 
214  db_work( $dbh, HOMECU_WORK_BEGIN); // For both the submitting of the transfer and the approval.
215 
216  // ** Use the results from ValidateTransfer and pass into SubmitTransfer
217  $submitTransferResult = SubmitTransfer( $dbh, $pHBEnv, $pMC, $txValidateTransfer['data'], $aryTransferResults );
218 
219 
220  if ( $submitTransferResult === false || ($aryTransferResults["status"]["code"] != "000") ) {
221  // error occurred - cannot allow operation
222  if ( strlen( $submitTransferResult["status"]["errors"] ) > 0 ) {
223  $errorMessage[] = $submitTransferResult["status"]["errors"];
224  } else {
225  $errorMessage[] = $pMC->msg("Transfer Error", HCU_DISPLAY_AS_HTML);
226  }
227  }
228 
229  // check if either of the two possible ways to return errors happened
230  if ( count( $errorMessage ) > 0 ) {
231  if ($pInputVars['txFrequency'] != 'OneTime') {
232  // ** If we have an error from the POST routine AND the member tried to add
233  // * a repeating, just mention that here..
234  $errorMessage[] = $pMC->msg("Repeating Transfer not saved", HCU_DISPLAY_AS_RAW);
235  }
236 
237  // need to return messages here since might have multiple
238  $retStatusAry["status"]["code"] = "999";
239  $retStatusAry["status"]["errors"] = $errorMessage;
240 
241  return $retStatusAry;
242  }
243 
244  // see if confirmation not required
245  $confirmationRequired = Perm_CheckConfirmReq($dbh, $pHBEnv, $permissionInputs);
246  if (!$confirmationRequired) {
247  // since can self-approve do that now (returning TX_Post data)
248  $entryId = $aryTransferResults["txn"]["trans_id"]; // result from SubmitTransfer
249 
250  if ( !ApproveTransfer( $dbh, $pHBEnv, $pMC, $entryId, $aryApprovalResults ) ) {
251  throw new Exception ( $pMC->msg("Trans approval failure", HCU_DISPLAY_AS_HTML) );
252  }
253 
254  // If the approval stage is successful, replace the confirmation code
255  // with the new one generated from this stage.
256  $aryTransferResults['txn']['data_confirm'] = $aryApprovalResults['status']['confirm'];
257 
258  // for internal transfers, if it was self-approved it can be processed right away
259  if ( $transferFeatureCode == FEATURE_TRANSFERS ||
260  $transferFeatureCode == FEATURE_M2M_TRANSFERS ) {
261  // * TRADITIONAL TRANSFER
262  if ( !ProcessTransfer( $dbh, $pHBEnv, $pMC, $entryId, $aryProcessResults ) ) {
263  $errorMessage[] = $pMC->msg("Trans processing failure", HCU_DISPLAY_AS_HTML);
264 
265  if ( count( $aryProcessResults["status"]["errors"] ) > 0 ) {
266  for ( $i = 0; $i < count( $aryProcessResults["status"]["errors"] ); $i++ ) {
267  $errorMessage[] = $aryProcessResults["status"]["errors"][$i];
268  }
269  }
270 
271  // need to return messages here since might have multiple
272  $retStatusAry["status"]["code"] = "999";
273  $retStatusAry["status"]["errors"] = $errorMessage;
274 
275  // At this point we have already inserted records on {CU}transhdr and
276  // {CU}transdtl tables but got the process transfer error. This implicitly
277  // rollbacks all the db insertion and update operations executed so far.
278  // However, we do want to record this request to the core in the `cucorerequests`
279  // table. The records in `cucorerequests` have already been populated, we just
280  // need to revert the transfer related insertions and commit the db transaction
281  if (HCU_array_key_exists('trans_id', $aryTransferResults['txn'])) {
282  RevertTransferAndAddCUCoreRequest($aryTransferResults['txn']['trans_id'],
283  $pHBEnv["dbh"],
284  $pHBEnv["Cu"]);
285  }
286 
287  return $retStatusAry;
288  }
289 
290  // If the process stage is successful, replace the confirmation code
291  // with the new one generated from this stage.
292  $aryTransferResults['txn']['data_confirm'] = $aryProcessResults['txn']['data_confirm'];
293  }
294  } else {
295  // return message about awaiting approval, and just enough data for confirmation
296  $retStatusAry["data"]['posted'] = $pMC->msg("Transfer submitted for confirmation", HCU_DISPLAY_AS_RAW);
297  }
298 
299  db_work($dbh, HOMECU_WORK_COMMIT);
300 
301  // ** Successful transfer -- Need to return information..
302  $retStatusAry["data"]["txn"] = $aryTransferResults["txn"];
303  }
304 
305  // ** CHECK
306  // ** - Future
307  // ** - scheduled transfers allowed
308  if ($txScheduledAllowed) {
309  // ** VERIFY SCHEDULED is allowed for the credit union
310  // ** REPEATING OR FUTURE
311 
312  /*
313  * mws 6/5
314  * When an option such as Mail Check is selected, the Repeating transfer options are GREYED OUT.
315  * and their values are NOT coming over. So, the ONLY time I will be making a repeating transfer
316  * is if they are EXPLICITLY set to a value. A blank means NO REPEATING TRANSFER WILL BE DONE
317  */
318  if (count($retStatusAry["status"]["errors"]) === 0) {
319 
320  // ** CHECK
321  // ** - interval not one time
322  // ** - option = future
323  $txIsInterval = ($pInputVars['txFrequency'] != "" && $pInputVars['txFrequency'] != "OneTime");
324  $txIsFuture = ($pInputVars['txOption'] != "" && $pInputVars['txOption'] == "Future");
325  if ($txIsInterval || $txIsFuture) {
326 
327  /*
328  * ** CHECK USER FEATURE PERMISSIONS **
329  * NOTE: DO NOT AUTO-REDIR. Handle perm error here
330  */
331  $permissionScheduled = array( "feature" => FEATURE_SCHEDULED_TRANSFERS );
332 
333  // check if user has create rights
334  $accessRights = Perm_AccessRights( $dbh, $pHBEnv, $permissionScheduled );
335  if ( !HCU_array_key_value( "access", $accessRights ) ) {
336  $retStatusAry["data"]['repeat'][] = $pMC->msg('Rights not set', HCU_DISPLAY_AS_HTML);
337  } else {
338  // ** VALIDATE - scheduled transfer values
339  $txValidateTransfer = ValidateTransfer($pHBEnv, $dbh, $pMC, $pInputVars);
340 
341  if ($txValidateTransfer['status']['code'] === "000") {
342  // Check Amount Per Transaction limit
343  $htmlTransferAmount = $txValidateTransfer['data']['txAmount'];
344 
345  if ( $htmlTransferAmount > $allowedAmount ) {
346  // error occurred - cannot allow operation - just say there was a limit error
347  throw new Exception ( $pMC->msg('Perm Limit - Request over authorized limit', HCU_DISPLAY_AS_HTML) );
348  }
349  // setup values for new entry
350  $txScheduleValues = array(
351  "txDateStart" => $txValidateTransfer['data']['txDateStart'],
352  "txDateEnd" => $txValidateTransfer['data']['txDateEnd'],
353  "txFrequency" => $txValidateTransfer['data']['txFrequency'],
354  "txFrequencyCount" => $txValidateTransfer['data']['txFrequencyCount'],
355  "txParameters" => TxnSchedParameters($txValidateTransfer['data']),
356  "txData" => TxnSchedData($txValidateTransfer['data']),
357  "txFeature" => $transferFeatureCode
358  );
359 
360  // ** CREATE - new schedule
361  $txCreateSchedule = TxnSchedCreate($pHBEnv, $dbh, $pMC, $txScheduleValues);
362 
363  // ** CHECK - errors
364  if ($txCreateSchedule['status']['code'] !== "000") {
365  // ** FOR the transfer script, the error for the repeating, after a successful
366  // * transfer is returned in the repeat element, instead of errors
367  $retStatusAry["data"]['repeat'] = $txCreateSchedule['status']['errors'];
368  } else {
369  $retStatusAry["data"]['repeat'] = $pMC->msg('Repeating transfer scheduled', HCU_DISPLAY_AS_RAW) . ".";
370  }
371  } else {
372  // need to return messages here since might have multiple
373  $retStatusAry["status"]["code"] = "999";
374  $retStatusAry["status"]["errors"] = $txValidateTransfer['status']['errors'];
375 
376  return $retStatusAry;
377  }
378  }
379  }
380  }
381  }
382  } catch(Exception $ex) {
383  // roll back the transaction
384  db_work($dbh, HOMECU_WORK_ROLLBACK);
385 
386  // pass back any messages
387  $retStatusAry["status"]["code"] = "999";
388  $retStatusAry["status"]["errors"] = $ex->getMessage();
389  }
390 
391  // safety check for errors being returned since not all paths necessarily set the status code
392  if ( is_array( $retStatusAry["status"]["errors"] ) ) {
393  if ( count( $retStatusAry["status"]["errors"] ) > 0 ) {
394  $retStatusAry["status"]["code"] = "999";
395  }
396  } else if ( strlen( $retStatusAry["status"]["errors"] ) > 0 ) {
397  $retStatusAry["status"]["code"] = "999";
398  }
399 
400  return $retStatusAry;
401 } // end PerformTransfer
402 
403 
404 /**
405  * Utility function to delete already inserted transfer transactions details
406  * from ${CU}transhdr and ${CU}transdtl tables when the attemnted transfer
407  * process fails.
408  *
409  * @param $transferId - transfer id to delete
410  * @param $dbh - database handler
411  * @param $Cu - CU code
412  *
413  * @throws Exception if the transhdr and transdtl records deletion fails
414  *
415  */
416 function RevertTransferAndAddCUCoreRequest($transferId, $dbh, $Cu) {
417  $cu = trim(strtolower($Cu));
418 
419  try {
420  $transHdrTbl = $cu . "transhdr";
421  $transDtlTbl = $cu . "transdtl";
422 
423  // delete transDTL and transHDR records
424  $sqlDeleteDtlHdr = "DELETE from ${transDtlTbl}".
425  " WHERE transhdr_id=${transferId};".
426  " DELETE from ${transHdrTbl}".
427  " WHERE id=${transferId};";
428 
429  $sqlDeleteDtlHdrRs = db_query($sqlDeleteDtlHdr, $dbh);
430 
431  if (!$sqlDeleteDtlHdrRs) {
432  throw new Exception("Reverting transfer failed on core request failure.");
433  } else {
434  // commit the db changes
435  // this will only commit the changes in the cucorerequests table
436  // as we deleted the un-commited transfers related records
437  db_work($dbh, HOMECU_WORK_COMMIT);
438  }
439  } catch (Exception $ex) {
440  throw $ex;
441  }
442 }
443 
444 /**
445  * PerformTransferScheduled:
446  * @uses Make sure transfers are allowed and check permissions; called from the hcuTransferSchedule.prg and
447  * can handle reading, deleting, or creating scheduled transfers.
448  *
449  * NOTE: Only the validation can return multple errors. Any other call will fail on tne first error. However,
450  * some error messages are added on to existing ones so need to be returned with the return structure,
451  * not thrown.
452  *
453  * @param $pHBEnv - HB_ENV values, $pTransferValues
454  * ["SYSENV"]["logger"]
455  * ['Cu']
456  * (lower calls may need more values)
457  * @param $pInputVars - all the inputs into the transfer
458  * @param $pMC - dictionary
459  *
460  * @return status array with errors or data and info
461  *
462  * If there are no errors then the return info can be in:
463  * return["data"] - scheduled dates info (multiple elements)
464  * return["info"]["repeat"] - scheduled transfer result - might be a success message or an error message
465  */
466 function PerformTransferScheduled( $pHBEnv, $pInputVars, $pMC ) {
467  $retStatusAry = Array(
468  'status' => Array('code'=>'000', 'errors' => Array()),
469  'data' => '',
470  'info' => ''
471  );
472 
473  try {
474  // set up some environment info for easier access
475  $dbh = $pHBEnv["dbh"];
476 
477  // these are needed in a variety of places so get them now
478  $sourceParts = isset( $pInputVars['txFromSuffix'] ) ? explode( "|", $pInputVars['txFromSuffix'] ) : array(); // eg "D|1103|10|0
479  $destParts = isset( $pInputVars["txToSuffix"] ) ? explode( "|", $pInputVars["txToSuffix"] ) : array();
480 
481  // check if feature is regular, external, M2M or ACH transfer
482  if ( (isset( $sourceParts[0] ) && $sourceParts[0] === "X") ||
483  (isset( $destParts[0] ) && $destParts[0] === "X") ) {
484  $transferFeatureCode = FEATURE_EXTERNAL_TRANSFERS;
485  } else if ( isset( $destParts[0] ) && $destParts[0] === "M" ) {
486  $transferFeatureCode = FEATURE_M2M_TRANSFERS;
487  } else if ( (isset( $sourceParts[0] ) && $sourceParts[0] === "AC") ) {
488  $transferFeatureCode = FEATURE_ACH_COLLECTIONS;
489  } else if ( (isset( $destParts[0] ) && $destParts[0] === "AP") ) {
490  $transferFeatureCode = FEATURE_ACH_PAYMENTS;
491  } else {
492  $transferFeatureCode = FEATURE_TRANSFERS;
493  }
494 
495  // ** Verify the feature is enabled for the Credit Union.
496  $hasTransferNotify = _HasTransferNotify( $dbh, $pHBEnv["Cu"] );
497 
498  if ( !$hasTransferNotify ) {
499  throw new Exception ( $pMC->msg('Option not set', HCU_DISPLAY_AS_RAW) );
500  }
501 
502  // ** are scheduled transfers allowed?
503  $txScheduledAllowed = (($pHBEnv['flagset2'] & GetFlagsetValue("CU2_PROCRECUR")) === GetFlagsetValue("CU2_PROCRECUR"));
504 
505  if (!$txScheduledAllowed) {
506  // ** Verify the option is enabled for the credit union
507  throw new Exception ( $pMC->msg('Option not set', HCU_DISPLAY_AS_RAW) );
508  }
509 
510  /*
511  * ** CHECK USER FEATURE PERMISSIONS **
512  * NOTE: DO NOT AUTO-REDIR. Handle perm error here
513  */
514  $permissionInputs = array( "feature" => FEATURE_SCHEDULED_TRANSFERS );
515 
516  if (!PermCheckFeatureScreen($dbh, $pHBEnv, $pMC, $permissionInputs["feature"], '', false)) {
517  throw new Exception ( $pMC->msg('Rights not set', HCU_DISPLAY_AS_RAW) );
518  }
519 
520  // get some allowed amounts for client-side validation
521  $permissionInputs = array ( "feature" => $transferFeatureCode );
522  $limits = Perm_GetValidationLimits( $dbh, $pHBEnv, $permissionInputs );
523 
524  if ( $limits === false ) {
525  // error occurred - assume count of zero
526  $allowedAmount = 0;
527  } else {
528  $allowedAmount = intval( $limits["amount_per_transaction"]);
529  }
530 
531  if ($pInputVars['action'] == "readSchedule") {
532  $txScheduleData = TxnSchedRead($pHBEnv, $dbh, $pMC);
533 
534  // check if valid read
535  if ($txScheduleData['status']['code'] !== "000") {
536  $retStatusAry["status"]["errors"] = $txScheduleData['status']['errors'];
537  } else {
538  // create start and end dates for calendar
539  $txDateStart = mktime(0,0,0, date("m"), date("d") + 1);
540  $txDateEnd = mktime(0,0,0, date("m") + 12, date("d") + 2);
541  $txScheduleDates = GetCalendarData($pHBEnv, $pMC,
542  $txScheduleData['data'],
543  date("m/d/Y", $txDateStart),
544  date("m/d/Y", $txDateEnd));
545 
546  $retStatusAry["data"]['scheduleItems'] = $txScheduleDates['valid'];
547  $retStatusAry["data"]['scheduleDates'] = $txScheduleDates['dates'];
548  $retStatusAry["data"]['scheduleOccur'] = $txScheduleDates['occur'];
549  }
550  } else if ($pInputVars['action'] == "deleteSchedule") {
551  $txDeleteTransfer = TxnSchedDelete($pHBEnv, $dbh, $pMC, $pInputVars);
552  // ** CHECK - errors
553  if ($txDeleteTransfer['status']['code'] !== "000") {
554  // ** FOR the transfer script, the error for the repeating, after a successful
555  // * transfer is returned in the repeat element, instead of errors
556  $retStatusAry["status"]["errors"] = $txDeleteTransfer['status']['errors'];
557  } else {
558  $retStatusAry['info']['repeat'] = $pMC->msg('Transfer Delete', HCU_DISPLAY_AS_RAW) . ".";
559  }
560  } else {
561  $pInputVars['txOption'] = "Future";
562  // ** VALIDATE - incoming parameters
563  // since read doesn't require parameter validation
564  // i've moved everything into one place to make the code much simpler.
565  // NOTE: ValidateTransfer also moves modified and new variables into the returned array.
566  $txValidateTransfer = ValidateTransfer($pHBEnv, $dbh, $pMC, $pInputVars, true);
567 
568  if ($txValidateTransfer['status']['code'] !== "000") {
569  $retStatusAry["status"]["code"] = "999";
570  $retStatusAry["status"]["errors"] = $txValidateTransfer['status']['errors'];
571 
572  return $retStatusAry;
573  }
574 
575  // Check Amount Per Transaction limit
576  $htmlTransferAmount = $txValidateTransfer['data']['txAmount'];
577 
578  if ( $htmlTransferAmount > $allowedAmount ) {
579  // error occurred - cannot allow operation - just say there was a limit error
580  throw new Exception ( $pMC->msg('Perm Limit - Request over authorized limit', HCU_DISPLAY_AS_HTML) );
581  }
582 
583  $txScheduleTransfer = null; // returned array from requested action
584  $txScheduleTransferInfo = ""; // information for successfull action
585 
586  // set up scheduled transaction for internal transactions
587  $txScheduleValues = array(
588  "txDateStart" => $txValidateTransfer['data']['txDateStart'],
589  "txDateEnd" => $txValidateTransfer['data']['txDateEnd'],
590  "txFrequency" => $txValidateTransfer['data']['txFrequency'],
591  "txFrequencyCount" => $txValidateTransfer['data']['txFrequencyCount'],
592  "txParameters" => TxnSchedParameters($txValidateTransfer['data'])
593  );
594 
595  // ** ACTION - perform requested action, create/update/delete
596  if ($pInputVars['action'] == 'skipSchedule') {
597  $txId = intval($pInputVars['txId']);
598  $changeList = array($txId);
599  $changeList = HCU_JsonEncode($changeList);
600  $txSkipSchedule = SkipTrans($dbh, $pHBEnv["Cu"], $pHBEnv['Uid'], $changeList, $pMC);
601 
602  if ($txSkipSchedule['status'] !== "000") {
603  throw new Exception($MC->msg("Skip Transaction Error", HCU_DISPLAY_AS_RAW), $txSkipSchedule['status']);
604  }
605  $txScheduleTransferInfo = $txSkipSchedule['info'];
606  } else if ($pInputVars['action'] == "createSchedule") {
607  // set up the transaction data
608  $txScheduleValues['txData'] = TxnSchedData( $txValidateTransfer['data'] );
609  $txScheduleValues['txFeature'] = $transferFeatureCode;
610 
611  // create te schedule entry
612  $txScheduleTransfer = TxnSchedCreate($pHBEnv, $dbh, $pMC, $txScheduleValues);
613  // ** CHECK - errors
614  if ($txScheduleTransfer['status']['code'] !== "000") {
615  throw new Exception( $pMC->msg("Transfer Save Error", HCU_DISPLAY_AS_RAW) );
616  }
617 
618  $txScheduleTransferInfo = $pMC->msg('Scheduled transfer was saved', HCU_DISPLAY_AS_RAW) . ".";
619  } else if ($pInputVars['action'] == "updateSchedule") {
620  $txScheduleValues['txId'] = $txValidateTransfer['data']['txId'];
621  $txScheduleValues['txStatus'] = $txValidateTransfer['data']['txStatus'];
622 
623  // get current tx_data values
624  // get current transaction feature
625  $txDataCurrent = FindTransactionData($dbh, $txValidateTransfer['data']['txId']);
626 
627  // decode data values to update
628  // update necessary fields
629  // ** this could need an if statement based on the FEATURE_CODE
630  // ** because each feature type has different fields int the txn_data
631  // ** string.
632  $txDataUpdated = HCU_JsonDecode($txDataCurrent['txn_data']);
633  $txDataUpdated['txn']['amount'] = $txValidateTransfer['data']['txAmount'];
634 
635  // decode parameters to get current interval count
636  // this will be used to determine next interval date
637  // if the frequency has changed, set interval count to 0.
638  // if the frequency has not changed use current parameters
639  $txDataParameters = HCU_JsonDecode($txDataCurrent['repeating_parameters']);
640 
641  if ($txDataParameters['interval'] !== $txScheduleValues['txFrequency']) {
642  $txScheduleValues['txFrequencyCount'] = 0;
643  } else {
644  $txScheduleValues['txParameters'] = $txDataCurrent['repeating_parameters'];
645  }
646 
647  // if the start date has changed, set interval count to 0.
648  // if the start date has not changed, set start date to current start date
649  if (strtotime($txDataCurrent['start_date']) !== strtotime($txScheduleValues['txDateStart'])) {
650  $txScheduleValues['txFrequencyCount'] = 0;
651  } else {
652  $txScheduleValues['txDateStart'] = $txDataCurrent['start_date'];
653  }
654 
655  // set new data to update
656  $txScheduleValues['txData'] = HCU_JsonEncode($txDataUpdated);
657  $txScheduleValues['txFeature'] = $txDataCurrent['feature_code'];
658  $txScheduleTransfer = TxnSchedUpdate($pHBEnv, $dbh, $pMC, $txScheduleValues);
659  // ** CHECK - errors
660  if ($txScheduleTransfer['status']['code'] !== "000") {
661  throw new Exception( $pMC->msg("Transfer Save Error", HCU_DISPLAY_AS_RAW) );
662  }
663 
664  $txScheduleTransferInfo = $pMC->msg('Repeating transfer scheduled', HCU_DISPLAY_AS_RAW) . ".";
665  }
666 
667  $retStatusAry['info']['repeat'] = $txScheduleTransferInfo;
668  }
669  } catch(Exception $ex) {
670  // no need to roll back transaction because each one is discrete
671  // pass back any messages
672  $retStatusAry["status"]["code"] = "999";
673  $retStatusAry["status"]["errors"] = $ex->getMessage();
674  }
675 
676  // safety check for errors being returned since not all paths set the status code
677  if ( count( $retStatusAry["status"]["errors"] > 0 ) ) {
678  $retStatusAry["status"]["code"] = "999";
679  }
680 
681  return $retStatusAry;
682 } // end PerformTransferScheduled
683 
684 
685 /**
686  * ValidateTransfer:
687  * @uses Validate incoming transfer requests, this will check for
688  * valid transfer types and correct dates and values. It will gather all
689  * the errors and return them all.
690  *
691  * @param $pEnv - HB_ENV values
692  * ["SYSENV"]["logger"]
693  * ['Cu']
694  * (lower calls may need more values)
695  * @param $pDbh - database object
696  * @param $pMC - dictionary object
697  * @param $pValues - transfer request values
698  * @param #pShowZeroBalance - if this validation is for the scheduled transfer
699  * screen, the TX_list function must show accounts with zero balance.
700  *
701  * [ txId ] - transfer id: 0 if new
702  * [ txFromMember ] - source account member number
703  * [ txFromSuffix ] - source account member sub-account
704  * [ txToMember ] - target account member number
705  * [ txToSuffix ] - target account member sub-account
706  * [ txCode ] - transaction code: null if new txn
707  * [ txAmount ] - amount to transfer
708  * [ txMemo ] - transfer comment
709  * [ txPaymentComment ] - Transfer Payment Comment -- use for secure form payments
710  * [ txFrequency ] - tranfer frequency
711  * [ txContinue ] - continuous/continue until
712  * [ txDateStart ] - date to start repeating transfer
713  * [ txDatEnd ] - date to end repeating transfer
714  * [ txStatus ] - active/inactive transfer status
715  *
716  * Removed txToMisc1 from list (4/16/2018.) This is a field passed from the core to the core when the transfer is sent. It is not meant to go to the client side code.
717  *
718  * @return $retStatusAry - validated values above
719  */
720 function ValidateTransfer($pEnv, $pDbh, $pMC, $pValues, $pShowZeroBalance = false) {
721  /*
722  * When possible errors will return an array of objects
723  * Array ( 'id' => 'ID of the field that errored',
724  * 'message' => 'Error string' )
725  */
726  $retStatusAry = Array(
727  'status' => Array('code'=>'000', 'errors' => Array()),
728  'data' => ''
729  );
730 
731  // ** Setup the array for the returned data row --
732  // ** This row will contain the valid options for inserting into the database
733  // ** their naming is slightly different then the values coming in.
734  $retDataRow = array();
735 
736 
737  $txFromMember = isset($pValues['txFromMember']) ? $pValues['txFromMember'] : null;
738  $txFromSuffix = isset($pValues['txFromSuffix']) ? $pValues['txFromSuffix'] : null;
739  $txFromType = "";
740  $txToMember = isset($pValues['txToMember']) ? $pValues['txToMember'] : null;
741  $txToSuffix = isset($pValues['txToSuffix']) ? $pValues['txToSuffix'] : null;
742  $txToType = "";
743  $txMemAccount = isset($pValues['txMemAccount']) ? $pValues['txMemAccount'] : null;
744  $txMemName = isset($pValues['txMemName']) ? $pValues['txMemName'] : null;
745  $txMemType = isset($pValues['txMemType']) ? $pValues['txMemType'] : null;
746  $txCode = isset($pValues['txCode']) ? $pValues['txCode'] : null;
747  $txAmount = isset($pValues['txAmount']) ? $pValues['txAmount'] : null;
748  $txMemo = isset($pValues['txMemo']) ? $pValues['txMemo'] : null;
749  $txFrequency = isset($pValues['txFrequency']) ? $pValues['txFrequency'] : null;
750  $txFrequencyCount = isset($pValues['txFrequencyCount']) ? $pValues['txFrequencyCount'] : 0;
751  $txContinue = isset($pValues['txContinue']) ? $pValues['txContinue'] : null;
752  $txDateStart = isset($pValues['txDateStart']) ? $pValues['txDateStart'] : null;
753  $txDateEnd = isset($pValues['txDateEnd']) ? $pValues['txDateEnd'] : null;
754  $txStatus = isset($pValues['txStatus']) ? $pValues['txStatus'] : null;
755  $txPmtComment = isset($pValues['paymentComment']) ? $pValues['paymentComment'] : null;
756  $txFeatureCode = isset($pValues['feature_code']) ? $pValues['feature_code'] : null;
757 
758  $txDeposit = isset($pValues['txDeposit']) ? $pValues['txDeposit'] : null;
759 
760  /* DEFAULT VALUES */
761  $txFromAcctSuffix = "";
762  $txToAcctSuffix = "";
763 
764  // make sure feature code is present
765  if ( strlen( trim( $txFeatureCode ) ) == 0 ) {
766  $retStatusAry['status']['code'] = "999";
767  $retStatusAry['status']['errors'][] = array(
768  "id" => "txFeatureCode",
769  "message" => $pMC->msg("Unknown feature type", HCU_DISPLAY_AS_RAW)
770  );
771 
772  return $retStatusAry;
773  }
774 
775  /**
776  * Is this an External Transfer ?
777  * This is identified by FROM starting with an X or TO starting with X
778  * NOTE: M2M is "internal" to the credit union
779  */
780  $isExtFromTrans = substr($txFromSuffix, 0, 1) == 'X';
781  $isExtToTrans = substr($txToSuffix, 0, 1) == 'X';
782 
783  // ** Retrieve all the accounts we may use for transfers
784  $txAcctList = TX_list($pDbh, $pEnv, "", $pShowZeroBalance);
785  setFmsgTxCookie($pEnv, $txAcctList);
786 
787  // ** For A SCHEDULED TXN UPDATE -- DO NOT VALIDATE the from/to accounts.
788  // ** they may not be changed
789  // ** Verify the same account is NOT selected
790  $txId = (isset($pValues['txId']) ? $pValues['txId'] : 0);
791  if ($txId <= 0) {
792  // ** VARIABLES - used for determining valid to/from accounts
793  $txFromInfo = "";
794  $txToInfo = "";
795 
796  // ** VALIDATE - to/from cannot be the same
797  if ($pValues['txFromSuffix'] === $pValues['txToSuffix']) {
798  $retStatusAry['status']['errors'][] = array(
799  "id" => "txFrom",
800  "message" => $pMC->msg('From account cannot be same', HCU_DISPLAY_AS_RAW)
801  );
802  } else {
803  // get destination account parts to determine member to member
804  // or regular transfers.
805  $txToAcctParts = explode("|", $txToSuffix);
806  $txFromAcctParts = explode("|", $txFromSuffix);
807 
808  // ** VALIDATE - to/from accounts are valid accounts
809  if (is_array($txAcctList['acctlist'])) {
810  // ** VALIDATE - from account is valid
811  if (HCU_array_key_exists($pValues['txFromSuffix'], $txAcctList['acctlist'])) {
812  // ** VALIDATE - ability to transfer FROM
813  if (($isExtToTrans && $txAcctList['acctlist'][$pValues['txFromSuffix']]['from_ext'] !== "Y")
814  || (!$isExtFromTrans && $txAcctList['acctlist'][$pValues['txFromSuffix']]['from_int'] !== "Y")) {
815  /**
816  * PERMISSION TEST
817  *
818  * IF EXTERNAL -- THEN THEY MUST HAVE ACCESS TO 'from_ext'
819  *
820  * IF INTERNAL -- THEN THEY MUST HAVE ACCESS TO 'from_int'
821  *
822  */
823  $retStatusAry['status']['errors'][] = array(
824  "id" => "txFrom",
825  "message" => $pMC->msg('Perm Limit - Not Authorized', HCU_DISPLAY_AS_RAW)
826  );
827  } else {
828  // ** PASSED
829 
830  $txFromInfo = $txAcctList['acctlist'][$pValues['txFromSuffix']];
831  $txFromAcctSuffix = $txFromInfo['suffix'];
832  $txFromType = $txFromInfo['acctclass'];
833 
834  if ($txFromType == "X") {
835  // ** EXTERNAL Account source -- Save empty accountnumber
836  $txFromMember = "";
837  } else {
838  $txFromMember = $txFromInfo['member'];
839  }
840 
841  // make sure valid From account
842  if ( $txFromInfo["from"] != 'Y' ) {
843  $retStatusAry['status']['errors'][] = array(
844  "id" => "txFrom",
845  "message" => $pMC->msg('From invalid', HCU_DISPLAY_AS_RAW)
846  );
847  }
848  }
849  } else {
850  // ** ERROR - FROM account not found
851  $retStatusAry['status']['errors'][] = array(
852  "id" => "txFrom",
853  "message" => $pMC->msg('Account Type Missing', HCU_DISPLAY_AS_RAW)
854  );
855  }
856 
857  // handle ad-hoc M2M differently
858  // ** This statment handles NON-M2M transfers
859  if ( !($txToAcctParts[0] === "M" && $txToAcctParts[2] === "0") ) {
860 
861  // ** VALIDATE - TO account is valid
862  if (HCU_array_key_exists($pValues['txToSuffix'], $txAcctList['acctlist'])) {
863 
864  $txToInfo = $txAcctList["acctlist"][$pValues['txToSuffix']];
865 
866  // At this point, validate the transfer across accounts flag.
867  if (($pEnv["flagset3"] & GetFlagsetValue("CU3_DISALLOW_MULT_ACCOUNTS_TRANSFER")) != 0) {
868  $relevantTypes = array("D", "L");
869  if ($txFromInfo['member'] != $txToInfo["member"] && in_array($txFromInfo["acctclass"], $relevantTypes) && in_array($txToInfo["acctclass"], $relevantTypes)) {
870 
871  // Failed! Check if there is a cross account. If there is, then run the same test but also change the $txToInfo to the cross account so that correct trans code is passed.
872  if (HCU_array_key_exists($txFromInfo['member'], $txAcctList["xacctlist"])
873  && HCU_array_key_exists($pValues["txToSuffix"], $txAcctList["xacctlist"][$txFromInfo['member']])) {
874  $xaTxToInfo = $txAcctList["xacctlist"][$txFromInfo['member']][$pValues["txToSuffix"]];
875 
876  if ($txFromInfo['member'] != $xaTxToInfo["member"] && in_array($txFromInfo["acctclass"], $relevantTypes)
877  && in_array($xaTxToInfo["acctclass"], $relevantTypes)) {
878  $retStatusAry['status']['errors'][] = array(
879  "id" => "txTo",
880  "message" => $pMC->msg('Transfers between accounts are prohibited', HCU_DISPLAY_AS_RAW)
881  );
882  } else {
883  // Here override the $txToInfo so other checks work against the cross account instead of the regular account.
884  $txToInfo = $xaTxToInfo;
885  }
886  } else { // Also failed at this point, there is no cross account and check returns false.
887  $retStatusAry['status']['errors'][] = array(
888  "id" => "txTo",
889  "message" => $pMC->msg('Transfers between accounts are prohibited', HCU_DISPLAY_AS_RAW)
890  );
891  }
892  }
893  }
894 
895  if (($isExtFromTrans && $txToInfo['to_ext'] !== "Y")
896  || (!$isExtToTrans && $txToInfo['to_int'] !== "Y")) {
897  /**
898  * PERMISSION TEST
899  *
900  * IF EXTERNAL -- THEN THEY MUST HAVE ACCESS TO 'from_ext'
901  *
902  * IF INTERNAL -- THEN THEY MUST HAVE ACCESS TO 'from_int'
903  *
904  */
905  $retStatusAry['status']['errors'][] = array(
906  "id" => "txTo",
907  "message" => $pMC->msg('Perm Limit - Not Authorized', HCU_DISPLAY_AS_RAW)
908  );
909  } else {
910  // ** PASSED
911  $txToAcctSuffix = $txToInfo['suffix'];
912  $txToType = $txToInfo['acctclass'];
913 
914  $txTrust = $txToInfo['trust'];
915 
916  $txToMember = $txToInfo['member'];
917 
918  if ( $txToInfo["to"] != 'Y' ) {
919  $retStatusAry['status']['errors'][] = array(
920  "id" => "txTo",
921  "message" => $pMC->msg('To invalid', HCU_DISPLAY_AS_RAW)
922  );
923  }
924  }
925  // Cannot have BOTH from and to accounts be external, or external to M2M, or external to Other,
926  // or Loan to external
927  if ( $txFromType == "X" && $txToType == "X" ||
928  $txFromType == "X" && $txToType == "M" ||
929  $txFromType == "X" && $txToType == "O" ) {
930  // ** ERROR - cannot do this type of transfer
931  $retStatusAry['status']['errors'][] = array(
932  "id" => "txTo",
933  "message" => $pMC->msg('Transfer Error external', HCU_DISPLAY_AS_RAW)
934  );
935  }
936 
937  } else {
938  // ** ERROR - TO account not found
939  $retStatusAry['status']['errors'][] = array(
940  "id" => "txTo",
941  "message" => $pMC->msg('Account Type Not Found To Account', HCU_DISPLAY_AS_RAW)
942  );
943  }
944  }
945 
946  // Loan to External account check
947  if ( $txFromAcctParts[0] == "L" && $txToType == "X" ){
948  // ** ERROR - cannot do this type of transfer
949  $retStatusAry['status']['errors'][] = array(
950  "id" => "txTo",
951  "message" => $pMC->msg('Transfer Error loan to external', HCU_DISPLAY_AS_RAW)
952  );
953  }
954 
955  // some M2M validation
956 
957  // Don't allow Loan to M2M account until we learn if it is allowed
958  if ( $txFromAcctParts[0] == "L" && $txToType == "M" ) {
959  // ** ERROR - cannot do this type of transfer
960  $retStatusAry['status']['errors'][] = array(
961  "id" => "txTo",
962  "message" => $pMC->msg('Transfer Error external', HCU_DISPLAY_AS_RAW)
963  );
964  }
965 
966  try {
967  // ** VALIDATE ad-hoc member to member destination account
968  if ($txToAcctParts[0] === "M" && $txToAcctParts[2] === "0") {
969  // verify last name on account
970  $txMemAccount = str_replace('_', '', $txMemAccount);
971  $txMemAccount = trim($txMemAccount);
972  $member = array(
973  "dfi_account" => $txMemAccount,
974  "name_on_account" => $txMemName
975  );
976 
977  // validate account number
978  if (trim($member['dfi_account']) === "") {
979  throw new Exception($pMC->msg("ACH Validation DFI Account"));
980  }
981 
982  // validate name on account
983  if (trim($member['name_on_account']) === "") {
984  throw new Exception($pMC->msg($pMC->msg("ACH Validation Name", HCU_DISPLAY_AS_HTML)));
985  }
986 
987  // validate account type
988  if (!(intval($pValues['txMemType']) === ACCOUNT_TYPE_CHECKING || intval($pValues['txMemType']) === ACCOUNT_TYPE_SAVINGS)) {
989  throw new Exception($pMC->msg("ACH Validation DFI Type"));
990  }
991 
992  // validate account using name on account
993  // NOTE: only do this if there are no other errors
994  if (count($retStatusAry['status']['errors']) === 0) {
995  if (!_ConfirmM2MAccount($pEnv, $member)) {
996  throw new Exception($pMC->msg("M2M Account Not Found", HCU_DISPLAY_AS_HTML));
997  }
998  }
999 
1000  // fake out the "to" info for later checks
1001  $txToInfo = array( "acctclass" => "M" );
1002  }
1003  } catch (Exception $e) {
1004  $retStatusAry['status']['errors'][] = array(
1005  "id" => "txTo",
1006  "message" => $e->getMessage()
1007  );
1008  }
1009 
1010  } else {
1011  // ** ERROR - no accounts returned for the user
1012  $retStatusAry['status']['errors'][] = array(
1013  "id" => "txFrom",
1014  "message" => $pMC->msg('Error Occurred During Transfer Processing', HCU_DISPLAY_AS_RAW)
1015  );
1016  }
1017  }
1018 
1019  // ** VALIDATE - validate transfer type if no errors to this point
1020  if (count($retStatusAry['status']['errors']) === 0) {
1021  // ** must get credit union transfer settings
1022  // ** must get transaction code
1023  $txAllowed = Get_HaveTrans($pDbh, $pEnv);
1024  $txCode = FindTransactionCode($txFromInfo, $txToInfo, $txAllowed);
1025  $txError = "";
1026 
1027  if ($txCode == "") {
1028  // ** ERROR - repeating transfer not available
1029  $retStatusAry['status']['errors'][] = array(
1030  "id" => "txFrom",
1031  "message" => $pMC->msg('Transfer Error', HCU_DISPLAY_AS_RAW)
1032  );
1033 
1034  // Don't allow Loan Add-on to any M2M account
1035  } else if ( $txFromAcctParts[0] === "L" && $txCode == "MM"
1036  && $txToAcctParts[0] === "M" ) {
1037  // ** ERROR - cannot do this type of transfer
1038  $txError = $pMC->msg('Transfer Error M2M', HCU_DISPLAY_AS_RAW);
1039 
1040  // Loan Add-on allows one time scheduled transfers in the future but doesn't allow recurring transfers.
1041  } else if ( ($txFrequency != "OneTime") && (($txCode == "LA")) ) {
1042  // at this point in time cannot do repeating transfers with "Other Transactions"
1043  $txError = $pMC->msg('Repeating transfer not available', HCU_DISPLAY_AS_RAW);
1044 
1045  // Adhoc M2M transfers, Mail Check (CW), and other transfers (OT) are not allowed to be scheduled at all.
1046  } else if ( ($txFrequency != "OneTime" ||
1047  date("Ymd", strtotime($txDateStart)) != date("Ymd")) &&
1048  (($txToAcctParts[0] === "M" && $txToAcctParts[2] === "0") || ($txCode == "OT") || ($txCode == "CW")) ) {
1049  $txError = $pMC->msg('Repeating transfer not available', HCU_DISPLAY_AS_RAW);
1050  } else {
1051  // ** VALIDATE - transaction code is allowed
1052  if (trim($txFromMember) == trim($txToMember) && $txCode == "AT" && !HCU_array_key_value('AT', $txAllowed)) {
1053  $txError = $pMC->msg('Transfer Deposit Not Allowed', HCU_DISPLAY_AS_RAW);
1054  }
1055  if (trim($txFromMember) == trim($txToMember) && $txCode == "LP" && !HCU_array_key_value('LP', $txAllowed)) {
1056  $txError = $pMC->msg('Transfer Loan Not Allowed', HCU_DISPLAY_AS_RAW);
1057  }
1058 
1059  // ** VALIDATE - cross account transaction code is allowed
1060  if (trim($txFromMember) != trim($txToMember) && $txCode == "XA" && !HCU_array_key_value('XA', $txAllowed)) {
1061  $txError = $pMC->msg('Transfer Cross Deposit Not Allowed', HCU_DISPLAY_AS_RAW);
1062  }
1063  if (trim($txFromMember) != trim($txToMember) && $txCode == "XP" && !HCU_array_key_value('XP', $txAllowed)) {
1064  $txError = $pMC->msg('Transfer Cross Loan Not Allowed', HCU_DISPLAY_AS_RAW);
1065  }
1066  }
1067 
1068  // ** ERROR - tranfer not allowed
1069  if ($txError !== "") {
1070  $retStatusAry['status']['errors'][] = array(
1071  "id" => "",
1072  "message" => $txError
1073  );
1074  }
1075  }
1076  } else {
1077  // Updated existing transaction - no need for further validation (already done)
1078  $txToInfo = "";
1079  $txToInfo['trust'] = '';
1080  }
1081 
1082  if (count($retStatusAry['status']['errors']) === 0) {
1083  // ** VALIDATE - amount
1084  if ( floatval( $txAmount ) <= 0 ) {
1085  $retStatusAry['status']['errors'][] = array(
1086  "id" => "txAmount",
1087  "message" => $pMC->msg('Amount Missing', HCU_DISPLAY_AS_RAW)
1088  );
1089  }
1090  }
1091 
1092  // validations for immediate transfers
1093  if (count($retStatusAry['status']['errors']) === 0) {
1094  if ( !empty( $pValues["txOption"] ) && ($pValues["txOption"] == "Immediate") ) {
1095  // immediate transfers need the funds available
1096  if ( $txFromInfo["available"] < floatval( $txAmount) ) {
1097  $retStatusAry['status']['errors'][] = array(
1098  "id" => "txFrequency",
1099  "message" => $pMC->msg('Transfer exceeds', HCU_DISPLAY_AS_RAW)
1100  );
1101  }
1102  }
1103  }
1104 
1105  if (count($retStatusAry['status']['errors']) === 0) {
1106  // ** VALIDATE - frequency
1107  $transferFrequencyList = TxIntervalList($pEnv["MC"]);
1108 
1109  if (!FindFrequencyValue($transferFrequencyList, $pValues['txFrequency'])) {
1110  $retStatusAry['status']['errors'][] = array(
1111  "id" => "txFrequency",
1112  "message" => $pMC->msg('Transfer Invalid Interval', HCU_DISPLAY_AS_RAW)
1113  );
1114  }
1115  }
1116 
1117  if (count($retStatusAry['status']['errors']) === 0) {
1118  // ** VALIDATE - start date is a valid date
1119  if (!validateDate($txDateStart)) {
1120  if ($txFrequency == "OneTime") {
1121  $retStatusAry['status']['errors'][] = array(
1122  "id" => "txDateStart",
1123  "message" => $pMC->msg('Transfer On', HCU_DISPLAY_AS_RAW) . " " . $pMC->msg('is not a valid date', HCU_DISPLAY_AS_RAW)
1124  );
1125  } else {
1126  $retStatusAry['status']['errors'][] = array(
1127  "id" => "txDateStart",
1128  "message" => $pMC->msg('Start Date', HCU_DISPLAY_AS_RAW) . " " . $pMC->msg('is not a valid date', HCU_DISPLAY_AS_RAW)
1129  );
1130  }
1131  }
1132  }
1133 
1134  if (count($retStatusAry['status']['errors']) === 0) {
1135  // ** VALIDATE - if one time on continuous, do not validate end date
1136  $startTime = strtotime($pValues['txDateStart']);
1137  $endTime = strtotime($pValues['txDateEnd']);
1138  $nextTime = TxNextInterval(
1139  $txDateStart,
1140  $txFrequency,
1141  $txFrequencyCount);
1142  $nextTime = strtotime($nextTime);
1143 
1144  if ($txFrequency == "OneTime") {
1145  $txDateEnd = "";
1146  } else if ($txContinue == "continuous") {
1147  $txDateEnd = "";
1148  } else {
1149  if (!validateDate($pValues['txDateEnd'])) {
1150  $retStatusAry['status']['errors'][] = array(
1151  "id" => "txDateStart",
1152  "message" => $pMC->msg('Stop Date', HCU_DISPLAY_AS_RAW) . " " . $pMC->msg('is not a valid date', HCU_DISPLAY_AS_RAW)
1153  );
1154  } else if ($endTime < $nextTime) {
1155  $retStatusAry['status']['errors'][] = array(
1156  "id" => "txDateEnd",
1157  "message" => $pMC->msg('Continue Until greater than next', HCU_DISPLAY_AS_RAW)
1158  );
1159  } else if ($endTime < $startTime) {
1160  $retStatusAry['status']['errors'][] = array(
1161  "id" => "txDateEnd",
1162  "message" => $pMC->msg('Continue until greater than start', HCU_DISPLAY_AS_RAW)
1163  );
1164  } else {
1165  $txDateEnd = $pValues['txDateEnd'];
1166  }
1167  }
1168  }
1169 
1170  // these should all be sanitized on the inputs
1171  $retDataRow["txFeatureCode"] = trim($txFeatureCode);
1172  $retDataRow['txId'] = intval($txId);
1173  $retDataRow['txFromMember'] = trim($txFromMember);
1174  $retDataRow['txFromSuffix'] = trim($txFromAcctSuffix);
1175  $retDataRow['txFromType'] = trim($txFromType);
1176  $retDataRow['txFromAcctId'] = trim($txFromSuffix);
1177  $retDataRow['txToMember'] = trim($txToMember);
1178  $retDataRow['txToSuffix'] = trim($txToAcctSuffix);
1179  $retDataRow['txToType'] = trim($txToType);
1180  $retDataRow['txToAcctId'] = trim($txToSuffix);
1181  $retDataRow['txMemAccount'] = trim($txMemAccount);
1182  $retDataRow['txMemName'] = trim($txMemName);
1183  $retDataRow['txMemType'] = intval($txMemType);
1184  $retDataRow['txCode'] = trim($txCode);
1185  $retDataRow['txAmount'] = floatval($txAmount);
1186  $retDataRow['txMemo'] = trim($txMemo);
1187  $retDataRow['txPmtComment'] = trim($txPmtComment);
1188  $retDataRow['txFrequency'] = trim($txFrequency);
1189  $retDataRow['txFrequencyCount'] = trim($txFrequencyCount);
1190  $retDataRow['txDateStart'] = trim($txDateStart);
1191  $retDataRow['txDateEnd'] = trim($txDateEnd);
1192  $retDataRow['txContinue'] = ($txContinue == "continuous" ? 1 : 0);
1193  $retDataRow['txStatus'] = trim($txStatus);
1194  $retDataRow["txTrust"] = $txToInfo["trust"]; // Needed to distinguish between cross account and primary account when submitting transfer.
1195 
1196  if (count($retStatusAry['status']['errors']) == 0) {
1197  $retStatusAry['data'] = $retDataRow;
1198  $retStatusAry['data']['txlist'] = $txAcctList;
1199  } else {
1200  $retStatusAry['status']['code'] = "999";
1201  }
1202 
1203  return $retStatusAry;
1204 } // end ValidateTransfer
1205 
1206 /**
1207  * FindTransactionCode:
1208  * @uses This function was moved from hcuTransfer.data; it is used to get
1209  * the transaction code given a from account and to account suffix. The transaction
1210  * code will be saved in the <client>transhdr table, so not returning one should cause
1211  * a validation error.
1212  *
1213  * @param $fromAcctinfo - information regarding the from account
1214  * @param $toAcctInfo - information regarding the to account
1215  * @param $cuTransSupport - ?
1216  *
1217  * @return $retTranCode - transfer code
1218  */
1219 function FindTransactionCode($fromAcctInfo, $toAcctInfo, $cuTransSupport) {
1220  $retTranCode = "";
1221 
1222  /*
1223  * Verify the acctclass key exists in the array,
1224  * If NOT, then return empty string, to error out
1225  */
1226  $transferFromAcctToTypes = "";
1227  if (HCU_array_key_exists('acctclass', $fromAcctInfo) && HCU_array_key_exists('acctclass', $toAcctInfo)) {
1228  $transferFromAcctToTypes = $fromAcctInfo['acctclass'] . '|' . $toAcctInfo['acctclass'];
1229  }
1230  // ** get the suffix for the toAcct
1231  $transferToSuffix = HCU_array_key_value("suffix", $toAcctInfo);
1232 
1233  switch($transferFromAcctToTypes) {
1234  case "D|D":
1235  if (HCU_array_key_value('trust', $toAcctInfo) == "transfer") {
1236  // ** Cross Account Transfer -- Use XA
1237  $retTranCode = 'XA';
1238  } else {
1239  $retTranCode = 'AT';
1240  }
1241  break;
1242  case "D|L":
1243  if (HCU_array_key_value('trust', $toAcctInfo) == "transfer") {
1244  // ** Cross Account Transfer -- Use XP
1245  $retTranCode = 'XP';
1246  } else {
1247  $retTranCode = 'LP';
1248  }
1249  break;
1250  case "L|D":
1251  // ** Loan Add-on Cross Account potential code definition
1252  $retTranCode = 'LA';
1253  break;
1254  case "D|C":
1255  $retTranCode = ($cuTransSupport['CC'] ? 'CP' : 'LP');
1256  break;
1257  case "C|D":
1258  /*
1259  *
1260  * Credit Card Advance
1261  * Original code had this coded as a 'CA', however, this is currently not an approved transaction code
1262  * This type of transaction should use 'LA', possibly in the future we can add CA, but we will still need
1263  * to offer an option similar to CP in Home Banking. For now all CC advance use LA posting to core
1264  * mws 12/30/14
1265  */
1266  $retTranCode = 'LA';
1267  break;
1268  case "L|O":
1269  case "C|O":
1270  /*
1271  * Other Special Types will NOT be allowed to have a repeating/scheduled
1272  * transfer. Caller should test for this.
1273  */
1274  $retTranCode = "OT"; // "Other" transactions may or may not cause Secure Form
1275  break;
1276  case "D|O":
1277  if ($transferToSuffix == "CW") {
1278  /*
1279  * CHECK WITHDRAWAL -- This overrides the OTHER type. It will be posted
1280  * to the appliance and needs to retain the Transaction Code of CW
1281  * ONLY available for Source Accounts that are Deposits
1282  */
1283  $retTranCode = "CW";
1284  } else {
1285  /*
1286  * Other Special Types will NOT be allowed to have a repeating/scheduled
1287  * transfer. Caller should test for this.
1288  */
1289  $retTranCode = "OT"; // "Other" transactions may or may not cause Secure Form
1290  }
1291  break;
1292  case "D|X":
1293  case "L|X":
1294  // to an external account
1295  $retTranCode = "1W"; // credit to receiving dfi, web transaction
1296  break;
1297  case "X|D":
1298  case "X|L":
1299  // from an external account
1300  $retTranCode = "2W"; // credit to receiving dfi, web transaction
1301  break;
1302  case "D|M":
1303  case "L|M":
1304  $retTranCode = "MM";
1305  break;
1306  }
1307 
1308  return $retTranCode;
1309 }
1310 
1311 /**
1312  * FindFrequencyValue:
1313  * @uses use this function to find the frequency value exists
1314  * in the global array
1315  *
1316  * this function was moved from hcuTransfer.data
1317  *
1318  * @param $freqArray - global frequencies
1319  * @param $valueToFind - frequency to find
1320  *
1321  * @return $valueFound - boolean
1322  */
1323 function FindFrequencyValue($freqArray, $valueToFind) {
1324  $valueFound = False;
1325 
1326  if (count($freqArray) > 0) {
1327  foreach ($freqArray as $freqIdx => $freqVal) {
1328  if ($freqVal['value'] === $valueToFind) {
1329  $valueFound = True;
1330  break;
1331  }
1332  }
1333  }
1334  return $valueFound;
1335 }
1336 
1337 /**
1338  * Submit this transfer after gathering all the necessary data. Only set
1339  * the posted information; other functions will handle the approve and processed
1340  * fields.
1341  *
1342  * @param integer $pDbh -- Current database handle
1343  * @param array $pHBEnv -- Current HB_ENV values
1344  * ["Uid"]
1345  * ["tz"]
1346  * ["SYSENV"]["logger"]
1347  * @param object $pMC -- Dictionary object
1348  * @param array $pPostVariables -- An array with information passed from client
1349  * "txFromAcctId"
1350  * "txToAcctId"
1351  * "txPmtComment"
1352  * "txMemo"
1353  * "txMemAccount"
1354  * "txMemType"
1355  * "txMemName"
1356  * "txToMisc1"
1357  * "txFromMember"
1358  * "txAmount"
1359  * "txFeatureCode"
1360  * @param object &$pResults -- results of the operation
1361  * -- trans_id of just-added transaction id
1362  *
1363  * @return True if operation successful, false or error structure otherwise
1364  */
1365 function SubmitTransfer( $pDbh, &$pHBEnv, $pMC, $pPostVariables, &$pResults ) {
1366  try {
1367  $sourceParts = explode( "|", $pPostVariables["txFromAcctId"] ); // eg "D|1103|10|0
1368  $destParts = explode( "|", $pPostVariables["txToAcctId"] ); // eg "D|1103|10|0" or "O|1103|CP"
1369 
1370  // if external transfer, local account is the one to save in the header
1371  // if m2m transfer, then local account is the "from" account and save it in the header
1372  if ( $sourceParts[0] == "X" ) {
1373  $recordType = $destParts[0];
1374  $account = $destParts[1];
1375  $accountType = $destParts[2];
1376  } else if ( $destParts[0] == "X" || $destParts[0] == "M" ) {
1377  $recordType = $sourceParts[0];
1378  $account = $sourceParts[1];
1379  $accountType = $sourceParts[2];
1380  } else {
1381  $recordType = $sourceParts[0];
1382  $account = $sourceParts[1];
1383  $accountType = $sourceParts[2];
1384  }
1385 
1386  $htmlTransferComment = "";
1387  if ( $destParts[0] == 'O' && substr($destParts[2], 0, 1 ) == "P" ) {
1388  // this is the 3rd party credit card set up in Monitor / HomeBanking with a pre-defined comment
1389  $htmlTransferComment = $pPostVariables['txPmtComment'];
1390  } else {
1391  $htmlTransferComment = $pPostVariables['txMemo'];
1392  }
1393 
1394  // just set up what is needed to determine the transaction code
1395  $txFromInfo = array( "acctclass" => $sourceParts[0] );
1396  $txToInfo = array( "acctclass" => $destParts[0] );
1397  $txAllowed = Get_HaveTrans( $pDbh, $pHBEnv );
1398 
1399  /**
1400  * Transaction code is generated in either ValidateTransfer or the Scheduled process
1401  * --TRUST-- the transaction code that is passed into the function
1402  */
1403  // $txCode = FindTransactionCode( $txFromInfo, $txToInfo, $txAllowed );
1404  $txCode = HCU_array_key_value("txCode", $pPostVariables);
1405 
1406  // this should have been validated earlier but just to make sure
1407  if ( $txCode == "" ) {
1408  throw new Exception( $pMC->msg('Transfer Error external', HCU_DISPLAY_AS_RAW) );
1409  }
1410 
1411  if ( $sourceParts[0] == "X" || $destParts[0] == "X" ) {
1412  // for external transfer set up rdfi info for the trans data
1413  $externalAccountId = ( $sourceParts[0] == "X" ) ? $sourceParts[2] : $destParts[2];
1414  $referenceId = $externalAccountId;
1415 
1416  $rdfiData = GetRemoteAccountInfo( $pDbh, $pHBEnv, $externalAccountId );
1417 
1418  // make sure we found RDFI account info
1419  if ( $rdfiData === "" ) {
1420  throw new Exception( $pMC->msg('EXT Acct Status Error', HCU_DISPLAY_AS_RAW) );
1421  }
1422 
1423  // unresolved - add addenda to the txn if there is any
1424  $rdfiData["rdfi"]["addenda"] = "";
1425  $sourceKey = null;
1426  $destKey = null;
1427 
1428  if ( $sourceParts[0] == "X" ) {
1429  // the external account is the source, local account is the destination
1430  $rdfiData["rdfi"]["rdfi_txn_type"] = "DB";
1431  $sourceAccountInfo = $rdfiData;
1432  $destAccountInfo = $pPostVariables["txToAcctId"];
1433  $destKey = array();
1434  if ($pPostVariables['txToType'] == "D") {
1435  $destKey['accountnumber'] = $pPostVariables['txToMember'];
1436  $destKey['recordtype'] = $pPostVariables['txToType'];
1437  $destKey['accounttype'] = $pPostVariables['txToSuffix'];
1438  $destKey['certnumber'] = $destParts[3];
1439  } else if ($pPostVariables['txToType'] == "L" || $pPostVariables['txToType'] == "C") {
1440  $destKey['accountnumber'] = $pPostVariables['txToMember'];
1441  $destKey['recordtype'] = $pPostVariables['txToType'];
1442  $destKey['loannumber'] = $pPostVariables['txToSuffix'];
1443  }
1444  } else {
1445  // the local account is the source, external account is the destination
1446  $rdfiData["rdfi"]["rdfi_txn_type"] = "CR";
1447  $destAccountInfo = $rdfiData;
1448  $sourceAccountInfo = $pPostVariables["txFromAcctId"];
1449  $sourceKey = array();
1450  if ($pPostVariables['txFromType'] == "D") {
1451  $sourceKey['accountnumber'] = $pPostVariables['txFromMember'];
1452  $sourceKey['recordtype'] = $pPostVariables['txFromType'];
1453  $sourceKey['accounttype'] = $pPostVariables['txFromSuffix'];
1454  $sourceKey['certnumber'] = $sourceParts[3];
1455  } else if ($pPostVariables['txFromType'] == "L" || $pPostVariables['txFromType'] == "C") {
1456  $sourceKey['accountnumber'] = $pPostVariables['txFromMember'];
1457  $sourceKey['recordtype'] = $pPostVariables['txFromType'];
1458  $sourceKey['loannumber'] = $pPostVariables['txFromSuffix'];
1459  }
1460  }
1461  } else if ( $destParts[0] == "M" ) {
1462  // for m2m transfer set up other member's account
1463  $externalAccountId = $destParts[2];
1464  $referenceId = $externalAccountId;
1465 
1466  $sourceKey = null;
1467  $destKey = null;
1468 
1469  // because some accounts may not be setup yet
1470  // we must fudge them here and not try to grab the info
1471  // from the database.
1472  if ($externalAccountId === "0") {
1473  $rdfiData = array(
1474  "account" => $pPostVariables['txMemAccount'],
1475  "type" => $pPostVariables['txMemType'],
1476  "name" => $pPostVariables['txMemName'],
1477  "display_name" => $pMC->msg( "M2M Account", HCU_DISPLAY_AS_RAW )
1478  );
1479  } else {
1480  $rdfiData = GetRemoteAccountInfo( $pDbh, $pHBEnv, $externalAccountId );
1481  }
1482 
1483  // make sure we found RDFI account info
1484  if ( $rdfiData === "" ) {
1485  throw new Exception( $pMC->msg('EXT Acct Status Error', HCU_DISPLAY_AS_RAW) );
1486  }
1487 
1488  // the local account is the source, m2m account is the destination
1489  $sourceAccountInfo = $pPostVariables["txFromAcctId"];
1490  $sourceKey = array();
1491  if ($pPostVariables['txFromType'] == "D") {
1492  $sourceKey['accountnumber'] = $pPostVariables['txFromMember'];
1493  $sourceKey['recordtype'] = $pPostVariables['txFromType'];
1494  $sourceKey['accounttype'] = $pPostVariables['txFromSuffix'];
1495  $sourceKey['certnumber'] = $sourceParts[3];
1496  } else if ($pPostVariables['txFromType'] == "L" || $pPostVariables['txFromType'] == "C") {
1497  $sourceKey['accountnumber'] = $pPostVariables['txFromMember'];
1498  $sourceKey['recordtype'] = $pPostVariables['txFromType'];
1499  $sourceKey['loannumber'] = $pPostVariables['txFromSuffix'];
1500  }
1501 
1502  // make into M|account|type (where type is "checking" or "savings")
1503  $destAccountInfo = "M|" . $rdfiData["account"] . "|" . $rdfiData["type"];
1504  $destKey = array();
1505  // some info that could be helpful if the account is removed
1506  $destKey['accountnumber'] = $rdfiData["account"];
1507  $destKey['accounttype'] = $rdfiData["type"]; // checking or savings
1508  $destKey["display_name"] = $rdfiData["display_name"];
1509  $destKey["name"] = $rdfiData["name"];
1510  $destKey['ext_id'] = $externalAccountId;
1511  } else {
1512 
1513  // Regular transfers
1514  $sourceAccountInfo = $pPostVariables["txFromAcctId"];
1515  $destAccountInfo = $pPostVariables["txToAcctId"];
1516  $sourceKey = array();
1517  if ($pPostVariables['txFromType'] == "D") {
1518  $sourceKey['accountnumber'] = $pPostVariables['txFromMember'];
1519  $sourceKey['recordtype'] = $pPostVariables['txFromType'];
1520  $sourceKey['accounttype'] = $pPostVariables['txFromSuffix'];
1521  $sourceKey['certnumber'] = $sourceParts[3];
1522  } else if ($pPostVariables['txFromType'] == "L" || $pPostVariables['txFromType'] == "C") {
1523  $sourceKey['accountnumber'] = $pPostVariables['txFromMember'];
1524  $sourceKey['recordtype'] = $pPostVariables['txFromType'];
1525  $sourceKey['loannumber'] = $pPostVariables['txFromSuffix'];
1526  }
1527 
1528  $destKey = array();
1529  if ($pPostVariables['txToType'] == "D") {
1530  // Cross accounts are slightly different. (Who do you trust?)
1531  $destKey['accountnumber'] = $pPostVariables["txTrust"] == "transfer" ? $destParts[1] : $pPostVariables['txToMember'];
1532  $destKey['recordtype'] = $pPostVariables['txToType'];
1533  $destKey['accounttype'] = $pPostVariables['txToSuffix'];
1534  $destKey['certnumber'] = $destParts[3];
1535  } else if ($pPostVariables['txToType'] == "L" || $pPostVariables['txToType'] == "C") {
1536  // Cross accounts are slightly different. (Who do you trust?)
1537  $destKey['accountnumber'] = $pPostVariables["txTrust"] == "transfer" ? $destParts[1] : $pPostVariables['txToMember'];
1538  $destKey['recordtype'] = $pPostVariables['txToType'];
1539  $destKey['loannumber'] = $pPostVariables['txToSuffix'];
1540  }
1541 
1542  $referenceId = $pHBEnv["Uid"]; // use the user id for regular transfers
1543  }
1544 
1545  // set up the transfer data
1546  $transData = array( "acct_source" => $sourceAccountInfo, "acct_dest" => $destAccountInfo, "source_key"=> $sourceKey, "dest_key" => $destKey );
1547  if (HCU_array_key_value('txToMisc1', $pPostVariables) !== '') {
1548  // using "misc" because M2M can also pass "misc" (name) info in the same field
1549  $transData['misc'] = HCU_array_key_value('txToMisc1', $pPostVariables);
1550  }
1551  // need to add the name for M2M transfer
1552  if ( $destParts[0] == "M" ) {
1553  // rdfiData should be present for M2M (it was checked above)
1554  $transData['misc'] = $rdfiData["name"];
1555  }
1556 
1557  $jsonTransData = HCU_JsonEncode( $transData );
1558 
1559  // Setup transaction metadata.
1560  // Single or Recurring transaction, and the current interval. In this case
1561  // the record is coming from an immediate source on Banking so it is not
1562  // recurring and has no interval.
1563  $transMeta = array();
1564  $transMeta["source"] = "immed";
1565  $transMeta["recurr"] = "no";
1566  $transMeta["interval"] = 0;
1567  $jsonTransMeta = HCU_JsonEncode($transMeta);
1568  $recordVariables["txMeta"] = $jsonTransMeta;
1569 
1570  $amount = $pPostVariables['txAmount'];
1571 
1572  // the feature code depends on where the transfer is going
1573  $recordVariables = array();
1574  $recordVariables["txFeatureCode"] = $pPostVariables['txFeatureCode'];
1575  $recordVariables["txPostedBy"] = $pHBEnv["Uid"];
1576  // ** Set the Authority Account for the transaction
1577  // Authority will always be the txFromMember that was determine in ValidateTransfer
1578  $recordVariables["txAuthAccount"] = $pPostVariables['txFromMember'];
1579  $recordVariables["txTransCode"] = $txCode;
1580  $recordVariables["txComment"] = $htmlTransferComment;
1581  $recordVariables["txReferenceId"] = $referenceId;
1582  $recordVariables["txAmount"] = $pPostVariables['txAmount'];
1583  $recordVariables["txData"] = $jsonTransData;
1584  $recordVariables["txMeta"] = $jsonTransMeta;
1585 
1586  // the pHBEnv is needed for CU and logging
1587  $transferResult = RecordTransfer( $pDbh, $pHBEnv, $recordVariables );
1588  if ( $transferResult["status"]["code"] != "000" ) {
1589  throw new Exception( $pMC->msg("Transfer Error", HCU_DISPLAY_AS_HTML) );
1590  }
1591 
1592  $postedDate = $transferResult["data"]["posted_date"];
1593 
1594  // build a confirmation code (so don't need to read the database)
1595  $confCodeBuilder["id"] = $transferResult["data"]["id"];
1596  $confCodeBuilder["posted_by"] = $pHBEnv["Uid"];
1597  $confCodeBuilder["posted_date"] = $postedDate;
1598  $confirmationCode = GetTransferConfirmCode( $pHBEnv, "post", $confCodeBuilder );
1599  $pHBEnv["confirmationCode"]= $confirmationCode; // #885 Ensure that confirmation codes are the same for a credit card payment for the confirmation and also the code in Admin.
1600 
1601  $myDateTime = new DateTime( $postedDate );
1602 
1603  // convert to local time for the display
1604  $myDateTime->setTimezone(new DateTimeZone($pHBEnv["tz"]));
1605  $readableDate = $myDateTime->format("m/d/Y g:ia");
1606 
1607  // need to use same names as database field names
1608  $transactionInfo = array( "transdata" => $jsonTransData,
1609  "transactioncode" => $txCode );
1610 /*
1611  * GetTransferDetails is expecting many more values in the array -
1612  * 'to' values for to_acct, to_desc, to_certnumber, to_accounttype, and to_displayname
1613  * 'from' values for from_acct, from_desc, from_certnumber, from_accounttype, and from_displayname
1614  *
1615  * Without them, data_from and data_to are coming back empty
1616  */
1617 
1618  $transDetail = GetTransferDetails( $pHBEnv, $transactionInfo );
1619 
1620  // return the id, amount, day, and confirmation code
1621  $pResults = array( "status" => array( "code" => "000" ),
1622  "txn" => array( "trans_id" => $transferResult["data"]["id"],
1623  "data_action" => $transDetail["action"],
1624  "data_from" => $transDetail["from"],
1625  "data_to" => $transDetail["to"],
1626  "data_amount" => $amount,
1627  "data_date" => $readableDate,
1628  "data_confirm" => $confirmationCode ) );
1629 
1630  $success = true;
1631  } catch (Exception $ex) {
1632  $errors = array( $ex->getMessage() );
1633  $pHBEnv["SYSENV"]["logger"]->debug( "Record Transfer:" . $errors );
1634 
1635  $pResults["status"] = array( "code" => "999",
1636  "severity" => "ERROR",
1637  "errors" => $errors );
1638 
1639  $success = false;
1640  }
1641 
1642  return $success;
1643 
1644 } // end SubmitTransfer
1645 
1646 /**
1647  * Get the previously set up external account information.
1648  *
1649  *Return an empty string if any error, otherwise return structured account info.
1650  */
1651 function GetRemoteAccountInfo( $pDbh, $pHBEnv, $externalAccountId ) {
1652  $returnInfo = "";
1653 
1654  // get the account, making sure it is active, AND this user owns the account
1655  $sql = "SELECT type, display_name, remote_info
1656  FROM {$pHBEnv["Cu"]}extaccount
1657  WHERE id = $externalAccountId
1658  AND user_id = {$pHBEnv["Uid"]}
1659  AND status = 'a' ";
1660 
1661  $rs = db_query( $sql, $pDbh );
1662 
1663  if ( $rs ) {
1664  // successful query
1665  list( $type, $displayName, $remoteInfo ) = db_fetch_array( $rs, 0 );
1666 
1667  $aryRemoteInfo = HCU_JsonDecode( $remoteInfo, true );
1668 
1669  if ( $type == "EXT" && strlen( $aryRemoteInfo["rdfi"]["routing"] ) > 0 ) {
1670  // {"rdfi":{"rdfi_routing":"878787878","rdfi_account":"888888","rdfi_account_type":"20","addenda":""},"remote_entity":{"name":"Mike Smith"}}
1671  $returnInfo = array( "rdfi" => array( "rdfi_routing" => $aryRemoteInfo["rdfi"]["routing"],
1672  "rdfi_account" => $aryRemoteInfo["rdfi"]["account"],
1673  "rdfi_account_type" => $aryRemoteInfo["rdfi"]["type"],
1674  "rdfi_txn_type" => "",
1675  "addenda" => "" ),
1676  "remote_entity" => array( "name" => $aryRemoteInfo["rdfi"]["name"],
1677  "entry_id" => $externalAccountId,
1678  "display_name" => $displayName )
1679  );
1680  } else if ( $type == "M2M" && strlen( $aryRemoteInfo["rdfi"]["account"] ) > 0 ) {
1681  // just the account is important
1682  $returnInfo = array( "account" => $aryRemoteInfo["rdfi"]["account"],
1683  "type" => $aryRemoteInfo["rdfi"]["type"],
1684  "name" => $aryRemoteInfo["rdfi"]["name"],
1685  "entry_id" => $externalAccountId,
1686  "display_name" => $displayName
1687  );
1688  }
1689  }
1690 
1691  return $returnInfo;
1692 } // end GetRemoteAccountInfo
1693 
1694 
1695 /**
1696  * Record this transfer into the <cu>transhdr and <cu>transdtl tables. Only set
1697  * the posted information; other functions will handle the approve and processed
1698  * fields. This can be called by the background process so it cannot be dependant
1699  * on banking environment.
1700  *
1701  * @param integer $pDbh -- Current database handle
1702  * @param array $pEnv -- Needed environment values (for logging errors)
1703  * @param array $pRecordVariables -- An array with information to be recorded
1704  * "txFeatureCode" -- HomeCU feature code
1705  * "txPostedBy" -- User Id of user posting the transfer
1706  * "txAuthAccount" -- authorizing account number (usually the "from" account)
1707  * "txTransCode" -- Internal transaction code (so processing code knows what to do with it)
1708  * "txComment" -- comment/memo for the transfer (size limits apply)
1709  * "txReferenceId" -- Reference Id (usually the Uid for this)
1710  * "txAmount" -- Amount of transfer
1711  * "txData" -- JSON representation of the transfer data
1712  *
1713  * @return structure with success or error info
1714  */
1715 function RecordTransfer( $pDbh, $pEnv, $pRecordVariables ) {
1716  $retStatusAry = array(
1717  "status" => array( "code"=>"000", "errors" => Array() ),
1718  "data" => ""
1719  );
1720 
1721  try {
1722  // get the necessary account meta data for use during processing the transfer
1723  $transDataWithMeta = AddAccountMetaData( $pEnv,
1724  $pRecordVariables["txFeatureCode"],
1725  $pRecordVariables["txTransCode"],
1726  $pRecordVariables["txData"] );
1727 
1728  // start a transaction if not already in one
1729  $transStatus = db_transaction_status( $pDbh );
1730  $startedTransaction = false;
1731  if ( !($transStatus === PGSQL_TRANSACTION_ACTIVE || $transStatus === PGSQL_TRANSACTION_INTRANS) ) {
1732  // start a transaction because adding both a header and a detail record
1733  $txnRs = db_work( $pDbh, HOMECU_WORK_BEGIN);
1734 
1735  $startedTransaction = true;
1736  }
1737 
1738  $txFeatureCode = prep_save($pRecordVariables["txFeatureCode"]);
1739  $txPostedBy = prep_save($pRecordVariables["txPostedBy"]);
1740  $txAuthAccount = prep_save($pRecordVariables["txAuthAccount"]);
1741  $txTransCode = prep_save($pRecordVariables["txTransCode"]);
1742  $txComment = prep_save($pRecordVariables["txComment"]);
1743  $txReferenceId = prep_save($pRecordVariables["txReferenceId"]);
1744  $txAmount = prep_save($pRecordVariables["txAmount"]);
1745  $txData = prep_save($transDataWithMeta);
1746  $txMeta = prep_save($pRecordVariables["txMeta"]);
1747 
1748  // this is only used on ach commercial transactions, no others will set this key.
1749  $txNotify = isset($pRecordVariables["txNotify"]) ? intval($pRecordVariables['txNotify']) : 0;
1750 
1751  // ACH effective date needs to account for cut-off time
1752  if ( $txFeatureCode == "TRNEXT") {
1753  // get the effective ACH date
1754  $effDate = ACH_GetEffectiveDate( $pEnv );
1755 
1756  // put it into correct format
1757  $effectiveDate = date( "Y-m-d", strtotime( $effDate ) );
1758 
1759  $effDateStr = "'$effectiveDate'";
1760  } else {
1761  $effDateStr = "(SELECT CURRENT_DATE)";
1762  }
1763 
1764  // set up the query for the header
1765  // ** Mws 7/2017 -- Saving 'NULL' for both accounttype and deposittype -- these values should be retrieved from transdtl
1766  $sql = "INSERT INTO {$pEnv["Cu"]}transhdr (feature_code, effective_date,
1767  posted_by, posted_date, accountnumber, transactioncode, memo, transmeta)
1768  VALUES ('$txFeatureCode', {$effDateStr},
1769  {$txPostedBy}, now(), '{$txAuthAccount}', '{$txTransCode}', '{$txComment}', '{$txMeta}' )
1770  RETURNING id, posted_date";
1771 
1772  $hdrRs = db_query( $sql, $pDbh );
1773  if ( !$hdrRs ) {
1774  throw new Exception( "Transfer header query error " . db_last_error() );
1775  }
1776 
1777  // use the insert id from the header in the detail
1778  list($headerId, $postedDate) = db_fetch_array($hdrRs, 0);
1779 
1780  if ( !$headerId ) {
1781  throw new Exception( "Error getting id of header after insert query" );
1782  }
1783 
1784  // set up the query for the detail
1785  $sql = "INSERT INTO {$pEnv["Cu"]}transdtl (transhdr_id, reference_id, amount, email_notify, transdata)
1786  VALUES ($headerId, $txReferenceId, $txAmount, $txNotify, '$txData')";
1787 
1788  $dtlRs = db_query( $sql, $pDbh );
1789 
1790  if ( !$dtlRs ) {
1791  throw new Exception( "Error inserting transfer detail" );
1792  }
1793 
1794  if ( $startedTransaction ) {
1795  // now commit
1796  $commitRs = db_work( $pDbh, HOMECU_WORK_COMMIT );
1797  if ( !$commitRs ) {
1798  // ** FAILED COMMIT
1799  throw new Exception ("Failed to Commit Work");
1800  }
1801  }
1802 
1803  // need to return posted date and header id
1804  $returnData = array( "id" => $headerId, "posted_date" => $postedDate );
1805  $retStatusAry["data"] = $returnData;
1806  } catch (Exception $ex) {
1807  $pEnv["SYSENV"]["logger"]->debug( "Record Transfer:" . $ex->getMessage() );
1808 
1809  if ( $startedTransaction ) {
1810  db_work( $pDbh, HOMECU_WORK_ROLLBACK );
1811  }
1812 
1813  $retStatusAry["status"]["code"] = "999";
1814  }
1815 
1816  return $retStatusAry;
1817 
1818 } // end RecordTransfer
1819 
1820 /**
1821  * Add the account meta data to the transdata information. Need to use the transdata info to
1822  * find the account meta data. The meta data depends on what type of account (e.g. local,
1823  * external, m2m).
1824  * If anything untoward happens just pass back the passed in data.
1825  *
1826  * @param array $pHBEnv - Current HB_ENV values
1827  * ["Uid"]
1828  * ["SYSENV"]["logger"]
1829  * @param string $pFeatureCode - Feature (e.g. TRN, TRNEXT)
1830  * @param string $pTxnCode - Transaction code (e.g. LP, 1P, 2W)
1831  * @param string $pTransData - Current transdata values in JSON string
1832  */
1833 function AddAccountMetaData( $pHBEnv, $pFeatureCode, $pTxnCode, $pTransData ) {
1834  $returnTransData = $pTransData;
1835 
1836  try {
1837  $transData = HCU_JsonDecode( $pTransData );
1838 
1839  if ( empty( $transData ) ) {
1840  throw new Exception ("Bad transfer data");
1841  }
1842 
1843  // get the transfer account list
1844  // Note: using defaults and want with respect to the uid in $pHBEnv.
1845  $txAcctList = TX_list( $pHBEnv["dbh"], $pHBEnv );
1846 
1847  $sourceMeta = array( "deposit_ach_type" => "" );
1848  $destMeta = array( "deposit_ach_type" => "" );
1849 
1850  switch ( $pTxnCode ) {
1851  case "AT":
1852  // both source and dest are deposit accounts
1853  break;
1854  case "LP":
1855  // source is deposit account, dest is loan
1856  break;
1857  case "LA":
1858  // source is loan, dest is deposit account
1859  break;
1860  case "CP":
1861  // source is deposit account, dest is credit account
1862  break;
1863  case "CA":
1864  // source is credit account, dest is deposit account
1865  break;
1866  case "OT":
1867  // source can be deposit/loan/credit account, dest is "other"
1868  break;
1869  case "1W": // External, L2R
1870  case "1P": // ACH PPD, L2R (Payment)
1871  case "1C": // ACH CCD, L2R (Payment)
1872  // source is deposit or loan, dest is external account
1873  $sourceParts = explode( "|", $transData["acct_source"]);
1874  if ( $sourceParts[0] == "D" ) {
1875  $sourceDepositType = $txAcctList["acctlist"][$transData["acct_source"]]["deposittype"];
1876  $sourceDepositCode = $sourceDepositType == "Y" ? 10 : 20;
1877  } else {
1878  $sourceDepositCode = "";
1879  }
1880 
1881  // add the source meta data
1882  $sourceMeta["deposit_ach_type"] = $sourceDepositCode;
1883  $transData["source_meta"] = $sourceMeta;
1884 
1885  // re-encode the transdata, but test it worked right
1886  $transDataJson = HCU_JsonEncode($transData);
1887  if ( !empty( $transDataJson ) ) {
1888  $returnTransData = $transDataJson;
1889  }
1890  break;
1891  case "2W": // External, R2L
1892  case "2P": // ACH PPD, R2L (Collection)
1893  case "2C": // ACH CCD, R2L (Collection)
1894  // source is external account, dest is deposit or loan
1895  $destParts = explode( "|", $transData["acct_dest"]);
1896  if ( $destParts[0] == "D" ) {
1897  $destDepositType = $txAcctList["acctlist"][$transData["acct_dest"]]["deposittype"];
1898  $destDepositCode = $destDepositType == "Y" ? 10 : 20;
1899  } else {
1900  $destDepositCode = "";
1901  }
1902 
1903  // add the destination meta data
1904  $destMeta["deposit_ach_type"] = $destDepositCode;
1905  $transData["dest_meta"] = $destMeta;
1906 
1907  // re-encode the transdata, but test it worked right
1908  $transDataJson = HCU_JsonEncode($transData);
1909  if ( !empty( $transDataJson ) ) {
1910  $returnTransData = $transDataJson;
1911  }
1912  break;
1913  case "MM":
1914  // source is deposit or loan, dest is (external) M2M account
1915  break;
1916  }
1917 
1918  } catch( Exception $e ) {
1919  // not doing anything on error because it returns the input transdata
1920  }
1921 
1922  return $returnTransData;
1923 } // end AddAccountMetaData
1924 
1925 /**
1926  * See if this user can approve this transfer. If the feature needs confirmation the user cannot
1927  * confirm their own transfer.
1928  *
1929  *
1930  * @param integer $pDbh -- Current database handle
1931  * @param array $pHBEnv -- Current HB_ENV values
1932  * ["Uid"]
1933  * ["SYSENV"]["logger"]
1934  * @param object $pMC -- Dictionary object
1935  * @param integer $pWhich -- Id of transfer to approve
1936  * @param object &$pResults -- results of the operation
1937  *
1938  * @return True if operation successful, false otherwise
1939  */
1940 function ApproveTransfer( $pDbh, $pHBEnv, $pMC, $pWhich, &$pResults ) {
1941 
1942  $approvalInfo = array( "txId" => $pWhich,
1943  "txApprover" => $pHBEnv["Uid"] );
1944 
1945  $returnInfo = MarkTransferApproved( $pDbh, $pHBEnv, $approvalInfo );
1946 
1947  // check the results
1948  if ( $returnInfo["status"]["code"] != "000" ) {
1949  $pResults["status"] = array( "code" => "999",
1950  "severity" => "ERROR",
1951  "errors" => $pMC->msg("Transfer Error", HCU_DISPLAY_AS_HTML) );
1952  $success = false;
1953  } else {
1954  $confCodeBuilder["id"] = $pWhich;
1955  $confCodeBuilder["approved_by"] = $pHBEnv["Uid"];
1956  $confCodeBuilder["approved_date"] = $returnInfo["data"]["approved_date"];
1957  $confirmationCode = GetTransferConfirmCode( $pHBEnv, "approve", $confCodeBuilder );
1958 
1959  // return confirmation code
1960  $pResults["status"] = array( "code" => "000", "confirm" => $confirmationCode );
1961 
1962  $success = true;
1963  }
1964 
1965  return $success;
1966 } // end ApproveTransfer
1967 
1968 
1969 /**
1970  * Mark this transfer as approved by updating the <cu>transhdr tables. Only set
1971  * the posted information; other functions will handle the approve and processed
1972  * fields. This can be called by the background process so it cannot be dependant
1973  * on banking environment.
1974  *
1975  * @param integer $pDbh -- Current database handle
1976  * @param array $pEnv -- Needed environment values (for logging errors)
1977  * @param array $pApprovalVariables -- An array with information used in approving
1978  * "txId" -- Id of transaction
1979  * "txApprover" -- User Id of user posting the transfer
1980  *
1981  * @return structure with success or failure code
1982  */
1983 function MarkTransferApproved( $pDbh, $pEnv, $pApprovalVariables ) {
1984  $retStatusAry = Array(
1985  "status" => Array( "code"=>"000", "errors" => Array() ),
1986  "data" => ""
1987  );
1988 
1989  // set up the query to mark the transaction approved
1990  $sql = "UPDATE {$pEnv["Cu"]}transhdr SET
1991  approved_by = '{$pApprovalVariables["txApprover"]}',
1992  approved_date = now(),
1993  approved_status = 10
1994  WHERE id = {$pApprovalVariables["txId"]}
1995  RETURNING id, approved_date";
1996 
1997  $hdrRs = db_query( $sql, $pDbh );
1998 
1999  // get the date
2000  list( $transId, $approvedDate) = db_fetch_array( $hdrRs, 0 );
2001 
2002  if ( !$hdrRs || !$transId ) {
2003  $retStatusAry["status"]["code"] = "999";
2004  } else {
2005  // return approved date
2006  $returnInfo["approved_date"] = $approvedDate;
2007  $retStatusAry["data"] = $returnInfo;
2008  }
2009 
2010  return $retStatusAry;
2011 } // end MarkTransferApproved
2012 
2013 /**
2014  * Post the transfer if it is an internal transfer this transaction as processed. If it is an ACH, this should not be called.
2015  * NOTE: This might be a secure form for a credit card transfer, or a submission
2016  * to the core. For external transfers (ACH) it will be picked up by the admin
2017  * processing.
2018  * NOTE: An internal transfer will onlyl have one detail record.
2019  * NOTE: This function can be called by the background scheduler, so don't rely on the $MC dictionary.
2020  * NOTE: The memo passed in via the $pTranRecord is assumed to meet the max length requirement for the core.
2021  *
2022  *
2023  * @param array $pHBEnv -- Current Environment values (see what is used to know what is needed)
2024  * Ml -- member's email (if is set)
2025  * @param array $pTranRecord -- Data for entry being processed
2026  * acct_source -- the source account data from the transdtl.transdata field ("D|1103|5|0")
2027  * acct_dest -- the destination account data from the transdtl.transdata field ("D|1103|5|0")
2028  * misc1 -- if it exists, the misc1 data from the transdtl.transdata field
2029  * accountnumber -- authorizing account (stored in the transhdr.accountnumber field)
2030  * transactioncode -- transaction code that the appliance/core understands (from transhdr.transactioncode code field)
2031  * memo -- memo for the transaction (from the transhdr.memo field)
2032  * amount -- amount of the transaction (from the transdtl.amount field)
2033  * posted_by -- original user_id (int) of user that created the transaction request
2034  *
2035  * @return True if successful; error structure if not
2036  */
2037 function PostInternalTransfer( $pHBEnv, $pTranRecord ) {
2038  try {
2039  $txfr = array(
2040  "status" => array( "code"=>'000', "errors" => Array() ),
2041  "txn" => array()
2042  );
2043 
2044  $dbh = HCU_array_key_value('dbh', $pHBEnv);
2045  $email = isset( $pHBEnv['Ml'] ) && strlen( $pHBEnv['Ml'] ) > 0 ? $pHBEnv['Ml'] : "";
2046 
2047  $txfr = array();
2048 
2049  $tranCode = $pTranRecord["transactioncode"];
2050 
2051  /**
2052  * Get the TX_list results
2053  * Use this list to determine the type of transfer and what values to send to core
2054  */
2055  $transferListAry = TX_list($dbh, $pHBEnv,'', false, intval(HCU_array_key_value('posted_by', $pTranRecord)));
2056 
2057  if (!HCU_array_key_exists('acctlist', $transferListAry)) {
2058  throw new Exception ("Unable to load transfer accounts. (99901)");
2059  }
2060  /* ** GET SOURCE ACCOUNT ** */
2061  $srcAcctList = HCU_array_key_value($pTranRecord['acct_source'], $transferListAry['acctlist']);
2062  /* ** GET DESTINATION ACCOUNT ** */
2063  $dstAcctList = ($tranCode != 'MM' ? HCU_array_key_value($pTranRecord['acct_dest'], $transferListAry['acctlist']) : Array());
2064 
2065  /* ** Throw an error if srcAcctList is empty
2066  ** OR if dstAcctList is empty and this is NOT Member2Member Transfer
2067  ** empty ___AcctList (NOT FOUND IN acctlist) ** */
2068  if (!$srcAcctList || (!$dstAcctList && $tranCode != 'MM')) {
2069  throw new Exception ("Unable to load transfer account. (99902)");
2070  }
2071 
2072  $tranAuthAccount = trim($pTranRecord['accountnumber']);
2073 
2074  $trMemo = $pTranRecord["memo"];
2075 
2076  # accept and validate transaction request
2077  $sourceInfo = explode( "|", $pTranRecord["acct_source"] );
2078  $destInfo = explode( "|", $pTranRecord["acct_dest"] );
2079 
2080  # try to post, but for now just return the stuff we will send
2081  $acct = $sourceInfo[1];
2082  $sourceSubAcct = $sourceInfo[2];
2083  # take care of one-sided (to-acctclass is O)
2084  $destSubAcct = ($destInfo[0] == 'O') ? "" : $destInfo[2];
2085 
2086  # don't think the no@email.com applies here, but make sure it works for legacy
2087  $memberEmail = ($email == "no\@email.com" ? " " : trim($email));
2088  $memberEmail = str_replace(" ","",trim($memberEmail));
2089 
2090  $miscField = "-";
2091 
2092  if ($tranCode == 'MM') {
2093  /**
2094  * Member 2 Member
2095  * for member to member transfers the miscField will come out of the 'misc' field
2096  * that should be in the pTranRecord. This will be the LAST NAME for the
2097  * associated account.
2098  *
2099  * **/
2100  if(HCU_array_key_exists("misc", $pTranRecord)) {
2101  $miscField = trim($pTranRecord['misc']);
2102  }
2103  } else {
2104  /**
2105  * For all other types get the misc1 from the dstAcctList if it is found.
2106  * This will normally only be present when the destination is a loan.
2107  */
2108  if(HCU_array_key_exists("misc1", $dstAcctList)) {
2109  $miscField = trim($dstAcctList['misc1']);
2110  }
2111  }
2112 
2113  $destAccount = $destInfo[1];
2114  $AMT = $pTranRecord["amount"];
2115 
2116 
2117 
2118  /* ** Evalute the member for the Source and Destination ** */
2119  /* IF they are the same then this is the traditional transfer code */
2120  /* If they are DIFFERENT then prepend an 'X' to the beginning of the transfer code */
2121  /* ** BUT ONLY FOR CERTAIN TRANSFER CODES ** */
2122  $tranCodeLookup = Array("AT", "LP", "CP", "LA");
2123  /* Cross Account Transaction Codes */
2124  $tranCodeXLookup = Array("XA", "XP");
2125  if (in_array($tranCode, $tranCodeLookup)) {
2126  if (HCU_array_key_value('member', $srcAcctList) != HCU_array_key_value('member', $dstAcctList)) {
2127  // ** ORIGINATING CORE member numbers are different -- BLIND TRANSFER -- no relationship on core
2128  /* ** BLIND TRANSFER ** */
2129  $tranCode = "X" . $tranCode;
2130  }
2131  } elseif (in_array($tranCode, $tranCodeXLookup)) {
2132  // ** Cross Account transfers have different transaction codes saved in banking {XA - Account Transfer, XP - Loan Payment}
2133  // ** However they still use the standard transfer types of AT, LP
2134  $tranCode = ($tranCode == "XP" ? "LP" : "AT");
2135  }
2136 
2137 
2138  /* How to post is now determined by a lower function. Batch or Live pass in parameters and let the function decide. */
2139 
2140  // This is a note for live
2141  # put this block of code where it will be executed for live only.
2142  // no batch clients have joint accounts, but looks like GetTrans
2143  // expects to simplify suffixes if overloads are found. So post
2144  // simplified for live, but leave batch alone for now.
2145  //
2146  // Wondering if we ought to simplify in TX_list instead, but need
2147  // to trace the impact for batch, and for confirmation screens
2148  // before making that change
2149  //
2150 
2151  $errors = array();
2152 
2153  $txnValues = array( "member" => $acct,
2154  "type" => "T",
2155  "tran_code" => $tranCode,
2156  "ref1" => $sourceSubAcct,
2157  "ref2" => $destSubAcct,
2158  "ref3" => $memberEmail,
2159  "ref4" => $miscField,
2160  "ref5" => $destAccount, // this could be 10/20 for checking/savings
2161  "amount" => $AMT,
2162  "tmemo" => $trMemo,
2163  "tauth" => $tranAuthAccount
2164  );
2165 
2166  $operation = "TRANSFER";
2167 
2168  $result = SendTransaction( $pHBEnv, $operation, $txnValues );
2169 
2170  if ( $result["status"]["code"] != "000" ) {
2171  // just assign all the errors
2172  $errors = $result["status"]["errors"];
2173  }
2174 
2175  if (count($errors) > 0) {
2176  $txfr['status']['code']='999';
2177  $txfr['status']['severity']='ERROR';
2178  $txfr['status']['errors']=$errors;
2179  } else {
2180  $txfr['status']['code']='000';
2181  $txfr['status']['severity']='INFO';
2182 
2183  // confimration code is now generated from the <client>transhdr table info, so we don't supply it here
2184  $txfr['txn']['acct']=$acct;
2185  $txfr['txn']['tcode']=$tranCode;
2186  $txfr['txn']['r1fsfx']=$sourceSubAcct;
2187  $txfr['txn']['r2tsfx']=$destSubAcct;
2188  $txfr['txn']['r3email']=urldecode($memberEmail);
2189  $txfr['txn']['r4misc1']=$miscField;
2190  $txfr['txn']['r5tmem']=$destAccount;
2191  $txfr['txn']['amount']=$AMT;
2192  $txfr['txn']['trmemo']=$trMemo;
2193  }
2194  } catch( Exception $ex ) {
2195  $pHBEnv["SYSENV"]["logger"]->debug( "PostInternalTransfer:" . $ex->getMessage() );
2196  $errors[] = $ex->getMessage();
2197 
2198  $txfr['status']['code'] = '999';
2199  $txfr['status']['severity'] = 'ERROR';
2200  $txfr['status']['errors'] = $errors;
2201 
2202  }
2203  return ($txfr);
2204 } // end PostInternalTransfer
2205 
2206 /**
2207  * Post the transfer as a secure form for a credit card transfer.
2208  * NOTE: Needs to return info similar to PostInternalTransfer since calling routine tests return info the same.
2209  * NOTE: This function is only expected to be called from the Banking transfer feature.
2210  *
2211  * @param array $pHBEnv -- Current Environment values (see what is used to know what is needed)
2212  * @param array $pTranRecord -- Data for entry being processed
2213  * acct_source -- the source account data from the transdtl.transdata field ("D|1103|5|0")
2214  * acct_dest -- the destination account data from the transdtl.transdata field ("D|1103|5|0")
2215  * misc1 -- if it exists, the misc1 data from the transdtl.transdata field
2216  * accountnumber -- authorizing account (stored in the transhdr.accountnumber field)
2217  * transactioncode -- transaction code that the appliance/core understands (from transhdr.transactioncode code field)
2218  * memo -- memo for the transaction (from the transhdr.memo field)
2219  * amount -- amount of the transaction (from the transdtl.amount field)
2220  *
2221  * @return True if successful; error structure if not
2222  */
2223 function PostSecureMessageTransfer( $pHBEnv, $tranData ) {
2224 
2225  try {
2226  $txfr = array(
2227  "status" => array( "code"=>'000', "errors" => Array() ),
2228  "txn" => array()
2229  );
2230  $errors = Array();
2231 
2232  $MC = $pHBEnv["MC"];
2233 
2234  // see if it is a credit card payment request (destination account has a Px)
2235  $destParts = explode( "|", $tranData["acct_dest"] );
2236  // unresolved - need to verify this: member or user?
2237  $sourceParts = explode( "|", $tranData["acct_source"] );
2238 
2239  $fromAcct = $sourceParts[2];
2240  $fromMbr = $sourceParts[1];
2241 
2242  // The request should already be validated; assume the core or admin use will make sure funds exist if the approval happend
2243  // at a later date then the initial posting.
2244 
2245  // see if the name has been customized
2246  $cuAllowedTransactionsAry = Get_HaveTrans( $pHBEnv["dbh"], $pHBEnv );
2247 
2248  $key= trim($destParts[2]);
2249 
2250  $secureFormTitle = "";
2251  if ( HCU_array_key_exists($key, $cuAllowedTransactionsAry) ) {
2252  $secureFormTitle = $cuAllowedTransactionsAry[$key][2];
2253  if ( $secureFormTitle == "" ) {
2254  $secureFormTitle = $cuAllowedTransactionsAry[$key][0];
2255  }
2256  } else {
2257  $errors[] = $MC->msg('Option not set');
2258  }
2259 
2260  if ( $secureFormTitle == "" ) {
2261  $secureFormTitle = "Credit Card Payment Request";
2262  }
2263 
2264  /*
2265  * Define the location of the sslforms directory
2266  */
2267  $sslFormsDir = "/home/{$pHBEnv['chome']}/sslforms/";
2268  /*
2269  * File name should be
2270  * Vpmt[YYYYMMDDHHNNSS{PID}].html
2271  */
2272  $sslFormsFileName = 'CCPmt' . date('YmdHis') . posix_getpid() . '.html';
2273 
2274  // get the notification email
2275  $sql = "SELECT email
2276  FROM cuadmnotify
2277  WHERE cu = '{$pHBEnv['Cu']}'
2278  AND role = 'ccemail'";
2279  $emRs = db_query($sql, $pHBEnv["dbh"]);
2280  list($notifyEmail) = db_fetch_array($emRs);
2281  db_free_result($emRs);
2282 
2283  // ** Check the Email exists and directory has sufficient rights
2284  if ( trim($notifyEmail) == '' || !(is_writable($sslFormsDir)) ) {
2285  $errors[] = $MC->msg('Contact CU');
2286  }
2287 
2288  if ( count( $errors ) > 0 ) {
2289  $txfr['status']['code'] = '999';
2290  $txfr['status']['severity'] = 'ERROR';
2291  $txfr['status']['errors'] = $errors;
2292  } else {
2293 
2294  $dataConfirm= $pHBEnv["confirmationCode"];
2295 
2296  $tranCode = $tranData["transactioncode"];
2297  $trMemo = $tranData["memo"];
2298 
2299  // set up the secure message
2300  $amount = number_format( $tranData["amount"], 2 );
2301  $secureFormDataAry = array();
2302  $secureFormDataAry[] = Array( 'type' => 'field',
2303  'label' => "Submission Date",
2304  'value' => "Received on " . date( "m/d/Y") . " at " . date( "H:i T" ) );
2305  $secureFormDataAry[] = Array( 'type' => 'field',
2306  'label' => "User",
2307  'value' => $pHBEnv["Cn"]);
2308  $secureFormDataAry[] = Array( 'type' => 'field',
2309  'label' => "Transfer From Account",
2310  'value' => $sourceParts[1]);
2311  $secureFormDataAry[] = Array( 'type' => 'field',
2312  'label' => "Amount",
2313  'value' => "\$" . money_format('%.2n', $amount) );
2314  $secureFormDataAry[] = Array( 'type' => 'field',
2315  'label' => "Comments",
2316  'value' => $trMemo );
2317  $secureFormDataAry[] = Array( 'type' => 'field',
2318  'label' => "Confirmation Code",
2319  'value' => $dataConfirm );
2320  $secureFormDataAry[] = Array( 'type' => 'field',
2321  'label' => "",
2322  'value' => $pHBEnv["MC"]->msg( "CCPayNote") );
2323 
2324  // set the destination path/file
2325  $securePathFileName = $sslFormsDir . $sslFormsFileName;
2326 
2327  // set up the email message
2328  $emailInfo["target"] = $notifyEmail;
2329  $emailInfo["reply"] = "";
2330  $emailInfo["subject"] = "SECURE FORM NOTIFICATION (CCPmt)";
2331  $body = "A secure document has been submitted to your site. You\n";
2332  $body .= "can retrieve it in the password protected admin directory.\n";
2333  $body .= "\n\n{$pHBEnv["Cu"]}\n";
2334  $emailInfo["body"] = $body;
2335 
2336  $result = PostSecureMessage( $secureFormDataAry, $securePathFileName, $secureFormTitle, $emailInfo, $pHBEnv );
2337 
2338  // ** Set the values for display
2339  $dataFrom = $sourceParts[1];
2340  // ** do not change status code to '000' to be more consistent with other functions,
2341  // * I believe the apps are looking for the string '0', changes to this could break
2342  // * the apps
2343  $txfr['status']['code'] = '0';
2344  $txfr['status']['severity'] = 'INFO';
2345  $txfr['txn']['confirmid'] = $dataConfirm;
2346  $txfr['txn']['acct'] = $fromMbr;
2347  $txfr['txn']['tcode'] = $tranCode;
2348  $txfr['txn']['r1fsfx'] = $dataFrom;
2349  $txfr['txn']['r2tsfx'] = '';
2350  $txfr['txn']['r3email'] = '';
2351  $txfr['txn']['r4misc1'] = '';
2352  $txfr['txn']['r5tmem'] = $fromMbr;
2353  $txfr['txn']['amount'] = $amount;
2354  $txfr['txn']['trmemo'] = $trMemo;
2355  }
2356  } catch( Exception $ex ) {
2357  $pHBEnv["SYSENV"]["logger"]->debug( "PostSecureMessageTransfer:" . $ex->getMessage() );
2358  $errors[] = $ex->getMessage();
2359 
2360  $txfr['status']['code'] = '999';
2361  $txfr['status']['severity'] = 'ERROR';
2362  $txfr['status']['errors'] = $errors;
2363 
2364  }
2365 
2366  return ($txfr);
2367 } // end PostSecureMessageTransfer
2368 
2369 /**
2370  * Submit the internal transfer and then mark this transaction as processed. This can be done at the same
2371  * time as the original submission or later when it is accepted.
2372  *
2373  *
2374  * @param integer $pDbh -- Current database handle
2375  * @param array $pHBEnv -- Current HB_ENV values
2376  * ["SYSENV"]["logger"]
2377  * ["Cu"]
2378  * ["tz"]
2379  * ["live"]
2380  * ["Ml"]
2381  * @param object $pMC -- Dictionary object
2382  * @param array $pWhich -- Which entry is being processed
2383  * @param boolean $fromUserActivity -- If from user activity, it is slightly different processing.
2384  *
2385  * @return False if error occurs, structure with info if successful
2386  */
2387 function ProcessTransfer( $pDbh, $pHBEnv, $pMC, $pWhich, &$pResults, $fromUserActivity = false) {
2388  try {
2389  $errors = array();
2390 
2391  $Cu = $pHBEnv["Cu"];
2392 
2393  // there should just be one header and one detail
2394  $sql = "with uaua as (select user_id, accountnumber, accounttype, display_name, certnumber,
2395  trim(recordtype) || '|' || trim(accountnumber) || '|' || trim(accounttype) || '|' || certnumber as key from ${Cu}useraccounts),
2396  albalb as (select description, 'D|' || trim(accountnumber) || '|' || trim(accounttype) || '|' || certnumber as key, misc1 from ${Cu}accountbalance
2397  union all select description, 'L|' || trim(accountnumber) || '|' || trim(loannumber) || '|0' as key, misc1 from ${Cu}loanbalance)
2398  select txn.*, uafrom.display_name as from_displayname, albto.misc1 as tomisc1, albfrom.misc1 as frommisc1,
2399  albfrom.description as from_desc, uafrom.accountnumber as from_acct, uafrom.accounttype as from_accounttype, uafrom.certnumber as from_certnumber,
2400  uato.display_name as to_displayname, albto.description as to_desc, uato.accountnumber as to_acct, uato.accounttype as to_accounttype, uato.certnumber as to_certnumber from
2401  (select h.*, d.amount, d.transdata, d.transdata::json->>'acct_source' as fromkey, d.transdata::json->>'acct_dest' as tokey
2402  from ${Cu}transhdr h
2403  inner join ${Cu}transdtl d on d.transhdr_id = h.id) txn
2404  left join uaua uafrom
2405  on txn.fromkey = uafrom.key or txn.fromkey || '|0' = uafrom.key and uafrom.user_id = txn.posted_by
2406  left join albalb albfrom
2407  on txn.fromkey = albfrom.key or txn.fromkey || '|0' = albfrom.key
2408  left join uaua uato
2409  on txn.tokey = uato.key or txn.tokey || '|0' = uato.key
2410  left join albalb albto
2411  on txn.tokey = albto.key or txn.tokey || '|0' = albto.key
2412  where txn.id = $pWhich";
2413  $rs = db_query( $sql, $pDbh );
2414 
2415  if ( !$rs ) {
2416  throw new Exception( $pMC->msg("Transfer Error", HCU_DISPLAY_AS_HTML) );
2417  }
2418 
2419  $transferRecord = db_fetch_array( $rs, 0 );
2420 
2421  // if the feature is an external transfer then exit
2422  // NOTE: this is a safety check in case the calling function doesn't know what is going on
2423  if (in_array($transferRecord["feature_code"], array(FEATURE_EXTERNAL_TRANSFERS, FEATURE_ACH_PAYMENTS, FEATURE_ACH_COLLECTIONS))) {
2424  $errors[] = $pMC->msg("External Transfer", HCU_DISPLAY_AS_HTML);
2425  $pResults = array( "status" => array( "code" => "999",
2426  "severity" => "ERROR",
2427  "errors" => $errors ) );
2428 
2429  return false;
2430  }
2431 
2432  // this should have acct_source, acct_dest, and misc1 (if present)
2433  $transData = HCU_JsonDecode( $transferRecord['transdata'] );
2434 
2435  // see if it is a credit card payment request (destination account has a Px)
2436  $destParts = explode( "|", $transData["acct_dest"] );
2437  $sourceParts = explode( "|", $transData["acct_source"] );
2438 
2439  if (empty($destParts) || empty($sourceParts)) {
2440  $errors[] = $pMC->msg( 'Missing required values' );
2441  }
2442 
2443  if (count($errors) > 0) {
2444  $txfr['status']['code']='999';
2445  $txfr['status']['severity']='ERROR';
2446  $txfr['status']['errors']=$errors;
2447  } else {
2448  // populate all the transfer data
2449  $transData["transactioncode"] = $transferRecord["transactioncode"];
2450  $transData["memo"] = $transferRecord["memo"];
2451  $transData["amount"] = $transferRecord["amount"];
2452  // the authority account
2453  $transData["accountnumber"] = $transferRecord["accountnumber"];
2454  $transData['posted_by'] = $transferRecord['posted_by'];
2455 
2456  /*
2457  * Modified -
2458  * Part two of the transferTo is the account number, but for
2459  * CUDP, they have a PLUS account, that starts with P, orginally this
2460  * code ONLY checked the first letter of the account number for 'P'
2461  * ADDED a check for the first segment to ensure it is a type 'O'ther, which
2462  * should remove conflict with types from the core
2463  */
2464 
2465  if ( $destParts[0] == 'O' && substr( $destParts[2], 0, 1 ) == "P" ) {
2466  $aryTransferRequestResult = PostSecureMessageTransfer( $pHBEnv, $transData );
2467  } else {
2468  $aryTransferRequestResult = PostInternalTransfer( $pHBEnv, $transData );
2469  }
2470 
2471  if ( $aryTransferRequestResult["status"]["code"] != "999" ) {
2472 
2473  // set up the query to mark the transaction as processed ("*immed*" means processed by Banking user)
2474  $sql = "UPDATE {$pHBEnv["Cu"]}transhdr SET
2475  processed_by = '*immed*',
2476  processed_date = now(),
2477  processed_status = 20
2478  WHERE id = $pWhich
2479  RETURNING processed_date, posted_by";
2480 
2481  $rs = db_query( $sql, $pDbh );
2482 
2483  list( $processedDate, $postedBy ) = db_fetch_array( $rs, 0 );
2484 
2485  // build a confirmation code (so don't need to read the database)
2486  $confCodeBuilder["id"] = $pWhich;
2487  $confCodeBuilder["processed_by"] = "*immed*";
2488  $confCodeBuilder["posted_by"] = $postedBy; // immediate transfers need the posted by id
2489  $confCodeBuilder["processed_date"] = $processedDate;
2490  $confirmationCode = GetTransferConfirmCode( $pHBEnv, "process", $confCodeBuilder );
2491 
2492  $confirmationCode = $confirmationCode;
2493 
2494  $transDetail = GetTransferDetails( $pHBEnv, $transferRecord );
2495  /*
2496  $pHBEnv["SYSENV"]["logger"]->info( "transDetail: " . print_r($transDetail,true) );
2497  * returns
2498  * (
2499  * [action] => Account Transfer
2500  * [from] => DRAFT/CHECKING
2501  * [to] => Savings x7654
2502  * )
2503 
2504  */
2505 
2506  // ** get the values for displaying the accounts, date, amount, and confirmation id
2507  $myDateTime = new DateTime( $processedDate );
2508 
2509  // convert to local time for the display
2510  $myDateTime->setTimezone(new DateTimeZone($pHBEnv["tz"]));
2511  $readableDate = $myDateTime->format("m/d/Y g:ia");
2512 
2513  $dataAmount = mobile_formatnumber($aryTransferRequestResult['txn']['amount'], ",");
2514  $pResults = array( "status" => array( "code" => "000" ),
2515  "txn" => array( "data_action" => $transDetail["action"],
2516  "data_from" => $transDetail["from"],
2517  "data_to" => $transDetail["to"],
2518  "data_amount" => $dataAmount,
2519  "data_date" => $readableDate,
2520  "data_confirm" => $confirmationCode ) );
2521 
2522  $success = true;
2523  } else {
2524  // just pass on the status
2525  $pResults = array( "status" => $aryTransferRequestResult["status"] );
2526 
2527  $success = false;
2528  }
2529  }
2530  } catch (Exception $ex) {
2531  $pHBEnv["SYSENV"]["logger"]->debug( "Process Transfer:" . $ex->getMessage() );
2532  $errors[] = $ex->getMessage();
2533  $pResults = array( "status" => array( "code" => "999",
2534  "severity" => "ERROR",
2535  "errors" => $errors ) );
2536  $success = false;
2537  }
2538 
2539 
2540 
2541  return $success;
2542 } // end ProcessTransfer
2543 
2544 /**
2545  * Get the transfer action name and the From and To information. This information is used in
2546  * more than one area so do it in a routine.
2547  *
2548  * @param array $pHBEnv -- Current Environment values (see what is used to know what is needed)
2549  * @param array $pTranRecord -- Data for entry being processed (just enough fields to be successful)
2550  *
2551  * @return structure with the action, from, and to information.
2552  */
2553 function GetTransferDetails( $pHBEnv, $pTranRecord ) {
2554 
2555  try {
2556  $txnCode = $pTranRecord["transactioncode"];
2557  $dbh = $pHBEnv["dbh"];
2558  $Cu = $pHBEnv["Cu"];
2559  $fset3 = $pHBEnv["Fset3"];
2560  $Uid = $pHBEnv['Uid'];
2561  $MC = $pHBEnv['MC'];
2562 
2563  // both paths need this
2564  $transData = HCU_JsonDecode( $pTranRecord["transdata"] );
2565  // handle external transfers
2566  if ( $txnCode == "1W" || $txnCode == "2W" ) {
2567  $dataAction = $pHBEnv["MC"]->msg( "External Transfer", HCU_DISPLAY_AS_RAW );
2568 
2569  if ( substr( $txnCode, 0, 1 ) == "1" ) {
2570 
2571  // ** Get the Local Description
2572  // -- Originating Account will be DEPOSIT ONLY
2573 
2574  $srcAcctId = HCU_AcctIdExplode(HCU_array_key_value('acct_source', $transData));
2575  $srcAcctKey = $transData['source_key'];
2576 
2577  if ($srcAcctId['valid']) {
2578  $localFromArray = GetMemberDescription($dbh, $Cu, $Uid, $srcAcctKey);
2579  //$localFromArray = GetMemberDescription($dbh, $Cu, $Uid, $srcAcctId['type'], $srcAcctId['acctnumber'], $srcAcctId['segment3'], $srcAcctId['segment4']);
2580  $localFromDesc = $localFromArray['description'];
2581  $localFromDisp = $localFromArray['display_name'];
2582  } else {
2583  // * Something went wrong --
2584  throw new Exception ("Unknown Acct ID");
2585  }
2586 
2587  // payment from local to external
2588  if ($srcAcctKey['recordtype'] == "D") {
2589  $dataFrom = getAccountDescription($dbh, $Cu, $srcAcctKey['accountnumber'], $localFromDesc, $srcAcctKey['accounttype'], $localFromDisp, $fset3, $srcAcctId['segment4']);
2590  } else {
2591  $dataFrom = getAccountDescription($dbh, $Cu, $srcAcctKey['accountnumber'], $localFromDesc, $srcAcctKey['loannumber'], $localFromDisp, $fset3, $srcAcctId['segment4']);
2592  }
2593 
2594  $acctDest = $transData["acct_dest"];
2595  if (HCU_array_key_value('remote_entity', $acctDest) !='' ) {
2596  $dataTo = HCU_array_key_value('name', $acctDest['remote_entity']);
2597  } else {
2598  $dataTo = getAccountDescription($dbh, $Cu, $acctDest["rdfi"]["rdfi_account"], "", $acctDest["rdfi"]["rdfi_account_type"], "", $fset3, 0, true, true);
2599  }
2600  } else {
2601  // collection from external to local
2602  $acctSource = $transData["acct_source"];
2603 
2604  if (HCU_array_key_value('remote_entity', $acctSource) !='' ) {
2605  $dataFrom = HCU_array_key_value('name', $acctSource['remote_entity']);
2606  } else {
2607  $dataFrom = getAccountDescription($dbh, $Cu, $acctSource["rdfi"]["rdfi_account"], "", $acctSource["rdfi"]["rdfi_account"], "", $fset3, 0, true, true);
2608  }
2609 
2610 
2611  // ** Destination can either be a loan or deposit
2612  // ** Break apart the 'acct_dest' field in transdata
2613  $dstAcctId = HCU_AcctIdExplode(HCU_array_key_value('acct_dest', $transData));
2614  $dstAcctKey = $transData['dest_key'];
2615  if ($dstAcctId['valid']) {
2616  $localToArray = GetMemberDescription($dbh, $Cu, $Uid, $dstAcctKey);
2617  $localToDesc = $localToArray['description'];
2618  $localToDisp = $localToArray['display_name'];
2619  } else {
2620  // * Something went wrong --
2621  throw new Exception ("Unknown Acct ID");
2622  }
2623 
2624  if ($dstAcctKey['recordtype'] == "D") {
2625  $dataTo = getAccountDescription($dbh, $Cu, $dstAcctKey['accountnumber'], $localToDesc, $dstAcctKey['accounttype'], $localToDisp, $fset3, $dstAcctId['segment4']);
2626  } else {
2627  $dataTo = getAccountDescription($dbh, $Cu, $dstAcctKey['accountnumber'], $localToDesc, $dstAcctKey['loannumber'], $localToDisp, $fset3, $dstAcctId['segment4']);
2628  }
2629  }
2630  } else if ( $txnCode == "MM" ) {
2631  $dataAction = $pHBEnv["MC"]->msg( "M2M Transfer", HCU_DISPLAY_AS_RAW );
2632 
2633  $srcAcctId = HCU_AcctIdExplode(HCU_array_key_value('acct_source', $transData));
2634  $srcAcctKey = $transData['source_key'];
2635 
2636  if ($srcAcctId['valid']) {
2637  $localFromArray = GetMemberDescription($dbh, $Cu, $Uid, $srcAcctKey);
2638  $localFromDesc = $localFromArray['description'];
2639  $localFromDisp = $localFromArray['display_name'];
2640  } else {
2641  // * Something went wrong --
2642  throw new Exception ("Unknown Acct ID");
2643  }
2644 
2645  if ($srcAcctKey['recordtype'] == "D") {
2646  $dataFrom = getAccountDescription($dbh, $Cu, $srcAcctKey['accountnumber'], $localFromDesc, $srcAcctKey['accounttype'], $localFromDisp, $fset3, $srcAcctId['segment4']);
2647  } else {
2648  $dataFrom = getAccountDescription($dbh, $Cu, $srcAcctKey['accountnumber'], $localFromDesc, $srcAcctKey['loannumber'], $localFromDisp, $fset3, $srcAcctId['segment4']);
2649  }
2650 
2651  $dstAcctId = HCU_AcctIdExplode(HCU_array_key_value('acct_dest', $transData));
2652  // ** 11/2/17 - M2M not completed yet..
2653  // The destination member -- we will know {account number, last name, and type [checking/savings]}
2654  // ** Last name should be saved in the 'misc' element of transData
2655  $localToDesc = HCU_array_key_value('misc', $transData); // Last Name
2656  // ** Instead of print 10 or 20 for the selection, I want to print {checking / savings}
2657  $localToType = ($dstAcctId['segment3'] == '10' ? $MC->msg('ACH Checking') : $MC->msg('ACH Savings'));
2658  $dataTo = getAccountDescription($dbh, $Cu, $dstAcctId['acctnumber'], $localToDesc, $localToType, '', $fset3, 0, true, true);
2659 
2660  } else {
2661  // internal transfer
2662  $transferSource = $transData["acct_source"];
2663  $sourceParts = explode( "|", $transferSource );
2664  $srcAcctId = HCU_AcctIdExplode(HCU_array_key_value('acct_source', $transData));
2665  $srcAcctKey = $transData['source_key'];
2666 
2667  $transferDest = $transData["acct_dest"];
2668  $destParts = explode( "|", $transferDest );
2669  $dstAcctId = HCU_AcctIdExplode(HCU_array_key_value('acct_dest', $transData));
2670  $dstAcctKey = $transData['dest_key'];
2671 
2672  /*
2673  * Don't show the user a message referencing "Cross Account Transfer"
2674  * Instead just show 'Account Transfer' / 'Loan Payment'
2675  */
2676  if ($txnCode == "XA") {
2677  $lookupTxCode = "AT";
2678  } elseif ($txnCode == "XP") {
2679  $lookupTxCode = "LP";
2680  } else {
2681  $lookupTxCode = $txnCode;
2682  }
2683 
2684  // get the data action
2685  $sql = "SELECT trandesc FROM cutrans WHERE trancode = '$lookupTxCode'";
2686  $sth = db_query($sql, $pHBEnv["dbh"]);
2687  list( $tranDesc ) = db_fetch_array( $sth, 0 );
2688 
2689  $dataAction = $tranDesc;
2690 
2691  # FIX FOR PROD - Error checking, anyone?
2692  # send appropriate status if post fails
2693  // get the "from" and "to" account info from the transData
2694 
2695  $thirdSpotTestCode = $dstAcctId['segment3']; // credit card payments look like "O|1103|CP"
2696 
2697  /* OTHER TRANSFER TYPES */
2698  if ($dstAcctId['type'] == 'O') {
2699  // ** LOAD OTHER TYPE FROM cu_havetrans
2700  $dataAction = '';
2701 
2702  $cuOtherTrans = Get_HaveTrans($dbh, $pHBEnv);
2703 
2704  // ** LOOK for the "OTHER" transaction code in the cuhavetrans table config
2705  if (HCU_array_key_exists($thirdSpotTestCode, $cuOtherTrans)) {
2706  $transdesc = HCU_array_key_value($thirdSpotTestCode, $cuOtherTrans);
2707  // ** FOUND ** cu has setup the OTHER Transaction type
2708  $cudesc = isset($transdesc[2]) ? trim($transdesc[2]) : ""; // ** Credit Union Specific Description
2709  $noncudesc = isset($transdesc[0]) ? trim($transdesc[0]) : ""; // ** General Description
2710  $dataAction = $cudesc != "" ? $cudesc : $noncudesc; // ** Determine the description to use for this transaction
2711 
2712  }
2713  if (!$dataAction) {
2714  $dataAction = $pHBEnv['MC']->msg("Unknown");
2715  }
2716  $dataTo = $dataAction;
2717  } else {
2718  // ** Get the description for the SOURCE account from the respective balance table
2719  $dstAcctId = HCU_AcctIdExplode(HCU_array_key_value('acct_dest', $transData));
2720 
2721  if ($dstAcctId['valid']) {
2722 
2723  /* Cross Account Transaction Codes */
2724  $tranCodeXLookup = Array("XA", "XP");
2725 
2726  if (in_array($txnCode, $tranCodeXLookup)) {
2727  /**
2728  * Cross Account Transaction
2729  * Construct the useraccounts lookup XX#1111 for the useraccounts table lookup
2730  */
2731  $acctTypeorLoan = ($dstAcctKey['recordtype'] == "L" || $dstAcctKey['recordtype'] == "C" ? "loannumber" : "accounttype");
2732  $localAcctKey = Array(
2733  "recordtype" => $dstAcctKey['recordtype'],
2734  "accountnumber" => $srcAcctKey['accountnumber'],
2735  $acctTypeorLoan => $dstAcctKey[$acctTypeorLoan] . '#' . $dstAcctKey['accountnumber'],
2736  "certnumber" => 0
2737  );
2738 
2739  } else {
2740  $localAcctKey = $dstAcctKey;
2741  }
2742 
2743  // ** Gets the appropriate Core Description
2744  $localToArray = GetMemberDescription($dbh, $Cu, $Uid, $localAcctKey);
2745 
2746  $localToDesc = $localToArray['description'];
2747  $localToDisp = $localToArray['display_name'];
2748 
2749  } else {
2750  // * Something went wrong --
2751  throw new Exception ("Unknown Acct ID");
2752  }
2753 
2754  if ($dstAcctKey['recordtype'] == "D") {
2755  $dataTo = getAccountDescription($dbh, $Cu, $dstAcctKey['accountnumber'], $localToDesc, $dstAcctKey['accounttype'], $localToDisp, $fset3, $dstAcctId['segment4']);
2756  } else {
2757  $dataTo = getAccountDescription($dbh, $Cu, $dstAcctKey['accountnumber'], $localToDesc, $dstAcctKey['loannumber'], $localToDisp, $fset3, $dstAcctId['segment4']);
2758  }
2759  }
2760 
2761  // ** Get the description for the SOURCE account from the respective balance table
2762  if ($srcAcctId['valid']) {
2763  $localFromArray = GetMemberDescription($dbh, $Cu, $Uid, $srcAcctKey, $srcAcctId);
2764  $localFromDesc = $localFromArray['description'];
2765  $localFromDisp = $localFromArray['display_name'];
2766  } else {
2767  // * Something went wrong -- return empty string
2768  throw new Exception ("Unknown Acct ID");
2769  }
2770 
2771  // should always be an internal account in the Source
2772  if ($srcAcctKey['recordtype'] == "D") {
2773  $dataFrom = getAccountDescription($dbh, $Cu, $srcAcctKey['accountnumber'], $localFromDesc, $srcAcctKey['accounttype'], $localFromDisp, $fset3, $srcAcctId['segment4']);
2774  } else {
2775  $dataFrom = getAccountDescription($dbh, $Cu, $srcAcctKey['accountnumber'], $localFromDesc, $srcAcctKey['loannumber'], $localFromDisp, $fset3, $srcAcctId['segment4']);
2776  }
2777  }
2778 
2779  $transferDetail = array(
2780  "action" => $dataAction,
2781  "from" => $dataFrom,
2782  "to" => $dataTo
2783  );
2784  } catch (Exception $ex) {
2785 
2786  // return something so the caller will work
2787  $transferDetail = array(
2788  "action" => "Transfer",
2789  "from" => "Unknown",
2790  "to" => "Unknown"
2791  );
2792  }
2793 
2794  return $transferDetail;
2795 } // end GetTransferDetails
2796 
2797 
2798 /**
2799  * This function will accept the posted values of the web form and will set defaults
2800  * where necessary.
2801  * It will NOT perform any validation.
2802  * These values can then be used in the function
2803  * ValidateTransaction
2804  * RecortTransaction
2805  *
2806  * @param array $pValues - Posted form values. Most likely the results of HB_ENV['HCUPOST']
2807  *
2808  * @return array
2809  */
2810 function SanitizeTransaction ($pValues) {
2811 
2812 
2813  $retDataRow = array();
2814 
2815  $txFrom = null;
2816  $txTo = null;
2817 
2818  $txFromMember = isset($pValues['txFromMember']) ? $pValues['txFromMember'] : null;
2819  $txFromSuffix = isset($pValues['txFromSuffix']) ? $pValues['txFromSuffix'] : null;
2820  $txFromType = "";
2821  $txToMember = isset($pValues['txToMember']) ? $pValues['txToMember'] : null;
2822  $txToSuffix = isset($pValues['txToSuffix']) ? $pValues['txToSuffix'] : null;
2823  $txToType = "";
2824  $txCode = isset($pValues['txCode']) ? $pValues['txCode'] : null;
2825  $txAmount = isset($pValues['txAmount']) ? $pValues['txAmount'] : null;
2826  $txMemo = isset($pValues['txMemo']) ? $pValues['txMemo'] : null;
2827  $txFrequency = isset($pValues['txFrequency']) ? $pValues['txFrequency'] : null;
2828  $txContinue = isset($pValues['txContinue']) ? $pValues['txContinue'] : null;
2829  $txDateStart = isset($pValues['txDateStart']) ? $pValues['txDateStart'] : null;
2830  $txDateEnd = isset($pValues['txDateEnd']) ? $pValues['txDateEnd'] : null;
2831  $txStatus = isset($pValues['txStatus']) ? $pValues['txStatus'] : null;
2832 
2833 
2834 
2835 
2836 }
2837 
2838 /**
2839  * function GetTransferToOptions($HB_ENV, $sourceParts, $txFromPermMember, $fromScheduledPage)
2840  * Get filtered list for the to dropdownlist on transfer and scheduled transactions.
2841  *
2842  * @param $HB_ENV -- the variables for banking.
2843  * @param $sourceParts -- the key split up in the from.
2844  * @param $txFromPermMember -- the account that is the permission member.
2845  * @param $fromScheduledPage -- if it is from the hcuTransferScheduled.prg page or not.
2846  *
2847  * @return $status -- "000" if successful, nonzero otherwise.
2848  * @return $error -- "" if successful, nonempty otherwise.
2849  * @return $transferToList -- filtered list for use in the to dropdownlists.
2850  */
2851 function GetTransferToOptions($HB_ENV, $sourceParts, $txFromPermMember, $fromScheduledPage) {
2852  try {
2853 
2854  $transferList = TX_list($HB_ENV["dbh"], $HB_ENV);
2855 
2856  $txFromType = HCU_array_key_value(0, $sourceParts);
2857  $txFromMember = HCU_array_key_value(1, $sourceParts);
2858  // In the case of joint or cross accounts, this is different from $txFromPermMember. In other cases, they are the same.
2859  $txFromSuffix = HCU_array_key_value(2, $sourceParts);
2860 
2861  $toArray = array();
2862  if (!HCU_array_key_exists("acctlist", $transferList)) {
2863  throw new exception ("Transfer list is not valid.", 1);
2864  }
2865 
2866  // ** get member to member/external account permissions
2867  $permissionInputs = array( "feature" => FEATURE_M2M_TRANSFERS );
2868  $permissionM2M = Perm_AccessRights($HB_ENV["dbh"], $HB_ENV, $permissionInputs );
2869 
2870  // get system status: live/batch
2871  $isLive = $HB_ENV['live'] == 0 ? false : true;
2872 
2873 
2874  // Need to check permissions from here if it is an external account.
2875  $fromAcctValues = $transferList["acctlist"][implode( "|", $sourceParts )];
2876 
2877  foreach ($transferList["acctlist"] as $acctKey => $acctValues) {
2878 
2879  $acctOrder = 0;
2880  $txToType = HCU_array_key_value("acctclass", $acctValues);
2881  $txToPermMember = HCU_array_key_value("member", $acctValues);
2882  $txToSuffix = HCU_array_key_value("suffix", $acctValues);
2883  $txToSuffix = explode("@", $txToSuffix)[0];
2884  $destParts = explode("|", $acctKey);
2885  $txToMember = HCU_array_key_value(1, $destParts);
2886  $acctInfo = Array();
2887 
2888  // Prevent "Other" types for scheduled transactions.
2889  if ($fromScheduledPage && $txToType == "O") {
2890  continue;
2891  }
2892 
2893  // Loan to External account check (Applied from #2464)
2894  if ($txFromType == "L" && $txToType == "X") {
2895  continue;
2896  }
2897 
2898  if ($acctValues["out_of_sync"] || !$acctValues["to"]) {
2899  continue; // Neither of these attributes show up in the to list.
2900  }
2901 
2902  // Do not allow the same account
2903  if ($txToMember == $txFromMember
2904  && $txToPermMember == $txFromPermMember
2905  && $txToSuffix == $txFromSuffix
2906  && $txToType == $txFromType) {
2907  continue;
2908  }
2909 
2910  // if member to member account and not live system or no access, do not add
2911  if ($txToType == "M" && !($isLive && $permissionM2M['access'])) {
2912  continue;
2913  }
2914 
2915  $relevantTypes = array("D", "L");
2916  // Do not allow transfer between accounts iff setting is set.
2917  if (($HB_ENV["flagset3"] & GetFlagsetValue("CU3_DISALLOW_MULT_ACCOUNTS_TRANSFER")) != 0) {
2918 
2919  if ($txToPermMember != $txFromPermMember && in_array($txFromType, $relevantTypes) && in_array($txToType, $relevantTypes)) {
2920 
2921  // TEMPORARY CHECK CROSS ACCOUNT LIST IN THE CASE THAT ACCOUNT IS IN CROSS ACCOUNTS AND HAS DIRECT ACCESS
2922  // SHOULD BE ABLE TO BE REMOVED AFTER #2062.
2923  if (HCU_array_key_exists($txFromPermMember, $transferList["xacctlist"])
2924  && HCU_array_key_exists($acctKey, $transferList["xacctlist"][$txFromPermMember])) {
2925  $acctValues = $transferList["xacctlist"][$txFromPermMember][$acctKey];
2926  $acctOrder = 0;
2927  $txToType = HCU_array_key_value("acctclass", $acctValues);
2928  $txToPermMember = HCU_array_key_value("member", $acctValues);
2929  $txToSuffix = HCU_array_key_value("suffix", $acctValues);
2930  $txToSuffix = explode("@", $txToSuffix)[0];
2931  $destParts = explode("|", $acctKey);
2932  $txToMember = HCU_array_key_value(1, $destParts);
2933  $acctInfo = Array();
2934 
2935  if ($txToPermMember != $txFromPermMember && in_array($txFromType, $relevantTypes) && in_array($txToType, $relevantTypes)) {
2936  continue;
2937  }
2938  } else {
2939  continue;
2940  }
2941 
2942  }
2943  }
2944 
2945  // ** are scheduled transfers allowed?
2946  $txScheduledAllowed = ($HB_ENV['flagset2'] & GetFlagsetValue("CU2_PROCRECUR")) != 0;
2947 
2948  if ($fromScheduledPage && !$txScheduledAllowed) {
2949  continue;
2950  }
2951 
2952 
2953  if ($txFromType == "X") {
2954  // Transfer Error external (PART1)
2955  if (!in_array($txToType, $relevantTypes)) {
2956  continue;
2957  }
2958 
2959  // Prevent EXT transfer out of account without permissions
2960  if ($acctValues["to_ext"] !== "Y") {
2961  continue;
2962  }
2963  } else {
2964  if (HCU_array_key_value("from_int", $fromAcctValues) !== "Y") {
2965  continue;
2966  }
2967  }
2968 
2969  if ($txToType == "X") {
2970  // Transfer Error external (PART2)
2971  if (!in_array($txFromType, $relevantTypes)) {
2972  continue;
2973  }
2974 
2975  if (HCU_array_key_value("from_ext", $fromAcctValues) !== "Y") {
2976  continue;
2977  }
2978 
2979  } else {
2980  if (HCU_array_key_value("to_int", $acctValues) !== "Y") {
2981  continue;
2982  }
2983  }
2984 
2985  // "The only valid source for M2M should be a Share account. (Savings/Checking)"
2986  if ($txFromType !== "D" && $txToType == "M") {
2987  continue;
2988  }
2989 
2990  // Loan Add-on cannot payment
2991  if ($txFromType == "L" && $txToType == "L") {
2992  continue;
2993  }
2994 
2995  // check if feature is regular, external, M2M or ACH transfer
2996  if ( $txFromType === "X" || $txToType === "X" ) {
2997  $transferFeatureCode = FEATURE_EXTERNAL_TRANSFERS;
2998  } else if ( $txToType === "M" ) {
2999  $transferFeatureCode = FEATURE_M2M_TRANSFERS;
3000  } else if ( $txFromType === "AC" ) {
3001  $transferFeatureCode = FEATURE_ACH_COLLECTIONS;
3002  } else if ( $txToType === "AP" ) {
3003  $transferFeatureCode = FEATURE_ACH_PAYMENTS;
3004  } else {
3005  $transferFeatureCode = FEATURE_TRANSFERS;
3006  }
3007 
3008  // ** Verify the feature is enabled for the Credit Union.
3009  $hasTransferNotify = _HasTransferNotify( $HB_ENV["dbh"], $HB_ENV["Cu"] );
3010 
3011  if ( !$hasTransferNotify && ($fromScheduledPage || ($HB_ENV["flagset"] & GetFlagsetValue("CU_MAILTXNS")))) {
3012  continue;
3013  }
3014 
3015  // Loan Add-on cannot check withdrawal
3016  $cuTransTypesAllowed = Get_HaveTrans($HB_ENV["dbh"], $HB_ENV);
3017  if ($txFromType == "L" && $acctValues["suffix"] == "CW" && !HCU_array_key_exists( 'LC', $cuTransTypesAllowed)) {
3018  continue;
3019  }
3020 
3021  /**
3022  * When 'trust' is transfer, this is a Cross-Account, there is no information to specify, the values would be empty
3023  */
3024  if ($acctValues['trust'] != 'transfer' && $acctValues['view_balances'] == 'Y') {
3025  switch ($txToType) {
3026  case "D":
3027  $acctInfo = Array(
3028  Array("desc" => $HB_ENV["MC"]->msg('Balance', HCU_DISPLAY_AS_RAW), "value" => $acctValues['balance'])
3029  );
3030  break;
3031 
3032  case "L":
3033  $acctInfo = Array(
3034  Array("desc" => $HB_ENV["MC"]->msg('Payoff', HCU_DISPLAY_AS_RAW), "value" => $acctValues['payoff']),
3035  Array("desc" => $HB_ENV["MC"]->msg('Balance', HCU_DISPLAY_AS_RAW), "value" => $acctValues['balance']),
3036  Array("desc" => $HB_ENV["MC"]->msg('Payment', HCU_DISPLAY_AS_RAW), "value" => $acctValues['paymentdue'])
3037  );
3038  break;
3039 
3040  case "C":
3041  // 10-19 make the transfer display follow the same rules as the balances screen in respect to CC's.
3042  $Fset2 = ($HB_ENV['Fset2']) ?? null;
3043  if (($Fset2 & GetFlagsetValue('CU2_CC18NOINFO')) != GetFlagsetValue('CU2_CC18NOINFO')) {
3044  $acctInfo = Array(
3045  Array("desc" => $HB_ENV["MC"]->msg('Payoff', HCU_DISPLAY_AS_RAW), "value" => $acctValues['payoff']),
3046  Array("desc" => $HB_ENV["MC"]->msg('Balance', HCU_DISPLAY_AS_RAW), "value" => $acctValues['balance']),
3047  Array("desc" => $HB_ENV["MC"]->msg('Payment', HCU_DISPLAY_AS_RAW), "value" => $acctValues['paymentdue'])
3048  );
3049  }
3050 
3051  break;
3052  }
3053  }
3054  $toArray[] = Array(
3055  "acctText" => htmlspecialchars_decode($acctValues['description'], ENT_QUOTES),
3056  "acctValue" => mobile_displayhtml($acctKey),
3057  "acctInfo" => $acctInfo,
3058  'acctClass' => $txToType,
3059  'acctGroup' => $acctValues['item-group'],
3060  'acctOrder' => $acctOrder,
3061  "permissionAcct" => $acctValues["member"] // For cross and joint accounts, I need the account that it is under for determining if the transfer is allowed.
3062  );
3063 
3064  } // End foreach
3065 
3066 
3067  // do not allow ad-hoc if system is not live or no permissions or it does not come from a Share account. (Savings/Checking)
3068  if (!$fromScheduledPage && $isLive && $permissionM2M['access'] && $txFromType == "D") {
3069  $toArray[] = array(
3070  "acctText" => $HB_ENV["MC"]->msg("Transfer to another member", HCU_DISPLAY_AS_RAW),
3071  "acctValue" => "M|" . $HB_ENV['Uid'] . "|0",
3072  "acctInfo" => array(),
3073  "acctClass" => "M",
3074  "acctGroup" => "6 - " . $HB_ENV["MC"]->msg("Other Member Accounts", HCU_DISPLAY_AS_RAW),
3075  "acctOrder" => $acctOrder
3076  );
3077  }
3078 
3079  $returnArray = array("status" => "000", "error" => "", "transferToList" => $toArray);
3080  } catch (exception $e) {
3081  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "transferToList" => array());
3082  }
3083  return $returnArray;
3084 }