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
165 for my $pay (@{$payments->{payments}}) {
166 my $xact_id = $pay->[0];
167 my $xact = $e->retrieve_money_billable_transaction_summary($xact_id)
168 or return $e->die_event;
170 if($xact->usr != $user_id) {
172 return OpenILS::Event->new('BAD_PARAMS', note => q/user does not match transaction/);
175 $xacts{$xact_id} = $xact;
180 for my $pay (@{$payments->{payments}}) {
181 my $transid = $pay->[0];
182 my $amount = $pay->[1];
183 $amount =~ s/\$//og; # just to be safe
184 my $trans = $xacts{$transid};
186 $total_paid += $amount;
188 my $org_id = $U->xact_org($transid, $e);
190 if (!$orgs{$org_id}) {
193 # patron credit has to be allowed at all orgs receiving payment
194 if ($type eq 'credit_payment' and $U->ou_ancestor_setting_value(
195 $org_id, 'circ.disable_patron_credit', $e)) {
197 return OpenILS::Event->new('PATRON_CREDIT_DISABLED');
201 # A negative payment is a refund.
204 # Negative credit card payments are not allowed
205 if($type eq 'credit_card_payment') {
207 return OpenILS::Event->new(
209 note => q/Negative credit card payments not allowed/
213 # If the refund causes the transaction balance to exceed 0 dollars,
214 # we are in effect loaning the patron money. This is not allowed.
215 if( ($trans->balance_owed - $amount) > 0 ) {
217 return OpenILS::Event->new('REFUND_EXCEEDS_BALANCE');
220 # Otherwise, make sure the refund does not exceed desk payments
221 # This is also not allowed
223 my $desk_payments = $e->search_money_desk_payment({xact => $transid, voided => 'f'});
224 $desk_total += $_->amount for @$desk_payments;
226 if( (-$amount) > $desk_total ) {
228 return OpenILS::Event->new(
229 'REFUND_EXCEEDS_DESK_PAYMENTS',
230 payload => { allowed_refund => $desk_total, submitted_refund => -$amount } );
234 my $payobj = "Fieldmapper::money::$type";
235 $payobj = $payobj->new;
237 $payobj->amount($amount);
238 $payobj->amount_collected($amount);
239 $payobj->xact($transid);
240 $payobj->note($note);
241 if ((not $payobj->note) and ($type eq 'credit_card_payment')) {
242 $payobj->note($cc_args->{note});
245 if ($payobj->has_field('accepting_usr')) { $payobj->accepting_usr($e->requestor->id); }
246 if ($payobj->has_field('cash_drawer')) { $payobj->cash_drawer($drawer); }
247 if ($payobj->has_field('cc_type')) { $payobj->cc_type($cc_args->{type}); }
248 if ($payobj->has_field('check_number')) { $payobj->check_number($check_number); }
250 # Store the last 4 digits of the CC number
251 if ($payobj->has_field('cc_number')) {
252 $payobj->cc_number(substr($cc_args->{number}, -4));
254 if ($payobj->has_field('expire_month')) { $payobj->expire_month($cc_args->{expire_month}); }
255 if ($payobj->has_field('expire_year')) { $payobj->expire_year($cc_args->{expire_year}); }
257 # Note: It is important not to set approval_code
258 # on the fieldmapper object yet.
260 push(@payment_objs, $payobj);
262 } # all payment objects have been created and inserted.
264 #### NO WRITES TO THE DB ABOVE THIS LINE -- THEY'LL ONLY BE DISCARDED ###
267 # After we try to externally process a credit card (if desired), we'll
268 # open a new transaction. We cannot leave one open while credit card
269 # processing might be happening, as it can easily time out the database
274 if($type eq 'credit_card_payment') {
275 $approval_code = $cc_args->{approval_code};
276 # If an approval code was not given, we'll need
277 # to call to the payment processor ourselves.
278 if ($cc_args->{where_process} == 1) {
279 return OpenILS::Event->new('BAD_PARAMS', note => 'Need CC number')
280 if not $cc_args->{number};
282 OpenILS::Application::Circ::CreditCard::process_payment({
283 "desc" => $cc_args->{note},
284 "amount" => $total_paid,
285 "patron_id" => $user_id,
286 "cc" => $cc_args->{number},
287 "expiration" => sprintf(
289 $cc_args->{expire_month},
290 $cc_args->{expire_year}
293 "first_name" => $cc_args->{billing_first},
294 "last_name" => $cc_args->{billing_last},
295 "address" => $cc_args->{billing_address},
296 "city" => $cc_args->{billing_city},
297 "state" => $cc_args->{billing_state},
298 "zip" => $cc_args->{billing_zip},
299 "cvv2" => $cc_args->{cvv2},
302 if ($U->event_code($response)) { # non-success
304 "Credit card payment for user $user_id failed: " .
305 $response->{"textcode"} . " " .
306 $response->{"payload"}->{"error_message"}
311 # We need to save this for later in case there's a failure on
312 # the EG side to store the processor's result.
313 $cc_payload = $response->{"payload"};
315 $approval_code = $cc_payload->{"authorization"};
316 $cc_type = $cc_payload->{"card_type"};
317 $cc_processor = $cc_payload->{"processor"};
318 $cc_order_number = $cc_payload->{"order_number"};
319 $logger->info("Credit card payment for user $user_id succeeded");
322 return OpenILS::Event->new(
323 'BAD_PARAMS', note => 'Need approval code'
324 ) if not $cc_args->{approval_code};
328 ### RE-OPEN TRANSACTION HERE ###
332 # create payment records
333 my $create_money_method = "create_money_" . $type;
334 for my $payment (@payment_objs) {
335 # update the transaction if it's done
336 my $amount = $payment->amount;
337 my $transid = $payment->xact;
338 my $trans = $xacts{$transid};
339 # making payment with existing patron credit.
340 $credit -= $amount if $type eq 'credit_payment';
341 if( (my $cred = ($trans->balance_owed - $amount)) <= 0 ) {
342 # Any overpay on this transaction goes directly into patron
346 my $circ = $e->retrieve_action_circulation($transid);
348 # Whether or not we close the transaction. We definitely
349 # close is no circulation transaction is present,
350 # otherwise we check if the circulation is in a state that
351 # allows itself to be closed.
352 if (!$circ || OpenILS::Application::Circ::CircCommon->can_close_circ($e, $circ)) {
353 $trans = $e->retrieve_money_billable_transaction($transid);
354 $trans->xact_finish("now");
355 if (!$e->update_money_billable_transaction($trans)) {
356 return _recording_failure(
357 $e, "update_money_billable_transaction() failed",
358 $payment, $cc_payload
364 $payment->approval_code($approval_code) if $approval_code;
365 $payment->cc_order_number($cc_order_number) if $cc_order_number;
366 $payment->cc_type($cc_type) if $cc_type;
367 $payment->cc_processor($cc_processor) if $cc_processor;
368 $payment->cc_first_name($cc_args->{'billing_first'}) if $cc_args->{'billing_first'};
369 $payment->cc_last_name($cc_args->{'billing_last'}) if $cc_args->{'billing_last'};
370 if (!$e->$create_money_method($payment)) {
371 return _recording_failure(
372 $e, "$create_money_method failed", $payment, $cc_payload
376 push(@payment_ids, $payment->id);
379 my $evt = _update_patron_credit($e, $patron, $credit);
381 return _recording_failure(
382 $e, "_update_patron_credit() failed", undef, $cc_payload
386 for my $org_id (keys %orgs) {
387 # calculate penalties for each of the affected orgs
388 $evt = OpenILS::Utils::Penalty->calculate_penalties(
389 $e, $user_id, $org_id
392 return _recording_failure(
393 $e, "calculate_penalties() failed", undef, $cc_payload
398 # update the user to create a new last_xact_id
399 $e->update_actor_user($patron) or return $e->die_event;
400 $patron = $e->retrieve_actor_user($patron) or return $e->die_event;
403 # update the cached user object if a user is making a payment toward
404 # his/her own account
405 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1)
406 if $user_id == $e->requestor->id;
408 return {last_xact_id => $patron->last_xact_id, payments => \@payment_ids};
411 sub _recording_failure {
412 my ($e, $msg, $payment, $payload) = @_;
414 if ($payload) { # If the payment processor already accepted a payment:
415 $logger->error($msg);
416 $logger->error("Payment processor payload: " . Dumper($payload));
417 # payment shouldn't contain CC number
418 $logger->error("Payment: " . Dumper($payment)) if $payment;
422 return new OpenILS::Event(
423 "CREDIT_PROCESSOR_SUCCESS_WO_RECORD",
424 "payload" => $payload
426 } else { # Otherwise, the problem is somewhat less severe:
428 $logger->warn("Payment: " . Dumper($payment)) if $payment;
429 return $e->die_event;
433 sub _update_patron_credit {
434 my($e, $patron, $credit) = @_;
435 return undef if $credit == 0;
436 $patron->credit_forward_balance($patron->credit_forward_balance + $credit);
437 return OpenILS::Event->new('NEGATIVE_PATRON_BALANCE') if $patron->credit_forward_balance < 0;
438 $e->update_actor_user($patron) or return $e->die_event;
443 __PACKAGE__->register_method(
444 method => "retrieve_payments",
445 api_name => "open-ils.circ.money.payment.retrieve.all_",
446 notes => "Returns a list of payments attached to a given transaction"
448 sub retrieve_payments {
449 my( $self, $client, $login, $transid ) = @_;
452 $apputils->checksesperm($login, 'VIEW_TRANSACTION');
455 # XXX the logic here is wrong.. we need to check the owner of the transaction
456 # to make sure the requestor has access
458 # XXX grab the view, for each object in the view, grab the real object
460 return $apputils->simplereq(
462 'open-ils.cstore.direct.money.payment.search.atomic', { xact => $transid } );
466 __PACKAGE__->register_method(
467 method => "retrieve_payments2",
469 api_name => "open-ils.circ.money.payment.retrieve.all",
470 notes => "Returns a list of payments attached to a given transaction"
473 sub retrieve_payments2 {
474 my( $self, $client, $login, $transid ) = @_;
476 my $e = new_editor(authtoken=>$login);
477 return $e->event unless $e->checkauth;
478 return $e->event unless $e->allowed('VIEW_TRANSACTION');
481 my $pmnts = $e->search_money_payment({ xact => $transid });
483 my $type = $_->payment_type;
484 my $meth = "retrieve_money_$type";
485 my $p = $e->$meth($_->id) or return $e->event;
486 $p->payment_type($type);
487 $p->cash_drawer($e->retrieve_actor_workstation($p->cash_drawer))
488 if $p->has_field('cash_drawer');
489 push( @payments, $p );
495 __PACKAGE__->register_method(
496 method => "format_payment_receipt",
497 api_name => "open-ils.circ.money.payment_receipt.print",
499 desc => 'Returns a printable receipt for the specified payments',
501 { desc => 'Authentication token', type => 'string'},
502 { desc => 'Payment ID or array of payment IDs', type => 'number' },
505 desc => q/An action_trigger.event object or error event./,
510 __PACKAGE__->register_method(
511 method => "format_payment_receipt",
512 api_name => "open-ils.circ.money.payment_receipt.email",
514 desc => 'Emails a receipt for the specified payments to the user associated with the first payment',
516 { desc => 'Authentication token', type => 'string'},
517 { desc => 'Payment ID or array of payment IDs', type => 'number' },
520 desc => q/Undefined on success, otherwise an error event./,
526 sub format_payment_receipt {
527 my($self, $conn, $auth, $mp_id) = @_;
530 if (ref $mp_id ne 'ARRAY') {
531 $mp_ids = [ $mp_id ];
536 my $for_print = ($self->api_name =~ /print/);
537 my $for_email = ($self->api_name =~ /email/);
539 # manually use xact (i.e. authoritative) so we can kill the cstore
540 # connection before sending the action/trigger request. This prevents our cstore
541 # backend from sitting idle while A/T (which uses its own transactions) runs.
542 my $e = new_editor(xact => 1, authtoken => $auth);
543 return $e->die_event unless $e->checkauth;
546 for my $id (@$mp_ids) {
548 my $payment = $e->retrieve_money_payment([
556 ]) or return $e->die_event;
558 return $e->die_event unless
559 $e->requestor->id == $payment->xact->usr->id or
560 $e->allowed('VIEW_TRANSACTION', $payment->xact->usr->home_ou);
562 push @$payments, $payment;
569 return $U->fire_object_event(undef, 'money.format.payment_receipt.print', $payments, $$payments[0]->xact->usr->home_ou);
571 } elsif ($for_email) {
573 for my $p (@$payments) {
574 $U->create_events_for_hook('money.format.payment_receipt.email', $p, $p->xact->usr->home_ou, undef, undef, 1);
581 __PACKAGE__->register_method(
582 method => "create_grocery_bill",
583 api_name => "open-ils.circ.money.grocery.create",
585 Creates a new grocery transaction using the transaction object provided
586 PARAMS: (login_session, money.grocery (mg) object)
589 sub create_grocery_bill {
590 my( $self, $client, $login, $transaction ) = @_;
592 my( $staff, $evt ) = $apputils->checkses($login);
594 $evt = $apputils->check_perms($staff->id,
595 $transaction->billing_location, 'CREATE_TRANSACTION' );
599 $logger->activity("Creating grocery bill " . Dumper($transaction) );
601 $transaction->clear_id;
602 my $session = $apputils->start_db_session;
603 $apputils->set_audit_info($session, $login, $staff->id, $staff->wsid);
604 my $transid = $session->request(
605 'open-ils.storage.direct.money.grocery.create', $transaction)->gather(1);
607 throw OpenSRF::EX ("Error creating new money.grocery") unless defined $transid;
609 $logger->debug("Created new grocery transaction $transid");
611 $apputils->commit_db_session($session);
613 my $e = new_editor(xact=>1);
614 $evt = $U->check_open_xact($e, $transid);
622 __PACKAGE__->register_method(
623 method => 'fetch_reservation',
624 api_name => 'open-ils.circ.booking.reservation.retrieve'
626 sub fetch_reservation {
627 my( $self, $conn, $auth, $id ) = @_;
628 my $e = new_editor(authtoken=>$auth);
629 return $e->event unless $e->checkauth;
630 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
631 my $g = $e->retrieve_booking_reservation($id)
636 __PACKAGE__->register_method(
637 method => 'fetch_grocery',
638 api_name => 'open-ils.circ.money.grocery.retrieve'
641 my( $self, $conn, $auth, $id ) = @_;
642 my $e = new_editor(authtoken=>$auth);
643 return $e->event unless $e->checkauth;
644 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
645 my $g = $e->retrieve_money_grocery($id)
651 __PACKAGE__->register_method(
652 method => "billing_items",
653 api_name => "open-ils.circ.money.billing.retrieve.all",
656 desc => 'Returns a list of billing items for the given transaction ID. ' .
657 'If the operator is not the owner of the transaction, the VIEW_TRANSACTION permission is required.',
659 { desc => 'Authentication token', type => 'string'},
660 { desc => 'Transaction ID', type => 'number'}
663 desc => 'Transaction object, event on error'
669 my( $self, $client, $login, $transid ) = @_;
671 my( $trans, $evt ) = $U->fetch_billable_xact($transid);
675 ($staff, $evt ) = $apputils->checkses($login);
678 if($staff->id ne $trans->usr) {
679 $evt = $U->check_perms($staff->id, $staff->home_ou, 'VIEW_TRANSACTION');
683 return $apputils->simplereq( 'open-ils.cstore',
684 'open-ils.cstore.direct.money.billing.search.atomic', { xact => $transid } )
688 __PACKAGE__->register_method(
689 method => "billing_items_create",
690 api_name => "open-ils.circ.money.billing.create",
692 Creates a new billing line item
693 PARAMS( login, bill_object (mb) )
696 sub billing_items_create {
697 my( $self, $client, $login, $billing ) = @_;
699 my $e = new_editor(authtoken => $login, xact => 1);
700 return $e->die_event unless $e->checkauth;
701 return $e->die_event unless $e->allowed('CREATE_BILL');
703 my $xact = $e->retrieve_money_billable_transaction($billing->xact)
704 or return $e->die_event;
706 # if the transaction was closed, re-open it
707 if($xact->xact_finish) {
708 $xact->clear_xact_finish;
709 $e->update_money_billable_transaction($xact)
710 or return $e->die_event;
713 my $amt = $billing->amount;
715 $billing->amount($amt);
717 $e->create_money_billing($billing) or return $e->die_event;
718 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $xact->usr, $U->xact_org($xact->id,$e));
721 $evt = $U->check_open_xact($e, $xact->id, $xact);
730 __PACKAGE__->register_method(
731 method => 'void_bill',
732 api_name => 'open-ils.circ.money.billing.void',
735 @param authtoken Login session key
736 @param billid Id for the bill to void. This parameter may be repeated to reference other bills.
737 @return 1 on success, Event on error
741 my( $s, $c, $authtoken, @billids ) = @_;
743 my $e = new_editor( authtoken => $authtoken, xact => 1 );
744 return $e->die_event unless $e->checkauth;
745 return $e->die_event unless $e->allowed('VOID_BILLING');
748 for my $billid (@billids) {
750 my $bill = $e->retrieve_money_billing($billid)
751 or return $e->die_event;
753 my $xact = $e->retrieve_money_billable_transaction($bill->xact)
754 or return $e->die_event;
756 if($U->is_true($bill->voided)) {
758 return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
761 my $org = $U->xact_org($bill->xact, $e);
762 $users{$xact->usr} = {} unless $users{$xact->usr};
763 $users{$xact->usr}->{$org} = 1;
766 $bill->voider($e->requestor->id);
767 $bill->void_time('now');
769 $e->update_money_billing($bill) or return $e->die_event;
770 my $evt = $U->check_open_xact($e, $bill->xact, $xact);
774 # calculate penalties for all user/org combinations
775 for my $user_id (keys %users) {
776 for my $org_id (keys %{$users{$user_id}}) {
777 OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
785 __PACKAGE__->register_method(
786 method => 'edit_bill_note',
787 api_name => 'open-ils.circ.money.billing.note.edit',
789 Edits the note for a bill
790 @param authtoken Login session key
791 @param note The replacement note for the bills we're editing
792 @param billid Id for the bill to edit the note of. This parameter may be repeated to reference other bills.
793 @return 1 on success, Event on error
797 my( $s, $c, $authtoken, $note, @billids ) = @_;
799 my $e = new_editor( authtoken => $authtoken, xact => 1 );
800 return $e->die_event unless $e->checkauth;
801 return $e->die_event unless $e->allowed('UPDATE_BILL_NOTE');
803 for my $billid (@billids) {
805 my $bill = $e->retrieve_money_billing($billid)
806 or return $e->die_event;
809 # 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.
811 $e->update_money_billing($bill) or return $e->die_event;
818 __PACKAGE__->register_method(
819 method => 'edit_payment_note',
820 api_name => 'open-ils.circ.money.payment.note.edit',
822 Edits the note for a payment
823 @param authtoken Login session key
824 @param note The replacement note for the payments we're editing
825 @param paymentid Id for the payment to edit the note of. This parameter may be repeated to reference other payments.
826 @return 1 on success, Event on error
829 sub edit_payment_note {
830 my( $s, $c, $authtoken, $note, @paymentids ) = @_;
832 my $e = new_editor( authtoken => $authtoken, xact => 1 );
833 return $e->die_event unless $e->checkauth;
834 return $e->die_event unless $e->allowed('UPDATE_PAYMENT_NOTE');
836 for my $paymentid (@paymentids) {
838 my $payment = $e->retrieve_money_payment($paymentid)
839 or return $e->die_event;
841 $payment->note($note);
842 # 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.
844 $e->update_money_payment($payment) or return $e->die_event;
852 __PACKAGE__->register_method (
853 method => 'fetch_mbts',
855 api_name => 'open-ils.circ.money.billable_xact_summary.retrieve'
858 my( $self, $conn, $auth, $id) = @_;
860 my $e = new_editor(xact => 1, authtoken=>$auth);
861 return $e->event unless $e->checkauth;
862 my ($mbts) = $U->fetch_mbts($id, $e);
864 my $user = $e->retrieve_actor_user($mbts->usr)
865 or return $e->die_event;
867 return $e->die_event unless $e->allowed('VIEW_TRANSACTION', $user->home_ou);
873 __PACKAGE__->register_method(
874 method => 'desk_payments',
875 api_name => 'open-ils.circ.money.org_unit.desk_payments'
878 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
879 my $e = new_editor(authtoken=>$auth);
880 return $e->event unless $e->checkauth;
881 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
882 my $data = $U->storagereq(
883 'open-ils.storage.money.org_unit.desk_payments.atomic',
884 $org, $start_date, $end_date );
886 $_->workstation( $_->workstation->name ) for(@$data);
891 __PACKAGE__->register_method(
892 method => 'user_payments',
893 api_name => 'open-ils.circ.money.org_unit.user_payments'
897 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
898 my $e = new_editor(authtoken=>$auth);
899 return $e->event unless $e->checkauth;
900 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
901 my $data = $U->storagereq(
902 'open-ils.storage.money.org_unit.user_payments.atomic',
903 $org, $start_date, $end_date );
906 $e->retrieve_actor_card($_->usr->card)->barcode);
908 $e->retrieve_actor_org_unit($_->usr->home_ou)->shortname);
914 __PACKAGE__->register_method(
915 method => 'retrieve_credit_payable_balance',
916 api_name => 'open-ils.circ.credit.payable_balance.retrieve',
919 desc => q/Returns the total amount the patron can pay via credit card/,
921 { desc => 'Authentication token', type => 'string' },
922 { desc => 'User id', type => 'number' }
924 return => { desc => 'The ID of the new provider' }
928 sub retrieve_credit_payable_balance {
929 my ( $self, $conn, $auth, $user_id ) = @_;
930 my $e = new_editor(authtoken => $auth);
931 return $e->event unless $e->checkauth;
933 my $user = $e->retrieve_actor_user($user_id)
936 if($e->requestor->id != $user_id) {
937 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou)
940 my $circ_orgs = $e->json_query({
941 "select" => {circ => ["circ_lib"]},
943 "where" => {usr => $user_id, xact_finish => undef},
947 my $groc_orgs = $e->json_query({
948 "select" => {mg => ["billing_location"]},
950 "where" => {usr => $user_id, xact_finish => undef},
955 for my $org ( @$circ_orgs, @$groc_orgs ) {
956 my $o = $org->{billing_location};
957 $o = $org->{circ_lib} unless $o;
958 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.
959 $hash{$o} = $U->ou_ancestor_setting_value($o, 'credit.payments.allow', $e);
962 my @credit_orgs = map { $hash{$_} ? ($_) : () } keys %hash;
963 $logger->debug("credit: relevant orgs that allow credit payments => @credit_orgs");
966 OpenILS::Application::AppUtils->simplereq('open-ils.actor',
967 'open-ils.actor.user.transactions.have_charge', $auth, $user_id);
971 for my $xact (@$xact_summaries) {
973 # make two lists and grab them in batch XXX
974 if ( $xact->xact_type eq 'circulation' ) {
975 my $circ = $e->retrieve_action_circulation($xact->id) or return $e->event;
976 next unless grep { $_ == $circ->circ_lib } @credit_orgs;
978 } elsif ($xact->xact_type eq 'grocery') {
979 my $bill = $e->retrieve_money_grocery($xact->id) or return $e->event;
980 next unless grep { $_ == $bill->billing_location } @credit_orgs;
981 } elsif ($xact->xact_type eq 'reservation') {
982 my $bill = $e->retrieve_booking_reservation($xact->id) or return $e->event;
983 next unless grep { $_ == $bill->pickup_lib } @credit_orgs;
985 $sum += $xact->balance_owed();