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;
145 # unless/until determined by payment processor API
146 my ($approval_code, $cc_processor, $cc_type, $cc_order_number) = (undef,undef,undef, undef);
148 my $patron = $e->retrieve_actor_user($user_id) or return $e->die_event;
150 if($patron->last_xact_id ne $last_xact_id) {
152 return OpenILS::Event->new('INVALID_USER_XACT_ID');
155 # A user is allowed to make credit card payments on his/her own behalf
156 # All other scenarious require permission
157 unless($type eq 'credit_card_payment' and $user_id == $e->requestor->id) {
158 return $e->die_event unless $e->allowed('CREATE_PAYMENT', $patron->home_ou);
161 # first collect the transactions and make sure the transaction
162 # user matches the requested user
164 for my $pay (@{$payments->{payments}}) {
165 my $xact_id = $pay->[0];
166 my $xact = $e->retrieve_money_billable_transaction_summary($xact_id)
167 or return $e->die_event;
169 if($xact->usr != $user_id) {
171 return OpenILS::Event->new('BAD_PARAMS', note => q/user does not match transaction/);
174 $xacts{$xact_id} = $xact;
179 for my $pay (@{$payments->{payments}}) {
180 my $transid = $pay->[0];
181 my $amount = $pay->[1];
182 $amount =~ s/\$//og; # just to be safe
183 my $trans = $xacts{$transid};
185 $total_paid += $amount;
187 $orgs{$U->xact_org($transid, $e)} = 1;
189 # A negative payment is a refund.
192 # Negative credit card payments are not allowed
193 if($type eq 'credit_card_payment') {
195 return OpenILS::Event->new(
197 note => q/Negative credit card payments not allowed/
201 # If the refund causes the transaction balance to exceed 0 dollars,
202 # we are in effect loaning the patron money. This is not allowed.
203 if( ($trans->balance_owed - $amount) > 0 ) {
205 return OpenILS::Event->new('REFUND_EXCEEDS_BALANCE');
208 # Otherwise, make sure the refund does not exceed desk payments
209 # This is also not allowed
211 my $desk_payments = $e->search_money_desk_payment({xact => $transid, voided => 'f'});
212 $desk_total += $_->amount for @$desk_payments;
214 if( (-$amount) > $desk_total ) {
216 return OpenILS::Event->new(
217 'REFUND_EXCEEDS_DESK_PAYMENTS',
218 payload => { allowed_refund => $desk_total, submitted_refund => -$amount } );
222 my $payobj = "Fieldmapper::money::$type";
223 $payobj = $payobj->new;
225 $payobj->amount($amount);
226 $payobj->amount_collected($amount);
227 $payobj->xact($transid);
228 $payobj->note($note);
229 if ((not $payobj->note) and ($type eq 'credit_card_payment')) {
230 $payobj->note($cc_args->{note});
233 if ($payobj->has_field('accepting_usr')) { $payobj->accepting_usr($e->requestor->id); }
234 if ($payobj->has_field('cash_drawer')) { $payobj->cash_drawer($drawer); }
235 if ($payobj->has_field('cc_type')) { $payobj->cc_type($cc_args->{type}); }
236 if ($payobj->has_field('check_number')) { $payobj->check_number($check_number); }
238 # Store the last 4 digits of the CC number
239 if ($payobj->has_field('cc_number')) {
240 $payobj->cc_number(substr($cc_args->{number}, -4));
242 if ($payobj->has_field('expire_month')) { $payobj->expire_month($cc_args->{expire_month}); }
243 if ($payobj->has_field('expire_year')) { $payobj->expire_year($cc_args->{expire_year}); }
245 # Note: It is important not to set approval_code
246 # on the fieldmapper object yet.
248 push(@payment_objs, $payobj);
250 } # all payment objects have been created and inserted.
252 #### NO WRITES TO THE DB ABOVE THIS LINE -- THEY'LL ONLY BE DISCARDED ###
255 # After we try to externally process a credit card (if desired), we'll
256 # open a new transaction. We cannot leave one open while credit card
257 # processing might be happening, as it can easily time out the database
262 if($type eq 'credit_card_payment') {
263 $approval_code = $cc_args->{approval_code};
264 # If an approval code was not given, we'll need
265 # to call to the payment processor ourselves.
266 if ($cc_args->{where_process} == 1) {
267 return OpenILS::Event->new('BAD_PARAMS', note => 'Need CC number')
268 if not $cc_args->{number};
270 OpenILS::Application::Circ::CreditCard::process_payment({
271 "desc" => $cc_args->{note},
272 "amount" => $total_paid,
273 "patron_id" => $user_id,
274 "cc" => $cc_args->{number},
275 "expiration" => sprintf(
277 $cc_args->{expire_month},
278 $cc_args->{expire_year}
281 "first_name" => $cc_args->{billing_first},
282 "last_name" => $cc_args->{billing_last},
283 "address" => $cc_args->{billing_address},
284 "city" => $cc_args->{billing_city},
285 "state" => $cc_args->{billing_state},
286 "zip" => $cc_args->{billing_zip},
287 "cvv2" => $cc_args->{cvv2},
290 if ($U->event_code($response)) { # non-success
292 "Credit card payment for user $user_id failed: " .
293 $response->{"textcode"} . " " .
294 $response->{"payload"}->{"error_message"}
299 # We need to save this for later in case there's a failure on
300 # the EG side to store the processor's result.
301 $cc_payload = $response->{"payload"};
303 $approval_code = $cc_payload->{"authorization"};
304 $cc_type = $cc_payload->{"card_type"};
305 $cc_processor = $cc_payload->{"processor"};
306 $cc_order_number = $cc_payload->{"order_number"};
307 $logger->info("Credit card payment for user $user_id succeeded");
310 return OpenILS::Event->new(
311 'BAD_PARAMS', note => 'Need approval code'
312 ) if not $cc_args->{approval_code};
316 ### RE-OPEN TRANSACTION HERE ###
320 # create payment records
321 my $create_money_method = "create_money_" . $type;
322 for my $payment (@payment_objs) {
323 # update the transaction if it's done
324 my $amount = $payment->amount;
325 my $transid = $payment->xact;
326 my $trans = $xacts{$transid};
327 # making payment with existing patron credit.
328 $credit -= $amount if $type eq 'credit_payment';
329 if( (my $cred = ($trans->balance_owed - $amount)) <= 0 ) {
330 # Any overpay on this transaction goes directly into patron
334 my $circ = $e->retrieve_action_circulation($transid);
336 # Whether or not we close the transaction. We definitely
337 # close is no circulation transaction is present,
338 # otherwise we check if the circulation is in a state that
339 # allows itself to be closed.
340 if (!$circ || OpenILS::Application::Circ::CircCommon->can_close_circ($e, $circ)) {
341 $trans = $e->retrieve_money_billable_transaction($transid);
342 $trans->xact_finish("now");
343 if (!$e->update_money_billable_transaction($trans)) {
344 return _recording_failure(
345 $e, "update_money_billable_transaction() failed",
346 $payment, $cc_payload
352 $payment->approval_code($approval_code) if $approval_code;
353 $payment->cc_order_number($cc_order_number) if $cc_order_number;
354 $payment->cc_type($cc_type) if $cc_type;
355 $payment->cc_processor($cc_processor) if $cc_processor;
356 $payment->cc_first_name($cc_args->{'billing_first'}) if $cc_args->{'billing_first'};
357 $payment->cc_last_name($cc_args->{'billing_last'}) if $cc_args->{'billing_last'};
358 if (!$e->$create_money_method($payment)) {
359 return _recording_failure(
360 $e, "$create_money_method failed", $payment, $cc_payload
364 push(@payment_ids, $payment->id);
367 my $evt = _update_patron_credit($e, $patron, $credit);
369 return _recording_failure(
370 $e, "_update_patron_credit() failed", undef, $cc_payload
374 for my $org_id (keys %orgs) {
375 # calculate penalties for each of the affected orgs
376 $evt = OpenILS::Utils::Penalty->calculate_penalties(
377 $e, $user_id, $org_id
380 return _recording_failure(
381 $e, "calculate_penalties() failed", undef, $cc_payload
386 # update the user to create a new last_xact_id
387 $e->update_actor_user($patron) or return $e->die_event;
388 $patron = $e->retrieve_actor_user($patron) or return $e->die_event;
391 # update the cached user object if a user is making a payment toward
392 # his/her own account
393 $U->simplereq('open-ils.auth', 'open-ils.auth.session.reset_timeout', $auth, 1)
394 if $user_id == $e->requestor->id;
396 return {last_xact_id => $patron->last_xact_id, payments => \@payment_ids};
399 sub _recording_failure {
400 my ($e, $msg, $payment, $payload) = @_;
402 if ($payload) { # If the payment processor already accepted a payment:
403 $logger->error($msg);
404 $logger->error("Payment processor payload: " . Dumper($payload));
405 # payment shouldn't contain CC number
406 $logger->error("Payment: " . Dumper($payment)) if $payment;
410 return new OpenILS::Event(
411 "CREDIT_PROCESSOR_SUCCESS_WO_RECORD",
412 "payload" => $payload
414 } else { # Otherwise, the problem is somewhat less severe:
416 $logger->warn("Payment: " . Dumper($payment)) if $payment;
417 return $e->die_event;
421 sub _update_patron_credit {
422 my($e, $patron, $credit) = @_;
423 return undef if $credit == 0;
424 $patron->credit_forward_balance($patron->credit_forward_balance + $credit);
425 return OpenILS::Event->new('NEGATIVE_PATRON_BALANCE') if $patron->credit_forward_balance < 0;
426 $e->update_actor_user($patron) or return $e->die_event;
431 __PACKAGE__->register_method(
432 method => "retrieve_payments",
433 api_name => "open-ils.circ.money.payment.retrieve.all_",
434 notes => "Returns a list of payments attached to a given transaction"
436 sub retrieve_payments {
437 my( $self, $client, $login, $transid ) = @_;
440 $apputils->checksesperm($login, 'VIEW_TRANSACTION');
443 # XXX the logic here is wrong.. we need to check the owner of the transaction
444 # to make sure the requestor has access
446 # XXX grab the view, for each object in the view, grab the real object
448 return $apputils->simplereq(
450 'open-ils.cstore.direct.money.payment.search.atomic', { xact => $transid } );
454 __PACKAGE__->register_method(
455 method => "retrieve_payments2",
457 api_name => "open-ils.circ.money.payment.retrieve.all",
458 notes => "Returns a list of payments attached to a given transaction"
461 sub retrieve_payments2 {
462 my( $self, $client, $login, $transid ) = @_;
464 my $e = new_editor(authtoken=>$login);
465 return $e->event unless $e->checkauth;
466 return $e->event unless $e->allowed('VIEW_TRANSACTION');
469 my $pmnts = $e->search_money_payment({ xact => $transid });
471 my $type = $_->payment_type;
472 my $meth = "retrieve_money_$type";
473 my $p = $e->$meth($_->id) or return $e->event;
474 $p->payment_type($type);
475 $p->cash_drawer($e->retrieve_actor_workstation($p->cash_drawer))
476 if $p->has_field('cash_drawer');
477 push( @payments, $p );
483 __PACKAGE__->register_method(
484 method => "format_payment_receipt",
485 api_name => "open-ils.circ.money.payment_receipt.print",
487 desc => 'Returns a printable receipt for the specified payments',
489 { desc => 'Authentication token', type => 'string'},
490 { desc => 'Payment ID or array of payment IDs', type => 'number' },
493 desc => q/An action_trigger.event object or error event./,
498 __PACKAGE__->register_method(
499 method => "format_payment_receipt",
500 api_name => "open-ils.circ.money.payment_receipt.email",
502 desc => 'Emails a receipt for the specified payments to the user associated with the first payment',
504 { desc => 'Authentication token', type => 'string'},
505 { desc => 'Payment ID or array of payment IDs', type => 'number' },
508 desc => q/Undefined on success, otherwise an error event./,
514 sub format_payment_receipt {
515 my($self, $conn, $auth, $mp_id) = @_;
518 if (ref $mp_id ne 'ARRAY') {
519 $mp_ids = [ $mp_id ];
524 my $for_print = ($self->api_name =~ /print/);
525 my $for_email = ($self->api_name =~ /email/);
527 # manually use xact (i.e. authoritative) so we can kill the cstore
528 # connection before sending the action/trigger request. This prevents our cstore
529 # backend from sitting idle while A/T (which uses its own transactions) runs.
530 my $e = new_editor(xact => 1, authtoken => $auth);
531 return $e->die_event unless $e->checkauth;
534 for my $id (@$mp_ids) {
536 my $payment = $e->retrieve_money_payment([
544 ]) or return $e->die_event;
546 return $e->die_event unless
547 $e->requestor->id == $payment->xact->usr->id or
548 $e->allowed('VIEW_TRANSACTION', $payment->xact->usr->home_ou);
550 push @$payments, $payment;
557 return $U->fire_object_event(undef, 'money.format.payment_receipt.print', $payments, $$payments[0]->xact->usr->home_ou);
559 } elsif ($for_email) {
561 for my $p (@$payments) {
562 $U->create_events_for_hook('money.format.payment_receipt.email', $p, $p->xact->usr->home_ou, undef, undef, 1);
569 __PACKAGE__->register_method(
570 method => "create_grocery_bill",
571 api_name => "open-ils.circ.money.grocery.create",
573 Creates a new grocery transaction using the transaction object provided
574 PARAMS: (login_session, money.grocery (mg) object)
577 sub create_grocery_bill {
578 my( $self, $client, $login, $transaction ) = @_;
580 my( $staff, $evt ) = $apputils->checkses($login);
582 $evt = $apputils->check_perms($staff->id,
583 $transaction->billing_location, 'CREATE_TRANSACTION' );
587 $logger->activity("Creating grocery bill " . Dumper($transaction) );
589 $transaction->clear_id;
590 my $session = $apputils->start_db_session;
591 $apputils->set_audit_info($session, $login, $staff->id, $staff->wsid);
592 my $transid = $session->request(
593 'open-ils.storage.direct.money.grocery.create', $transaction)->gather(1);
595 throw OpenSRF::EX ("Error creating new money.grocery") unless defined $transid;
597 $logger->debug("Created new grocery transaction $transid");
599 $apputils->commit_db_session($session);
601 my $e = new_editor(xact=>1);
602 $evt = _check_open_xact($e, $transid);
610 __PACKAGE__->register_method(
611 method => 'fetch_reservation',
612 api_name => 'open-ils.circ.booking.reservation.retrieve'
614 sub fetch_reservation {
615 my( $self, $conn, $auth, $id ) = @_;
616 my $e = new_editor(authtoken=>$auth);
617 return $e->event unless $e->checkauth;
618 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
619 my $g = $e->retrieve_booking_reservation($id)
624 __PACKAGE__->register_method(
625 method => 'fetch_grocery',
626 api_name => 'open-ils.circ.money.grocery.retrieve'
629 my( $self, $conn, $auth, $id ) = @_;
630 my $e = new_editor(authtoken=>$auth);
631 return $e->event unless $e->checkauth;
632 return $e->event unless $e->allowed('VIEW_TRANSACTION'); # eh.. basically the same permission
633 my $g = $e->retrieve_money_grocery($id)
639 __PACKAGE__->register_method(
640 method => "billing_items",
641 api_name => "open-ils.circ.money.billing.retrieve.all",
644 desc => 'Returns a list of billing items for the given transaction ID. ' .
645 'If the operator is not the owner of the transaction, the VIEW_TRANSACTION permission is required.',
647 { desc => 'Authentication token', type => 'string'},
648 { desc => 'Transaction ID', type => 'number'}
651 desc => 'Transaction object, event on error'
657 my( $self, $client, $login, $transid ) = @_;
659 my( $trans, $evt ) = $U->fetch_billable_xact($transid);
663 ($staff, $evt ) = $apputils->checkses($login);
666 if($staff->id ne $trans->usr) {
667 $evt = $U->check_perms($staff->id, $staff->home_ou, 'VIEW_TRANSACTION');
671 return $apputils->simplereq( 'open-ils.cstore',
672 'open-ils.cstore.direct.money.billing.search.atomic', { xact => $transid } )
676 __PACKAGE__->register_method(
677 method => "billing_items_create",
678 api_name => "open-ils.circ.money.billing.create",
680 Creates a new billing line item
681 PARAMS( login, bill_object (mb) )
684 sub billing_items_create {
685 my( $self, $client, $login, $billing ) = @_;
687 my $e = new_editor(authtoken => $login, xact => 1);
688 return $e->die_event unless $e->checkauth;
689 return $e->die_event unless $e->allowed('CREATE_BILL');
691 my $xact = $e->retrieve_money_billable_transaction($billing->xact)
692 or return $e->die_event;
694 # if the transaction was closed, re-open it
695 if($xact->xact_finish) {
696 $xact->clear_xact_finish;
697 $e->update_money_billable_transaction($xact)
698 or return $e->die_event;
701 my $amt = $billing->amount;
703 $billing->amount($amt);
705 $e->create_money_billing($billing) or return $e->die_event;
706 my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $xact->usr, $U->xact_org($xact->id,$e));
709 $evt = _check_open_xact($e, $xact->id, $xact);
718 __PACKAGE__->register_method(
719 method => 'void_bill',
720 api_name => 'open-ils.circ.money.billing.void',
723 @param authtoken Login session key
724 @param billid Id for the bill to void. This parameter may be repeated to reference other bills.
725 @return 1 on success, Event on error
729 my( $s, $c, $authtoken, @billids ) = @_;
731 my $e = new_editor( authtoken => $authtoken, xact => 1 );
732 return $e->die_event unless $e->checkauth;
733 return $e->die_event unless $e->allowed('VOID_BILLING');
736 for my $billid (@billids) {
738 my $bill = $e->retrieve_money_billing($billid)
739 or return $e->die_event;
741 my $xact = $e->retrieve_money_billable_transaction($bill->xact)
742 or return $e->die_event;
744 if($U->is_true($bill->voided)) {
746 return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill);
749 my $org = $U->xact_org($bill->xact, $e);
750 $users{$xact->usr} = {} unless $users{$xact->usr};
751 $users{$xact->usr}->{$org} = 1;
754 $bill->voider($e->requestor->id);
755 $bill->void_time('now');
757 $e->update_money_billing($bill) or return $e->die_event;
758 my $evt = _check_open_xact($e, $bill->xact, $xact);
762 # calculate penalties for all user/org combinations
763 for my $user_id (keys %users) {
764 for my $org_id (keys %{$users{$user_id}}) {
765 OpenILS::Utils::Penalty->calculate_penalties($e, $user_id, $org_id);
773 __PACKAGE__->register_method(
774 method => 'edit_bill_note',
775 api_name => 'open-ils.circ.money.billing.note.edit',
777 Edits the note for a bill
778 @param authtoken Login session key
779 @param note The replacement note for the bills we're editing
780 @param billid Id for the bill to edit the note of. This parameter may be repeated to reference other bills.
781 @return 1 on success, Event on error
785 my( $s, $c, $authtoken, $note, @billids ) = @_;
787 my $e = new_editor( authtoken => $authtoken, xact => 1 );
788 return $e->die_event unless $e->checkauth;
789 return $e->die_event unless $e->allowed('UPDATE_BILL_NOTE');
791 for my $billid (@billids) {
793 my $bill = $e->retrieve_money_billing($billid)
794 or return $e->die_event;
797 # 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.
799 $e->update_money_billing($bill) or return $e->die_event;
806 __PACKAGE__->register_method(
807 method => 'edit_payment_note',
808 api_name => 'open-ils.circ.money.payment.note.edit',
810 Edits the note for a payment
811 @param authtoken Login session key
812 @param note The replacement note for the payments we're editing
813 @param paymentid Id for the payment to edit the note of. This parameter may be repeated to reference other payments.
814 @return 1 on success, Event on error
817 sub edit_payment_note {
818 my( $s, $c, $authtoken, $note, @paymentids ) = @_;
820 my $e = new_editor( authtoken => $authtoken, xact => 1 );
821 return $e->die_event unless $e->checkauth;
822 return $e->die_event unless $e->allowed('UPDATE_PAYMENT_NOTE');
824 for my $paymentid (@paymentids) {
826 my $payment = $e->retrieve_money_payment($paymentid)
827 or return $e->die_event;
829 $payment->note($note);
830 # 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.
832 $e->update_money_payment($payment) or return $e->die_event;
839 sub _check_open_xact {
840 my( $editor, $xactid, $xact ) = @_;
842 # Grab the transaction
843 $xact ||= $editor->retrieve_money_billable_transaction($xactid);
844 return $editor->event unless $xact;
845 $xactid ||= $xact->id;
847 # grab the summary and see how much is owed on this transaction
848 my ($summary) = $U->fetch_mbts($xactid, $editor);
850 # grab the circulation if it is a circ;
851 my $circ = $editor->retrieve_action_circulation($xactid);
853 # If nothing is owed on the transaction but it is still open
854 # and this transaction is not an open circulation, close it
856 ( $summary->balance_owed == 0 and ! $xact->xact_finish ) and
857 ( !$circ or $circ->stop_fines )) {
859 $logger->info("closing transaction ".$xact->id. ' becauase balance_owed == 0');
860 $xact->xact_finish('now');
861 $editor->update_money_billable_transaction($xact)
862 or return $editor->event;
866 # If money is owed or a refund is due on the xact and xact_finish
867 # is set, clear it (to reopen the xact) and update
868 if( $summary->balance_owed != 0 and $xact->xact_finish ) {
869 $logger->info("re-opening transaction ".$xact->id. ' becauase balance_owed != 0');
870 $xact->clear_xact_finish;
871 $editor->update_money_billable_transaction($xact)
872 or return $editor->event;
879 __PACKAGE__->register_method (
880 method => 'fetch_mbts',
882 api_name => 'open-ils.circ.money.billable_xact_summary.retrieve'
885 my( $self, $conn, $auth, $id) = @_;
887 my $e = new_editor(xact => 1, authtoken=>$auth);
888 return $e->event unless $e->checkauth;
889 my ($mbts) = $U->fetch_mbts($id, $e);
891 my $user = $e->retrieve_actor_user($mbts->usr)
892 or return $e->die_event;
894 return $e->die_event unless $e->allowed('VIEW_TRANSACTION', $user->home_ou);
900 __PACKAGE__->register_method(
901 method => 'desk_payments',
902 api_name => 'open-ils.circ.money.org_unit.desk_payments'
905 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
906 my $e = new_editor(authtoken=>$auth);
907 return $e->event unless $e->checkauth;
908 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
909 my $data = $U->storagereq(
910 'open-ils.storage.money.org_unit.desk_payments.atomic',
911 $org, $start_date, $end_date );
913 $_->workstation( $_->workstation->name ) for(@$data);
918 __PACKAGE__->register_method(
919 method => 'user_payments',
920 api_name => 'open-ils.circ.money.org_unit.user_payments'
924 my( $self, $conn, $auth, $org, $start_date, $end_date ) = @_;
925 my $e = new_editor(authtoken=>$auth);
926 return $e->event unless $e->checkauth;
927 return $e->event unless $e->allowed('VIEW_TRANSACTION', $org);
928 my $data = $U->storagereq(
929 'open-ils.storage.money.org_unit.user_payments.atomic',
930 $org, $start_date, $end_date );
933 $e->retrieve_actor_card($_->usr->card)->barcode);
935 $e->retrieve_actor_org_unit($_->usr->home_ou)->shortname);
941 __PACKAGE__->register_method(
942 method => 'retrieve_credit_payable_balance',
943 api_name => 'open-ils.circ.credit.payable_balance.retrieve',
946 desc => q/Returns the total amount the patron can pay via credit card/,
948 { desc => 'Authentication token', type => 'string' },
949 { desc => 'User id', type => 'number' }
951 return => { desc => 'The ID of the new provider' }
955 sub retrieve_credit_payable_balance {
956 my ( $self, $conn, $auth, $user_id ) = @_;
957 my $e = new_editor(authtoken => $auth);
958 return $e->event unless $e->checkauth;
960 my $user = $e->retrieve_actor_user($user_id)
963 if($e->requestor->id != $user_id) {
964 return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS', $user->home_ou)
967 my $circ_orgs = $e->json_query({
968 "select" => {circ => ["circ_lib"]},
970 "where" => {usr => $user_id, xact_finish => undef},
974 my $groc_orgs = $e->json_query({
975 "select" => {mg => ["billing_location"]},
977 "where" => {usr => $user_id, xact_finish => undef},
982 for my $org ( @$circ_orgs, @$groc_orgs ) {
983 my $o = $org->{billing_location};
984 $o = $org->{circ_lib} unless $o;
985 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.
986 $hash{$o} = $U->ou_ancestor_setting_value($o, 'credit.payments.allow', $e);
989 my @credit_orgs = map { $hash{$_} ? ($_) : () } keys %hash;
990 $logger->debug("credit: relevant orgs that allow credit payments => @credit_orgs");
993 OpenILS::Application::AppUtils->simplereq('open-ils.actor',
994 'open-ils.actor.user.transactions.have_charge', $auth, $user_id);
998 for my $xact (@$xact_summaries) {
1000 # make two lists and grab them in batch XXX
1001 if ( $xact->xact_type eq 'circulation' ) {
1002 my $circ = $e->retrieve_action_circulation($xact->id) or return $e->event;
1003 next unless grep { $_ == $circ->circ_lib } @credit_orgs;
1005 } elsif ($xact->xact_type eq 'grocery') {
1006 my $bill = $e->retrieve_money_grocery($xact->id) or return $e->event;
1007 next unless grep { $_ == $bill->billing_location } @credit_orgs;
1008 } elsif ($xact->xact_type eq 'reservation') {
1009 my $bill = $e->retrieve_booking_reservation($xact->id) or return $e->event;
1010 next unless grep { $_ == $bill->pickup_lib } @credit_orgs;
1012 $sum += $xact->balance_owed();