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 || $e->requestor->home_ou;
146 # unless/until determined by payment processor API
147 my ($approval_code, $cc_processor, $cc_type, $cc_order_number) = (undef,undef,undef, undef);
149 my $patron = $e->retrieve_actor_user($user_id) or return $e->die_event;
151 if($patron->last_xact_id ne $last_xact_id) {
153 return OpenILS::Event->new('INVALID_USER_XACT_ID');
156 # A user is allowed to make credit card payments on his/her own behalf
157 # All other scenarious require permission
158 unless($type eq 'credit_card_payment' and $user_id == $e->requestor->id) {
159 return $e->die_event unless $e->allowed('CREATE_PAYMENT', $patron->home_ou);
162 # first collect the transactions and make sure the transaction
163 # user matches the requested user
166 # We rewrite the payments array for sanity's sake, to avoid more
167 # than one payment per transaction per call, which is not legitimate
168 # but has been seen in the wild coming from the staff client. This
169 # is presumably a staff client (xulrunner) bug.
170 my @unique_xact_payments;
171 for my $pay (@{$payments->{payments}}) {
172 my $xact_id = $pay->[0];
173 next if (exists($xacts{$xact_id}));
175 my $xact = $e->retrieve_money_billable_transaction_summary($xact_id)
176 or return $e->die_event;
178 if($xact->usr != $user_id) {
180 return OpenILS::Event->new('BAD_PARAMS', note => q/user does not match transaction/);
183 $xacts{$xact_id} = $xact;
184 push @unique_xact_payments, $pay;
186 $payments->{payments} = \@unique_xact_payments;
190 for my $pay (@{$payments->{payments}}) {
191 my $transid = $pay->[0];
192 my $amount = $pay->[1];
193 $amount =~ s/\$//og; # just to be safe
194 my $trans = $xacts{$transid};
196 $total_paid += $amount;
198 my $org_id = $U->xact_org($transid, $e);
200 if (!$orgs{$org_id}) {
203 # patron credit has to be allowed at all orgs receiving payment
204 if ($type eq 'credit_payment' and $U->ou_ancestor_setting_value(
205 $org_id, 'circ.disable_patron_credit', $e)) {
207 return OpenILS::Event->new('PATRON_CREDIT_DISABLED');
211 # A negative payment is a refund.
214 # Negative credit card payments are not allowed
215 if($type eq 'credit_card_payment') {
217 return OpenILS::Event->new(
219 note => q/Negative credit card payments not allowed/
223 # If the refund causes the transaction balance to exceed 0 dollars,
224 # we are in effect loaning the patron money. This is not allowed.
225 if( ($trans->balance_owed - $amount) > 0 ) {
227 return OpenILS::Event->new('REFUND_EXCEEDS_BALANCE');
230 # Otherwise, make sure the refund does not exceed desk payments
231 # This is also not allowed
233 my $desk_payments = $e->search_money_desk_payment({xact => $transid, voided => 'f'});
234 $desk_total += $_->amount for @$desk_payments;
236 if( (-$amount) > $desk_total ) {
238 return OpenILS::Event->new(
239 'REFUND_EXCEEDS_DESK_PAYMENTS',
240 payload => { allowed_refund => $desk_total, submitted_refund => -$amount } );
244 my $payobj = "Fieldmapper::money::$type";
245 $payobj = $payobj->new;
247 $payobj->amount($amount);
248 $payobj->amount_collected($amount);
249 $payobj->xact($transid);
250 $payobj->note($note);
251 if ((not $payobj->note) and ($type eq 'credit_card_payment')) {
252 $payobj->note($cc_args->{note});
255 if ($payobj->has_field('accepting_usr')) { $payobj->accepting_usr($e->requestor->id); }
256 if ($payobj->has_field('cash_drawer')) { $payobj->cash_drawer($drawer); }
257 if ($payobj->has_field('cc_type')) { $payobj->cc_type($cc_args->{type}); }
258 if ($payobj->has_field('check_number')) { $payobj->check_number($check_number); }
260 # Store the last 4 digits of the CC number
261 if ($payobj->has_field('cc_number')) {
262 $payobj->cc_number(substr($cc_args->{number}, -4));
264 if ($payobj->has_field('expire_month')) { $payobj->expire_month($cc_args->{expire_month}); }
265 if ($payobj->has_field('expire_year')) { $payobj->expire_year($cc_args->{expire_year}); }
267 # Note: It is important not to set approval_code
268 # on the fieldmapper object yet.
270 push(@payment_objs, $payobj);
272 } # all payment objects have been created and inserted.
274 #### NO WRITES TO THE DB ABOVE THIS LINE -- THEY'LL ONLY BE DISCARDED ###
277 # After we try to externally process a credit card (if desired), we'll
278 # open a new transaction. We cannot leave one open while credit card
279 # processing might be happening, as it can easily time out the database
284 if($type eq 'credit_card_payment') {
285 $approval_code = $cc_args->{approval_code};
286 # If an approval code was not given, we'll need
287 # to call to the payment processor ourselves.
288 if ($cc_args->{where_process} == 1) {
289 return OpenILS::Event->new('BAD_PARAMS', note => 'Need CC number')
290 if not $cc_args->{number};
292 OpenILS::Application::Circ::CreditCard::process_payment({
293 "desc" => $cc_args->{note},
294 "amount" => $total_paid,
295 "patron_id" => $user_id,
296 "cc" => $cc_args->{number},
297 "expiration" => sprintf(
299 $cc_args->{expire_month},
300 $cc_args->{expire_year}
303 "first_name" => $cc_args->{billing_first},
304 "last_name" => $cc_args->{billing_last},
305 "address" => $cc_args->{billing_address},
306 "city" => $cc_args->{billing_city},
307 "state" => $cc_args->{billing_state},
308 "zip" => $cc_args->{billing_zip},
309 "cvv2" => $cc_args->{cvv2},
312 if ($U->event_code($response)) { # non-success
314 "Credit card payment for user $user_id failed: " .
315 $response->{"textcode"} . " " .
316 $response->{"payload"}->{"error_message"}
321 # We need to save this for later in case there's a failure on
322 # the EG side to store the processor's result.
323 $cc_payload = $response->{"payload"};
325 $approval_code = $cc_payload->{"authorization"};
326 $cc_type = $cc_payload->{"card_type"};
327 $cc_processor = $cc_payload->{"processor"};
328 $cc_order_number = $cc_payload->{"order_number"};
329 $logger->info("Credit card payment for user $user_id succeeded");
332 return OpenILS::Event->new(
333 'BAD_PARAMS', note => 'Need approval code'
334 ) if not $cc_args->{approval_code};
338 ### RE-OPEN TRANSACTION HERE ###
342 # create payment records
343 my $create_money_method = "create_money_" . $type;
344 for my $payment (@payment_objs) {
345 # update the transaction if it's done
346 my $amount = $payment->amount;
347 my $transid = $payment->xact;
348 my $trans = $xacts{$transid};
349 # making payment with existing patron credit.
350 $credit -= $amount if $type eq 'credit_payment';
351 if( (my $cred = ($trans->balance_owed - $amount)) <= 0 ) {
352 # Any overpay on this transaction goes directly into patron
356 my $circ = $e->retrieve_action_circulation($transid);
358 # Whether or not we close the transaction. We definitely
359 # close is no circulation transaction is present,
360 # otherwise we check if the circulation is in a state that
361 # allows itself to be closed.
362 if (!$circ || OpenILS::Application::Circ::CircCommon->can_close_circ($e, $circ)) {
363 $trans = $e->retrieve_money_billable_transaction($transid);
364 $trans->xact_finish("now");
365 if (!$e->update_money_billable_transaction($trans)) {
366 return _recording_failure(
367 $e, "update_money_billable_transaction() failed",
368 $payment, $cc_payload
374 $payment->approval_code($approval_code) if $approval_code;
375 $payment->cc_order_number($cc_order_number) if $cc_order_number;
376 $payment->cc_type($cc_type) if $cc_type;
377 $payment->cc_processor($cc_processor) if $cc_processor;
378 $payment->cc_first_name($cc_args->{'billing_first'}) if $cc_args->{'billing_first'};
379 $payment->cc_last_name($cc_args->{'billing_last'}) if $cc_args->{'billing_last'};
380 if (!$e->$create_money_method($payment)) {
381 return _recording_failure(
382 $e, "$create_money_method failed", $payment, $cc_payload
386 push(@payment_ids, $payment->id);
389 my $evt = _update_patron_credit($e, $patron, $credit);
391 return _recording_failure(
392 $e, "_update_patron_credit() failed", undef, $cc_payload
396 for my $org_id (keys %orgs) {
397 # calculate penalties for each of the affected orgs
398 $evt = OpenILS::Utils::Penalty->calculate_penalties(
399 $e, $user_id, $org_id
402 return _recording_failure(
403 $e, "calculate_penalties() failed", undef, $cc_payload
408 # update the user to create a new last_xact_id
409 $e->update_actor_user($patron) or return $e->die_event;
410 $patron = $e->retrieve_actor_user($patron) or return $e->die_event;
413 # update the cached user object if a user is making a payment toward
414 # his/her own account
415 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1)
416 if $user_id == $e->requestor->id;
418 return {last_xact_id => $patron->last_xact_id, payments => \@payment_ids};
421 sub _recording_failure {
422 my ($e, $msg, $payment, $payload) = @_;
424 if ($payload) { # If the payment processor already accepted a payment:
425 $logger->error($msg);
426 $logger->error("Payment processor payload: " . Dumper($payload));
427 # payment shouldn't contain CC number
428 $logger->error("Payment: " . Dumper($payment)) if $payment;
432 return new OpenILS::Event(
433 "CREDIT_PROCESSOR_SUCCESS_WO_RECORD",
434 "payload" => $payload
436 } else { # Otherwise, the problem is somewhat less severe:
438 $logger->warn("Payment: " . Dumper($payment)) if $payment;
439 return $e->die_event;
443 sub _update_patron_credit {
444 my($e, $patron, $credit) = @_;
445 return undef if $credit == 0;
446 $patron->credit_forward_balance($patron->credit_forward_balance + $credit);
447 return OpenILS::Event->new('NEGATIVE_PATRON_BALANCE') if $patron->credit_forward_balance < 0;
448 $e->update_actor_user($patron) or return $e->die_event;
453 __PACKAGE__->register_method(
454 method => "retrieve_payments",
455 api_name => "open-ils.circ.money.payment.retrieve.all_",
456 notes => "Returns a list of payments attached to a given transaction"
458 sub retrieve_payments {
459 my( $self, $client, $login, $transid ) = @_;
462 $apputils->checksesperm($login, 'VIEW_TRANSACTION');
465 # XXX the logic here is wrong.. we need to check the owner of the transaction
466 # to make sure the requestor has access
468 # XXX grab the view, for each object in the view, grab the real object
470 return $apputils->simplereq(
472 'open-ils.cstore.direct.money.payment.search.atomic', { xact => $transid } );
476 __PACKAGE__->register_method(
477 method => "retrieve_payments2",
479 api_name => "open-ils.circ.money.payment.retrieve.all",
480 notes => "Returns a list of payments attached to a given transaction"
483 sub retrieve_payments2 {
484 my( $self, $client, $login, $transid ) = @_;
486 my $e = new_editor(authtoken=>$login);
487 return $e->event unless $e->checkauth;
488 return $e->event unless $e->allowed('VIEW_TRANSACTION');
491 my $pmnts = $e->search_money_payment({ xact => $transid });
493 my $type = $_->payment_type;
494 my $meth = "retrieve_money_$type";
495 my $p = $e->$meth($_->id) or return $e->event;
496 $p->payment_type($type);
497 $p->cash_drawer($e->retrieve_actor_workstation($p->cash_drawer))
498 if $p->has_field('cash_drawer');
499 push( @payments, $p );
505 __PACKAGE__->register_method(
506 method => "format_payment_receipt",
507 api_name => "open-ils.circ.money.payment_receipt.print",
509 desc => 'Returns a printable receipt for the specified payments',
511 { desc => 'Authentication token', type => 'string'},
512 { desc => 'Payment ID or array of payment IDs', type => 'number' },
515 desc => q/An action_trigger.event object or error event./,
520 __PACKAGE__->register_method(
521 method => "format_payment_receipt",
522 api_name => "open-ils.circ.money.payment_receipt.email",
524 desc => 'Emails a receipt for the specified payments to the user associated with the first payment',
526 { desc => 'Authentication token', type => 'string'},
527 { desc => 'Payment ID or array of payment IDs', type => 'number' },
530 desc => q/Undefined on success, otherwise an error event./,
536 sub format_payment_receipt {
537 my($self, $conn, $auth, $mp_id) = @_;
540 if (ref $mp_id ne 'ARRAY') {
541 $mp_ids = [ $mp_id ];
546 my $for_print = ($self->api_name =~ /print/);
547 my $for_email = ($self->api_name =~ /email/);
549 # manually use xact (i.e. authoritative) so we can kill the cstore
550 # connection before sending the action/trigger request. This prevents our cstore
551 # backend from sitting idle while A/T (which uses its own transactions) runs.
552 my $e = new_editor(xact => 1, authtoken => $auth);
553 return $e->die_event unless $e->checkauth;
556 for my $id (@$mp_ids) {
558 my $payment = $e->retrieve_money_payment([
566 ]) or return $e->die_event;
568 return $e->die_event unless
569 $e->requestor->id == $payment->xact->usr->id or
570 $e->allowed('VIEW_TRANSACTION', $payment->xact->usr->home_ou);
572 push @$payments, $payment;
579 return $U->fire_object_event(undef, 'money.format.payment_receipt.print', $payments, $$payments[0]->xact->usr->home_ou);
581 } elsif ($for_email) {
583 for my $p (@$payments) {
584 $U->create_events_for_hook('money.format.payment_receipt.email', $p, $p->xact->usr->home_ou, undef, undef, 1);
591 __PACKAGE__->register_method(
592 method => "create_grocery_bill",
593 api_name => "open-ils.circ.money.grocery.create",
595 Creates a new grocery transaction using the transaction object provided
596 PARAMS: (login_session, money.grocery (mg) object)
599 sub create_grocery_bill {
600 my( $self, $client, $login, $transaction ) = @_;
602 my( $staff, $evt ) = $apputils->checkses($login);
604 $evt = $apputils->check_perms($staff->id,
605 $transaction->billing_location, 'CREATE_TRANSACTION' );
609 $logger->activity("Creating grocery bill " . Dumper($transaction) );
611 $transaction->clear_id;
612 my $session = $apputils->start_db_session;
613 $apputils->set_audit_info($session, $login, $staff->id, $staff->wsid);
614 my $transid = $session->request(
615 'open-ils.storage.direct.money.grocery.create', $transaction)->gather(1);
617 throw OpenSRF::EX ("Error creating new money.grocery") unless defined $transid;
619 $logger->debug("Created new grocery transaction $transid");
621 $apputils->commit_db_session($session);
623 my $e = new_editor(xact=>1);
624 $evt = $U->check_open_xact($e, $transid);
632 __PACKAGE__->register_method(
633 method => 'fetch_reservation',
634 api_name => 'open-ils.circ.booking.reservation.retrieve'
636 sub fetch_reservation {
637 my( $self, $conn, $auth, $id ) = @_;
638 my $e = new_editor(authtoken=>$auth);
639 return $e->event unless $e->checkauth;
640 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
641 my $g = $e->retrieve_booking_reservation($id)
646 __PACKAGE__->register_method(
647 method => 'fetch_grocery',
648 api_name => 'open-ils.circ.money.grocery.retrieve'
651 my( $self, $conn, $auth, $id ) = @_;
652 my $e = new_editor(authtoken=>$auth);
653 return $e->event unless $e->checkauth;
654 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
655 my $g = $e->retrieve_money_grocery($id)
661 __PACKAGE__->register_method(
662 method => "billing_items",
663 api_name => "open-ils.circ.money.billing.retrieve.all",
666 desc => 'Returns a list of billing items for the given transaction ID. ' .
667 'If the operator is not the owner of the transaction, the VIEW_TRANSACTION permission is required.',
669 { desc => 'Authentication token', type => 'string'},
670 { desc => 'Transaction ID', type => 'number'}
673 desc => 'Transaction object, event on error'
679 my( $self, $client, $login, $transid ) = @_;
681 my( $trans, $evt ) = $U->fetch_billable_xact($transid);
685 ($staff, $evt ) = $apputils->checkses($login);
688 if($staff->id ne $trans->usr) {
689 $evt = $U->check_perms($staff->id, $staff->home_ou, 'VIEW_TRANSACTION');
693 return $apputils->simplereq( 'open-ils.cstore',
694 'open-ils.cstore.direct.money.billing.search.atomic', { xact => $transid } )
698 __PACKAGE__->register_method(
699 method => "billing_items_create",
700 api_name => "open-ils.circ.money.billing.create",
702 Creates a new billing line item
703 PARAMS( login, bill_object (mb) )
706 sub billing_items_create {
707 my( $self, $client, $login, $billing ) = @_;
709 my $e = new_editor(authtoken => $login, xact => 1);
710 return $e->die_event unless $e->checkauth;
711 return $e->die_event unless $e->allowed('CREATE_BILL');
713 my $xact = $e->retrieve_money_billable_transaction($billing->xact)
714 or return $e->die_event;
716 # if the transaction was closed, re-open it
717 if($xact->xact_finish) {
718 $xact->clear_xact_finish;
719 $e->update_money_billable_transaction($xact)
720 or return $e->die_event;
723 my $amt = $billing->amount;
725 $billing->amount($amt);
727 $e->create_money_billing($billing) or return $e->die_event;
728 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $xact->usr, $U->xact_org($xact->id,$e));
731 $evt = $U->check_open_xact($e, $xact->id, $xact);
740 __PACKAGE__->register_method(
741 method => 'void_bill',
742 api_name => 'open-ils.circ.money.billing.void',
745 @param authtoken Login session key
746 @param billid Id for the bill to void. This parameter may be repeated to reference other bills.
747 @return 1 on success, Event on error
751 my( $s, $c, $authtoken, @billids ) = @_;
753 my $e = new_editor( authtoken => $authtoken, xact => 1 );
754 return $e->die_event unless $e->checkauth;
755 return $e->die_event unless $e->allowed('VOID_BILLING');
758 for my $billid (@billids) {
760 my $bill = $e->retrieve_money_billing($billid)
761 or return $e->die_event;
763 my $xact = $e->retrieve_money_billable_transaction($bill->xact)
764 or return $e->die_event;
766 if($U->is_true($bill->voided)) {
768 return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
771 my $org = $U->xact_org($bill->xact, $e);
772 $users{$xact->usr} = {} unless $users{$xact->usr};
773 $users{$xact->usr}->{$org} = 1;
776 $bill->voider($e->requestor->id);
777 $bill->void_time('now');
779 $e->update_money_billing($bill) or return $e->die_event;
780 my $evt = $U->check_open_xact($e, $bill->xact, $xact);
784 # calculate penalties for all user/org combinations
785 for my $user_id (keys %users) {
786 for my $org_id (keys %{$users{$user_id}}) {
787 OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
795 __PACKAGE__->register_method(
796 method => 'edit_bill_note',
797 api_name => 'open-ils.circ.money.billing.note.edit',
799 Edits the note for a bill
800 @param authtoken Login session key
801 @param note The replacement note for the bills we're editing
802 @param billid Id for the bill to edit the note of. This parameter may be repeated to reference other bills.
803 @return 1 on success, Event on error
807 my( $s, $c, $authtoken, $note, @billids ) = @_;
809 my $e = new_editor( authtoken => $authtoken, xact => 1 );
810 return $e->die_event unless $e->checkauth;
811 return $e->die_event unless $e->allowed('UPDATE_BILL_NOTE');
813 for my $billid (@billids) {
815 my $bill = $e->retrieve_money_billing($billid)
816 or return $e->die_event;
819 # 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.
821 $e->update_money_billing($bill) or return $e->die_event;
828 __PACKAGE__->register_method(
829 method => 'edit_payment_note',
830 api_name => 'open-ils.circ.money.payment.note.edit',
832 Edits the note for a payment
833 @param authtoken Login session key
834 @param note The replacement note for the payments we're editing
835 @param paymentid Id for the payment to edit the note of. This parameter may be repeated to reference other payments.
836 @return 1 on success, Event on error
839 sub edit_payment_note {
840 my( $s, $c, $authtoken, $note, @paymentids ) = @_;
842 my $e = new_editor( authtoken => $authtoken, xact => 1 );
843 return $e->die_event unless $e->checkauth;
844 return $e->die_event unless $e->allowed('UPDATE_PAYMENT_NOTE');
846 for my $paymentid (@paymentids) {
848 my $payment = $e->retrieve_money_payment($paymentid)
849 or return $e->die_event;
851 $payment->note($note);
852 # 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.
854 $e->update_money_payment($payment) or return $e->die_event;
862 __PACKAGE__->register_method (
863 method => 'fetch_mbts',
865 api_name => 'open-ils.circ.money.billable_xact_summary.retrieve'
868 my( $self, $conn, $auth, $id) = @_;
870 my $e = new_editor(xact => 1, authtoken=>$auth);
871 return $e->event unless $e->checkauth;
872 my ($mbts) = $U->fetch_mbts($id, $e);
874 my $user = $e->retrieve_actor_user($mbts->usr)
875 or return $e->die_event;
877 return $e->die_event unless $e->allowed('VIEW_TRANSACTION', $user->home_ou);
883 __PACKAGE__->register_method(
884 method => 'desk_payments',
885 api_name => 'open-ils.circ.money.org_unit.desk_payments'
888 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
889 my $e = new_editor(authtoken=>$auth);
890 return $e->event unless $e->checkauth;
891 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
892 my $data = $U->storagereq(
893 'open-ils.storage.money.org_unit.desk_payments.atomic',
894 $org, $start_date, $end_date );
896 $_->workstation( $_->workstation->name ) for(@$data);
901 __PACKAGE__->register_method(
902 method => 'user_payments',
903 api_name => 'open-ils.circ.money.org_unit.user_payments'
907 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
908 my $e = new_editor(authtoken=>$auth);
909 return $e->event unless $e->checkauth;
910 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
911 my $data = $U->storagereq(
912 'open-ils.storage.money.org_unit.user_payments.atomic',
913 $org, $start_date, $end_date );
916 $e->retrieve_actor_card($_->usr->card)->barcode);
918 $e->retrieve_actor_org_unit($_->usr->home_ou)->shortname);
924 __PACKAGE__->register_method(
925 method => 'retrieve_credit_payable_balance',
926 api_name => 'open-ils.circ.credit.payable_balance.retrieve',
929 desc => q/Returns the total amount the patron can pay via credit card/,
931 { desc => 'Authentication token', type => 'string' },
932 { desc => 'User id', type => 'number' }
934 return => { desc => 'The ID of the new provider' }
938 sub retrieve_credit_payable_balance {
939 my ( $self, $conn, $auth, $user_id ) = @_;
940 my $e = new_editor(authtoken => $auth);
941 return $e->event unless $e->checkauth;
943 my $user = $e->retrieve_actor_user($user_id)
946 if($e->requestor->id != $user_id) {
947 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou)
950 my $circ_orgs = $e->json_query({
951 "select" => {circ => ["circ_lib"]},
953 "where" => {usr => $user_id, xact_finish => undef},
957 my $groc_orgs = $e->json_query({
958 "select" => {mg => ["billing_location"]},
960 "where" => {usr => $user_id, xact_finish => undef},
965 for my $org ( @$circ_orgs, @$groc_orgs ) {
966 my $o = $org->{billing_location};
967 $o = $org->{circ_lib} unless $o;
968 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.
969 $hash{$o} = $U->ou_ancestor_setting_value($o, 'credit.payments.allow', $e);
972 my @credit_orgs = map { $hash{$_} ? ($_) : () } keys %hash;
973 $logger->debug("credit: relevant orgs that allow credit payments => @credit_orgs");
976 OpenILS::Application::AppUtils->simplereq('open-ils.actor',
977 'open-ils.actor.user.transactions.have_charge', $auth, $user_id);
981 for my $xact (@$xact_summaries) {
983 # make two lists and grab them in batch XXX
984 if ( $xact->xact_type eq 'circulation' ) {
985 my $circ = $e->retrieve_action_circulation($xact->id) or return $e->event;
986 next unless grep { $_ == $circ->circ_lib } @credit_orgs;
988 } elsif ($xact->xact_type eq 'grocery') {
989 my $bill = $e->retrieve_money_grocery($xact->id) or return $e->event;
990 next unless grep { $_ == $bill->billing_location } @credit_orgs;
991 } elsif ($xact->xact_type eq 'reservation') {
992 my $bill = $e->retrieve_booking_reservation($xact->id) or return $e->event;
993 next unless grep { $_ == $bill->pickup_lib } @credit_orgs;
995 $sum += $xact->balance_owed();