Odyssey
hcuExternalAccts.i
1 <?php
2 /*
3  * File: hcuExternalAccts.i
4  *
5  * Purpose: Contains helper functions that are used with both External Accounts and M2M Accounts features. Can be called
6  * from the .prg or the .data files.
7  *
8  * Standard format: Each function should return a FALSE if there is an error or non-successful
9  * operation or a structure with the desired return information.
10  * Alternative format: Return a structure with a code = "000" for success, and a list of
11  * errors in an array.
12  *
13  * The "type" field is "EXT" or "M2M" for external accounts and member-to-member accounts, respectively.
14  *
15  * Status values for <cu>extaccount.status (character):
16  * 0 - pending (waiting for user approval)
17  * p - pending (waiting for micro-deposit confirmation)
18  * a - active (available for use)
19  * l - (lower case L) locked due to validation retry limit
20  * i - inactive (still considered confirmed but not available for use)
21  * (remove account with a delete action)
22  * NOTE: initially, there is not going to be a "pending approval" step.
23  *
24  */
25 
26 // this should be configurable
27 define( "M2M_SIGNIFICANT_LEN", 5 );
28 
29 /**
30  * Process an External Accounts action. This handles both External Accounts and M2M Accounts
31  * because they share the update functionaltiy.
32  *
33  * @param array $pHBEnv -- Current environment
34  * @param array $pInputVars -- Input variables specific to the call.
35  *
36  * @return array -- An array with success code or error information
37  */
38 //
39 function ManageExternalAccount( $pHBEnv, $pInputVars ) {
40  $retStatusAry = Array(
41  'status' => Array('code'=>'000', 'errors' => Array()),
42  'data' => '',
43  'info' => array() // informational messages
44  );
45 
46  try {
47  $aryErrors = array();
48 
49  $MC = $pHBEnv["MC"];
50  $dbh = $pHBEnv["dbh"];
51 
52  // do the requested operation
53  $action = $pInputVars["action"];
54  switch ( $action ) {
55  case "add_account":
56  // validate the inputs
57  $aryValidate = EXT_ValidateInputs( $pHBEnv, $dbh, $pInputVars, $MC );
58 
59  if ( !$aryValidate || ($aryValidate["code"] !== "000") ) {
60  // an error occurred
61  if ( is_array( $aryValidate["errors"] ) ) {
62  for ( $e = 0; $e < count( $aryValidate["errors"] ); $e++ ) {
63  $aryErrors[] = $aryValidate["errors"][$e];
64  }
65  } else {
66  $aryErrors[] = $aryValidate["errors"];
67  }
68 
69  throw new Exception (HCU_JsonEncode($aryErrors));
70  }
71 
72  // clean up any user-supplied data - convert any UTF-8 characters to encoded html entities
73  // NOTE: data was sanitized on input
74  $pInputVars["display_name"] = ConvertFromUTF8( $pInputVars["display_name"] );
75 
76  // add the account
77  $return = EXT_AddAccount( $dbh, $pHBEnv, $pInputVars, $MC );
78  if ( $return["code"] !== "000" ) {
79  if ( is_array( $return["errors"] ) ) {
80  for ( $e = 0; $e < count( $return["errors"] ); $e++ ) {
81  $aryErrors[] = $return["errors"][$e];
82  }
83  } else {
84  $aryErrors[] = $return["errors"];
85  }
86 
87  throw new Exception (HCU_JsonEncode($aryErrors));
88  }
89 
90  // if we got here we were successful with the initial external account creation
91  $retStatusAry["info"][] = $MC->msg('EXT Account Added', HCU_DISPLAY_AS_RAW);
92 
93  // NOTE: this is a separate entry-point in case later need to have approval of external account setup
94 
95  // start the validation
96  $pInputVars["ext_acct_id"] = $return["data"]["id"];
97  $return = EXT_StartAccountVerify( $dbh, $pHBEnv, $pInputVars, $MC );
98  if ( $return["code"] !== "000" ) {
99  if ( is_array( $return["errors"] ) ) {
100  for ( $e = 0; $e < count( $return["errors"] ); $e++ ) {
101  $aryErrors[] = $return["errors"][$e];
102  }
103  } else {
104  $aryErrors[] = $return["errors"];
105  }
106 
107  throw new Exception (HCU_JsonEncode($aryErrors));
108  }
109 
110  // if we got here we were successful with the initial external account creation
111  $retStatusAry["info"][] = $MC->msg('EXT Account verify started', HCU_DISPLAY_AS_RAW);
112 
113  // if we got here, return the updated account list
114  $return = EXT_GetAccounts( $dbh, $pHBEnv );
115  if ( $return["code"] !== "000" ) {
116  if ( is_array( $return["errors"] ) ) {
117  for ( $e = 0; $e < count( $return["errors"] ); $e++ ) {
118  $aryErrors[] = $return["errors"][$e];
119  }
120  } else {
121  $aryErrors[] = $return["errors"];
122  }
123 
124  throw new Exception (HCU_JsonEncode($aryErrors));
125  }
126 
127  // return the account lists
128  $retStatusAry["data"] = $return["data"];
129 
130  break;
131  case "add_m2m_account":
132  // validate the inputs
133  $aryValidate = EXT_ValidateM2MInputs( $pHBEnv, $dbh, $pInputVars, $MC );
134 
135  if ( !$aryValidate || ($aryValidate["code"] !== "000") ) {
136  // an error occurred
137  if ( is_array( $aryValidate["errors"] ) ) {
138  for ( $e = 0; $e < count( $aryValidate["errors"] ); $e++ ) {
139  $aryErrors[] = $aryValidate["errors"][$e];
140  }
141  } else {
142  $aryErrors[] = $aryValidate["errors"];
143  }
144 
145  throw new Exception (HCU_JsonEncode($aryErrors));
146  }
147 
148  // clean up any user-supplied data - convert any UTF-8 characters to encoded html entities
149  // NOTE: data was sanitized on input
150  $pInputVars["display_name"] = ConvertFromUTF8( $pInputVars["display_name"] );
151 
152  // add the account
153  $return = EXT_AddM2MAccount( $dbh, $pHBEnv, $pInputVars, $MC );
154  if ( $return["code"] !== "000" ) {
155  if ( is_array( $return["errors"] ) ) {
156  for ( $e = 0; $e < count( $return["errors"] ); $e++ ) {
157  $aryErrors[] = $return["errors"][$e];
158  }
159  } else {
160  $aryErrors[] = $return["errors"];
161  }
162 
163  throw new Exception (HCU_JsonEncode($aryErrors));
164  }
165 
166  // if we got here we were successful with the initial external account creation
167  $retStatusAry["info"][] = $MC->msg('Member Account Added', HCU_DISPLAY_AS_RAW);
168 
169  // NOTE: This account validates right away (i.e. no error -> validated)
170 
171  $return = EXT_GetM2MAccounts( $dbh, $pHBEnv );
172  if ( $return["code"] !== "000" ) {
173  if ( is_array( $return["errors"] ) ) {
174  for ( $e = 0; $e < count( $return["errors"] ); $e++ ) {
175  $aryErrors[] = $return["errors"][$e];
176  }
177  } else {
178  $aryErrors[] = $return["errors"];
179  }
180 
181  throw new Exception (HCU_JsonEncode($aryErrors));
182  }
183 
184  // return the account lists
185  $retStatusAry["data"] = $return["data"];
186 
187  break;
188  case "update_account":
189  // the names were expected to be sanitized before calling this
190  // clean up any user-supplied data - convert any UTF-8 characters to encoded html entities
191  $pInputVars["display_name"] = ConvertFromUTF8( $pInputVars["display_name"] );
192 
193  // update the account
194  $return = EXT_UpdateAccount( $dbh, $pHBEnv, $pInputVars, $MC );
195  if ( $return["code"] !== "000" ) {
196  if ( is_array( $return["errors"] ) ) {
197  for ( $e = 0; $e < count( $return["errors"] ); $e++ ) {
198  $aryErrors[] = $return["errors"][$e];
199  }
200  } else {
201  $aryErrors[] = $return["errors"];
202  }
203 
204  throw new Exception (HCU_JsonEncode($aryErrors));
205  }
206 
207  // if we got here we were successful with the initial external account creation
208  $retStatusAry["info"][] = $MC->msg('EXT Account Updated', HCU_DISPLAY_AS_RAW);
209 
210  // if we got here, return the updated account info
211  $retStatusAry["data"] = $return["data"];
212 
213  break;
214  case "validate_account":
215  // validate the micro deposit amounts are correct
216 
217  // update the account
218  $return = EXT_ValidateAccount( $dbh, $pHBEnv, $pInputVars, $MC );
219  if ( $return["code"] !== "000" ) {
220  if ( is_array( $return["errors"] ) ) {
221  for ( $e = 0; $e < count( $return["errors"] ); $e++ ) {
222  $aryErrors[] = $return["errors"][$e];
223  }
224  } else {
225  $aryErrors[] = $return["errors"];
226  }
227 
228  throw new Exception (HCU_JsonEncode($aryErrors));
229  }
230 
231  // this can return an error or success as part of the reply so only say validated if the status is active
232  if ( isset( $return["status"] ) && $return["status"] == "a" ) {
233  $retStatusAry["info"][] = $MC->msg('EXT Account Validated', HCU_DISPLAY_AS_RAW);
234  }
235 
236  // return the updated account info
237  $retStatusAry["data"] = $return["data"];
238 
239  break;
240  case "delete_account":
241  // delete the account
242  $return = EXT_DeleteAccount( $dbh, $pHBEnv, $pInputVars, $MC );
243  if ( $return["code"] !== "000" ) {
244  if ( is_array( $return["errors"] ) ) {
245  for ( $e = 0; $e < count( $return["errors"] ); $e++ ) {
246  $aryErrors[] = $return["errors"][$e];
247  }
248  } else {
249  $aryErrors[] = $return["errors"];
250  }
251 
252  throw new Exception (HCU_JsonEncode($aryErrors));
253  }
254 
255  // if we got here we were successful with the initial external account creation
256  $retStatusAry["info"][] = $MC->msg('EXT Account Deleted', HCU_DISPLAY_AS_RAW);
257 
258  // if we got here, return the id that was updated
259 
260  // return the account id just deleted
261  $retStatusAry["data"] = $pInputVars["id"];
262 
263  break;
264  case "get_accounts":
265  // get the external account for the current user
266  $return = EXT_GetAccounts( $dbh, $pHBEnv );
267  if ( $return["code"] !== "000" ) {
268  $aryErrors[] = $MC->msg('EXT Error getting accounts', HCU_DISPLAY_AS_RAW);
269 
270  if ( is_array( $return["errors"] ) ) {
271  for ( $e = 0; $e < count( $return["errors"] ); $e++ ) {
272  $aryErrors[] = $return["errors"][$e];
273  }
274  } else {
275  $aryErrors[] = $return["errors"];
276  }
277 
278  throw new Exception (HCU_JsonEncode($aryErrors));
279  }
280 
281  // return the requested list
282  $retStatusAry["data"] = $return["data"];
283  break;
284  case "get_m2m_accounts":
285  // get the M2M accounts for the current user
286  $return = EXT_GetM2MAccounts( $dbh, $pHBEnv );
287  if ( $return["code"] !== "000" ) {
288  $aryErrors[] = $MC->msg('EXT Error getting accounts', HCU_DISPLAY_AS_RAW);
289 
290  if ( is_array( $return["errors"] ) ) {
291  for ( $e = 0; $e < count( $return["errors"] ); $e++ ) {
292  $aryErrors[] = $return["errors"][$e];
293  }
294  } else {
295  $aryErrors[] = $return["errors"];
296  }
297 
298  throw new Exception (HCU_JsonEncode($aryErrors));
299  }
300 
301  // return the requested list
302  $retStatusAry["data"] = $return["data"];
303  break;
304  default:
305  $aryErrors[] = "Unexpected action: {$pInputVars["action"]}";
306  throw new Exception (HCU_JsonEncode($aryErrors));
307  }
308 
309  // from here, success will be returned
310  } catch( Exception $ex ) {
311  //Return error message(s)
312  $retStatusAry["status"]["errors"] = HCU_JsonDecode( $ex->getMessage() );
313  $retStatusAry["status"]["code"] = "999";
314  }
315 
316  // safety check
317  if ( $retStatusAry["status"]["errors"] != array() ) {
318  $retStatusAry["status"]["code"] = "999";
319  }
320 
321  return $retStatusAry;
322 } // end ManageExternalAccount
323 
324 // Check the menuing features to verify this feature is available to this credit union.
325 function Check_ExternalTransfersEnabled( $pDbh, $pHBEnv ) {
326  try {
327  } catch (Exception $ex) {
328  $logInfo = array( "message" => $ex->getMessage(), "code" => $ex->getCode() );
329  $pHBEnv["SYSENV"]["logger"]->error( HCU_JsonEncode( $logInfo ) );
330  }
331 
332  return true;
333 } // end Check_ExternalTransfersEnabled
334 
335 // Check the menuing features to verify this feature is available to this credit union.
336 function Check_M2MTransfersEnabled( $pDbh, $pHBEnv ) {
337  try {
338  } catch (Exception $ex) {
339  $logInfo = array( "message" => $ex->getMessage(), "code" => $ex->getCode() );
340  $pHBEnv["SYSENV"]["logger"]->error( HCU_JsonEncode( $logInfo ) );
341  }
342 
343  return true;
344 } // end Check_ExternalTransfersEnabled
345 
346 // Return the status lookup
347 function Get_ExternalTransferStatusLookup( $pMC ) {
348  return array ( "0" => $pMC->msg( "EXT Pending confirmation" ),
349  "p" => $pMC->msg( "EXT Pending verification" ),
350  "a" => $pMC->msg( "Active" ),
351  "l" => $pMC->msg( "EXT Locked" ),
352  "i" => $pMC->msg( "Inactive" ) );
353 } // end Get_ExternalTransferStatusLookup
354 
355 /**
356  * Get the external accounts that have been configured for this user.
357  *
358  * @param integer $pDbh -- Current database handle
359  * @param array $pHBEnv -- Current HB_ENV values
360  *
361  * @return array -- An array with the correct accounts
362  */
363 function EXT_GetAccounts( $pDbh, $pHBEnv ) {
364  // initialize the return array
365  $returnData = array( "code" => "000", "errors" => array(), "data" => array() );
366  $pMC = $pHBEnv['MC'];
367 
368  try {
369  // get the deposit accounts can deposit into and loans that can have payments made into
370  $SQL = "SELECT *
371  FROM {$pHBEnv["Cu"]}extaccount ea
372  WHERE ea.user_id = {$pHBEnv["Uid"]}
373  AND type = 'EXT'
374  ORDER BY lower( display_name ), status, id
375  ";
376  $rs = db_query( $SQL, $pDbh );
377  if ( !$rs ) {
378  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1133" );
379  }
380 
381  // cycle through the rows
382  $row = 0;
383  $return = array();
384 
385  $mask= GetMask($pDbh, $pHBEnv["Cu"]);
386  $mask= $mask["code"] != 0 ? array("start" => -2) : $mask["data"]; // Just return the default mask in this case.
387 
388  while ( $extRow = db_fetch_array( $rs, $row++ ) ) {
389  $displayName = trim( $extRow["display_name"] );
390 
391  $remoteInfo = HCU_JsonDecode( $extRow["remote_info"]);
392 
393  $nameOnAccount = $remoteInfo["rdfi"]["name"];
394 
395  // dont return the micro deposit amounts
396  $status = trim( $extRow["status"] );
397  if ( $status == 'p' ) {
398  $keep = array( "rdfi" => $remoteInfo["rdfi"] );
399 
400  // need to convert to local time
401  $myDateTime = new DateTime( date( "m/d/y g:ia", $remoteInfo["verify"]["pending_date"] ) );
402  $myDateTime->setTimezone( new DateTimeZone( $pHBEnv["tz"] ) );
403 
404  $keep["verify"] = array( "pending_date" => $myDateTime->format( "m/d/y g:ia" ),
405  "tries" => $remoteInfo["verify"]["tries"] );
406  $returnRemoteInfo = HCU_JsonEncode( $keep );
407 
408  } else {
409  $returnRemoteInfo = $extRow["remote_info"];
410  }
411 
412 
413 
414  $saveRow = array( "id" => $extRow["id"],
415  "user_id" => $extRow["user_id"],
416  "display_name" => $displayName,
417  "name_on_account" => $nameOnAccount,
418  "status" => $status,
419  "remote_info" => $returnRemoteInfo,
420  "remoteAccount" => $remoteInfo["rdfi"]["routing"] . " / " . ApplyMask($remoteInfo["rdfi"]["account"], $mask));
421 
422  $return[] = $saveRow;
423  }
424 
425  $returnData["data"] = $return;
426  } catch (Exception $ex) {
427  $logInfo = array( "message" => $ex->getMessage(), "code" => $ex->getCode() );
428  $pHBEnv["SYSENV"]["logger"]->error( HCU_JsonEncode( $logInfo ) );
429 
430  $returnData["code"] = "999";
431  }
432 
433  return $returnData;
434 } // end EXT_GetAccounts
435 
436 
437 /**
438  * Get the M2M accounts that have been configured for this user.
439  *
440  * @param integer $pDbh -- Current database handle
441  * @param array $pHBEnv -- Current HB_ENV values
442  *
443  * @return array -- An array with the correct accounts
444  */
445 function EXT_GetM2MAccounts( $pDbh, $pHBEnv ) {
446  // initialize the return array
447  $returnData = array( "code" => "000", "errors" => array(), "data" => array() );
448  $pMC = $pHBEnv['MC'];
449 
450  try {
451  // get the deposit accounts can deposit into and loans that can have payments made into
452  $SQL = "SELECT *
453  FROM {$pHBEnv["Cu"]}extaccount ea
454  WHERE ea.user_id = {$pHBEnv["Uid"]}
455  AND type = 'M2M'
456  ORDER BY lower( display_name ), status, id
457  ";
458  $rs = db_query( $SQL, $pDbh );
459  if ( !$rs ) {
460  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1134" );
461  }
462 
463  // cycle through the rows
464  $row = 0;
465  $return = array();
466 
467  $mask= GetMask($pDbh, $pHBEnv["Cu"]);
468  $mask= $mask["code"] != 0 ? array("start" => -2) : $mask["data"]; // Just return the default mask in this case.
469 
470  while ( $extRow = db_fetch_array( $rs, $row++ ) ) {
471  $displayName = trim( $extRow["display_name"] );
472 
473  $remoteInfo = HCU_JsonDecode( $extRow["remote_info"]);
474 
475  $status = trim( $extRow["status"] );
476  $returnRemoteInfo = $extRow["remote_info"];
477 
478  $saveRow = array( "id" => $extRow["id"],
479  "user_id" => $extRow["user_id"],
480  "display_name" => $displayName,
481  "status" => $status,
482  "remote_info" => $returnRemoteInfo,
483  "remoteAccount" => ApplyMask($remoteInfo["rdfi"]["account"], $mask));
484 
485  $return[] = $saveRow;
486  }
487 
488  $returnData["data"] = $return;
489  } catch (Exception $ex) {
490  $logInfo = array( "message" => $ex->getMessage(), "code" => $ex->getCode() );
491  $pHBEnv["SYSENV"]["logger"]->error( HCU_JsonEncode( $logInfo ) );
492 
493  $returnData["code"] = "999";
494  }
495 
496  return $returnData;
497 } // end EXT_GetM2MAccounts
498 
499 
500 /**
501  * Check the inputs for valid data. Note that some inputs are optional and not checked
502  * because the inputs were sanitized.
503  *
504  * @param array $pInputVars -- Current database handle
505  *
506  * @return array -- An array with success code or error information
507  */
508 function EXT_ValidateInputs( $pHBEnv, $pDbh, $pInputVars, $pMC ) {
509  // initialize the return array
510  $aryReturnErrors = array();
511  $retACHValidate = array( "code" => "000", "errors" => array(), "data" => array() );
512 
513  try {
514  // can only check if name exists because we don't know if being saved or not
515  if ( strlen( trim( $pInputVars["display_name"] ) ) == 0 ) {
516  $aryReturnErrors = $pMC->msg("ACH Validation Name", HCU_DISPLAY_AS_HTML) . " 1119";
517  }
518 
519  // make sure display name is unique (across all types of entries)
520  $testDisplay = prep_save( html_entity_decode( $pInputVars["display_name"], ENT_QUOTES ), 20 );
521  $sql = "SELECT count(*)
522  FROM {$pHBEnv["Cu"]}extaccount
523  WHERE user_id = {$pHBEnv["Uid"]}
524  AND lower( display_name ) = lower( '{$testDisplay}' )
525  ";
526 
527  $rs = db_query( $sql, $pDbh );
528  if ( !$rs ) {
529  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1135" );
530  }
531 
532  $countRow = db_fetch_array( $rs, 0 );
533  if ( $countRow[0] > 0 ) {
534  $aryReturnErrors = $pMC->msg("ACH Unique display name required", HCU_DISPLAY_AS_HTML) . " 1125";
535  }
536 
537  if ( trim( $pInputVars["dfi_routing"] ) == "" ||
538  !ctype_digit( trim( $pInputVars["dfi_routing"] ) ) ||
539  strlen( trim( $pInputVars["dfi_routing"] ) ) != 9 ) {
540  $aryReturnErrors[] = $pMC->msg("ACH Validation DFI Routing") . " 1122";
541  }
542 
543  if ( trim( $pInputVars["dfi_account"] ) == "" ) {
544  $aryReturnErrors[] = $pMC->msg("ACH Validation DFI Account") . " 1121";
545  }
546 
547  if ( !(trim( $pInputVars["dfi_account_type"] ) == ACCOUNT_TYPE_CHECKING ||
548  trim( $pInputVars["dfi_account_type"] ) == ACCOUNT_TYPE_SAVINGS )) {
549  $aryReturnErrors[] = $pMC->msg("ACH Validation DFI Type") . " 1120";
550  }
551  } catch (Exception $ex) {
552  $message = $ex->getMessage();
553 
554  $aryReturnErrors[] = $message;
555  }
556 
557  if ( count( $aryReturnErrors ) > 0 ) {
558  // * validation errors occurred
559  $retACHValidate['code'] = '999';
560  $retACHValidate["errors"] = $aryReturnErrors;
561  }
562 
563  return $retACHValidate;
564 } // end EXT_ValidateInputs
565 
566 
567 /**
568  * Check the inputs for valid data. This is for M2M feature.
569  *
570  * @param array $pInputVars -- Current database handle
571  *
572  * @return array -- An array with success code or error information
573  */
574 function EXT_ValidateM2MInputs( $pHBEnv, $pDbh, $pInputVars, $pMC ) {
575  // initialize the return array
576  $aryReturnErrors = array();
577  $retACHValidate = array( "code" => "000", "errors" => array(), "data" => array() );
578 
579  try {
580  // can only check if name exists because we don't know if being saved or not
581  if ( strlen( trim( $pInputVars["display_name"] ) ) == 0 ) {
582  $aryReturnErrors = $pMC->msg("ACH Validation Name", HCU_DISPLAY_AS_HTML) . " 1127";
583  }
584 
585  // make sure display name is unique (across all types of entries)
586  $testDisplay = prep_save( html_entity_decode( $pInputVars["display_name"], ENT_QUOTES ), 20 );
587  $sql = "SELECT count(*)
588  FROM {$pHBEnv["Cu"]}extaccount
589  WHERE user_id = {$pHBEnv["Uid"]}
590  AND lower( display_name ) = lower( '{$testDisplay}' )
591  ";
592 
593  $rs = db_query( $sql, $pDbh );
594  if ( !$rs ) {
595  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1136" );
596  }
597 
598  $countRow = db_fetch_array( $rs, 0 );
599  if ( $countRow[0] > 0 ) {
600  $aryReturnErrors = $pMC->msg("ACH Unique display name required", HCU_DISPLAY_AS_HTML) . " 1128";
601  }
602 
603  if ( trim( $pInputVars["dfi_account"] ) == "" ) {
604  $aryReturnErrors[] = $pMC->msg("ACH Validation DFI Account") . " 1129";
605  }
606 
607  if ( !(trim( $pInputVars["dfi_account_type"] ) == ACCOUNT_TYPE_CHECKING ||
608  trim( $pInputVars["dfi_account_type"] ) == ACCOUNT_TYPE_SAVINGS )) {
609  $aryReturnErrors[] = $pMC->msg("ACH Validation DFI Type") . " 1131";
610  }
611  } catch (Exception $ex) {
612  $message = $ex->getMessage();
613 
614  $aryReturnErrors[] = $message;
615  }
616 
617  if ( count( $aryReturnErrors ) > 0 ) {
618  // * validation errors occurred
619  $retACHValidate['code'] = '999';
620  $retACHValidate["errors"] = $aryReturnErrors;
621  }
622 
623  return $retACHValidate;
624 } // end EXT_ValidateM2MInputs
625 
626 
627 /**
628  * Add an account with the initial status.
629  *
630  * @param array $pDbh -- Current database handle
631  * @param array $pHBEnv -- Current server environment
632  * @param array $pInputVars -- Validated data from the client
633  *
634  * @return array -- An array with success code and extaccount.id or error information
635  */
636 function EXT_AddAccount( $pDbh, $pHBEnv, $pInputVars, $pMC ) {
637  // initialize the return array
638  $returnData = array( "code" => "000", "errors" => array(), "data" => array() );
639 
640  try {
641  // gather any information we need
642 
643  // add the record to the <cu>extaccount table
644  $displayName = prep_save( html_entity_decode( $pInputVars["display_name"], ENT_QUOTES ), 20 );
645  $nameOnAccount = prep_save( html_entity_decode( $pInputVars["name_on_account"], ENT_QUOTES ) );
646 
647  // set up the account info
648  $remoteInfo = array( "rdfi" => array( "routing" => $pInputVars["dfi_routing"],
649  "account" => $pInputVars["dfi_account"],
650  "type" => $pInputVars["dfi_account_type"],
651  "name" => $nameOnAccount ) );
652 
653  $remoteInfoStr = HCU_JsonEncode( $remoteInfo );
654 
655  $sql = "INSERT INTO {$pHBEnv["Cu"]}extaccount (user_id, type, display_name, status, remote_info)
656  VALUES ({$pHBEnv["Uid"]}, 'EXT', '$displayName', '0', '$remoteInfoStr')
657  RETURNING id";
658 
659  $extRs = db_query( $sql, $pDbh );
660 
661  if ( !$extRs ) {
662  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1101" );
663  }
664 
665  // get the id that was added
666  list($extId) = db_fetch_array($extRs, 0);
667 
668  if ( !$extId ) {
669  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1102" );
670  }
671 
672  // return the id to the caller
673  $returnData["data"]["id"] = $extId;
674 
675  } catch (Exception $ex) {
676  // capture the exception
677  $message = $ex->getMessage();
678  $code = $ex->getCode();
679 
680  $logInfo = array( "message" => $message, "code" => $code );
681  $pHBEnv["SYSENV"]["logger"]->error( HCU_JsonEncode( $logInfo ) );
682 
683  $returnData["code"] = "999";
684  $returnData["errors"][] = $message;
685  }
686 
687  return $returnData;
688 } // end EXT_AddAccount
689 
690 /**
691  * Add an M2M account with the initial status. This will validate the account using a MIR packet, so an entry in the
692  * table is an active account.
693  *
694  * @param array $pDbh -- Current database handle
695  * @param array $pHBEnv -- Current server environment
696  * @param array $pInputVars -- Validated data from the client
697  *
698  * @return array -- An array with success code and extaccount.id or error information
699  */
700 function EXT_AddM2MAccount( $pDbh, $pHBEnv, $pInputVars, $pMC ) {
701  // initialize the return array
702  $returnData = array( "code" => "000", "errors" => array(), "data" => array() );
703 
704  try {
705  // call the core to see if this member is valid (throw exception if not)
706  if ( _ConfirmM2MAccount( $pHBEnv, $pInputVars ) == false ) {
707  throw new Exception( $pMC->msg("M2M Account Not Found", HCU_DISPLAY_AS_HTML) . " 1130" );
708  }
709 
710  // gather any information we need
711 
712  // add the record to the <cu>extaccount table
713  $displayName = prep_save( html_entity_decode( $pInputVars["display_name"], ENT_QUOTES ), 20 );
714  $nameOnAccount = prep_save( html_entity_decode( $pInputVars["name_on_account"], ENT_QUOTES ) );
715 
716  // set up the account info
717  $remoteInfo = array( "rdfi" => array( "account" => $pInputVars["dfi_account"],
718  "type" => $pInputVars["dfi_account_type"],
719  "name" => $nameOnAccount ),
720  "verify" => array( "verified" => time() ) );
721 
722  $remoteInfoStr = HCU_JsonEncode( $remoteInfo );
723 
724  $sql = "INSERT INTO {$pHBEnv["Cu"]}extaccount (user_id, type, display_name, status, remote_info)
725  VALUES ({$pHBEnv["Uid"]}, 'M2M', '$displayName', 'a', '$remoteInfoStr')
726  RETURNING id";
727 
728  $extRs = db_query( $sql, $pDbh );
729 
730  if ( !$extRs ) {
731  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1101" );
732  }
733 
734  // get the id that was added
735  list($extId) = db_fetch_array($extRs, 0);
736 
737  if ( !$extId ) {
738  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1102" );
739  }
740 
741  // return the id to the caller
742  $returnData["data"]["id"] = $extId;
743 
744  } catch (Exception $ex) {
745  // capture the exception
746  $message = $ex->getMessage();
747  $code = $ex->getCode();
748 
749  $logInfo = array( "message" => $message, "code" => $code );
750  $pHBEnv["SYSENV"]["logger"]->error( HCU_JsonEncode( $logInfo ) );
751 
752  $returnData["code"] = "999";
753  $returnData["errors"][] = $message;
754  }
755 
756  return $returnData;
757 } // end EXT_AddM2MAccount
758 
759 /**
760  * Call the core to get a MIR packet to validate if the given name is correct.
761  *
762  * @param array $pEnv -- Current server environment
763  * @param array $pInputVars -- Validated data from the client
764  * dfi_account - member number of the MIR packet we want to request
765  * name_on_account - name on the account
766  *
767  * @return array -- An array with success code and extaccount.id or error information
768  */
769 function _ConfirmM2MAccount( $pEnv, $pInputVars ) {
770 
771  $isValid = false;
772  $pMC = $pEnv['MC'];
773 
774  if ($pEnv['live'] == 0) {
775  throw new Exception( $pMC->msg("Option not set", HCU_DISPLAY_AS_HTML) . " 1144" );
776  }
777 
778  $mbrRequest = array( "type" => PACKET_REQUEST_MIR,
779  "member" => $pInputVars["dfi_account"] );
780  $memberInfo = GetMemberInfo( $pEnv, $mbrRequest );
781 
782  if ( $memberInfo["code"] === "000" ) {
783  if ( isset( $memberInfo["data"]["lastname"] ) ) {
784  // figure out what we are comparing in case it is shorter than the expected length
785  // NOTE, if it is shorter, then need to match exactly, including length
786  $testCompare = substr( $pInputVars["name_on_account"], 0, M2M_SIGNIFICANT_LEN );
787  $compareLen = strlen( $testCompare );
788  $compareExact = $compareLen < M2M_SIGNIFICANT_LEN;
789 
790  $actualName = substr( trim( $memberInfo["data"]["lastname"] ), 0, M2M_SIGNIFICANT_LEN );
791 
792  if ( $compareExact ) {
793  // do the length compare on what we got from the core
794  $isValid = strcasecmp( $actualName, $testCompare ) == 0 && strlen( trim( $memberInfo["data"]["lastname"]) ) == $compareLen;
795  } else {
796  $isValid = strcasecmp( $actualName, $testCompare ) == 0;
797  }
798  }
799  }
800 
801  return $isValid;
802 
803 
804 } // end _ConfirmM2MAccount
805 
806 /**
807  * Update just the names on the account. Return the name that was updated so the client-side has
808  * for sure what is in the database. Remember the encoding!
809  *
810  * @param array $pDbh -- Current database handle
811  * @param array $pHBEnv -- Current server environment
812  * @param array $pInputVars -- Validated data from the client
813  *
814  * @return array -- An array with success code and extaccount.id or error information
815  */
816 function EXT_UpdateAccount( $pDbh, $pHBEnv, $pInputVars, $pMC ) {
817  // initialize the return array
818  $returnData = array( "code" => "000", "errors" => array(), "data" => array() );
819 
820  try {
821  // make sure display name is unique (across all types of entries)
822  $testDisplay = prep_save( html_entity_decode( $pInputVars["display_name"], ENT_QUOTES ), 20 );
823  $sql = "SELECT count(*)
824  FROM {$pHBEnv["Cu"]}extaccount
825  WHERE user_id = {$pHBEnv["Uid"]}
826  AND lower( display_name ) = lower( '{$testDisplay}' )
827  AND id <> {$pInputVars["id"]}
828  ";
829 
830  $rs = db_query( $sql, $pDbh );
831  if ( !$rs ) {
832  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1137" );
833  }
834 
835  $countRow = db_fetch_array( $rs, 0 );
836  if ( $countRow[0] > 0 ) {
837  throw new Exception( $pMC->msg("ACH Unique display name required", HCU_DISPLAY_AS_HTML) . " 1126" );
838  }
839 
840  // gather any information we need
841  $sql = "SELECT ea.*
842  FROM {$pHBEnv["Cu"]}extaccount ea
843  WHERE ea.id = {$pInputVars["id"]}
844  ";
845 
846  $rs = db_query( $sql, $pDbh );
847 
848  $extRow = db_fetch_array( $rs, 0 );
849 
850  if ( $extRow["id"] == 0 ) {
851  throw new Exception( $pMC->msg("EXT Acct Status Error", HCU_DISPLAY_AS_HTML) . " 1103" );
852  }
853 
854  // create the update statement
855  $needUpdate = false;
856  $sqlUpdate = "";
857 
858  // handle the types differently
859  if ( $extRow["type"] == "EXT" ) {
860  $remoteInfo = HCU_JsonDecode( $extRow["remote_info"]);
861 
862  if ( $remoteInfo["rdfi"]["name"] != $pInputVars["name_on_account"] ) {
863  $returnNameOnAccount = html_entity_decode( $pInputVars["name_on_account"], ENT_QUOTES );
864  $remoteInfo["rdfi"]["name"] = prep_save( $returnNameOnAccount );
865  $updateRemoteInfo = HCU_JsonEncode( $remoteInfo );
866 
867  $needUpdate = true;
868 
869  if ( strlen( $sqlUpdate ) > 0 ) {
870  $sqlUpdate .= ", ";
871  }
872 
873  $sqlUpdate .= "remote_info = '$updateRemoteInfo' ";
874  } else {
875  $returnNameOnAccount = $remoteInfo["rdfi"]["name"];
876  }
877  }
878 
879  if ( $extRow["display_name"] != $pInputVars["display_name"] ) {
880  $returnDisplayName = html_entity_decode( $pInputVars["display_name"], ENT_QUOTES );
881  $displayName = prep_save( $returnDisplayName, 20 );
882  $needUpdate = true;
883 
884  if ( strlen( $sqlUpdate ) > 0 ) {
885  $sqlUpdate .= ", ";
886  }
887 
888  $sqlUpdate .= "display_name = '$displayName' ";
889  } else {
890  // so returning a consistent value
891  $returnDisplayName = $extRow["display_name"];
892  }
893 
894  if ( $needUpdate ) {
895  $sql = "UPDATE {$pHBEnv["Cu"]}extaccount SET $sqlUpdate WHERE id = {$pInputVars["id"]}";
896 
897  $extRs = db_query( $sql, $pDbh );
898 
899  if ( !$extRs ) {
900  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1104" );
901  }
902  }
903 
904  if ( $extRow["type"] == "EXT" ) {
905  // return the updated record info (new status)
906  $returnData["data"] = array( "id" => $pInputVars["id"],
907  "name_on_account" => $returnNameOnAccount,
908  "display_name" => $returnDisplayName
909  );
910  } else {
911  $returnData["data"] = array( "id" => $pInputVars["id"],
912  "display_name" => $returnDisplayName
913  );
914  }
915  } catch (Exception $ex) {
916  // capture the exception
917  $message = $ex->getMessage();
918  $code = $ex->getCode();
919 
920  $logInfo = array( "message" => $message, "code" => $code );
921  $pHBEnv["SYSENV"]["logger"]->error( HCU_JsonEncode( $logInfo ) );
922 
923  $returnData["code"] = "999";
924  $returnData["errors"][] = $message;
925  }
926 
927  return $returnData;
928 } // end EXT_UpdateAccount
929 
930 
931 /**
932  * Validate the account by comparing the micro deposit amounts. Update status if successful.
933  *
934  * @param array $pDbh -- Current database handle
935  * @param array $pHBEnv -- Current server environment
936  * @param array $pInputVars -- Validated data from the client
937  *
938  * @return array -- An array with success code and updated account info if successful.
939  */
940 function EXT_ValidateAccount( $pDbh, $pHBEnv, $pInputVars, $pMC ) {
941  // initialize the return array
942  $returnData = array( "code" => "000", "errors" => array(), "data" => array() );
943 
944  try {
945  if ( $pInputVars["id"] == 0 ) {
946  throw new Exception( $pMC->msg("EXT Acct Status Error", HCU_DISPLAY_AS_HTML) . " 1105" );
947  }
948 
949  $sql = "SELECT * FROM {$pHBEnv["Cu"]}extaccount WHERE id = {$pInputVars["id"]}";
950 
951  $extRs = db_query( $sql, $pDbh );
952 
953  $extRow = db_fetch_array( $extRs, 0 );
954 
955  if ( $extRow["id"] == 0 || ($extRow["status"] != "p" && $extRow["status"] != "l") ) {
956  throw new Exception( $pMC->msg("EXT Acct Status Error", HCU_DISPLAY_AS_HTML) . " 1106" );
957  }
958 
959  // get the saved micro amounts and retry count
960  $remoteInfo = HCU_JsonDecode( $extRow["remote_info"]);
961 
962  if ( !isset( $remoteInfo["verify"] ) || ($remoteInfo["verify"]["micro1"] <= 0) || ($remoteInfo["verify"]["micro2"] <= 0) ) {
963  throw new Exception( $pMC->msg("EXT Acct Status Error", HCU_DISPLAY_AS_HTML) . " 1107" );
964  }
965 
966  // test the tries
967  if ( $remoteInfo["verify"]["tries"] <= 0 || $extRow["status"] == "l" ) {
968  throw new Exception( $pMC->msg("EXT Retry Attempt", HCU_DISPLAY_AS_HTML) . " 1108" );
969  }
970 
971  // the saved smaller one in the first slot
972  $savedMicro1 = $remoteInfo["verify"]["micro1"];
973  $savedMicro2 = $remoteInfo["verify"]["micro2"];
974 
975  // don't know which order the micro amounts are coming, so compare smallest first
976  $failedValidation = true;
977  if ( $pInputVars["micro1"] < $pInputVars["micro2"] ) {
978  if ( ( $savedMicro1 == $pInputVars["micro1"] ) &&
979  ( $savedMicro2 == $pInputVars["micro2"] ) ) {
980  $failedValidation = false;
981  }
982  } else {
983  if ( ( $savedMicro1 == $pInputVars["micro2"] ) &&
984  ( $savedMicro2 == $pInputVars["micro1"] ) ) {
985  $failedValidation = false;
986  }
987  }
988 
989  if ( $failedValidation ) {
990  // count down that they tried
991  $remoteInfo["verify"]["tries"] -= 1;
992  $remoteInfoStr = prep_save( HCU_JsonEncode( $remoteInfo ) );
993 
994  $returnStatus = $remoteInfo["verify"]["tries"] <= 0 ? "l" : $extRow["status"];
995 
996  // return as data (not error) so can show updated retry count on client
997  if ( $returnStatus == "l" ) {
998  $returnMessage = $pMC->msg("EXT Retry Attempt", HCU_DISPLAY_AS_HTML) . " 1123";
999  } else {
1000  $returnMessage = $pMC->msg("EXT Acct validation fail", HCU_DISPLAY_AS_HTML) . " 1124";
1001  }
1002  } else {
1003  // update the record
1004  $returnStatus = "a";
1005 
1006  date_default_timezone_set("UTC");
1007  $remoteInfo["verify"] = array( "verified" => time() );
1008  $remoteInfoStr = prep_save( HCU_JsonEncode( $remoteInfo ) );
1009 
1010 
1011  $returnMessage = $pMC->msg("EXT Acct validation success", HCU_DISPLAY_AS_HTML);
1012  }
1013 
1014  $sql = "UPDATE {$pHBEnv["Cu"]}extaccount SET remote_info = '$remoteInfoStr', status = '$returnStatus'
1015  WHERE id = {$extRow["id"]}; ";
1016 
1017  $rs = db_query( $sql, $pDbh );
1018 
1019  // make sure the update worked
1020  if ( !$rs ) {
1021  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1109" );
1022  }
1023 
1024  // return the updated record info (new status)
1025  $returnData["data"] = array( "id" => $pInputVars["id"],
1026  "status" => $returnStatus,
1027  "tries" => HCU_array_key_value( "tries", $remoteInfo["verify"] ),
1028  "message" => $returnMessage
1029  );
1030  } catch (Exception $ex) {
1031  // capture the exception
1032  $message = $ex->getMessage();
1033  $code = $ex->getCode();
1034 
1035  $logInfo = array( "message" => $message, "code" => $code );
1036  $pHBEnv["SYSENV"]["logger"]->error( HCU_JsonEncode( $logInfo ) );
1037 
1038  $returnData["code"] = "999";
1039  $returnData["errors"][] = $message;
1040  }
1041 
1042  return $returnData;
1043 } // end EXT_ValidateAccount
1044 
1045 
1046 /**
1047  * Remove the account information from the database.
1048  *
1049  * @param array $pDbh -- Current database handle
1050  * @param array $pHBEnv -- Current server environment
1051  * @param array $pInputVars -- Validated data from the client
1052  *
1053  * @return array -- An array with success code and extaccount.id or error information
1054  */
1055 function EXT_DeleteAccount( $pDbh, $pHBEnv, $pInputVars, $pMC ) {
1056  // initialize the return array
1057  $returnData = array( "code" => "000", "errors" => array(), "data" => array() );
1058 
1059  try {
1060  if ( $pInputVars["id"] == 0 ) {
1061  throw new Exception( $pMC->msg("EXT Acct Status Error", HCU_DISPLAY_AS_HTML) . " 1110" );
1062  }
1063 
1064  // check for scheduled transfers that use this accounts
1065  // if any scheduled transfers use this accounts, do not allow delete.
1066  $sql = "
1067  SELECT * FROM cu_scheduledtxn WHERE cu = '{$pHBEnv['Cu']}' AND txn_data::json->'txn'->>'from'='{$pInputVars["id"]}'
1068  UNION
1069  SELECT * FROM cu_scheduledtxn WHERE cu = '{$pHBEnv['Cu']}' AND txn_data::json->'txn'->>'to'='{$pInputVars["id"]}'";
1070  $txnRs = db_query( $sql, $pDbh );
1071  if ( !$txnRs ) {
1072  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1140" );
1073  }
1074  $txnData = db_fetch_all($txnRs);
1075 
1076  // if no scheduled transactions are found, continue with delete.
1077  // else throw error to user.
1078  if (!empty($txnData) && count($txnData) > 0) {
1079  throw new Exception($pMC->msg("EXT Error Delete Accounts Scheduled", HCU_DISPLAY_AS_HTML) . "1141");
1080  }
1081 
1082  // check for any non-scheduled transactions that have no been processed or cancelled
1083  // if any are found using this account, do not allow delete of external account.
1084  // the below conditions include micro deposit transactions.
1085 
1086  /**
1087  * in the hdr table, tere are two columns we need to look at to allow deletion of
1088  * an external account.
1089  *
1090  * approved status:
1091  * approved = 10, declined = 90, cancelled = 99, pending = NULL
1092  * processed status:
1093  * approved = 10, cancelled = 99, pending = NULL
1094  *
1095  * if approved status is NULL: user may not delete
1096  * if approved status is declined: user may delete
1097  * if approved status is cancelled: user may delete
1098  * if approved status is approved: look at processed status
1099  *
1100  * if processed status is NULL: user may not delete
1101  * if processed status is approved: user may delete
1102  * if processed status is cancelled: user may delete
1103  */
1104  $sql = "
1105  SELECT * FROM {$pHBEnv['Cu']}transhdr h
1106  LEFT JOIN {$pHBEnv['Cu']}transdtl d ON h.id = d.transhdr_id
1107  WHERE h.feature_code='TRNEXT'
1108  AND h.approved_status is NULL
1109  OR (h.approved_status=10 AND h.processed_status is NULL)
1110  AND
1111  CASE WHEN h.transactioncode='1P' OR h.transactioncode='1W'
1112  THEN d.transdata::json->'acct_dest'->'remote_entity'->>'entry_id'='{$pInputVars['id']}'
1113  ELSE d.transdata::json->'acct_source'->'remote_entity'->>'entry_id'='{$pInputVars['id']}'
1114  END;";
1115  $txnRs = db_query( $sql, $pDbh );
1116  if ( !$txnRs ) {
1117  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1142" );
1118  }
1119  $txnData = db_fetch_all($txnRs);
1120 
1121  // if no transactions are found, continue with delete.
1122  // else throw error to user.
1123  if (!empty($txnData) && count($txnData) > 0) {
1124  throw new Exception($pMC->msg("EXT Error Delete Accounts", HCU_DISPLAY_AS_HTML) . " 1143");
1125  }
1126 
1127  $sql = "DELETE FROM {$pHBEnv["Cu"]}extaccount WHERE id = {$pInputVars["id"]}";
1128  $extRs = db_query( $sql, $pDbh );
1129  if ( !$extRs ) {
1130  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1111" );
1131  }
1132  } catch (Exception $ex) {
1133  // capture the exception
1134  $message = $ex->getMessage();
1135  $code = $ex->getCode();
1136 
1137  $logInfo = array( "message" => $message, "code" => $code );
1138  $pHBEnv["SYSENV"]["logger"]->error( HCU_JsonEncode( $logInfo ) );
1139 
1140  $returnData["code"] = "999";
1141  $returnData["errors"][] = $message;
1142  }
1143 
1144  return $returnData;
1145 } // end EXT_DeleteAccount
1146 
1147 
1148 /**
1149  * Determine the micro deposit amounts and create the ACH transaction.
1150  *
1151  * @param array $pDbh -- Current database handle
1152  * @param array $pHBEnv -- Current server environment
1153  * @param array $pInputVars -- Validated data from the client
1154  *
1155  * @return array -- An array with success code or error information
1156  */
1157 function EXT_StartAccountVerify( $pDbh, $pHBEnv, $pInputVars, $pMC ) {
1158  // initialize the return array
1159  $returnData = array( "code" => "000", "errors" => array(), "data" => array() );
1160 
1161  try {
1162  // read the admin settings for ach cutoff and micro deposit offset
1163  $sql = "SELECT settings::json->>'offsetting' AS offsetting,
1164  settings::json->>'cutoff' AS cutoff
1165  FROM cuadmin a
1166  WHERE cu = '{$pHBEnv["Cu"]}'";
1167  $sqlRs = db_query( $sql, $pDbh );
1168  if ( !$sqlRs ) {
1169  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1131" );
1170  }
1171 
1172  $adminData = db_fetch_assoc( $sqlRs );
1173 
1174  $offsetMicroFlag = $adminData['offsetting'] === "null" ? 0 : $adminData['offsetting'];
1175  $achCutoffTime = $adminData['cutoff'] === "null" ? 0 : $adminData['cutoff'];
1176 
1177  // read the extaccount entry
1178  $sql = "SELECT ea.*
1179  FROM {$pHBEnv["Cu"]}extaccount ea
1180  WHERE ea.id = {$pInputVars["ext_acct_id"]}
1181  ";
1182 
1183  $rs = db_query( $sql, $pDbh );
1184  if ( !$rs ) {
1185  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1132" );
1186  }
1187 
1188  $extRow = db_fetch_array( $rs, 0 );
1189 
1190  if ( $extRow["id"] == 0 || $extRow["status"] != "0" ) {
1191  throw new Exception( $pMC->msg("EXT Acct Status Error", HCU_DISPLAY_AS_HTML) . " 1112" );
1192  }
1193 
1194  $newStatus = "p";
1195  $remoteInfo = HCU_JsonDecode( $extRow["remote_info"]);
1196 
1197  // add the micro deposits (use whole numbers but treat as decimals)
1198  $microOne = rand( 10, 70 );
1199  $microTwo = rand( 10, 70 );
1200 
1201  $microOneSubmit = round( $microOne / 100, 2 );
1202  $microTwoSubmit = round( $microTwo / 100, 2 );
1203  $microSumSubmit = round( ($microOneSubmit + $microTwoSubmit), 2 );
1204 
1205  // put the smaller value in the 1st slot
1206  if ( $microOne < $microTwo ) {
1207  $microSave1 = $microOne;
1208  $microSave2 = $microTwo;
1209  } else {
1210  $microSave1 = $microTwo;
1211  $microSave2 = $microOne;
1212  }
1213 
1214  date_default_timezone_set("UTC");
1215  $remoteInfo["verify"] = array( "micro1" => $microSave1,
1216  "micro2" => $microSave2,
1217  "tries" => 5,
1218  "pending_date" => time() );
1219 
1220  $remoteInfoStr = prep_save( HCU_JsonEncode( $remoteInfo ) );
1221 
1222  $sql = "BEGIN TRANSACTION";
1223  $rs = db_query( $sql, $pDbh );
1224 
1225  $sql = "UPDATE {$pHBEnv["Cu"]}extaccount SET remote_info = '$remoteInfoStr', status = 'p'
1226  WHERE id = {$extRow["id"]}; ";
1227 
1228  $rs = db_query( $sql, $pDbh );
1229  if ( !$rs ) {
1230  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1138" );
1231  }
1232 
1233  // set up the query for the transaction header - do deposits and withdrawal separately
1234  // get the effective date
1235 // unresolved - need to handle the cutoff time
1236  // the account for the micro deposit is determined on the Admin side when ach file is built
1237  $effDate = date( "Y-m-d" );
1238  $featureCode = FEATURE_EXTERNAL_TRANSFERS;
1239  $transCode = "1P"; // credit, PPD
1240  $sql = "INSERT INTO {$pHBEnv["Cu"]}transhdr (feature_code, effective_date,
1241  posted_by, posted_date, approved_by, approved_date, approved_status,
1242  transactioncode, memo)
1243  VALUES ('{$featureCode}', '{$effDate}',
1244  {$pHBEnv["Uid"]}, now(), {$pHBEnv["Uid"]}, now(), 10,
1245  '$transCode', 'External Account Activate' )
1246  RETURNING id";
1247 
1248  $hdrRs = db_query( $sql, $pDbh );
1249 
1250  if ( !$hdrRs ) {
1251  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1113" );
1252  }
1253 
1254  // use the insert id from the header in the detail
1255  list($headerId) = db_fetch_array($hdrRs, 0);
1256 
1257  if ( !$headerId ) {
1258  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1114" );
1259  }
1260 
1261  // "rdfi" is the remote dfi - this is the same for each transaction detail
1262  $achData = array( "rdfi" => array( "rdfi_routing" => $remoteInfo["rdfi"]["routing"],
1263  "rdfi_account" => $remoteInfo["rdfi"]["account"],
1264  "rdfi_account_type" => $remoteInfo["rdfi"]["type"],
1265  "rdfi_txn_type" => "CR",
1266  "addenda" => "" ),
1267  "remote_entity" => array( "name" => $remoteInfo["rdfi"]["name"],
1268  "entry_id" => $extRow["id"],
1269  "display_name" => $extRow["display_name"] ) );
1270 
1271  // set up the transfer data (matching how TRNEXT saves info)
1272  $transData = array( "acct_source" => "micro", "acct_dest" => $achData );
1273 
1274  $jsonTransData = prep_save( HCU_JsonEncode( $transData ) );
1275 
1276  // use the partner id for now
1277  $referenceId = $pHBEnv["Uid"];
1278 
1279  // add the transaction detail
1280  // reference_id is a quasi-foreigh key to either the ach partner, external accounts, or wire partner table
1281  $sql = "INSERT INTO {$pHBEnv["Cu"]}transdtl (transhdr_id, reference_id, amount, email_notify, transdata)
1282  VALUES ($headerId, $referenceId, $microOneSubmit, 0, '$jsonTransData');";
1283  $sql .= "INSERT INTO {$pHBEnv["Cu"]}transdtl (transhdr_id, reference_id, amount, email_notify, transdata)
1284  VALUES ($headerId, $referenceId, $microTwoSubmit, 0, '$jsonTransData')";
1285 
1286  $dtlRs = db_query( $sql, $pDbh );
1287 
1288  if ( !$dtlRs ) {
1289  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1115" );
1290  }
1291 
1292  // see if configured for offsetting debit
1293  if ( $offsetMicroFlag ) {
1294  // unresolved - should this be the next business day after the deposits? Or when the deposit is confirmed?
1295  $transCode = "2P"; // debit, PPD
1296  // keep the memo under 30
1297  $sql = "INSERT INTO {$pHBEnv["Cu"]}transhdr (feature_code, effective_date,
1298  posted_by, posted_date, approved_by, approved_date, approved_status,
1299  transactioncode, memo)
1300  VALUES ('{$featureCode}', '{$effDate}',
1301  {$pHBEnv["Uid"]}, now(), {$pHBEnv["Uid"]}, now(), 10,
1302  '$transCode', 'Ext Acct Activate Offset' )
1303  RETURNING id";
1304 
1305  $hdrRs = db_query( $sql, $pDbh );
1306 
1307  if ( !$hdrRs ) {
1308  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1116" );
1309  }
1310 
1311  // use the insert id from the header in the detail
1312  list($headerId) = db_fetch_array($hdrRs, 0);
1313 
1314  if ( !$headerId ) {
1315  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1117" );
1316  }
1317 
1318  // set up the transfer data to debit from the remote account (matching how TRNEXT saves info)
1319  $achData["rdfi"]["rdfi_txn_type"] = "DB";
1320  $transData = array( "acct_source" => $achData, "acct_dest" => "micro" );
1321 
1322  $jsonTransData = prep_save( HCU_JsonEncode( $transData ) );
1323 
1324  // add the transaction detail (all the same values but the amount)
1325  $sql = "INSERT INTO {$pHBEnv["Cu"]}transdtl (transhdr_id, reference_id, amount, email_notify, transdata)
1326  VALUES ($headerId, $referenceId, $microSumSubmit, 0, '$jsonTransData')";
1327 
1328  $dtlRs = db_query( $sql, $pDbh );
1329 
1330  if ( !$dtlRs ) {
1331  throw new Exception( $pMC->msg("ACH Query Error", HCU_DISPLAY_AS_HTML) . " 1118" );
1332  }
1333  }
1334 
1335  // commit the transaction
1336  $sql = "COMMIT TRANSACTION";
1337  $achRs = db_query( $sql, $pDbh );
1338 
1339  // return success
1340 
1341  } catch (Exception $ex) {
1342  // capture the exception
1343  $message = $ex->getMessage();
1344  $code = $ex->getCode();
1345 
1346  // roll back the transaction
1347  $sql = "ROLLBACK TRANSACTION";
1348  db_query( $sql, $pDbh );
1349 
1350  $logInfo = array( "message" => $message, "code" => $code );
1351  $pHBEnv["SYSENV"]["logger"]->error( HCU_JsonEncode( $logInfo ) );
1352 
1353  $returnData["code"] = "999";
1354  $returnData["errors"][] = $message;
1355  }
1356 
1357  return $returnData;
1358 
1359 } // end EXT_StartAccountVerify
1360 
1361