-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPayPointTransaction
1207 lines (1117 loc) · 67.2 KB
/
PayPointTransaction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*Name: PayPointTransaction
*Description: This class is used to manage the interaction with CEPAS (AKA PayPoint). There are several objects involved with the PayPoint integration:
* - BGOV Cart - this is used to select the fees to be paid
* - CEPAS Transactions - This object is used to log all interactions with PayPoint. It also serves as an interface from which the client
* can review issues arising from interactions with PayPoint.
* - CEPAS Postings - This object is a detail object under CEPAS Transaction. It's loaded via a nightly batch interface and stores a record
* of all money going in and out of the business area.
* - MLCC Log - stores a record of all interactions with PayPoint
* - BGOV Receipt - A summary of a payments made by the client and the fees they correspond to
* - BGOV Payment - A child object to Receipt. It gives details on the payment for one Account. If the receipts typically have only one account then
* there will be one Payment under each Receipt.
*
* This class has 5 public functions (all static):
* - cartPaymentIncomplete - Just returns true/false if there's any incomplete payments on the Cart.
* We want to use this to prevent the cart from being used to create another payment before the first is resolved.
* - cartPaidSuccessfully - Returns true if payment is already complete...
* - getPayPointURL:
* - Creation of the CEPAS Transaction object
* - Generation of the URL for redirection to PayPoint (all PayPoint parameters are encoded in the URL including the
return URL used by this class can pick up and process the payment in separate invokation).
* - savePayPointResponse - Validate and save the Response from PayPoint, also update the CEPAS transaction
* - processPayPointResponse - Processing of the Response from PayPoint - Including generating receipts, payments, updating the cart, and the CEPAS Transaction
*
* ASSUMPTIONS:
* - The general assumption is that the reply from CEPAS must be saved then processed. This two step strategy allows the reply to be
* committed to the database (once it's consistency is validated) in the first step then processed in a second transaction. This isolates processing issues from communication issues.
* - If a reply has failed to be proessed the client will be asked to contact support. Once the issue has been resolved just re-opening the cart will retry the processing.
* - This class works closley with the Cart as well as the PayPoint response controller and several classes designed to load and validate the posting data.
* - All end user error messages are stored in static constants in this class.
*
*
* Date: - Case#/Project: - Developer/Company - Description
* ------------------------------------------------------------------------------------------------------------------------- *
* 05/16/2016 - PayPoint Integration - Basil Dobek, Deloitte - Initial Implementation.
* 08/22/2017 - MLCC - Scott Harbaugh, Deloitte - Adding braces to if statements
* 01/10/2018 - MLCC - Sachet Khanal, Deloitte - Removing Account lookup on cart & receipt as Cart/Transaction might include fees under multiple Accounts.
* 01/30/2018 - MLCC - Sachet Khanal, Deloitte - Adding Account lookup back cart. Account ID is master business ID.
*/
public without sharing class PayPointTransaction {
// The following contstants are used to store end user messages generated by this service. Messages recorded in the error logs are buried close to the issue identified
private static final string errorProcessingPaymentResponse = 'Please contact administrator - An error occurred processing payment response.';
private static final string cartPaymentInProgress = 'Please contact administrator - A payment for this cart is already in progress or has errored.';
private static final string cartPaymentAlreadyMade = 'Please contact administrator - A payment for this cart has already been made.';
private static final string paymentDeclinedMessage = 'Payment declined. Please verify information and/or use a different account. ';
private static final string exceededTxSizeLimitMessage = 'Payment has exceeded the single transaction limit. Please verify information or pay in multiple transactions.';
// These constants are initialized from global settings here and used by other classes.
private static final string PayPointPassword; //Used to store the custom setting containing the PayPoint password
//public static final string adminEmailList; //Used to store an email list which errors can be sent to
@TestVisible private static final string PayPointBaseURL; //Base URL for PayPoint in the current envrionment
//Make it test visible so unit test can use this to validate URLs generated
//Static initializer to get custom settings....
//It is safe to change the source of these values as no other classes
//access the custom settings directly.
static {
Global_Settings__c globalSettings = Global_Settings__c.getOrgDefaults();
PayPointPassword = globalSettings.PayPoint_Password__c;
//adminEmailList = globalSettings.PayPoint_Admin_Email_List__c;
PayPointBaseURL = globalSettings.PayPoint_Base_URL__c;
}
/*
* Method name : cartPaymentIncomplete
* Description : This service checks to see if any incomplete or errored payments exist for the cart.
*
*
* Return Type : boolean - Return true if incomplete payments exist for this cart.
* Parameter : cartId - The cart to check.
*/
public static boolean cartPaymentIncomplete(Id cartId) {
if (cartId == null) return false;
// find any existing CEPAS Transactions that are not either firmly abandonded or potentially abandoned
// It's too bad we need to include "In Progress" in this query, however as the user could close their browser before
// completing, or the network could fail we need to allow these to proceed. The posting file will pick up if any of these
// were completed.
list<Payment_Processor_Transaction__c> CEPAS_Transactions = [select id, name, status__c, cart__c
from Payment_Processor_Transaction__c where cart__c = :cartId and
(status__c != :MlccConstants.CEPASTxStatus_Abandoned and
status__c != :MlccConstants.CEPASTxStatus_In_Progress and
status__c != :MlccConstants.CEPASTxStatus_Declined and
status__c != :MlccConstants.CEPASTxStatus_Payment_Cancelled_By_Admin)];
//system.debug('CEPAS_Transactions='+CEPAS_Transactions);
if (CEPAS_Transactions.size() > 1) {
return true;
} else if (CEPAS_Transactions.size() == 1 &&
CEPAS_Transactions[0].status__c != MlccConstants.CEPASTxStatus_Payment_Successfully_Processed) {
return true;
} else {
return false;
}
}
/*
* Method name : cartPaidSuccessfully
* Description : This service just returns true if the cart has been successfully paid.
*
*
* Return Type : boolean - Return true if cart has been successfully paid.
* Parameter : cartId - The cart to check.
*/
public static boolean cartPaidSuccessfully(Id cartId) {
if (cartId == null) return false;
// find any existing CEPAS Transactions that are not either firmly abandonded or potentially abandoned
// It's too bad we need to include "In Progress" in this query, however as the user could close their browser before
// completing, or the network could fail we need to allow these to proceed. The posting file will pick up if any of these
// were completed.
list<Payment_Processor_Transaction__c> CEPAS_Transactions = [select id, name, status__c, cart__c
from Payment_Processor_Transaction__c where cart__c = :cartId and
(status__c = :MlccConstants.CEPASTxStatus_Payment_Successfully_Processed)];
//system.debug('CEPAS_Transactions='+CEPAS_Transactions);
if (CEPAS_Transactions.size() > 0) {
return true;
} else {
return false;
}
}
/*
* Method name : getPayPointURL
* Description : This service generates a URL which can be used by the current user to pay the Cart provided.
* This service also creates a corresponding CEPAS transaction which is used to track the success/fail of the client's payment.
* The URL has many parts to it including a return URL which is called either when the payment completes or the client cancels.
* This service ensures that the return ULR redirects the client to the right cart and/or success notification.
* Return Type : String - The PayPoint URL and URL encoded parameters
* Parameter : requestedPaymentAmount - The amount we are requesting the client to pay. PayPoint may add a service fee on top of this depending on the payment type.
* CartID - The ID of the cart the client is paying
* returnVFPageURL - the URL of the VF page that will be processing the response (includig the host).
* For example: 'https://' + ApexPages.currentPage().getHeaders().get('Host') +'/apex/HelloWorld'
* Several optional fields are included for use in pre-populating the PayPoint consumer web site. These include:
* - firstName, lastName
* - street1, street2
* - city, state, zip, country
* - email
*/
// This exception is thrown when there is already a payment that's incomplete on this cart.
public class CartPaymentException extends Exception { }
public static string getPayPointURL(decimal requestedPaymentAmount, ID cartId, String returnVFPageURL,
string firstName, string lastName, string street1, string street2,
string city, string state, string zip, string country, string email) {
if (cartPaymentIncomplete(cartId)) {
throw new CartPaymentException(cartPaymentInProgress); // return a message indicating the payment is in progress or has errored.
}
if (cartPaidSuccessfully(cartId)) {
throw new CartPaymentException(cartPaymentAlreadyMade); // return a message indicating the payment has already been madee.
}
Payment_Processor_Transaction__c CEPAS_Transaction = new Payment_Processor_Transaction__c();
CEPAS_Transaction.Requested_Payment_Amount__c = requestedPaymentAmount;
CEPAS_Transaction.Cart__c = CartID;
// Need to insert now to generate the reference ID used in the payment request url
insert CEPAS_Transaction;
CEPAS_Transaction = [select id, name, Status__c from Payment_Processor_Transaction__c where id = :CEPAS_transaction.id];
system.debug('PaypointTransaction***** CEPAS_Transaction=:' + CEPAS_Transaction);
string paymentURL;
paymentURL = PayPointBaseURL;
System.debug('****' + paymentURL);
paymentURL += MlccConstants.CEPAS_Req_Param_returnurl + EncodingUtil.URLEncode(returnVFPageURL, 'UTF-8');
paymentURL += MlccConstants.CEPAS_Req_Param_amount + requestedPaymentAmount;
paymentURL += MlccConstants.CEPAS_Req_Param_id + CEPAS_Transaction.name; // This value is returned right away in the reply url. I'm using it to find the CEPAS transaction once the payment completes
paymentURL += MlccConstants.CEPAS_Req_Param_referenceID + CEPAS_Transaction.name; // This value is retuurned in the posting file. I'm also using there to find the CEPAS transaction and reconcile any further events on it.
if (MlccUtils.isNotBlank(firstName)) {
paymentURL += MlccConstants.CEPAS_Req_Param_billingFirstName + EncodingUtil.URLEncode(firstName, 'UTF-8');
}
if (MlccUtils.isNotBlank(lastName)) {
paymentURL += MlccConstants.CEPAS_Req_Param_billingLastName + EncodingUtil.URLEncode(lastName, 'UTF-8');
}
if (MlccUtils.isNotBlank(street1)) {
paymentURL += MlccConstants.CEPAS_Req_Param_billingStreet1 + EncodingUtil.URLEncode(street1, 'UTF-8');
}
if (MlccUtils.isNotBlank(street2)) {
paymentURL += MlccConstants.CEPAS_Req_Param_billingStreet2 + EncodingUtil.URLEncode(street2, 'UTF-8');
}
if (MlccUtils.isNotBlank(city)) {
paymentURL += MlccConstants.CEPAS_Req_Param_city + EncodingUtil.URLEncode(city, 'UTF-8');
}
if (MlccUtils.isNotBlank(state)) {
paymentURL += MlccConstants.CEPAS_Req_Param_state + EncodingUtil.URLEncode(state, 'UTF-8');
}
if (MlccUtils.isNotBlank(zip)) {
paymentURL += MlccConstants.CEPAS_Req_Param_zip + EncodingUtil.URLEncode(zip, 'UTF-8');
}
if (MlccUtils.isNotBlank(country)) {
paymentURL += MlccConstants.CEPAS_Req_Param_country + EncodingUtil.URLEncode(country, 'UTF-8');
}
if (MlccUtils.isNotBlank(email)) {
paymentURL += MlccConstants.CEPAS_Req_Param_email + EncodingUtil.URLEncode(email, 'UTF-8');
}
//record the URL generated (as it incluceds the auto generated name value, this could not be done as part of the insert)
CEPAS_Transaction.Payment_Request_URL__c = paymentURL;
update CEPAS_Transaction;
Return paymentURL;
}
/*
* Method name : savePayPointResponse
* Description : This service:
* - Parses the PayPoint response
* - Validates the responde for internal consistency
* - Finds our corresponding CEPAS transaction
* - Validates the response against what we sent (ensuring consistency)
* - Saves the response
* - Returns details needed to redirect the user to the next page
* - Logs the details of any issues
* - Returns an error message that can be displayed to the user if necessary.
* This service does not continue on to process the response (generate receipts) as we wanted to ensure that the
* response is saved before trying to process it. There's an assumption that the user will immediatly be redirected
* to a new page allowing the save transaction to complete.
*
* Return Type : saveResult - A value object with key details about the transaction and the response.
* Parameter : pageWithResponseData - the response page. Because the reply parameters are URL encoded we can find all response details in the url of the PageReference
*/
public static saveResult savePayPointResponse(PageReference pageWithResponseData) {
System.debug('PayPointTransaction::savePayPointResponse()');
// declare variables used in logging outside exception handler
Map<String, String> pageParameters;
string responseURL;
saveResult result = new saveResult();
string referenceID;
// initialize for errors.. should be replaced with a detailed value on error but just in case.
result.errorMessage = errorProcessingPaymentResponse;
result.paymentResultSaved = false;
result.paymentAbandoned = false;
List<Payment_Processor_Transaction__c> CEPAS_Transactions; //declare here so it can be used in error logging.
try {
pageParameters = pageWithResponseData.getParameters();
responseURL = pageWithResponseData.getURL(); // save the URL for logging
System.debug('Response URL: ' + responseURL);
// extract the reply parameters
string ePayReturnCode = pageParameters.get(MlccConstants.CEPAS_Reply_Param_ePayReturnCode);
string epayResultMessage = pageParameters.get(MlccConstants.CEPAS_Reply_Param_epayResultMessage);
string confirmationNumber = pageParameters.get(MlccConstants.CEPAS_Reply_Param_confirmationNumber);
string totalAmount = pageParameters.get(MlccConstants.CEPAS_Reply_Param_totalAmount);
string settlementSubmissionDate = pageParameters.get(MlccConstants.CEPAS_Reply_Param_settlementSubmissionDate);
string authorizationCode = pageParameters.get(MlccConstants.CEPAS_Reply_Param_authorizationCode);
referenceID = pageParameters.get(MlccConstants.CEPAS_Reply_Param_referenceID);
if (string.isNotBlank(referenceID)) {
referenceID = String.escapeSingleQuotes(referenceID);
}
string cardType = pageParameters.get(MlccConstants.CEPAS_Reply_Param_cardType);
string hash = pageParameters.get(MlccConstants.CEPAS_Reply_Param_hash);
boolean hashValidated;
//system.debug('confirmationNumber='+confirmationNumber+', totalAmount='+totalAmount+', hash='+hash+', generatedHash='+generateHash(confirmationNumber, totalAmount));
if (hash == generateHash(confirmationNumber, totalAmount)) {
hashValidated = true;
} else {
hashValidated = false;
}
// Get the original CEPAS Transaction (ensuring it's in the correct status)
CEPAS_Transactions = [select id, name, Payment_Amount__c, cart__c, status__c, Admin_Investigation_Reason__c from Payment_Processor_Transaction__c where name = :referenceID];
System.debug('PaypointTransaction(savePayPointResponse)**** CEPAS_Transactions' + CEPAS_Transactions);
// log error if CEPAS Transaction not found
if (CEPAS_Transactions.size() != 1) {
result.errorMessage = errorProcessingPaymentResponse;
result.paymentResultSaved = false;
result.paymentAbandoned = false;
result.CEPASTransactionRef = referenceID;
//log the exception
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'savePayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
null, // objectId --use null so secondary error doesn't occur
'ERROR', // status
'Invalid response from PayPoint - CEPAS Transaction not found. referenceID=' + referenceID, // errorMessage
'', // stringRequestValue
responseURL); // stringResponseValue
return result;
}
// log error if CEPAS Transaction not in the correct status to save the response
if (CEPAS_Transactions[0].status__c != MlccConstants.CEPASTxStatus_In_Progress &&
CEPAS_Transactions[0].status__c != MlccConstants.CEPASTxStatus_Error_Saving_Response &&
CEPAS_Transactions[0].status__c != MlccConstants.CEPASTxStatus_Error_Processing_Response &&
CEPAS_Transactions[0].status__c != MlccConstants.CEPASTxStatus_Payment_Response_Saved) {
result.errorMessage = errorProcessingPaymentResponse;
result.paymentResultSaved = false;
result.paymentAbandoned = false;
result.CEPASTransactionRef = referenceID;
//log the exception
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'savePayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
CEPAS_Transactions[0].id, // objectId
'ERROR', // status
'Error processing response from PayPoint - transaction should be "In Progress" while awaiting CEPAS response', // errorMessage
'', // stringRequestValue
responseURL); // stringResponseValue
CEPAS_Transactions[0].Admin_Investigation_Required__c = true;
CEPAS_Transactions[0].Admin_Investigation_Reason__c = appendErr(CEPAS_Transactions[0].Admin_Investigation_Reason__c, 'Error saving response from PayPoint - transaction status should be either "In Progress" or "Payment Accepted - Error Processing Payment" while awaiting CEPAS response.');
update CEPAS_Transactions;
return result;
}
// As we have now passed the basic consistency checks we can start to save the returned information
try {
CEPAS_Transactions[0].Payment_Response_URL__c = responseURL;
CEPAS_Transactions[0].EPayReturnCode__c = ePayReturnCode;
CEPAS_Transactions[0].EpayResultMessage__c = epayResultMessage;
CEPAS_Transactions[0].Payment_Confirmation_Number__c = confirmationNumber;
CEPAS_Transactions[0].Authorization_Code__c = authorizationCode;
CEPAS_Transactions[0].Hash__c = hash;
CEPAS_Transactions[0].Hash_Validated__c = hashValidated;
if (string.isNotBlank(totalAmount)) {
CEPAS_Transactions[0].Payment_Amount__c = Decimal.valueOf(totalAmount);
}
if (string.isNotBlank(settlementSubmissionDate)) {
CEPAS_Transactions[0].Settlement_Submission_Date__c = convertSettlementSubmissionDate(settlementSubmissionDate);
}
CEPAS_Transactions[0].Card_Type__c = mapCardTypeCode(cardType);
} catch(exception e) {
result.errorMessage = errorProcessingPaymentResponse;
result.paymentResultSaved = false;
result.paymentAbandoned = false;
result.CEPASTransactionRef = CEPAS_Transactions[0].name;
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'savePayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
CEPAS_Transactions[0].Id, // objectId
'ERROR', // status
'Error mapping response from PayPoint. Error=' + e.getMessage(), // errorMessage
'', // stringRequestValue
responseURL); // stringResponseValue
CEPAS_Transactions[0].Admin_Investigation_Required__c = true;
CEPAS_Transactions[0].Admin_Investigation_Reason__c = appendErr(CEPAS_Transactions[0].Admin_Investigation_Reason__c, 'Error saving payment response. Error details =' + e.getMessage());
CEPAS_Transactions[0].status__c = MlccConstants.CEPASTxStatus_Error_Saving_Response;
update CEPAS_Transactions;
return result;
}
// Determine if payment was processed or abandoned
if (MlccUtils.IsBlank(ePayReturnCode)) {
// the transaction was abandoned update the CEPAS Transaction status to abandoned
CEPAS_Transactions[0].Status__c = MlccConstants.CEPASTxStatus_Abandoned;
update CEPAS_Transactions;
BGBK__Cart__c cart = getCart(CEPAS_Transactions[0].Cart__c);
if (cart.BGBK__Status__c != 'Open') {
List<BGBK__Cart__c> carts = [SELECT Id FROM BGBK__Cart__c WHERE BGBK__Contact__c = :cart.BGBK__Contact__c AND BGBK__Status__c = 'Open'];
//MEC[8/25]: COE recommendation for isDeletable();
if (Schema.sObjectType.BGBK__Cart__c.isDeletable()) {
delete carts;
} else {
throw new CustomClassException('Object BGBK__Cart__c is not deletable');
}
}
cart.BGBK__Status__c = 'Open';
update cart;
result.errorMessage = null;
result.paymentResultSaved = true;
result.paymentAbandoned = true;
result.CEPASTransactionRef = CEPAS_Transactions[0].name;
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'savePayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
CEPAS_Transactions[0].Id, // objectId
'SUCCESS', // status
result.errorMessage, // errorMessage
'', // stringRequestValue
responseURL); // stringResponseValue
return result;
} else if (!hashValidated & (totalAmount != null || hash != null)) {
// log error as Hash isn't valid (and an amount or hash was actually returned)
result.errorMessage = errorProcessingPaymentResponse;
result.paymentResultSaved = false;
result.paymentAbandoned = false;
result.CEPASTransactionRef = referenceID;
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'savePayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
CEPAS_Transactions[0].Id, // objectId
'ERROR', // status
'Invalid response from PayPoint - PayPoint encrypted hash is not valid.', // errorMessage
'', // stringRequestValue
responseURL); // stringResponseValue
CEPAS_Transactions[0].Admin_Investigation_Required__c = true;
CEPAS_Transactions[0].Admin_Investigation_Reason__c = appendErr(CEPAS_Transactions[0].Admin_Investigation_Reason__c, 'Invalid response from PayPoint - PayPoint encrypted hash is not valid.');
CEPAS_Transactions[0].status__c = MlccConstants.CEPASTxStatus_Error_Saving_Response;
update CEPAS_Transactions;
return result;
} else {
System.debug('Success... save the transaction');
// Success... save the transaction
// don't clear the error as worker will need to update Manual_Corrective_Action__c taken anyway. -- CEPAS_Transactions[0].Admin_Investigation_Required__c=false; // clear if set previously
CEPAS_Transactions[0].Status__c = MlccConstants.CEPASTxStatus_Payment_Response_Saved;
update CEPAS_Transactions;
result.errorMessage = null;
result.paymentResultSaved = true;
result.paymentAbandoned = false;
result.CEPASTransactionRef = CEPAS_Transactions[0].name;
System.debug('CEPAS Transaction: ' + CEPAS_Transactions[0]);
return result;
}
} catch(Exception e) {
result.errorMessage = errorProcessingPaymentResponse;
result.paymentResultSaved = false;
result.paymentAbandoned = false;
result.CEPASTransactionRef = referenceID;
id objectId = null;
if (CEPAS_Transactions != null && CEPAS_Transactions[0] != null && CEPAS_Transactions[0].id != null) {
objectId = CEPAS_Transactions[0].id;
}
try {
if (objectId != null) {
CEPAS_Transactions[0].Status__c = MlccConstants.CEPASTxStatus_Error_Saving_Response;
CEPAS_Transactions[0].Admin_Investigation_Required__c = true;
CEPAS_Transactions[0].Admin_Investigation_Reason__c = appendErr(CEPAS_Transactions[0].Admin_Investigation_Reason__c, 'Unexpected error saving response from CEPAS, message =' + e.getMessage());
update CEPAS_Transactions;
}
} catch(Exception innerError) {
// can't do much other than replace the error with additional details
result.errorMessage = errorProcessingPaymentResponse + ' (' + e.getMessage() + ')';
}
try {
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'savePayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
objectId, // objectId
'ERROR', // status
'Unexpected error saving response from CEPAS, Ref Id=' + referenceID + ', message =' + e.getMessage(), // errorMessage
'', // stringRequestValue
responseURL); // stringResponseValue
} catch(Exception innerError) {
// can't do much other than replace the error with additional details
result.errorMessage = errorProcessingPaymentResponse + ' (' + e.getMessage() + ')';
}
}
return result;
}
/*
* Method name : processPayPointResponse
* Description : This service:
* - Loads up all the Cart, Fees, and CEPAS Transaction objects and services
* - Verifies the above entities are consistent and ready to be processed
* - Cart fees still match CEPAS Transaction
* - CEPAS transaction is Accepted by Processor (or in status error processing response)
* - If credit card conveinience fees were added to the payment create and add an additional fee to cart.
* - Create the Receipt with all fees
* - Process the payment
* - Close the Cart
* - Update the CEPAS Tx with:
* - Receipt
* - Successfully Processed status
*
* Return Type : processingResult - A value object with key details about the transaction and the response.
* Parameter : CEPASTxRef - the CEPAS Transaction reference number
*/
public static processingResult processPayPointResponse(string CEPASTxRef) {
System.debug('PayPointTransaction::processPayPointResponse()');
processingResult result = new processingResult();
list<Payment_Processor_Transaction__c> CEPAS_Transactions = [select id, name, Payment_Amount__c, Requested_Payment_Amount__c, Payment_Amount_Variance__c,
status__c, Admin_Investigation_Required__c, Admin_Investigation_Reason__c,
cart__c, receipt__c,
EPayReturnCode__c, EpayResultMessage__c
from Payment_Processor_Transaction__c where name = :CEPASTxRef];
System.debug('processPayPointResponse****CEPAS_Transactions' + CEPAS_Transactions);
if (CEPAS_Transactions.size() == 0) {
result.paymentSuccess = false;
result.errorMessage = errorProcessingPaymentResponse;
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'processPayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
null, // objectId
'ERROR', // status
'Error processing payment, CEPAS Transaction not found. CEPAS_Tx_ref=' + CEPASTxRef, // errorMessage
'', // stringRequestValue
''); // stringResponseValue
return result;
}
if (CEPAS_Transactions.size() > 1) { // Validations prevent the transaction name from changing, however, just in case.
result.paymentSuccess = false;
result.errorMessage = errorProcessingPaymentResponse;
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'processPayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
CEPAS_Transactions[0].id, // objectId
'ERROR', // status
'Error processing payment, Multiple CEPAS Transactions found with same reference number. CEPAS_Tx_ref=' + CEPASTxRef, // errorMessage
'', // stringRequestValue
''); // stringResponseValue
CEPAS_Transactions[0].Admin_Investigation_Required__c = true;
CEPAS_Transactions[0].Admin_Investigation_Reason__c = appendErr(CEPAS_Transactions[0].Admin_Investigation_Reason__c, 'Error processing payment, Multiple CEPAS Transactions found with same reference number.');
CEPAS_Transactions[0].status__c = MlccConstants.CEPASTxStatus_Error_Processing_Response;
update CEPAS_Transactions;
return result;
}
// just return without doing anything if payment's already processed
if (CEPAS_Transactions[0].status__c == MlccConstants.CEPASTxStatus_Payment_Successfully_Processed || CEPAS_Transactions[0].status__c == MlccConstants.CEPASTxStatus_Payment_In_Progress) {
result.paymentSuccess = true;
result.errorMessage = null;
return result;
}
if (CEPAS_Transactions[0].status__c != MlccConstants.CEPASTxStatus_Payment_Response_Saved && CEPAS_Transactions[0].status__c != MlccConstants.CEPASTxStatus_Error_Processing_Response) {
result.paymentSuccess = false;
result.errorMessage = errorProcessingPaymentResponse;
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'processPayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
CEPAS_Transactions[0].id, // objectId
'ERROR', // status
'Error processing payment, CEPAS Transaction not in status where payments eligible to be processed. CEPAS_Tx_Id=' + CEPASTxRef, // errorMessage
'', // stringRequestValue
''); // stringResponseValue
CEPAS_Transactions[0].Admin_Investigation_Required__c = true;
CEPAS_Transactions[0].Admin_Investigation_Reason__c = appendErr(CEPAS_Transactions[0].Admin_Investigation_Reason__c, 'Error processing payment, CEPAS Transaction not in status where payments are eligible to be processed. ');
// didn't want to just fix the status.. CEPAS_Transactions[0].status__c = MlccConstants.CEPASTxStatus_Error_Processing_Response;
update CEPAS_Transactions;
return result;
}
if (CEPAS_Transactions[0].receipt__c != null) {
result.paymentSuccess = false;
result.errorMessage = errorProcessingPaymentResponse;
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'processPayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
CEPAS_Transactions[0].id, // objectId
'ERROR', // status
'Error processing payment, CEPAS Transaction is already associated with a receipt. CEPAS_Tx_Id=' + CEPASTxRef, // errorMessage
'', // stringRequestValue
''); // stringResponseValue
CEPAS_Transactions[0].Admin_Investigation_Required__c = true;
CEPAS_Transactions[0].Admin_Investigation_Reason__c = appendErr(CEPAS_Transactions[0].Admin_Investigation_Reason__c, 'Error processing payment, CEPAS Transaction is already associated with a receipt.');
CEPAS_Transactions[0].status__c = MlccConstants.CEPASTxStatus_Error_Processing_Response;
update CEPAS_Transactions;
return result;
}
// Verify Cart fees still match what was requested in the CEPAS Transaction
// BGBK.CartService cartService = new BGBK.CartService(null, CEPAS_Transactions[0].cart__c); // load up the BGBK cart service
BGBK__Cart__c cart = getCart(CEPAS_Transactions[0].Cart__c);
MUSW__Fee__c[] fees = getCartFees(cart, true);
BGBK__Bill__c[] invoices = new List<BGBK__Bill__c> (); // = cartService.getCartInvoices(true);
decimal cartTotal = 0;
for (MUSW__Fee__c f : fees)
{
cartTotal += f.MUSW__Outstanding_Fee__c;
}
for (BGBK__Bill__c i : invoices)
{
cartTotal += i.BGBK__Total_Fees__c;
}
if (cartTotal != CEPAS_Transactions[0].Requested_Payment_Amount__c || cartTotal != cart.BGBK__Total_Amount__c) {
result.paymentSuccess = false;
result.errorMessage = errorProcessingPaymentResponse;
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'processPayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
CEPAS_Transactions[0].Id, // objectId
'ERROR', // status
'Error processing payment, CEPAS Transaction requested amount. different from cart fee total, CEPASTxRef=' + CEPASTxRef, // errorMessage
'', // stringRequestValue
''); // stringResponseValue
CEPAS_Transactions[0].Admin_Investigation_Required__c = true;
CEPAS_Transactions[0].Admin_Investigation_Reason__c = appendErr(CEPAS_Transactions[0].Admin_Investigation_Reason__c, 'Error processing payment, CEPAS Transaction requested payment amount does not match cart. Fees may have been moved to a different cart during payment. Please validate fees to be paid to avoid an overpayment.');
CEPAS_Transactions[0].status__c = MlccConstants.CEPASTxStatus_Error_Processing_Response;
update CEPAS_Transactions;
System.debug('processPayPointResponse cartTotal!= ****CEPAS_Transactions' + CEPAS_Transactions);
return result;
}
// Verify that a cart contact exists (Not sure how this error can happen, Basic Gov recommended this check)
//Contact contactFromCart = CartService.getActiveContact();
//system.debug('contactFromCart='+contactFromCart);
//if (contactFromCart == null) {
if (cart.BGBK__Contact__c == null) {
result.paymentSuccess = false;
result.errorMessage = errorProcessingPaymentResponse;
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'processPayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
CEPAS_Transactions[0].Id, // objectId
'ERROR', // status
'Error processing payment, No contact is associated with the cart. Please contact administrator.', // errorMessage
'', // stringRequestValue
''); // stringResponseValue
CEPAS_Transactions[0].Admin_Investigation_Required__c = true;
CEPAS_Transactions[0].Admin_Investigation_Reason__c = appendErr(CEPAS_Transactions[0].Admin_Investigation_Reason__c, 'No contact is associated with the cart. Please contact administrator.');
CEPAS_Transactions[0].status__c = MlccConstants.CEPASTxStatus_Error_Processing_Response;
update CEPAS_Transactions;
return result;
}
// We have now Passed validation and consisitency checks... inspect the payment result
// If payment wasn't successful on the CEPAS side for any reason get an appropriate message for the user and update the CEPAS Transaction
if (CEPAS_Transactions[0].EPayReturnCode__c != MlccConstants.PayPointReturnCodePayment_Success) {
//system.debug('CEPAS_Transactions='+CEPAS_Transactions);
result.paymentSuccess = false;
result.errorMessage = mapRejectionDetailsToUserMessage(CEPAS_Transactions[0]); // inspect the result code and result message and generate a user message.
updateCEPASTransactionWithRejectionDetails(CEPAS_Transactions[0]); //Update transaction Status and flag anything that needs to be seen by admin, log any additional information needed
update CEPAS_Transactions;
return result;
}
// Do one final consistency check, verify that the requested amount is less than or equal to the actual payment amount.
// This could have been done earlier but it triggers before the return code check when a normal decline message is returned when placed earlier
//
system.debug('CEPAS_Transactions[0]=' + CEPAS_Transactions[0]);
if (CEPAS_Transactions[0].Payment_Amount_Variance__c< 0) {
result.paymentSuccess = false;
result.errorMessage = errorProcessingPaymentResponse;
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'processPayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
CEPAS_Transactions[0].Id, // objectId
'ERROR', // status
'CEPAS Transaction requested payment amount (' + CEPAS_Transactions[0].Requested_Payment_Amount__c + ')is greater than actual payment amount(' + CEPAS_Transactions[0].Payment_Amount__c + '). Please validate this transaction. CEPASTxRef=' + CEPASTxRef, // errorMessage
'', // stringRequestValue
''); // stringResponseValue
CEPAS_Transactions[0].Admin_Investigation_Required__c = true;
CEPAS_Transactions[0].Admin_Investigation_Reason__c = appendErr(CEPAS_Transactions[0].Admin_Investigation_Reason__c, 'Error processing payment, CEPAS Transaction requested payment amount (' + CEPAS_Transactions[0].Requested_Payment_Amount__c + ')is greater than actual payment amount(' + CEPAS_Transactions[0].Payment_Amount__c + '). Please validate this transaction.');
CEPAS_Transactions[0].status__c = MlccConstants.CEPASTxStatus_Error_Processing_Response;
update CEPAS_Transactions;
return result;
}
// Transaction was successful on the CEPAS Side, continue on to process the payment in AIMS
System.debug('Transaction was successful on the CEPAS Side, continue on to process the payment in AIMS');
// The payment updates updates must be either all committed or rolled back so we can record the failure details...
// A SavePoint will be used to ensure this consistency.
savepoint sp = null;
try {
sp = database.setSavepoint();
createPayment(CEPAS_Transactions[0].id, cart, fees, invoices);
result.paymentSuccess = true;
} catch(exception e) {
// rollback the savepoint
database.rollback(sp);
// Update the status of the CEPAS Transaction
System.debug('Exception Creating Payments message=' + e.getMessage() + ', Stack Trace:' + e.getStackTraceString());
try {
// set the return codes
result.paymentSuccess = false;
result.errorMessage = errorProcessingPaymentResponse;
// log the exception
MlccLogging.addToMlccWebserviceLog('PayPointTransaction', // className
'processPayPointResponse', // methodName
'', // objectRequestName
'', // objectResponseName
CEPAS_Transactions[0].Id, // objectId
'ERROR', // status
'Error processing payment, message=' + e.getMessage() + ', Stack Trace:' + e.getStackTraceString(), // errorMessage
'', // stringRequestValue
''); // stringResponseValue
system.debug('Error processing payment, message=' + e.getMessage() + ', Stack Trace:' + e.getStackTraceString());
CEPAS_Transactions[0].Admin_Investigation_Required__c = true;
CEPAS_Transactions[0].Admin_Investigation_Reason__c = appendErr(CEPAS_Transactions[0].Admin_Investigation_Reason__c, 'Error processing payment, unexpected error occurred in PayPointTransaction.createPayment.');
CEPAS_Transactions[0].status__c = MlccConstants.CEPASTxStatus_Error_Processing_Response;
update CEPAS_Transactions;
return result;
}
catch(exception innerException) {
// nothing can be done here.
system.debug('PayPointTransaction.processPayPointResponse - multiple exception occurred. Initial exception=' + e.getMessage());
system.debug('PayPointTransaction.processPayPointResponse - multiple exception occurred. Secondary exception=' + innerException.getMessage());
}
}
return result;
}
/*
* Method name : mapRejectionDetailsToUserMessage
* Description : As the payment did not go through, figure out what to tell the client based on response information.
*
* Return Type : string - message to display to user regarding payment rejection.
* Parameter : CEPAS_Tx - The CEPAS transaction on which the CEPAS response information is stored.
*/
private static string mapRejectionDetailsToUserMessage(Payment_Processor_Transaction__c CEPAS_Tx) {
string clientMessage = null;
if (CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeUndefined_Item ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeVerification_Failed ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeMissing_Identification ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeError ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeCommunication_Error ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeNetwork_Error ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeUnaccepted_Card_Type ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeDeclined) {
clientMessage = paymentDeclinedMessage;
if (string.isNotBlank(CEPAS_Tx.EpayResultMessage__c) && CEPAS_Tx.EpayResultMessage__c != '""') {
clientMessage += 'Additional Details: ' + CEPAS_Tx.EpayResultMessage__c;
}
} else if (CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodePayment_Exceeds_System_Limit ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodePayment_Exceeds_Allowable_Limit) {
clientMessage = exceededTxSizeLimitMessage;
} else {
clientMessage = errorProcessingPaymentResponse;
if (string.isNotBlank(CEPAS_Tx.EpayResultMessage__c)) {
clientMessage += 'Additional Details: ' + CEPAS_Tx.EpayResultMessage__c;
}
}
return clientMessage;
}
/*
* Method name : updateCEPASTransactionWithRejectionDetails
* Description : As the payment did not go through, figure out what status to put the CEPAS Transaction into and update it.
* Also, if an admin investigation is required update it with the details.
*
* Return Type : none
* Parameter : CEPAS_Tx - The CEPAS transaction on which the CEPAS response information is stored.
*/
private static void updateCEPASTransactionWithRejectionDetails(Payment_Processor_Transaction__c CEPAS_Tx) {
if (CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeUndefined_Item ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodePayment_Exceeds_System_Limit ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodePayment_Exceeds_Allowable_Limit ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeVerification_Failed ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeMissing_Identification ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeError ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeCommunication_Error ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeNetwork_Error ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeUnaccepted_Card_Type ||
CEPAS_Tx.EPayReturnCode__c == MlccConstants.PayPointReturnCodeDeclined) {
CEPAS_Tx.status__c = MlccConstants.CEPASTxStatus_Declined;
} else {
CEPAS_Tx.status__c = MlccConstants.CEPASTxStatus_Error_Processing_Response;
CEPAS_Tx.Admin_Investigation_Required__c = true;
CEPAS_Tx.Admin_Investigation_Reason__c = appendErr(CEPAS_Tx.Admin_Investigation_Reason__c, 'Unexpected return code from CEPAS. Please investigate Return Code and Result Message.');
}
return;
}
public static BGBK__Cart__c getCart(Id cartId) {
System.debug('PayPointTransaction::getCart()');
String sObjName = (cartId != null) ? cartId.getSObjectType().getDescribe().getName() : '';
BGBK__Cart__c cart = new BGBK__Cart__c();
User usr = [SELECT Id, ContactId, AccountId FROM User WHERE Id = :UserInfo.getUserId()];
if (cartId != null) {
sObjName = cartId.getSObjectType().getDescribe().getName();
String soql = 'SELECT Id, Name, BGBK__Contact__c, BGBK__Account__c, BGBK__Process_DateTime__c, BGBK__Expiration_DateTime__c, ' +
'BGBK__Contact__r.Email, BGBK__Total_Amount__c, ' +
'BGBK__Last_Payment_Amount__c, BGBK__Status__c, ' +
'(SELECT Id FROM BGBK__Fees__r WHERE SoM_Renewal_License_Permit__c!=null LIMIT 1) ' +
'FROM BGBK__Cart__c ';
soql += 'WHERE Id = :cartId OR (BGBK__Contact__c=:cartId AND BGBK__Status__c=\'Open\') ';
if (usr.ContactId != null) {
soql += 'OR (BGBK__Contact__c=\'' + usr.ContactId + '\' AND BGBK__Status__c=\'Open\') ';
}
List<BGBK__Cart__c> carts = Database.query(soql + 'ORDER BY LastModifiedDate DESC ');
System.debug('Carts: ' + carts);
if (carts.size()> 0) {
cart = carts[0];
for (Integer i = 1; i<carts.size(); i++) {
carts[i].BGBK__Status__c = 'Abandoned';
}
update carts;
}
if (sObjName.equalsIgnoreCase('Account')) {
Account acc = [SELECT Id, Type, SoM_Master_or_Coordinated_Business__c FROM Account WHERE Id = :cartId LIMIT 1];
cart.BGBK__Account__c = (acc.Type == 'Business Location') ? acc.SoM_Master_or_Coordinated_Business__c : acc.Id;
}
if (cart.Id != null) {
update cart;
}
}
if (cart.Id == null) {
cart.Name = 'X-' + System.now().format('yyyy-MM-dd_HH-mm-ss');
cart.BGBK__Status__c = 'Open';
if (sObjName.equalsIgnoreCase('Contact')) {
cart.BGBK__Contact__c = cartId;
} else {
if (usr.ContactId != null) {
cart.BGBK__Contact__c = usr.ContactId;
}
}
insert cart;
}
return cart;
}
public static void generateAndSendReceiptPDF(MUSW__Receipt__c receipt) {
PageReference pdfPage = Page.SoM_ViewPrintReceipt;
pdfPage.getParameters().put('id', receipt.Id);
Attachment receiptPDF = new Attachment();
receiptPDF.Body = pdfPage.getContentAsPDF();
receiptPDF.Name = 'PaymentReceipt_' + receipt.Name + '.pdf';
receiptPDF.ContentType = 'application/pdf';
if (receipt.BGBK__Cart__c != null) {
receiptPDF.ParentId = receipt.BGBK__Cart__c;
} else {
receiptPDF.ParentId = receipt.Id;
}
insert receiptPDF;
if (receipt.BGBK__Cart__c != null) {
BGBK__Cart__c cart = getCart(receipt.BGBK__Cart__c);
Contact cartContact = [SELECT Id, Name, FirstName, LastName, Email, AccountId FROM Contact WHERE Id = :cart.BGBK__Contact__c];
List<String> CourtesyCopyEmails = new List<String> ();
// control parameters on cart settings
BGBK__CartSettings__c settings = BGBK__CartSettings__c.getValues('Default');
if (settings != null)
{
CourtesyCopyEmails = null;
if (settings.ccEmailList__c != null) {
CourtesyCopyEmails = settings.ccEmailList__c.Split(',|;');
}
}
sendEmail(receipt, cartContact, CourtesyCopyEmails, new List<Attachment> { receiptPDF });
}
}
public static void sendEmail(MUSW__Receipt__c receipt, Contact con, List<String> ccEmails, List<Attachment> attachments) {
List<String> emailAddresses = new List<String> ();
//New instance of a single email message
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
// Who you are sending the email to
mail.setTargetObjectId(con.Id);
mail.setTreatTargetObjectAsRecipient(false);
emailAddresses.add(receipt.Cart_Email_Receipt_To__c);
// The email template ID used for the email
Id templateId = [Select id from EmailTemplate where name = 'Cart Payment Receipt'].Id;
mail.setTemplateId(templateId);
mail.setToAddresses(emailAddresses);
mail.setCCAddresses(ccEmails);
if (receipt != null) {
mail.setWhatId(receipt.Id);
}
mail.setBccSender(false);
mail.setUseSignature(false);
mail.setSaveAsActivity(false);
List<Messaging.Emailfileattachment> fileAttachments = new List<Messaging.Emailfileattachment> ();
for (Attachment attch : attachments) {
Messaging.Emailfileattachment efa = new Messaging.Emailfileattachment();
efa.setFileName(attch.Name);
efa.setBody(attch.Body);
fileAttachments.add(efa);
}
if (fileAttachments != null && fileAttachments.size()> 0) {
mail.setFileAttachments(fileAttachments);
}
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
public static List<MUSW__Fee__c> getCartFees(BGBK__Cart__c cart, Boolean outstandingOnly) {
String soql = 'select CreatedDate, Id, Name, MUSW__Amended_Amount__c, MUSW__Amount_Paid__c, MUSW__Amount_Waived__c,' +
'MUSW__Amount__c, MUSW__Application2__c, MUSW__Complaint2__c, MUSW__Permit__c, MUSW__Planning_Application__c, MUSW__License1__c, MUSW__Complaint__c, SoM_Hearing_and_Appeals__c,' +
'MUSW__Description__c, MUSW__Effective_Date__c, MUSW__Fee_Paid_By__c, MUSW__Fee_Paid_Date2__c,' +
'MUSW__Fee_Paid_Date__c, MUSW__Fee_Paid__c, MUSW__License2__c, MUSW__Rank__c, SoM_Complaint_Violation__c,' +
'MUSW__Outstanding_Fee__c, MUSW__Paid_By__c, MUSW__Payment_Comment__c, MUSW__Payment_Method__c, MUSW__Permit2__c,' +
'MUSW__Receipt_Number__c, MUSW__Type__c, BGBK__Cart_Exclude__c, BGBK__Cart__c, BGBK__Due_Date__c, BGBK__Invoice__c,' +
'Related_To__c, SoM_View_Cart__c, SoM_Request__c, SOM_License_Renewal__c, SoM_Renewal_License_Permit__c, SoM_Is_Returnable__c, SOM_Historical__c,' +
'Revenue_Source__c, BGBK__Cart_Cashier__c, DDRE_Value__c, Fund_Splits__c, Magic_Code__c, Fee_Description__c,' +
'Fee_Name__c, Fee_Number__c ' +
'from MUSW__Fee__c where BGBK__Cart__c=\'' + cart.Id + '\' ';
if (outstandingOnly) {
soql += 'AND MUSW__Outstanding_Fee__c>0 ';
}
return Database.query(soql);
}
/*
* Method name : createPayment
* Description : Assumptions:
* - Validations:
* - All validations on the PayPoint response have now been made. In particular, the cart total amount
* matches the requested amount as cart fees can be pulled out by other users (no way to stop this)
* - Database transaction management and error handling:
* - A save point was created before this method is called.
* - All updates made in this module will be rolled back by the calling service (via the above savepoint) if any errors are thrown by this service.
* - If any issues occur in this service an error with the details in the message will be thrown.
* - Errors cannot be logged here as the caller must roll back all updates that have occured first before logging!
* - Transaction fees:
* - If the amount paid is greater than the amount requested this difference is assumed to be transation fees.
*
* Processing:
* - Create the Receipt with all fees
* - If credit card conveinience fees were added to the payment add this amount to the Receipt.
* - Close the Cart
* - Update the CEPAS Tx with:
* - Receipt
* - Successfully Processed status
*
* Return Type : nothing -
* Parameter : CEPAS_TxId - The Id CEPAS transaction to process. Passed in just the id so the calling process can safely
* update the copy of this record in the calling process on error. With out this updates made in
* this method before an error occurs were getting comitted (causing bad data or exceptions like INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY due to a rolled back receipt being referenced).
* cartService - The related cart (Must be initilized to the same cart as the CEPAS Transaction references)
* fees - the Cart fees
* invoices - the Cart invoices
*/
// This exception is thrown when the payment creation fails
public class PaymentCreationException extends Exception { }
public static void createPayment(id CEPAS_TxId,
//BGBK.CartService cartService,
BGBK__Cart__c cart,
MUSW__Fee__c[] fees,
BGBK__Bill__c[] invoices) {
System.debug('PayPointTransaction::createPayment()');
Payment_Processor_Transaction__c CEPAS_Tx = [select id, name, Payment_Amount__c, Requested_Payment_Amount__c, Card_Type__c,
cart__c, status__c, Payment_Amount_Variance__c, receipt__c, Admin_Investigation_Reason__c
from Payment_Processor_Transaction__c where id = :CEPAS_TxId];
System.debug('CEPAS Transaction: ' + CEPAS_Tx);
DateTime processDt = DateTime.now(); // when payment was processed
//BGBK__Cart__c cart = getCart(CEPAS_Tx.Cart__c);
cart.BGBK__Process_DateTime__c = processDt; //set Process Date
update cart; // update the process date
//Account cartAccount = cartService.getActiveAccount();
//ID cartAccountId = null;
//if (cartAccount != null) {
//cartAccountId = cartAccount.id;
//}
ID cartAccountId = cart.BGBK__Account__c;
ID cartContactId = cart.BGBK__Contact__c;
//ID cartContactId = null;
//Contact cartContact = cartService.getActiveContact();
//if (cartContact != null) {
//cartContactId = cartContact.id;
//}
// validate the amounts can be trusted -- just paranoid
if ((CEPAS_TX.Payment_Amount__c - CEPAS_TX.Requested_Payment_Amount__c) != CEPAS_TX.Payment_Amount_Variance__c) {
throw new PaymentCreationException('Payment_Processor_Transaction__c.Payment_Amount_Variance__c does not equal payment amount - requested amount');
}
// create a new receipt
string paymentMethod;
if (string.isNotBlank(CEPAS_Tx.Card_Type__c)) {
paymentMethod = MlccConstants.ReceiptPaymentMethodCreditCard;
} else {
paymentMethod = MlccConstants.ReceiptPaymentMethodECheck;
}
MUSW__Receipt__c receipt = new MUSW__Receipt__c(MUSW__Payment_Method__c = paymentMethod,
MUSW__Amount_Tendered__c = CEPAS_TX.Requested_Payment_Amount__c,
SoM_CEPAS_Convenience_Fee__c = CEPAS_TX.Payment_Amount_Variance__c,
MUSW__Paid_By__c = cartAccountId,
MUSW__Receipt_Paid_by__c = cartContactId,
BGBK__Cart__c = cart.id,
PayPoint_Transaction__c = CEPAS_Tx.id,
MUSW__Card_Type__c = CEPAS_Tx.Card_Type__c);
insert receipt;
// construct map of Receipts to amounts (used by processPayment)
Map<MUSW__Receipt__c, Decimal> receiptAmounts = new Map<MUSW__Receipt__c, Decimal> ();
receiptAmounts.put(receipt, CEPAS_TX.Payment_Amount__c);
// create a list of payables for processPayment
BGBK.Payable[] payables = new list<BGBK.Payable> ();
for (MUSW__Fee__c f : fees) {
payables.add(new BGBK.Payable(f, f.MUSW__Outstanding_Fee__c));
}
for (BGBK__Bill__c i : invoices) {