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 call to payment processor)
55 billing_last (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)
72 q{Array of payment IDs on success, event on failure. Event possibilities include:
74 Bad parameters were given to this API method itself.
76 REFUND_EXCEEDS_BALANCE
77 REFUND_EXCEEDS_DESK_PAYMENTS
78 CREDIT_PROCESSOR_NOT_SPECIFIED
79 Evergreen has not been set up to process CC payments.
80 CREDIT_PROCESSOR_NOT_ALLOWED
81 Evergreen has been incorrectly setup for CC payments.
82 CREDIT_PROCESSOR_NOT_ENABLED
83 Evergreen has been set up for CC payments, but an admin
84 has not explicitly enabled them.
85 CREDIT_PROCESSOR_BAD_PARAMS
86 Evergreen has been incorrectly setup for CC payments;
87 specifically, the login and/or password for the CC
88 processor weren't provided.
89 CREDIT_PROCESSOR_INVALID_CC_NUMBER
90 You have supplied a credit card number that Evergreen
91 has judged to be invalid even before attempting to contact
92 the payment processor.
93 CREDIT_PROCESSOR_DECLINED_TRANSACTION
94 We contacted the CC processor to attempt the charge, but
96 The error_message field of the event payload will
97 contain the payment processor's response. This
98 typically includes a message in plain English intended
99 for human consumption. In PayPal's case, the message
100 is preceded by an integer, a colon, and a space, so
101 a caller might take the 2nd match from /^(\d+: )?(.+)$/
102 to present to the user.
103 The payload also contains other fields from the payment
104 processor, but these are generally not user-friendly
106 CREDIT_PROCESSOR_SUCCESS_WO_RECORD
107 A payment was processed successfully, but couldn't be
108 recorded in Evergreen. This is _bad bad bad_, as it means
109 somebody made a payment but isn't getting credit for it.
110 See errors in the system log if this happens. Info from
111 the credit card transaction will also be available in the
112 event payload, although this probably won't be suitable for
113 staff client/OPAC display.
120 my($self, $client, $auth, $payments) = @_;
122 my $e = new_editor(authtoken => $auth, xact => 1);
123 return $e->die_event unless $e->checkauth;
125 my $type = $payments->{payment_type};
126 my $user_id = $payments->{userid};
127 my $credit = $payments->{patron_credit} || 0;
128 my $drawer = $e->requestor->wsid;
129 my $note = $payments->{note};
130 my $cc_args = $payments->{cc_args};
131 my $check_number = $payments->{check_number};
133 my $this_ou = $e->requestor->ws_ou;
136 # unless/until determined by payment processor API
137 my ($approval_code, $cc_processor, $cc_type) = (undef,undef,undef);
139 my $patron = $e->retrieve_actor_user($user_id) or return $e->die_event;
141 # A user is allowed to make credit card payments on his/her own behalf
142 # All other scenarious require permission
143 unless($type eq 'credit_card_payment' and $user_id == $e->requestor->id) {
144 return $e->die_event unless $e->allowed('CREATE_PAYMENT', $patron->home_ou);
147 # first collect the transactions and make sure the transaction
148 # user matches the requested user
150 for my $pay (@{$payments->{payments}}) {
151 my $xact_id = $pay->[0];
152 my $xact = $e->retrieve_money_billable_transaction_summary($xact_id)
153 or return $e->die_event;
155 if($xact->usr != $user_id) {
157 return OpenILS::Event->new('BAD_PARAMS', note => q/user does not match transaction/);
160 $xacts{$xact_id} = $xact;
165 for my $pay (@{$payments->{payments}}) {
166 my $transid = $pay->[0];
167 my $amount = $pay->[1];
168 $amount =~ s/\$//og; # just to be safe
169 my $trans = $xacts{$transid};
171 $total_paid += $amount;
173 $orgs{$U->xact_org($transid, $e)} = 1;
175 # A negative payment is a refund.
178 # Negative credit card payments are not allowed
179 if($type eq 'credit_card_payment') {
181 return OpenILS::Event->new(
183 note => q/Negative credit card payments not allowed/
187 # If the refund causes the transaction balance to exceed 0 dollars,
188 # we are in effect loaning the patron money. This is not allowed.
189 if( ($trans->balance_owed - $amount) > 0 ) {
191 return OpenILS::Event->new('REFUND_EXCEEDS_BALANCE');
194 # Otherwise, make sure the refund does not exceed desk payments
195 # This is also not allowed
197 my $desk_payments = $e->search_money_desk_payment({xact => $transid, voided => 'f'});
198 $desk_total += $_->amount for @$desk_payments;
200 if( (-$amount) > $desk_total ) {
202 return OpenILS::Event->new(
203 'REFUND_EXCEEDS_DESK_PAYMENTS',
204 payload => { allowed_refund => $desk_total, submitted_refund => -$amount } );
208 my $payobj = "Fieldmapper::money::$type";
209 $payobj = $payobj->new;
211 $payobj->amount($amount);
212 $payobj->amount_collected($amount);
213 $payobj->xact($transid);
214 $payobj->note($note);
215 if ((not $payobj->note) and ($type eq 'credit_card_payment')) {
216 $payobj->note($cc_args->{note});
219 if ($payobj->has_field('accepting_usr')) { $payobj->accepting_usr($e->requestor->id); }
220 if ($payobj->has_field('cash_drawer')) { $payobj->cash_drawer($drawer); }
221 if ($payobj->has_field('cc_type')) { $payobj->cc_type($cc_args->{type}); }
222 if ($payobj->has_field('check_number')) { $payobj->check_number($check_number); }
224 # Store the last 4 digits of the CC number
225 if ($payobj->has_field('cc_number')) {
226 $payobj->cc_number(substr($cc_args->{number}, -4));
228 if ($payobj->has_field('expire_month')) { $payobj->expire_month($cc_args->{expire_month}); }
229 if ($payobj->has_field('expire_year')) { $payobj->expire_year($cc_args->{expire_year}); }
231 # Note: It is important not to set approval_code
232 # on the fieldmapper object yet.
234 push(@payment_objs, $payobj);
236 } # all payment objects have been created and inserted.
238 #### NO WRITES TO THE DB ABOVE THIS LINE -- THEY'LL ONLY BE DISCARDED ###
241 # After we try to externally process a credit card (if desired), we'll
242 # open a new transaction. We cannot leave one open while credit card
243 # processing might be happening, as it can easily time out the database
248 if($type eq 'credit_card_payment') {
249 $approval_code = $cc_args->{approval_code};
250 # If an approval code was not given, we'll need
251 # to call to the payment processor ourselves.
252 if ($cc_args->{where_process} == 1) {
253 return OpenILS::Event->new('BAD_PARAMS', note => 'Need CC number')
254 if not $cc_args->{number};
256 OpenILS::Application::Circ::CreditCard::process_payment({
257 "desc" => $cc_args->{note},
258 "amount" => $total_paid,
259 "patron_id" => $user_id,
260 "cc" => $cc_args->{number},
261 "expiration" => sprintf(
263 $cc_args->{expire_month},
264 $cc_args->{expire_year}
267 "first_name" => $cc_args->{billing_first},
268 "last_name" => $cc_args->{billing_last},
269 "address" => $cc_args->{billing_address},
270 "city" => $cc_args->{billing_city},
271 "state" => $cc_args->{billing_state},
272 "zip" => $cc_args->{billing_zip},
275 if ($U->event_code($response)) { # non-success
277 "Credit card payment for user $user_id failed: " .
278 $response->{"textcode"} . " " .
279 $response->{"payload"}->{"error_message"}
284 # We need to save this for later in case there's a failure on
285 # the EG side to store the processor's result.
286 $cc_payload = $response->{"payload"};
288 $approval_code = $cc_payload->{"authorization"};
289 $cc_type = $cc_payload->{"card_type"};
290 $cc_processor = $cc_payload->{"processor"};
291 $logger->info("Credit card payment for user $user_id succeeded");
294 return OpenILS::Event->new(
295 'BAD_PARAMS', note => 'Need approval code'
296 ) if not $cc_args->{approval_code};
300 ### RE-OPEN TRANSACTION HERE ###
304 # create payment records
305 my $create_money_method = "create_money_" . $type;
306 for my $payment (@payment_objs) {
307 # update the transaction if it's done
308 my $amount = $payment->amount;
309 my $transid = $payment->xact;
310 my $trans = $xacts{$transid};
311 if( (my $cred = ($trans->balance_owed - $amount)) <= 0 ) {
312 # Any overpay on this transaction goes directly into patron
313 # credit making payment with existing patron credit.
314 $credit -= $amount if $type eq 'credit_payment';
318 my $circ = $e->retrieve_action_circulation($transid);
320 if(!$circ || $circ->stop_fines) {
321 # If this is a circulation, we can't close the transaction
322 # unless stop_fines is set.
323 $trans = $e->retrieve_money_billable_transaction($transid);
324 $trans->xact_finish("now");
325 if (!$e->update_money_billable_transaction($trans)) {
326 return _recording_failure(
327 $e, "update_money_billable_transaction() failed",
328 $payment, $cc_payload
334 $payment->approval_code($approval_code) if $approval_code;
335 $payment->cc_type($cc_type) if $cc_type;
336 $payment->cc_processor($cc_processor) if $cc_processor;
337 if (!$e->$create_money_method($payment)) {
338 return _recording_failure(
339 $e, "$create_money_method failed", $payment, $cc_payload
343 push(@payment_ids, $payment->id);
346 my $evt = _update_patron_credit($e, $patron, $credit);
348 return _recording_failure(
349 $e, "_update_patron_credit() failed", undef, $cc_payload
353 for my $org_id (keys %orgs) {
354 # calculate penalties for each of the affected orgs
355 $evt = OpenILS::Utils::Penalty->calculate_penalties(
356 $e, $user_id, $org_id
359 return _recording_failure(
360 $e, "calculate_penalties() failed", undef, $cc_payload
366 return \@payment_ids;
369 sub _recording_failure {
370 my ($e, $msg, $payment, $payload) = @_;
372 if ($payload) { # If the payment processor already accepted a payment:
373 $logger->error($msg);
374 $logger->error("Payment processor payload: " . Dumper($payload));
375 # payment shouldn't contain CC number
376 $logger->error("Payment: " . Dumper($payment)) if $payment;
380 return new OpenILS::Event(
381 "CREDIT_PROCESSOR_SUCCESS_WO_RECORD",
382 "payload" => $payload
384 } else { # Otherwise, the problem is somewhat less severe:
386 $logger->warn("Payment: " . Dumper($payment)) if $payment;
387 return $e->die_event;
391 sub _update_patron_credit {
392 my($e, $patron, $credit) = @_;
393 return undef if $credit == 0;
394 $patron->credit_forward_balance($patron->credit_forward_balance + $credit);
395 return OpenILS::Event->new('NEGATIVE_PATRON_BALANCE') if $patron->credit_forward_balance < 0;
396 $e->update_actor_user($patron) or return $e->die_event;
401 __PACKAGE__->register_method(
402 method => "retrieve_payments",
403 api_name => "open-ils.circ.money.payment.retrieve.all_",
404 notes => "Returns a list of payments attached to a given transaction"
406 sub retrieve_payments {
407 my( $self, $client, $login, $transid ) = @_;
410 $apputils->checksesperm($login, 'VIEW_TRANSACTION');
413 # XXX the logic here is wrong.. we need to check the owner of the transaction
414 # to make sure the requestor has access
416 # XXX grab the view, for each object in the view, grab the real object
418 return $apputils->simplereq(
420 'open-ils.cstore.direct.money.payment.search.atomic', { xact => $transid } );
424 __PACKAGE__->register_method(
425 method => "retrieve_payments2",
427 api_name => "open-ils.circ.money.payment.retrieve.all",
428 notes => "Returns a list of payments attached to a given transaction"
431 sub retrieve_payments2 {
432 my( $self, $client, $login, $transid ) = @_;
434 my $e = new_editor(authtoken=>$login);
435 return $e->event unless $e->checkauth;
436 return $e->event unless $e->allowed('VIEW_TRANSACTION');
439 my $pmnts = $e->search_money_payment({ xact => $transid });
441 my $type = $_->payment_type;
442 my $meth = "retrieve_money_$type";
443 my $p = $e->$meth($_->id) or return $e->event;
444 $p->payment_type($type);
445 $p->cash_drawer($e->retrieve_actor_workstation($p->cash_drawer))
446 if $p->has_field('cash_drawer');
447 push( @payments, $p );
453 __PACKAGE__->register_method(
454 method => "format_payment_receipt",
455 api_name => "open-ils.circ.money.payment_receipt.print",
457 desc => 'Returns a printable receipt for the specified payments',
459 { desc => 'Authentication token', type => 'string'},
460 { desc => 'Payment ID or array of payment IDs', type => 'number' },
463 desc => q/An action_trigger.event object or error event./,
468 __PACKAGE__->register_method(
469 method => "format_payment_receipt",
470 api_name => "open-ils.circ.money.payment_receipt.email",
472 desc => 'Emails a receipt for the specified payments to the user associated with the first payment',
474 { desc => 'Authentication token', type => 'string'},
475 { desc => 'Payment ID or array of payment IDs', type => 'number' },
478 desc => q/Undefined on success, otherwise an error event./,
484 sub format_payment_receipt {
485 my($self, $conn, $auth, $mp_id) = @_;
488 if (ref $mp_id ne 'ARRAY') {
489 $mp_ids = [ $mp_id ];
494 my $for_print = ($self->api_name =~ /print/);
495 my $for_email = ($self->api_name =~ /email/);
496 my $e = new_editor(authtoken => $auth);
497 return $e->event unless $e->checkauth;
500 for my $id (@$mp_ids) {
502 my $payment = $e->retrieve_money_payment([
510 ]) or return OpenILS::Event->new('MP_NOT_FOUND');
512 return $e->event unless $e->allowed('VIEW_TRANSACTION', $payment->xact->usr->home_ou);
514 push @$payments, $payment;
519 return $U->fire_object_event(undef, 'money.format.payment_receipt.print', $payments, $$payments[0]->xact->usr->home_ou);
521 } elsif ($for_email) {
523 for my $p (@$payments) {
524 $U->create_events_for_hook('money.format.payment_receipt.email', $p, $p->xact->usr->home_ou, undef, undef, 1);
531 __PACKAGE__->register_method(
532 method => "create_grocery_bill",
533 api_name => "open-ils.circ.money.grocery.create",
535 Creates a new grocery transaction using the transaction object provided
536 PARAMS: (login_session, money.grocery (mg) object)
539 sub create_grocery_bill {
540 my( $self, $client, $login, $transaction ) = @_;
542 my( $staff, $evt ) = $apputils->checkses($login);
544 $evt = $apputils->check_perms($staff->id,
545 $transaction->billing_location, 'CREATE_TRANSACTION' );
549 $logger->activity("Creating grocery bill " . Dumper($transaction) );
551 $transaction->clear_id;
552 my $session = $apputils->start_db_session;
553 my $transid = $session->request(
554 'open-ils.storage.direct.money.grocery.create', $transaction)->gather(1);
556 throw OpenSRF::EX ("Error creating new money.grocery") unless defined $transid;
558 $logger->debug("Created new grocery transaction $transid");
560 $apputils->commit_db_session($session);
562 my $e = new_editor(xact=>1);
563 $evt = _check_open_xact($e, $transid);
571 __PACKAGE__->register_method(
572 method => 'fetch_reservation',
573 api_name => 'open-ils.circ.booking.reservation.retrieve'
575 sub fetch_reservation {
576 my( $self, $conn, $auth, $id ) = @_;
577 my $e = new_editor(authtoken=>$auth);
578 return $e->event unless $e->checkauth;
579 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
580 my $g = $e->retrieve_booking_reservation($id)
585 __PACKAGE__->register_method(
586 method => 'fetch_grocery',
587 api_name => 'open-ils.circ.money.grocery.retrieve'
590 my( $self, $conn, $auth, $id ) = @_;
591 my $e = new_editor(authtoken=>$auth);
592 return $e->event unless $e->checkauth;
593 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
594 my $g = $e->retrieve_money_grocery($id)
600 __PACKAGE__->register_method(
601 method => "billing_items",
602 api_name => "open-ils.circ.money.billing.retrieve.all",
605 desc => 'Returns a list of billing items for the given transaction ID. ' .
606 'If the operator is not the owner of the transaction, the VIEW_TRANSACTION permission is required.',
608 { desc => 'Authentication token', type => 'string'},
609 { desc => 'Transaction ID', type => 'number'}
612 desc => 'Transaction object, event on error'
618 my( $self, $client, $login, $transid ) = @_;
620 my( $trans, $evt ) = $U->fetch_billable_xact($transid);
624 ($staff, $evt ) = $apputils->checkses($login);
627 if($staff->id ne $trans->usr) {
628 $evt = $U->check_perms($staff->id, $staff->home_ou, 'VIEW_TRANSACTION');
632 return $apputils->simplereq( 'open-ils.cstore',
633 'open-ils.cstore.direct.money.billing.search.atomic', { xact => $transid } )
637 __PACKAGE__->register_method(
638 method => "billing_items_create",
639 api_name => "open-ils.circ.money.billing.create",
641 Creates a new billing line item
642 PARAMS( login, bill_object (mb) )
645 sub billing_items_create {
646 my( $self, $client, $login, $billing ) = @_;
648 my $e = new_editor(authtoken => $login, xact => 1);
649 return $e->die_event unless $e->checkauth;
650 return $e->die_event unless $e->allowed('CREATE_BILL');
652 my $xact = $e->retrieve_money_billable_transaction($billing->xact)
653 or return $e->die_event;
655 # if the transaction was closed, re-open it
656 if($xact->xact_finish) {
657 $xact->clear_xact_finish;
658 $e->update_money_billable_transaction($xact)
659 or return $e->die_event;
662 my $amt = $billing->amount;
664 $billing->amount($amt);
666 $e->create_money_billing($billing) or return $e->die_event;
667 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $xact->usr, $U->xact_org($xact->id));
675 __PACKAGE__->register_method(
676 method => 'void_bill',
677 api_name => 'open-ils.circ.money.billing.void',
680 @param authtoken Login session key
681 @param billid Id for the bill to void. This parameter may be repeated to reference other bills.
682 @return 1 on success, Event on error
686 my( $s, $c, $authtoken, @billids ) = @_;
688 my $e = new_editor( authtoken => $authtoken, xact => 1 );
689 return $e->die_event unless $e->checkauth;
690 return $e->die_event unless $e->allowed('VOID_BILLING');
693 for my $billid (@billids) {
695 my $bill = $e->retrieve_money_billing($billid)
696 or return $e->die_event;
698 my $xact = $e->retrieve_money_billable_transaction($bill->xact)
699 or return $e->die_event;
701 if($U->is_true($bill->voided)) {
703 return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
706 my $org = $U->xact_org($bill->xact, $e);
707 $users{$xact->usr} = {} unless $users{$xact->usr};
708 $users{$xact->usr}->{$org} = 1;
711 $bill->voider($e->requestor->id);
712 $bill->void_time('now');
714 $e->update_money_billing($bill) or return $e->die_event;
715 my $evt = _check_open_xact($e, $bill->xact, $xact);
719 # calculate penalties for all user/org combinations
720 for my $user_id (keys %users) {
721 for my $org_id (keys %{$users{$user_id}}) {
722 OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
730 __PACKAGE__->register_method(
731 method => 'edit_bill_note',
732 api_name => 'open-ils.circ.money.billing.note.edit',
734 Edits the note for a bill
735 @param authtoken Login session key
736 @param note The replacement note for the bills we're editing
737 @param billid Id for the bill to edit the note of. This parameter may be repeated to reference other bills.
738 @return 1 on success, Event on error
742 my( $s, $c, $authtoken, $note, @billids ) = @_;
744 my $e = new_editor( authtoken => $authtoken, xact => 1 );
745 return $e->die_event unless $e->checkauth;
746 return $e->die_event unless $e->allowed('UPDATE_BILL_NOTE');
748 for my $billid (@billids) {
750 my $bill = $e->retrieve_money_billing($billid)
751 or return $e->die_event;
754 # 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.
756 $e->update_money_billing($bill) or return $e->die_event;
763 __PACKAGE__->register_method(
764 method => 'edit_payment_note',
765 api_name => 'open-ils.circ.money.payment.note.edit',
767 Edits the note for a payment
768 @param authtoken Login session key
769 @param note The replacement note for the payments we're editing
770 @param paymentid Id for the payment to edit the note of. This parameter may be repeated to reference other payments.
771 @return 1 on success, Event on error
774 sub edit_payment_note {
775 my( $s, $c, $authtoken, $note, @paymentids ) = @_;
777 my $e = new_editor( authtoken => $authtoken, xact => 1 );
778 return $e->die_event unless $e->checkauth;
779 return $e->die_event unless $e->allowed('UPDATE_PAYMENT_NOTE');
781 for my $paymentid (@paymentids) {
783 my $payment = $e->retrieve_money_payment($paymentid)
784 or return $e->die_event;
786 $payment->note($note);
787 # 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.
789 $e->update_money_payment($payment) or return $e->die_event;
796 sub _check_open_xact {
797 my( $editor, $xactid, $xact ) = @_;
799 # Grab the transaction
800 $xact ||= $editor->retrieve_money_billable_transaction($xactid);
801 return $editor->event unless $xact;
802 $xactid ||= $xact->id;
804 # grab the summary and see how much is owed on this transaction
805 my ($summary) = $U->fetch_mbts($xactid, $editor);
807 # grab the circulation if it is a circ;
808 my $circ = $editor->retrieve_action_circulation($xactid);
810 # If nothing is owed on the transaction but it is still open
811 # and this transaction is not an open circulation, close it
813 ( $summary->balance_owed == 0 and ! $xact->xact_finish ) and
814 ( !$circ or $circ->stop_fines )) {
816 $logger->info("closing transaction ".$xact->id. ' becauase balance_owed == 0');
817 $xact->xact_finish('now');
818 $editor->update_money_billable_transaction($xact)
819 or return $editor->event;
823 # If money is owed or a refund is due on the xact and xact_finish
824 # is set, clear it (to reopen the xact) and update
825 if( $summary->balance_owed != 0 and $xact->xact_finish ) {
826 $logger->info("re-opening transaction ".$xact->id. ' becauase balance_owed != 0');
827 $xact->clear_xact_finish;
828 $editor->update_money_billable_transaction($xact)
829 or return $editor->event;
836 __PACKAGE__->register_method (
837 method => 'fetch_mbts',
839 api_name => 'open-ils.circ.money.billable_xact_summary.retrieve'
842 my( $self, $conn, $auth, $id) = @_;
844 my $e = new_editor(xact => 1, authtoken=>$auth);
845 return $e->event unless $e->checkauth;
846 my ($mbts) = $U->fetch_mbts($id, $e);
848 my $user = $e->retrieve_actor_user($mbts->usr)
849 or return $e->die_event;
851 return $e->die_event unless $e->allowed('VIEW_TRANSACTION', $user->home_ou);
857 __PACKAGE__->register_method(
858 method => 'desk_payments',
859 api_name => 'open-ils.circ.money.org_unit.desk_payments'
862 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
863 my $e = new_editor(authtoken=>$auth);
864 return $e->event unless $e->checkauth;
865 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
866 my $data = $U->storagereq(
867 'open-ils.storage.money.org_unit.desk_payments.atomic',
868 $org, $start_date, $end_date );
870 $_->workstation( $_->workstation->name ) for(@$data);
875 __PACKAGE__->register_method(
876 method => 'user_payments',
877 api_name => 'open-ils.circ.money.org_unit.user_payments'
881 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
882 my $e = new_editor(authtoken=>$auth);
883 return $e->event unless $e->checkauth;
884 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
885 my $data = $U->storagereq(
886 'open-ils.storage.money.org_unit.user_payments.atomic',
887 $org, $start_date, $end_date );
890 $e->retrieve_actor_card($_->usr->card)->barcode);
892 $e->retrieve_actor_org_unit($_->usr->home_ou)->shortname);
898 __PACKAGE__->register_method(
899 method => 'retrieve_credit_payable_balance',
900 api_name => 'open-ils.circ.credit.payable_balance.retrieve',
903 desc => q/Returns the total amount the patron can pay via credit card/,
905 { desc => 'Authentication token', type => 'string' },
906 { desc => 'User id', type => 'number' }
908 return => { desc => 'The ID of the new provider' }
912 sub retrieve_credit_payable_balance {
913 my ( $self, $conn, $auth, $user_id ) = @_;
914 my $e = new_editor(authtoken => $auth);
915 return $e->event unless $e->checkauth;
917 my $user = $e->retrieve_actor_user($user_id)
920 if($e->requestor->id != $user_id) {
921 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou)
924 my $circ_orgs = $e->json_query({
925 "select" => {circ => ["circ_lib"]},
927 "where" => {usr => $user_id, xact_finish => undef},
931 my $groc_orgs = $e->json_query({
932 "select" => {mg => ["billing_location"]},
934 "where" => {usr => $user_id, xact_finish => undef},
939 for my $org ( @$circ_orgs, @$groc_orgs ) {
940 my $o = $org->{billing_location};
941 $o = $org->{circ_lib} unless $o;
942 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.
943 $hash{$o} = $U->ou_ancestor_setting_value($o, 'credit.payments.allow', $e);
946 my @credit_orgs = map { $hash{$_} ? ($_) : () } keys %hash;
947 $logger->debug("credit: relevant orgs that allow credit payments => @credit_orgs");
950 OpenILS::Application::AppUtils->simplereq('open-ils.actor',
951 'open-ils.actor.user.transactions.have_charge', $auth, $user_id);
955 for my $xact (@$xact_summaries) {
957 # make two lists and grab them in batch XXX
958 if ( $xact->xact_type eq 'circulation' ) {
959 my $circ = $e->retrieve_action_circulation($xact->id) or return $e->event;
960 next unless grep { $_ == $circ->circ_lib } @credit_orgs;
962 } elsif ($xact->xact_type eq 'grocery') {
963 my $bill = $e->retrieve_money_grocery($xact->id) or return $e->event;
964 next unless grep { $_ == $bill->billing_location } @credit_orgs;
965 } elsif ($xact->xact_type eq 'reservation') {
966 my $bill = $e->retrieve_booking_reservation($xact->id) or return $e->event;
967 next unless grep { $_ == $bill->pickup_lib } @credit_orgs;
969 $sum += $xact->balance_owed();