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 if (exists($xacts{$xact_id})) {
175 return OpenILS::Event->new('MULTIPLE_PAYMENTS_FOR_XACT');
178 my $xact = $e->retrieve_money_billable_transaction_summary($xact_id)
179 or return $e->die_event;
181 if($xact->usr != $user_id) {
183 return OpenILS::Event->new('BAD_PARAMS', note => q/user does not match transaction/);
186 $xacts{$xact_id} = $xact;
187 push @unique_xact_payments, $pay;
189 $payments->{payments} = \@unique_xact_payments;
193 for my $pay (@{$payments->{payments}}) {
194 my $transid = $pay->[0];
195 my $amount = $pay->[1];
196 $amount =~ s/\$//og; # just to be safe
197 my $trans = $xacts{$transid};
199 $total_paid += $amount;
201 my $org_id = $U->xact_org($transid, $e);
203 if (!$orgs{$org_id}) {
206 # patron credit has to be allowed at all orgs receiving payment
207 if ($type eq 'credit_payment' and $U->ou_ancestor_setting_value(
208 $org_id, 'circ.disable_patron_credit', $e)) {
210 return OpenILS::Event->new('PATRON_CREDIT_DISABLED');
214 # A negative payment is a refund.
217 # Negative credit card payments are not allowed
218 if($type eq 'credit_card_payment') {
220 return OpenILS::Event->new(
222 note => q/Negative credit card payments not allowed/
226 # If the refund causes the transaction balance to exceed 0 dollars,
227 # we are in effect loaning the patron money. This is not allowed.
228 if( ($trans->balance_owed - $amount) > 0 ) {
230 return OpenILS::Event->new('REFUND_EXCEEDS_BALANCE');
233 # Otherwise, make sure the refund does not exceed desk payments
234 # This is also not allowed
236 my $desk_payments = $e->search_money_desk_payment({xact => $transid, voided => 'f'});
237 $desk_total += $_->amount for @$desk_payments;
239 if( (-$amount) > $desk_total ) {
241 return OpenILS::Event->new(
242 'REFUND_EXCEEDS_DESK_PAYMENTS',
243 payload => { allowed_refund => $desk_total, submitted_refund => -$amount } );
247 my $payobj = "Fieldmapper::money::$type";
248 $payobj = $payobj->new;
250 $payobj->amount($amount);
251 $payobj->amount_collected($amount);
252 $payobj->xact($transid);
253 $payobj->note($note);
254 if ((not $payobj->note) and ($type eq 'credit_card_payment')) {
255 $payobj->note($cc_args->{note});
258 if ($payobj->has_field('accepting_usr')) { $payobj->accepting_usr($e->requestor->id); }
259 if ($payobj->has_field('cash_drawer')) { $payobj->cash_drawer($drawer); }
260 if ($payobj->has_field('cc_type')) { $payobj->cc_type($cc_args->{type}); }
261 if ($payobj->has_field('check_number')) { $payobj->check_number($check_number); }
263 # Store the last 4 digits of the CC number
264 if ($payobj->has_field('cc_number')) {
265 $payobj->cc_number(substr($cc_args->{number}, -4));
267 if ($payobj->has_field('expire_month')) { $payobj->expire_month($cc_args->{expire_month}); }
268 if ($payobj->has_field('expire_year')) { $payobj->expire_year($cc_args->{expire_year}); }
270 # Note: It is important not to set approval_code
271 # on the fieldmapper object yet.
273 push(@payment_objs, $payobj);
275 } # all payment objects have been created and inserted.
277 #### NO WRITES TO THE DB ABOVE THIS LINE -- THEY'LL ONLY BE DISCARDED ###
280 # After we try to externally process a credit card (if desired), we'll
281 # open a new transaction. We cannot leave one open while credit card
282 # processing might be happening, as it can easily time out the database
287 if($type eq 'credit_card_payment') {
288 $approval_code = $cc_args->{approval_code};
289 # If an approval code was not given, we'll need
290 # to call to the payment processor ourselves.
291 if ($cc_args->{where_process} == 1) {
292 return OpenILS::Event->new('BAD_PARAMS', note => 'Need CC number')
293 if not $cc_args->{number};
295 OpenILS::Application::Circ::CreditCard::process_payment({
296 "desc" => $cc_args->{note},
297 "amount" => $total_paid,
298 "patron_id" => $user_id,
299 "cc" => $cc_args->{number},
300 "expiration" => sprintf(
302 $cc_args->{expire_month},
303 $cc_args->{expire_year}
306 "first_name" => $cc_args->{billing_first},
307 "last_name" => $cc_args->{billing_last},
308 "address" => $cc_args->{billing_address},
309 "city" => $cc_args->{billing_city},
310 "state" => $cc_args->{billing_state},
311 "zip" => $cc_args->{billing_zip},
312 "cvv2" => $cc_args->{cvv2},
315 if ($U->event_code($response)) { # non-success
317 "Credit card payment for user $user_id failed: " .
318 $response->{"textcode"} . " " .
319 $response->{"payload"}->{"error_message"}
324 # We need to save this for later in case there's a failure on
325 # the EG side to store the processor's result.
326 $cc_payload = $response->{"payload"};
328 $approval_code = $cc_payload->{"authorization"};
329 $cc_type = $cc_payload->{"card_type"};
330 $cc_processor = $cc_payload->{"processor"};
331 $cc_order_number = $cc_payload->{"order_number"};
332 $logger->info("Credit card payment for user $user_id succeeded");
335 return OpenILS::Event->new(
336 'BAD_PARAMS', note => 'Need approval code'
337 ) if not $cc_args->{approval_code};
341 ### RE-OPEN TRANSACTION HERE ###
345 # create payment records
346 my $create_money_method = "create_money_" . $type;
347 for my $payment (@payment_objs) {
348 # update the transaction if it's done
349 my $amount = $payment->amount;
350 my $transid = $payment->xact;
351 my $trans = $xacts{$transid};
352 # making payment with existing patron credit.
353 $credit -= $amount if $type eq 'credit_payment';
354 if( (my $cred = ($trans->balance_owed - $amount)) <= 0 ) {
355 # Any overpay on this transaction goes directly into patron
359 my $circ = $e->retrieve_action_circulation($transid);
361 # Whether or not we close the transaction. We definitely
362 # close is no circulation transaction is present,
363 # otherwise we check if the circulation is in a state that
364 # allows itself to be closed.
365 if (!$circ || OpenILS::Application::Circ::CircCommon->can_close_circ($e, $circ)) {
366 $trans = $e->retrieve_money_billable_transaction($transid);
367 $trans->xact_finish("now");
368 if (!$e->update_money_billable_transaction($trans)) {
369 return _recording_failure(
370 $e, "update_money_billable_transaction() failed",
371 $payment, $cc_payload
377 $payment->approval_code($approval_code) if $approval_code;
378 $payment->cc_order_number($cc_order_number) if $cc_order_number;
379 $payment->cc_type($cc_type) if $cc_type;
380 $payment->cc_processor($cc_processor) if $cc_processor;
381 $payment->cc_first_name($cc_args->{'billing_first'}) if $cc_args->{'billing_first'};
382 $payment->cc_last_name($cc_args->{'billing_last'}) if $cc_args->{'billing_last'};
383 if (!$e->$create_money_method($payment)) {
384 return _recording_failure(
385 $e, "$create_money_method failed", $payment, $cc_payload
389 push(@payment_ids, $payment->id);
392 my $evt = _update_patron_credit($e, $patron, $credit);
394 return _recording_failure(
395 $e, "_update_patron_credit() failed", undef, $cc_payload
399 for my $org_id (keys %orgs) {
400 # calculate penalties for each of the affected orgs
401 $evt = OpenILS::Utils::Penalty->calculate_penalties(
402 $e, $user_id, $org_id
405 return _recording_failure(
406 $e, "calculate_penalties() failed", undef, $cc_payload
411 # update the user to create a new last_xact_id
412 $e->update_actor_user($patron) or return $e->die_event;
413 $patron = $e->retrieve_actor_user($patron) or return $e->die_event;
416 # update the cached user object if a user is making a payment toward
417 # his/her own account
418 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1)
419 if $user_id == $e->requestor->id;
421 return {last_xact_id => $patron->last_xact_id, payments => \@payment_ids};
424 sub _recording_failure {
425 my ($e, $msg, $payment, $payload) = @_;
427 if ($payload) { # If the payment processor already accepted a payment:
428 $logger->error($msg);
429 $logger->error("Payment processor payload: " . Dumper($payload));
430 # payment shouldn't contain CC number
431 $logger->error("Payment: " . Dumper($payment)) if $payment;
435 return new OpenILS::Event(
436 "CREDIT_PROCESSOR_SUCCESS_WO_RECORD",
437 "payload" => $payload
439 } else { # Otherwise, the problem is somewhat less severe:
441 $logger->warn("Payment: " . Dumper($payment)) if $payment;
442 return $e->die_event;
446 sub _update_patron_credit {
447 my($e, $patron, $credit) = @_;
448 return undef if $credit == 0;
449 $patron->credit_forward_balance($patron->credit_forward_balance + $credit);
450 return OpenILS::Event->new('NEGATIVE_PATRON_BALANCE') if $patron->credit_forward_balance < 0;
451 $e->update_actor_user($patron) or return $e->die_event;
456 __PACKAGE__->register_method(
457 method => "retrieve_payments",
458 api_name => "open-ils.circ.money.payment.retrieve.all_",
459 notes => "Returns a list of payments attached to a given transaction"
461 sub retrieve_payments {
462 my( $self, $client, $login, $transid ) = @_;
465 $apputils->checksesperm($login, 'VIEW_TRANSACTION');
468 # XXX the logic here is wrong.. we need to check the owner of the transaction
469 # to make sure the requestor has access
471 # XXX grab the view, for each object in the view, grab the real object
473 return $apputils->simplereq(
475 'open-ils.cstore.direct.money.payment.search.atomic', { xact => $transid } );
479 __PACKAGE__->register_method(
480 method => "retrieve_payments2",
482 api_name => "open-ils.circ.money.payment.retrieve.all",
483 notes => "Returns a list of payments attached to a given transaction"
486 sub retrieve_payments2 {
487 my( $self, $client, $login, $transid ) = @_;
489 my $e = new_editor(authtoken=>$login);
490 return $e->event unless $e->checkauth;
491 return $e->event unless $e->allowed('VIEW_TRANSACTION');
494 my $pmnts = $e->search_money_payment({ xact => $transid });
496 my $type = $_->payment_type;
497 my $meth = "retrieve_money_$type";
498 my $p = $e->$meth($_->id) or return $e->event;
499 $p->payment_type($type);
500 $p->cash_drawer($e->retrieve_actor_workstation($p->cash_drawer))
501 if $p->has_field('cash_drawer');
502 push( @payments, $p );
508 __PACKAGE__->register_method(
509 method => "format_payment_receipt",
510 api_name => "open-ils.circ.money.payment_receipt.print",
512 desc => 'Returns a printable receipt for the specified payments',
514 { desc => 'Authentication token', type => 'string'},
515 { desc => 'Payment ID or array of payment IDs', type => 'number' },
518 desc => q/An action_trigger.event object or error event./,
523 __PACKAGE__->register_method(
524 method => "format_payment_receipt",
525 api_name => "open-ils.circ.money.payment_receipt.email",
527 desc => 'Emails a receipt for the specified payments to the user associated with the first payment',
529 { desc => 'Authentication token', type => 'string'},
530 { desc => 'Payment ID or array of payment IDs', type => 'number' },
533 desc => q/Undefined on success, otherwise an error event./,
539 sub format_payment_receipt {
540 my($self, $conn, $auth, $mp_id) = @_;
543 if (ref $mp_id ne 'ARRAY') {
544 $mp_ids = [ $mp_id ];
549 my $for_print = ($self->api_name =~ /print/);
550 my $for_email = ($self->api_name =~ /email/);
552 # manually use xact (i.e. authoritative) so we can kill the cstore
553 # connection before sending the action/trigger request. This prevents our cstore
554 # backend from sitting idle while A/T (which uses its own transactions) runs.
555 my $e = new_editor(xact => 1, authtoken => $auth);
556 return $e->die_event unless $e->checkauth;
559 for my $id (@$mp_ids) {
561 my $payment = $e->retrieve_money_payment([
569 ]) or return $e->die_event;
571 return $e->die_event unless
572 $e->requestor->id == $payment->xact->usr->id or
573 $e->allowed('VIEW_TRANSACTION', $payment->xact->usr->home_ou);
575 push @$payments, $payment;
582 return $U->fire_object_event(undef, 'money.format.payment_receipt.print', $payments, $$payments[0]->xact->usr->home_ou);
584 } elsif ($for_email) {
586 for my $p (@$payments) {
587 $U->create_events_for_hook('money.format.payment_receipt.email', $p, $p->xact->usr->home_ou, undef, undef, 1);
594 __PACKAGE__->register_method(
595 method => "create_grocery_bill",
596 api_name => "open-ils.circ.money.grocery.create",
598 Creates a new grocery transaction using the transaction object provided
599 PARAMS: (login_session, money.grocery (mg) object)
602 sub create_grocery_bill {
603 my( $self, $client, $login, $transaction ) = @_;
605 my( $staff, $evt ) = $apputils->checkses($login);
607 $evt = $apputils->check_perms($staff->id,
608 $transaction->billing_location, 'CREATE_TRANSACTION' );
612 $logger->activity("Creating grocery bill " . Dumper($transaction) );
614 $transaction->clear_id;
615 my $session = $apputils->start_db_session;
616 $apputils->set_audit_info($session, $login, $staff->id, $staff->wsid);
617 my $transid = $session->request(
618 'open-ils.storage.direct.money.grocery.create', $transaction)->gather(1);
620 throw OpenSRF::EX ("Error creating new money.grocery") unless defined $transid;
622 $logger->debug("Created new grocery transaction $transid");
624 $apputils->commit_db_session($session);
626 my $e = new_editor(xact=>1);
627 $evt = $U->check_open_xact($e, $transid);
635 __PACKAGE__->register_method(
636 method => 'fetch_reservation',
637 api_name => 'open-ils.circ.booking.reservation.retrieve'
639 sub fetch_reservation {
640 my( $self, $conn, $auth, $id ) = @_;
641 my $e = new_editor(authtoken=>$auth);
642 return $e->event unless $e->checkauth;
643 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
644 my $g = $e->retrieve_booking_reservation($id)
649 __PACKAGE__->register_method(
650 method => 'fetch_grocery',
651 api_name => 'open-ils.circ.money.grocery.retrieve'
654 my( $self, $conn, $auth, $id ) = @_;
655 my $e = new_editor(authtoken=>$auth);
656 return $e->event unless $e->checkauth;
657 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
658 my $g = $e->retrieve_money_grocery($id)
664 __PACKAGE__->register_method(
665 method => "billing_items",
666 api_name => "open-ils.circ.money.billing.retrieve.all",
669 desc => 'Returns a list of billing items for the given transaction ID. ' .
670 'If the operator is not the owner of the transaction, the VIEW_TRANSACTION permission is required.',
672 { desc => 'Authentication token', type => 'string'},
673 { desc => 'Transaction ID', type => 'number'}
676 desc => 'Transaction object, event on error'
682 my( $self, $client, $login, $transid ) = @_;
684 my( $trans, $evt ) = $U->fetch_billable_xact($transid);
688 ($staff, $evt ) = $apputils->checkses($login);
691 if($staff->id ne $trans->usr) {
692 $evt = $U->check_perms($staff->id, $staff->home_ou, 'VIEW_TRANSACTION');
696 return $apputils->simplereq( 'open-ils.cstore',
697 'open-ils.cstore.direct.money.billing.search.atomic', { xact => $transid } )
701 __PACKAGE__->register_method(
702 method => "billing_items_create",
703 api_name => "open-ils.circ.money.billing.create",
705 Creates a new billing line item
706 PARAMS( login, bill_object (mb) )
709 sub billing_items_create {
710 my( $self, $client, $login, $billing ) = @_;
712 my $e = new_editor(authtoken => $login, xact => 1);
713 return $e->die_event unless $e->checkauth;
714 return $e->die_event unless $e->allowed('CREATE_BILL');
716 my $xact = $e->retrieve_money_billable_transaction($billing->xact)
717 or return $e->die_event;
719 # if the transaction was closed, re-open it
720 if($xact->xact_finish) {
721 $xact->clear_xact_finish;
722 $e->update_money_billable_transaction($xact)
723 or return $e->die_event;
726 my $amt = $billing->amount;
728 $billing->amount($amt);
730 $e->create_money_billing($billing) or return $e->die_event;
731 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $xact->usr, $U->xact_org($xact->id,$e));
734 $evt = $U->check_open_xact($e, $xact->id, $xact);
743 __PACKAGE__->register_method(
744 method => 'void_bill',
745 api_name => 'open-ils.circ.money.billing.void',
748 @param authtoken Login session key
749 @param billid Id for the bill to void. This parameter may be repeated to reference other bills.
750 @return 1 on success, Event on error
754 my( $s, $c, $authtoken, @billids ) = @_;
756 my $e = new_editor( authtoken => $authtoken, xact => 1 );
757 return $e->die_event unless $e->checkauth;
758 return $e->die_event unless $e->allowed('VOID_BILLING');
761 for my $billid (@billids) {
763 my $bill = $e->retrieve_money_billing($billid)
764 or return $e->die_event;
766 my $xact = $e->retrieve_money_billable_transaction($bill->xact)
767 or return $e->die_event;
769 if($U->is_true($bill->voided)) {
771 return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
774 my $org = $U->xact_org($bill->xact, $e);
775 $users{$xact->usr} = {} unless $users{$xact->usr};
776 $users{$xact->usr}->{$org} = 1;
779 $bill->voider($e->requestor->id);
780 $bill->void_time('now');
782 $e->update_money_billing($bill) or return $e->die_event;
783 my $evt = $U->check_open_xact($e, $bill->xact, $xact);
787 # calculate penalties for all user/org combinations
788 for my $user_id (keys %users) {
789 for my $org_id (keys %{$users{$user_id}}) {
790 OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
798 __PACKAGE__->register_method(
799 method => 'edit_bill_note',
800 api_name => 'open-ils.circ.money.billing.note.edit',
802 Edits the note for a bill
803 @param authtoken Login session key
804 @param note The replacement note for the bills we're editing
805 @param billid Id for the bill to edit the note of. This parameter may be repeated to reference other bills.
806 @return 1 on success, Event on error
810 my( $s, $c, $authtoken, $note, @billids ) = @_;
812 my $e = new_editor( authtoken => $authtoken, xact => 1 );
813 return $e->die_event unless $e->checkauth;
814 return $e->die_event unless $e->allowed('UPDATE_BILL_NOTE');
816 for my $billid (@billids) {
818 my $bill = $e->retrieve_money_billing($billid)
819 or return $e->die_event;
822 # 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.
824 $e->update_money_billing($bill) or return $e->die_event;
831 __PACKAGE__->register_method(
832 method => 'edit_payment_note',
833 api_name => 'open-ils.circ.money.payment.note.edit',
835 Edits the note for a payment
836 @param authtoken Login session key
837 @param note The replacement note for the payments we're editing
838 @param paymentid Id for the payment to edit the note of. This parameter may be repeated to reference other payments.
839 @return 1 on success, Event on error
842 sub edit_payment_note {
843 my( $s, $c, $authtoken, $note, @paymentids ) = @_;
845 my $e = new_editor( authtoken => $authtoken, xact => 1 );
846 return $e->die_event unless $e->checkauth;
847 return $e->die_event unless $e->allowed('UPDATE_PAYMENT_NOTE');
849 for my $paymentid (@paymentids) {
851 my $payment = $e->retrieve_money_payment($paymentid)
852 or return $e->die_event;
854 $payment->note($note);
855 # 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.
857 $e->update_money_payment($payment) or return $e->die_event;
865 __PACKAGE__->register_method (
866 method => 'fetch_mbts',
868 api_name => 'open-ils.circ.money.billable_xact_summary.retrieve'
871 my( $self, $conn, $auth, $id) = @_;
873 my $e = new_editor(xact => 1, authtoken=>$auth);
874 return $e->event unless $e->checkauth;
875 my ($mbts) = $U->fetch_mbts($id, $e);
877 my $user = $e->retrieve_actor_user($mbts->usr)
878 or return $e->die_event;
880 return $e->die_event unless $e->allowed('VIEW_TRANSACTION', $user->home_ou);
886 __PACKAGE__->register_method(
887 method => 'desk_payments',
888 api_name => 'open-ils.circ.money.org_unit.desk_payments'
891 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
892 my $e = new_editor(authtoken=>$auth);
893 return $e->event unless $e->checkauth;
894 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
895 my $data = $U->storagereq(
896 'open-ils.storage.money.org_unit.desk_payments.atomic',
897 $org, $start_date, $end_date );
899 $_->workstation( $_->workstation->name ) for(@$data);
904 __PACKAGE__->register_method(
905 method => 'user_payments',
906 api_name => 'open-ils.circ.money.org_unit.user_payments'
910 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
911 my $e = new_editor(authtoken=>$auth);
912 return $e->event unless $e->checkauth;
913 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
914 my $data = $U->storagereq(
915 'open-ils.storage.money.org_unit.user_payments.atomic',
916 $org, $start_date, $end_date );
919 $e->retrieve_actor_card($_->usr->card)->barcode);
921 $e->retrieve_actor_org_unit($_->usr->home_ou)->shortname);
927 __PACKAGE__->register_method(
928 method => 'retrieve_credit_payable_balance',
929 api_name => 'open-ils.circ.credit.payable_balance.retrieve',
932 desc => q/Returns the total amount the patron can pay via credit card/,
934 { desc => 'Authentication token', type => 'string' },
935 { desc => 'User id', type => 'number' }
937 return => { desc => 'The ID of the new provider' }
941 sub retrieve_credit_payable_balance {
942 my ( $self, $conn, $auth, $user_id ) = @_;
943 my $e = new_editor(authtoken => $auth);
944 return $e->event unless $e->checkauth;
946 my $user = $e->retrieve_actor_user($user_id)
949 if($e->requestor->id != $user_id) {
950 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou)
953 my $circ_orgs = $e->json_query({
954 "select" => {circ => ["circ_lib"]},
956 "where" => {usr => $user_id, xact_finish => undef},
960 my $groc_orgs = $e->json_query({
961 "select" => {mg => ["billing_location"]},
963 "where" => {usr => $user_id, xact_finish => undef},
968 for my $org ( @$circ_orgs, @$groc_orgs ) {
969 my $o = $org->{billing_location};
970 $o = $org->{circ_lib} unless $o;
971 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.
972 $hash{$o} = $U->ou_ancestor_setting_value($o, 'credit.payments.allow', $e);
975 my @credit_orgs = map { $hash{$_} ? ($_) : () } keys %hash;
976 $logger->debug("credit: relevant orgs that allow credit payments => @credit_orgs");
979 OpenILS::Application::AppUtils->simplereq('open-ils.actor',
980 'open-ils.actor.user.transactions.have_charge', $auth, $user_id);
984 for my $xact (@$xact_summaries) {
986 # make two lists and grab them in batch XXX
987 if ( $xact->xact_type eq 'circulation' ) {
988 my $circ = $e->retrieve_action_circulation($xact->id) or return $e->event;
989 next unless grep { $_ == $circ->circ_lib } @credit_orgs;
991 } elsif ($xact->xact_type eq 'grocery') {
992 my $bill = $e->retrieve_money_grocery($xact->id) or return $e->event;
993 next unless grep { $_ == $bill->billing_location } @credit_orgs;
994 } elsif ($xact->xact_type eq 'reservation') {
995 my $bill = $e->retrieve_booking_reservation($xact->id) or return $e->event;
996 next unless grep { $_ == $bill->pickup_lib } @credit_orgs;
998 $sum += $xact->balance_owed();