1 # ---------------------------------------------------------------
2 # Copyright (C) 2005 Georgia Public Library Service
3 # Bill Erickson <billserickson@gmail.com>
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 # ---------------------------------------------------------------
16 package OpenILS::Application::Circ::Money;
17 use base qw/OpenILS::Application/;
18 use strict; use warnings;
19 use OpenILS::Application::AppUtils;
20 my $apputils = "OpenILS::Application::AppUtils";
21 my $U = "OpenILS::Application::AppUtils";
23 use OpenSRF::EX qw(:try);
27 use OpenSRF::Utils::Logger qw/:logger/;
28 use OpenILS::Utils::CStoreEditor qw/:funcs/;
29 use OpenILS::Utils::Penalty;
30 $Data::Dumper::Indent = 0;
32 __PACKAGE__->register_method(
33 method => "make_payments",
34 api_name => "open-ils.circ.money.payment",
36 desc => q/Create payments for a given user and set of transactions,
37 login must have CREATE_PAYMENT privileges.
38 If any payments fail, all are reverted back./,
40 {desc => 'Authtoken', type => 'string'},
41 {desc => q/Arguments Hash, supporting the following params:
48 where_process 1 to use processor, !1 for out-of-band
49 approval_code (for out-of-band payment)
50 type (for out-of-band payment)
51 number (for call to payment processor)
52 expire_month (for call to payment processor)
53 expire_year (for call to payment processor)
54 billing_first (for out-of-band payments and for call to payment processor)
55 billing_last (for out-of-band payments and for call to payment processor)
56 billing_address (for call to payment processor)
57 billing_city (for call to payment processor)
58 billing_state (for call to payment processor)
59 billing_zip (for call to payment processor)
60 note (if payments->{note} is blank, use this)
70 desc => q/Last user transaction ID. This is the actor.usr.last_xact_id value/,
76 q{Array of payment IDs on success, event on failure. Event possibilities include:
78 Bad parameters were given to this API method itself.
81 The last user transaction ID does not match the ID in the database. This means
82 the user object has been updated since the last retrieval. The client should
83 be instructed to reload the user object and related transactions before attempting
85 REFUND_EXCEEDS_BALANCE
86 REFUND_EXCEEDS_DESK_PAYMENTS
87 CREDIT_PROCESSOR_NOT_SPECIFIED
88 Evergreen has not been set up to process CC payments.
89 CREDIT_PROCESSOR_NOT_ALLOWED
90 Evergreen has been incorrectly setup for CC payments.
91 CREDIT_PROCESSOR_NOT_ENABLED
92 Evergreen has been set up for CC payments, but an admin
93 has not explicitly enabled them.
94 CREDIT_PROCESSOR_BAD_PARAMS
95 Evergreen has been incorrectly setup for CC payments;
96 specifically, the login and/or password for the CC
97 processor weren't provided.
98 CREDIT_PROCESSOR_INVALID_CC_NUMBER
99 You have supplied a credit card number that Evergreen
100 has judged to be invalid even before attempting to contact
101 the payment processor.
102 CREDIT_PROCESSOR_DECLINED_TRANSACTION
103 We contacted the CC processor to attempt the charge, but
105 The error_message field of the event payload will
106 contain the payment processor's response. This
107 typically includes a message in plain English intended
108 for human consumption. In PayPal's case, the message
109 is preceded by an integer, a colon, and a space, so
110 a caller might take the 2nd match from /^(\d+: )?(.+)$/
111 to present to the user.
112 The payload also contains other fields from the payment
113 processor, but these are generally not user-friendly
115 CREDIT_PROCESSOR_SUCCESS_WO_RECORD
116 A payment was processed successfully, but couldn't be
117 recorded in Evergreen. This is _bad bad bad_, as it means
118 somebody made a payment but isn't getting credit for it.
119 See errors in the system log if this happens. Info from
120 the credit card transaction will also be available in the
121 event payload, although this probably won't be suitable for
122 staff client/OPAC display.
129 my($self, $client, $auth, $payments, $last_xact_id) = @_;
131 my $e = new_editor(authtoken => $auth, xact => 1);
132 return $e->die_event unless $e->checkauth;
134 my $type = $payments->{payment_type};
135 my $user_id = $payments->{userid};
136 my $credit = $payments->{patron_credit} || 0;
137 my $drawer = $e->requestor->wsid;
138 my $note = $payments->{note};
139 my $cc_args = $payments->{cc_args};
140 my $check_number = $payments->{check_number};
142 my $this_ou = $e->requestor->ws_ou;
145 # unless/until determined by payment processor API
146 my ($approval_code, $cc_processor, $cc_type) = (undef,undef,undef);
148 my $patron = $e->retrieve_actor_user($user_id) or return $e->die_event;
150 if($patron->last_xact_id ne $last_xact_id) {
152 return OpenILS::Event->new('INVALID_USER_XACT_ID');
155 # A user is allowed to make credit card payments on his/her own behalf
156 # All other scenarious require permission
157 unless($type eq 'credit_card_payment' and $user_id == $e->requestor->id) {
158 return $e->die_event unless $e->allowed('CREATE_PAYMENT', $patron->home_ou);
161 # first collect the transactions and make sure the transaction
162 # user matches the requested user
164 for my $pay (@{$payments->{payments}}) {
165 my $xact_id = $pay->[0];
166 my $xact = $e->retrieve_money_billable_transaction_summary($xact_id)
167 or return $e->die_event;
169 if($xact->usr != $user_id) {
171 return OpenILS::Event->new('BAD_PARAMS', note => q/user does not match transaction/);
174 $xacts{$xact_id} = $xact;
179 for my $pay (@{$payments->{payments}}) {
180 my $transid = $pay->[0];
181 my $amount = $pay->[1];
182 $amount =~ s/\$//og; # just to be safe
183 my $trans = $xacts{$transid};
185 $total_paid += $amount;
187 $orgs{$U->xact_org($transid, $e)} = 1;
189 # A negative payment is a refund.
192 # Negative credit card payments are not allowed
193 if($type eq 'credit_card_payment') {
195 return OpenILS::Event->new(
197 note => q/Negative credit card payments not allowed/
201 # If the refund causes the transaction balance to exceed 0 dollars,
202 # we are in effect loaning the patron money. This is not allowed.
203 if( ($trans->balance_owed - $amount) > 0 ) {
205 return OpenILS::Event->new('REFUND_EXCEEDS_BALANCE');
208 # Otherwise, make sure the refund does not exceed desk payments
209 # This is also not allowed
211 my $desk_payments = $e->search_money_desk_payment({xact => $transid, voided => 'f'});
212 $desk_total += $_->amount for @$desk_payments;
214 if( (-$amount) > $desk_total ) {
216 return OpenILS::Event->new(
217 'REFUND_EXCEEDS_DESK_PAYMENTS',
218 payload => { allowed_refund => $desk_total, submitted_refund => -$amount } );
222 my $payobj = "Fieldmapper::money::$type";
223 $payobj = $payobj->new;
225 $payobj->amount($amount);
226 $payobj->amount_collected($amount);
227 $payobj->xact($transid);
228 $payobj->note($note);
229 if ((not $payobj->note) and ($type eq 'credit_card_payment')) {
230 $payobj->note($cc_args->{note});
233 if ($payobj->has_field('accepting_usr')) { $payobj->accepting_usr($e->requestor->id); }
234 if ($payobj->has_field('cash_drawer')) { $payobj->cash_drawer($drawer); }
235 if ($payobj->has_field('cc_type')) { $payobj->cc_type($cc_args->{type}); }
236 if ($payobj->has_field('check_number')) { $payobj->check_number($check_number); }
238 # Store the last 4 digits of the CC number
239 if ($payobj->has_field('cc_number')) {
240 $payobj->cc_number(substr($cc_args->{number}, -4));
242 if ($payobj->has_field('expire_month')) { $payobj->expire_month($cc_args->{expire_month}); }
243 if ($payobj->has_field('expire_year')) { $payobj->expire_year($cc_args->{expire_year}); }
245 # Note: It is important not to set approval_code
246 # on the fieldmapper object yet.
248 push(@payment_objs, $payobj);
250 } # all payment objects have been created and inserted.
252 #### NO WRITES TO THE DB ABOVE THIS LINE -- THEY'LL ONLY BE DISCARDED ###
255 # After we try to externally process a credit card (if desired), we'll
256 # open a new transaction. We cannot leave one open while credit card
257 # processing might be happening, as it can easily time out the database
262 if($type eq 'credit_card_payment') {
263 $approval_code = $cc_args->{approval_code};
264 # If an approval code was not given, we'll need
265 # to call to the payment processor ourselves.
266 if ($cc_args->{where_process} == 1) {
267 return OpenILS::Event->new('BAD_PARAMS', note => 'Need CC number')
268 if not $cc_args->{number};
270 OpenILS::Application::Circ::CreditCard::process_payment({
271 "desc" => $cc_args->{note},
272 "amount" => $total_paid,
273 "patron_id" => $user_id,
274 "cc" => $cc_args->{number},
275 "expiration" => sprintf(
277 $cc_args->{expire_month},
278 $cc_args->{expire_year}
281 "first_name" => $cc_args->{billing_first},
282 "last_name" => $cc_args->{billing_last},
283 "address" => $cc_args->{billing_address},
284 "city" => $cc_args->{billing_city},
285 "state" => $cc_args->{billing_state},
286 "zip" => $cc_args->{billing_zip},
289 if ($U->event_code($response)) { # non-success
291 "Credit card payment for user $user_id failed: " .
292 $response->{"textcode"} . " " .
293 $response->{"payload"}->{"error_message"}
298 # We need to save this for later in case there's a failure on
299 # the EG side to store the processor's result.
300 $cc_payload = $response->{"payload"};
302 $approval_code = $cc_payload->{"authorization"};
303 $cc_type = $cc_payload->{"card_type"};
304 $cc_processor = $cc_payload->{"processor"};
305 $logger->info("Credit card payment for user $user_id succeeded");
308 return OpenILS::Event->new(
309 'BAD_PARAMS', note => 'Need approval code'
310 ) if not $cc_args->{approval_code};
314 ### RE-OPEN TRANSACTION HERE ###
318 # create payment records
319 my $create_money_method = "create_money_" . $type;
320 for my $payment (@payment_objs) {
321 # update the transaction if it's done
322 my $amount = $payment->amount;
323 my $transid = $payment->xact;
324 my $trans = $xacts{$transid};
325 if( (my $cred = ($trans->balance_owed - $amount)) <= 0 ) {
326 # Any overpay on this transaction goes directly into patron
327 # credit making payment with existing patron credit.
328 $credit -= $amount if $type eq 'credit_payment';
332 my $circ = $e->retrieve_action_circulation($transid);
334 if(!$circ || $circ->stop_fines) {
335 # If this is a circulation, we can't close the transaction
336 # unless stop_fines is set.
337 $trans = $e->retrieve_money_billable_transaction($transid);
338 $trans->xact_finish("now");
339 if (!$e->update_money_billable_transaction($trans)) {
340 return _recording_failure(
341 $e, "update_money_billable_transaction() failed",
342 $payment, $cc_payload
348 $payment->approval_code($approval_code) if $approval_code;
349 $payment->cc_type($cc_type) if $cc_type;
350 $payment->cc_processor($cc_processor) if $cc_processor;
351 $payment->cc_first_name($cc_args->{'billing_first'}) if $cc_args->{'billing_first'};
352 $payment->cc_last_name($cc_args->{'billing_last'}) if $cc_args->{'billing_last'};
353 if (!$e->$create_money_method($payment)) {
354 return _recording_failure(
355 $e, "$create_money_method failed", $payment, $cc_payload
359 push(@payment_ids, $payment->id);
362 my $evt = _update_patron_credit($e, $patron, $credit);
364 return _recording_failure(
365 $e, "_update_patron_credit() failed", undef, $cc_payload
369 for my $org_id (keys %orgs) {
370 # calculate penalties for each of the affected orgs
371 $evt = OpenILS::Utils::Penalty->calculate_penalties(
372 $e, $user_id, $org_id
375 return _recording_failure(
376 $e, "calculate_penalties() failed", undef, $cc_payload
381 # update the user to create a new last_xact_id
382 $e->update_actor_user($patron) or return $e->die_event;
383 $patron = $e->retrieve_actor_user($patron) or return $e->die_event;
386 return {last_xact_id => $patron->last_xact_id, payments => \@payment_ids};
389 sub _recording_failure {
390 my ($e, $msg, $payment, $payload) = @_;
392 if ($payload) { # If the payment processor already accepted a payment:
393 $logger->error($msg);
394 $logger->error("Payment processor payload: " . Dumper($payload));
395 # payment shouldn't contain CC number
396 $logger->error("Payment: " . Dumper($payment)) if $payment;
400 return new OpenILS::Event(
401 "CREDIT_PROCESSOR_SUCCESS_WO_RECORD",
402 "payload" => $payload
404 } else { # Otherwise, the problem is somewhat less severe:
406 $logger->warn("Payment: " . Dumper($payment)) if $payment;
407 return $e->die_event;
411 sub _update_patron_credit {
412 my($e, $patron, $credit) = @_;
413 return undef if $credit == 0;
414 $patron->credit_forward_balance($patron->credit_forward_balance + $credit);
415 return OpenILS::Event->new('NEGATIVE_PATRON_BALANCE') if $patron->credit_forward_balance < 0;
416 $e->update_actor_user($patron) or return $e->die_event;
421 __PACKAGE__->register_method(
422 method => "retrieve_payments",
423 api_name => "open-ils.circ.money.payment.retrieve.all_",
424 notes => "Returns a list of payments attached to a given transaction"
426 sub retrieve_payments {
427 my( $self, $client, $login, $transid ) = @_;
430 $apputils->checksesperm($login, 'VIEW_TRANSACTION');
433 # XXX the logic here is wrong.. we need to check the owner of the transaction
434 # to make sure the requestor has access
436 # XXX grab the view, for each object in the view, grab the real object
438 return $apputils->simplereq(
440 'open-ils.cstore.direct.money.payment.search.atomic', { xact => $transid } );
444 __PACKAGE__->register_method(
445 method => "retrieve_payments2",
447 api_name => "open-ils.circ.money.payment.retrieve.all",
448 notes => "Returns a list of payments attached to a given transaction"
451 sub retrieve_payments2 {
452 my( $self, $client, $login, $transid ) = @_;
454 my $e = new_editor(authtoken=>$login);
455 return $e->event unless $e->checkauth;
456 return $e->event unless $e->allowed('VIEW_TRANSACTION');
459 my $pmnts = $e->search_money_payment({ xact => $transid });
461 my $type = $_->payment_type;
462 my $meth = "retrieve_money_$type";
463 my $p = $e->$meth($_->id) or return $e->event;
464 $p->payment_type($type);
465 $p->cash_drawer($e->retrieve_actor_workstation($p->cash_drawer))
466 if $p->has_field('cash_drawer');
467 push( @payments, $p );
473 __PACKAGE__->register_method(
474 method => "format_payment_receipt",
475 api_name => "open-ils.circ.money.payment_receipt.print",
477 desc => 'Returns a printable receipt for the specified payments',
479 { desc => 'Authentication token', type => 'string'},
480 { desc => 'Payment ID or array of payment IDs', type => 'number' },
483 desc => q/An action_trigger.event object or error event./,
488 __PACKAGE__->register_method(
489 method => "format_payment_receipt",
490 api_name => "open-ils.circ.money.payment_receipt.email",
492 desc => 'Emails a receipt for the specified payments to the user associated with the first payment',
494 { desc => 'Authentication token', type => 'string'},
495 { desc => 'Payment ID or array of payment IDs', type => 'number' },
498 desc => q/Undefined on success, otherwise an error event./,
504 sub format_payment_receipt {
505 my($self, $conn, $auth, $mp_id) = @_;
508 if (ref $mp_id ne 'ARRAY') {
509 $mp_ids = [ $mp_id ];
514 my $for_print = ($self->api_name =~ /print/);
515 my $for_email = ($self->api_name =~ /email/);
516 my $e = new_editor(authtoken => $auth);
517 return $e->event unless $e->checkauth;
520 for my $id (@$mp_ids) {
522 my $payment = $e->retrieve_money_payment([
530 ]) or return OpenILS::Event->new('MP_NOT_FOUND');
532 return $e->event unless $e->allowed('VIEW_TRANSACTION', $payment->xact->usr->home_ou);
534 push @$payments, $payment;
539 return $U->fire_object_event(undef, 'money.format.payment_receipt.print', $payments, $$payments[0]->xact->usr->home_ou);
541 } elsif ($for_email) {
543 for my $p (@$payments) {
544 $U->create_events_for_hook('money.format.payment_receipt.email', $p, $p->xact->usr->home_ou, undef, undef, 1);
551 __PACKAGE__->register_method(
552 method => "create_grocery_bill",
553 api_name => "open-ils.circ.money.grocery.create",
555 Creates a new grocery transaction using the transaction object provided
556 PARAMS: (login_session, money.grocery (mg) object)
559 sub create_grocery_bill {
560 my( $self, $client, $login, $transaction ) = @_;
562 my( $staff, $evt ) = $apputils->checkses($login);
564 $evt = $apputils->check_perms($staff->id,
565 $transaction->billing_location, 'CREATE_TRANSACTION' );
569 $logger->activity("Creating grocery bill " . Dumper($transaction) );
571 $transaction->clear_id;
572 my $session = $apputils->start_db_session;
573 my $transid = $session->request(
574 'open-ils.storage.direct.money.grocery.create', $transaction)->gather(1);
576 throw OpenSRF::EX ("Error creating new money.grocery") unless defined $transid;
578 $logger->debug("Created new grocery transaction $transid");
580 $apputils->commit_db_session($session);
582 my $e = new_editor(xact=>1);
583 $evt = _check_open_xact($e, $transid);
591 __PACKAGE__->register_method(
592 method => 'fetch_reservation',
593 api_name => 'open-ils.circ.booking.reservation.retrieve'
595 sub fetch_reservation {
596 my( $self, $conn, $auth, $id ) = @_;
597 my $e = new_editor(authtoken=>$auth);
598 return $e->event unless $e->checkauth;
599 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
600 my $g = $e->retrieve_booking_reservation($id)
605 __PACKAGE__->register_method(
606 method => 'fetch_grocery',
607 api_name => 'open-ils.circ.money.grocery.retrieve'
610 my( $self, $conn, $auth, $id ) = @_;
611 my $e = new_editor(authtoken=>$auth);
612 return $e->event unless $e->checkauth;
613 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
614 my $g = $e->retrieve_money_grocery($id)
620 __PACKAGE__->register_method(
621 method => "billing_items",
622 api_name => "open-ils.circ.money.billing.retrieve.all",
625 desc => 'Returns a list of billing items for the given transaction ID. ' .
626 'If the operator is not the owner of the transaction, the VIEW_TRANSACTION permission is required.',
628 { desc => 'Authentication token', type => 'string'},
629 { desc => 'Transaction ID', type => 'number'}
632 desc => 'Transaction object, event on error'
638 my( $self, $client, $login, $transid ) = @_;
640 my( $trans, $evt ) = $U->fetch_billable_xact($transid);
644 ($staff, $evt ) = $apputils->checkses($login);
647 if($staff->id ne $trans->usr) {
648 $evt = $U->check_perms($staff->id, $staff->home_ou, 'VIEW_TRANSACTION');
652 return $apputils->simplereq( 'open-ils.cstore',
653 'open-ils.cstore.direct.money.billing.search.atomic', { xact => $transid } )
657 __PACKAGE__->register_method(
658 method => "billing_items_create",
659 api_name => "open-ils.circ.money.billing.create",
661 Creates a new billing line item
662 PARAMS( login, bill_object (mb) )
665 sub billing_items_create {
666 my( $self, $client, $login, $billing ) = @_;
668 my $e = new_editor(authtoken => $login, xact => 1);
669 return $e->die_event unless $e->checkauth;
670 return $e->die_event unless $e->allowed('CREATE_BILL');
672 my $xact = $e->retrieve_money_billable_transaction($billing->xact)
673 or return $e->die_event;
675 # if the transaction was closed, re-open it
676 if($xact->xact_finish) {
677 $xact->clear_xact_finish;
678 $e->update_money_billable_transaction($xact)
679 or return $e->die_event;
682 my $amt = $billing->amount;
684 $billing->amount($amt);
686 $e->create_money_billing($billing) or return $e->die_event;
687 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $xact->usr, $U->xact_org($xact->id));
695 __PACKAGE__->register_method(
696 method => 'void_bill',
697 api_name => 'open-ils.circ.money.billing.void',
700 @param authtoken Login session key
701 @param billid Id for the bill to void. This parameter may be repeated to reference other bills.
702 @return 1 on success, Event on error
706 my( $s, $c, $authtoken, @billids ) = @_;
708 my $e = new_editor( authtoken => $authtoken, xact => 1 );
709 return $e->die_event unless $e->checkauth;
710 return $e->die_event unless $e->allowed('VOID_BILLING');
713 for my $billid (@billids) {
715 my $bill = $e->retrieve_money_billing($billid)
716 or return $e->die_event;
718 my $xact = $e->retrieve_money_billable_transaction($bill->xact)
719 or return $e->die_event;
721 if($U->is_true($bill->voided)) {
723 return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
726 my $org = $U->xact_org($bill->xact, $e);
727 $users{$xact->usr} = {} unless $users{$xact->usr};
728 $users{$xact->usr}->{$org} = 1;
731 $bill->voider($e->requestor->id);
732 $bill->void_time('now');
734 $e->update_money_billing($bill) or return $e->die_event;
735 my $evt = _check_open_xact($e, $bill->xact, $xact);
739 # calculate penalties for all user/org combinations
740 for my $user_id (keys %users) {
741 for my $org_id (keys %{$users{$user_id}}) {
742 OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
750 __PACKAGE__->register_method(
751 method => 'edit_bill_note',
752 api_name => 'open-ils.circ.money.billing.note.edit',
754 Edits the note for a bill
755 @param authtoken Login session key
756 @param note The replacement note for the bills we're editing
757 @param billid Id for the bill to edit the note of. This parameter may be repeated to reference other bills.
758 @return 1 on success, Event on error
762 my( $s, $c, $authtoken, $note, @billids ) = @_;
764 my $e = new_editor( authtoken => $authtoken, xact => 1 );
765 return $e->die_event unless $e->checkauth;
766 return $e->die_event unless $e->allowed('UPDATE_BILL_NOTE');
768 for my $billid (@billids) {
770 my $bill = $e->retrieve_money_billing($billid)
771 or return $e->die_event;
774 # FIXME: Does this get audited? Need some way so that the original creator of the bill does not get credit/blame for the new note.
776 $e->update_money_billing($bill) or return $e->die_event;
783 __PACKAGE__->register_method(
784 method => 'edit_payment_note',
785 api_name => 'open-ils.circ.money.payment.note.edit',
787 Edits the note for a payment
788 @param authtoken Login session key
789 @param note The replacement note for the payments we're editing
790 @param paymentid Id for the payment to edit the note of. This parameter may be repeated to reference other payments.
791 @return 1 on success, Event on error
794 sub edit_payment_note {
795 my( $s, $c, $authtoken, $note, @paymentids ) = @_;
797 my $e = new_editor( authtoken => $authtoken, xact => 1 );
798 return $e->die_event unless $e->checkauth;
799 return $e->die_event unless $e->allowed('UPDATE_PAYMENT_NOTE');
801 for my $paymentid (@paymentids) {
803 my $payment = $e->retrieve_money_payment($paymentid)
804 or return $e->die_event;
806 $payment->note($note);
807 # FIXME: Does this get audited? Need some way so that the original taker of the payment does not get credit/blame for the new note.
809 $e->update_money_payment($payment) or return $e->die_event;
816 sub _check_open_xact {
817 my( $editor, $xactid, $xact ) = @_;
819 # Grab the transaction
820 $xact ||= $editor->retrieve_money_billable_transaction($xactid);
821 return $editor->event unless $xact;
822 $xactid ||= $xact->id;
824 # grab the summary and see how much is owed on this transaction
825 my ($summary) = $U->fetch_mbts($xactid, $editor);
827 # grab the circulation if it is a circ;
828 my $circ = $editor->retrieve_action_circulation($xactid);
830 # If nothing is owed on the transaction but it is still open
831 # and this transaction is not an open circulation, close it
833 ( $summary->balance_owed == 0 and ! $xact->xact_finish ) and
834 ( !$circ or $circ->stop_fines )) {
836 $logger->info("closing transaction ".$xact->id. ' becauase balance_owed == 0');
837 $xact->xact_finish('now');
838 $editor->update_money_billable_transaction($xact)
839 or return $editor->event;
843 # If money is owed or a refund is due on the xact and xact_finish
844 # is set, clear it (to reopen the xact) and update
845 if( $summary->balance_owed != 0 and $xact->xact_finish ) {
846 $logger->info("re-opening transaction ".$xact->id. ' becauase balance_owed != 0');
847 $xact->clear_xact_finish;
848 $editor->update_money_billable_transaction($xact)
849 or return $editor->event;
856 __PACKAGE__->register_method (
857 method => 'fetch_mbts',
859 api_name => 'open-ils.circ.money.billable_xact_summary.retrieve'
862 my( $self, $conn, $auth, $id) = @_;
864 my $e = new_editor(xact => 1, authtoken=>$auth);
865 return $e->event unless $e->checkauth;
866 my ($mbts) = $U->fetch_mbts($id, $e);
868 my $user = $e->retrieve_actor_user($mbts->usr)
869 or return $e->die_event;
871 return $e->die_event unless $e->allowed('VIEW_TRANSACTION', $user->home_ou);
877 __PACKAGE__->register_method(
878 method => 'desk_payments',
879 api_name => 'open-ils.circ.money.org_unit.desk_payments'
882 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
883 my $e = new_editor(authtoken=>$auth);
884 return $e->event unless $e->checkauth;
885 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
886 my $data = $U->storagereq(
887 'open-ils.storage.money.org_unit.desk_payments.atomic',
888 $org, $start_date, $end_date );
890 $_->workstation( $_->workstation->name ) for(@$data);
895 __PACKAGE__->register_method(
896 method => 'user_payments',
897 api_name => 'open-ils.circ.money.org_unit.user_payments'
901 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
902 my $e = new_editor(authtoken=>$auth);
903 return $e->event unless $e->checkauth;
904 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
905 my $data = $U->storagereq(
906 'open-ils.storage.money.org_unit.user_payments.atomic',
907 $org, $start_date, $end_date );
910 $e->retrieve_actor_card($_->usr->card)->barcode);
912 $e->retrieve_actor_org_unit($_->usr->home_ou)->shortname);
918 __PACKAGE__->register_method(
919 method => 'retrieve_credit_payable_balance',
920 api_name => 'open-ils.circ.credit.payable_balance.retrieve',
923 desc => q/Returns the total amount the patron can pay via credit card/,
925 { desc => 'Authentication token', type => 'string' },
926 { desc => 'User id', type => 'number' }
928 return => { desc => 'The ID of the new provider' }
932 sub retrieve_credit_payable_balance {
933 my ( $self, $conn, $auth, $user_id ) = @_;
934 my $e = new_editor(authtoken => $auth);
935 return $e->event unless $e->checkauth;
937 my $user = $e->retrieve_actor_user($user_id)
940 if($e->requestor->id != $user_id) {
941 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou)
944 my $circ_orgs = $e->json_query({
945 "select" => {circ => ["circ_lib"]},
947 "where" => {usr => $user_id, xact_finish => undef},
951 my $groc_orgs = $e->json_query({
952 "select" => {mg => ["billing_location"]},
954 "where" => {usr => $user_id, xact_finish => undef},
959 for my $org ( @$circ_orgs, @$groc_orgs ) {
960 my $o = $org->{billing_location};
961 $o = $org->{circ_lib} unless $o;
962 next if $hash{$o}; # was $hash{$org}, but that doesn't make sense. $org is a hashref and $o gets added in the next line.
963 $hash{$o} = $U->ou_ancestor_setting_value($o, 'credit.payments.allow', $e);
966 my @credit_orgs = map { $hash{$_} ? ($_) : () } keys %hash;
967 $logger->debug("credit: relevant orgs that allow credit payments => @credit_orgs");
970 OpenILS::Application::AppUtils->simplereq('open-ils.actor',
971 'open-ils.actor.user.transactions.have_charge', $auth, $user_id);
975 for my $xact (@$xact_summaries) {
977 # make two lists and grab them in batch XXX
978 if ( $xact->xact_type eq 'circulation' ) {
979 my $circ = $e->retrieve_action_circulation($xact->id) or return $e->event;
980 next unless grep { $_ == $circ->circ_lib } @credit_orgs;
982 } elsif ($xact->xact_type eq 'grocery') {
983 my $bill = $e->retrieve_money_grocery($xact->id) or return $e->event;
984 next unless grep { $_ == $bill->billing_location } @credit_orgs;
985 } elsif ($xact->xact_type eq 'reservation') {
986 my $bill = $e->retrieve_booking_reservation($xact->id) or return $e->event;
987 next unless grep { $_ == $bill->pickup_lib } @credit_orgs;
989 $sum += $xact->balance_owed();