use OpenILS::Application::AppUtils;
my $U = "OpenILS::Application::AppUtils";
-use constant CREDIT_NS => "credit";
-
# Given the argshash from process_payment(), this helper function just finds
# a function in the current namespace named "bop_args_{processor}" and calls
# it with $argshash as an argument, returning the result, or returning an
);
}
-sub get_processor_settings {
- my $org_unit = shift;
- my $processor = lc shift;
-
- # XXX TODO: make this one single cstore request instead of many
- +{ map { ($_ =>
- $U->ou_ancestor_setting_value(
- $org_unit, CREDIT_NS . ".processor.${processor}.${_}"
- )) } qw/enabled login password signature server testmode vendor partner/
- };
-}
-
# argshash (Hash of arguments with these keys):
# patron_id: Not a barcode, but a patron's internal ID
# ou: Org unit where transaction happens
and $argshash->{expiration}
and $argshash->{ou};
- if (!$argshash->{processor}) {
- if (!($argshash->{processor} =
- $U->ou_ancestor_setting_value(
- $argshash->{ou}, CREDIT_NS . '.processor.default'))) {
- return OpenILS::Event->new('CREDIT_PROCESSOR_NOT_SPECIFIED');
- }
- }
- # Basic sanity check on processor name.
- if ($argshash->{processor} !~ /^[a-z0-9_\-]+$/i) {
- return OpenILS::Event->new('CREDIT_PROCESSOR_NOT_ALLOWED');
- }
-
- # Get org unit settings related to our processor
- my $psettings = get_processor_settings(
- $argshash->{ou}, $argshash->{processor}
- );
-
- if (!$psettings->{enabled}) {
- return OpenILS::Event->new('CREDIT_PROCESSOR_NOT_ENABLED');
- }
-
- # Add the org unit settings for the chosen processor to our argshash.
- $argshash = +{ %{$argshash}, %{$psettings} };
+ # Used to test argshash->{processor} here, but now that's handled earlier.
# At least the following (derived from org unit settings) are required.
return OpenILS::Event->new('CREDIT_PROCESSOR_BAD_PARAMS')
use OpenSRF::Utils::Logger qw/:logger/;
use OpenILS::Utils::CStoreEditor qw/:funcs/;
use OpenILS::Utils::Penalty;
+use Business::Stripe;
$Data::Dumper::Indent = 0;
+sub get_processor_settings {
+ my $e = shift;
+ my $org_unit = shift;
+ my $processor = lc shift;
+
+ # Get the names of every credit processor setting for our given processor.
+ # They're a little different per processor.
+ my $setting_names = $e->json_query({
+ select => {coust => ["name"]},
+ from => {coust => {}},
+ where => {name => {like => "credit.processor.${processor}.%"}}
+ }) or return $e->die_event;
+
+ # Make keys for a hash we're going to build out of the last dot-delimited
+ # component of each setting name.
+ ($_->{key} = $_->{name}) =~ s/.+\.(\w+)$/$1/ for @$setting_names;
+
+ # Return a hash with those short keys, and for values the value of
+ # the corresponding OU setting within our scope.
+ return {
+ map {
+ $_->{key} => $U->ou_ancestor_setting_value($org_unit, $_->{name})
+ } @$setting_names
+ };
+}
+
+# process_stripe_or_bop_payment()
+# This is a helper method to make_payments() below (specifically,
+# the credit-card part). It's the first point in the Perl code where
+# we need to care about the distinction between Stripe and the
+# Paypal/PayflowPro/AuthorizeNet kinds of processors (the latter group
+# uses B::OP and handles payment card info, whereas Stripe doesn't use
+# B::OP and doesn't require us to know anything about the payment card
+# info).
+#
+# Return an event in all cases. That means a success returns a SUCCESS
+# event.
+sub process_stripe_or_bop_payment {
+ my ($e, $user_id, $this_ou, $total_paid, $cc_args) = @_;
+
+ # A few stanzas to determine which processor we're using and whether we're
+ # really adequately set up for it.
+ if (!$cc_args->{processor}) {
+ if (!($cc_args->{processor} =
+ $U->ou_ancestor_setting_value(
+ $this_ou, 'credit.processor.default'
+ )
+ )
+ ) {
+ return OpenILS::Event->new('CREDIT_PROCESSOR_NOT_SPECIFIED');
+ }
+ }
+
+ # Make sure the configured credit processor has a safe/correct name.
+ return OpenILS::Event->new('CREDIT_PROCESSOR_NOT_ALLOWED')
+ unless $cc_args->{processor} =~ /^[a-z0-9_\-]+$/i;
+
+ # Get the settings for the processor and make sure they're serviceable.
+ my $psettings = get_processor_settings($e, $this_ou, $cc_args->{processor});
+ return $psettings if defined $U->event_code($psettings);
+ return OpenILS::Event->new('CREDIT_PROCESSOR_NOT_ENABLED')
+ unless $psettings->{enabled};
+
+ # Now we branch. Stripe is one thing, and everything else is another.
+
+ if ($cc_args->{processor} eq 'Stripe') { # Stripe
+ my $stripe = Business::Stripe->new(-api_key => $psettings->{secretkey});
+ $stripe->charges_create(
+ amount => int($total_paid * 100.0), # Stripe takes amount in pennies
+ card => $cc_args->{stripe_token},
+ description => $cc_args->{note}
+ );
+
+ if ($stripe->success) {
+ $logger->info("Stripe payment succeeded");
+ return OpenILS::Event->new(
+ "SUCCESS", payload => {
+ map { $_ => $stripe->success->{$_} } qw(
+ invoice customer balance_transaction id created card
+ )
+ }
+ );
+ } else {
+ $logger->info("Stripe payment failed");
+ return OpenILS::Event->new(
+ "CREDIT_PROCESSOR_DECLINED_TRANSACTION",
+ payload => $stripe->error # XXX what happens if this contains
+ # JSON::backportPP::* objects?
+ );
+ }
+
+ } else { # B::OP style (Paypal/PayflowPro/AuthorizeNet)
+ return OpenILS::Event->new('BAD_PARAMS', note => 'Need CC number')
+ unless $cc_args->{number};
+
+ return OpenILS::Application::Circ::CreditCard::process_payment({
+ "desc" => $cc_args->{note},
+ "amount" => $total_paid,
+ "patron_id" => $user_id,
+ "cc" => $cc_args->{number},
+ "expiration" => sprintf(
+ "%02d-%04d",
+ $cc_args->{expire_month},
+ $cc_args->{expire_year}
+ ),
+ "ou" => $this_ou,
+ "first_name" => $cc_args->{billing_first},
+ "last_name" => $cc_args->{billing_last},
+ "address" => $cc_args->{billing_address},
+ "city" => $cc_args->{billing_city},
+ "state" => $cc_args->{billing_state},
+ "zip" => $cc_args->{billing_zip},
+ "cvv2" => $cc_args->{cvv2},
+ %$psettings
+ });
+
+ }
+}
+
__PACKAGE__->register_method(
method => "make_payments",
api_name => "open-ils.circ.money.payment",
approval_code (for out-of-band payment)
type (for out-of-band payment)
number (for call to payment processor)
+ stripe_token (for call to Stripe payment processor)
expire_month (for call to payment processor)
expire_year (for call to payment processor)
billing_first (for out-of-band payments and for call to payment processor)
if ($payobj->has_field('cc_number')) {
$payobj->cc_number(substr($cc_args->{number}, -4));
}
- if ($payobj->has_field('expire_month')) { $payobj->expire_month($cc_args->{expire_month}); }
+ if ($payobj->has_field('expire_month')) { $payobj->expire_month($cc_args->{expire_month}); $logger->info("LFW XXX expire_month is $cc_args->{expire_month}"); }
if ($payobj->has_field('expire_year')) { $payobj->expire_year($cc_args->{expire_year}); }
# Note: It is important not to set approval_code
# If an approval code was not given, we'll need
# to call to the payment processor ourselves.
if ($cc_args->{where_process} == 1) {
- return OpenILS::Event->new('BAD_PARAMS', note => 'Need CC number')
- if not $cc_args->{number};
- my $response =
- OpenILS::Application::Circ::CreditCard::process_payment({
- "desc" => $cc_args->{note},
- "amount" => $total_paid,
- "patron_id" => $user_id,
- "cc" => $cc_args->{number},
- "expiration" => sprintf(
- "%02d-%04d",
- $cc_args->{expire_month},
- $cc_args->{expire_year}
- ),
- "ou" => $this_ou,
- "first_name" => $cc_args->{billing_first},
- "last_name" => $cc_args->{billing_last},
- "address" => $cc_args->{billing_address},
- "city" => $cc_args->{billing_city},
- "state" => $cc_args->{billing_state},
- "zip" => $cc_args->{billing_zip},
- "cvv2" => $cc_args->{cvv2},
- });
-
- if ($U->event_code($response)) { # non-success
+ my $response = process_stripe_or_bop_payment(
+ $e, $user_id, $this_ou, $total_paid, $cc_args
+ );
+
+ if ($U->event_code($response)) { # non-success (success is 0)
$logger->info(
"Credit card payment for user $user_id failed: " .
- $response->{"textcode"} . " " .
- $response->{"payload"}->{"error_message"}
+ $response->{textcode} . " " .
+ ($response->{payload}->{error_message} ||
+ $response->{payload}{message})
);
-
return $response;
} else {
# We need to save this for later in case there's a failure on
# the EG side to store the processor's result.
- $cc_payload = $response->{"payload"};
- $approval_code = $cc_payload->{"authorization"};
- $cc_type = $cc_payload->{"card_type"};
- $cc_processor = $cc_payload->{"processor"};
- $cc_order_number = $cc_payload->{"order_number"};
+ $cc_payload = $response->{"payload"}; # also used way later
+
+ {
+ no warnings 'uninitialized';
+ $cc_type = $cc_payload->{card_type};
+ $approval_code = $cc_payload->{authorization} ||
+ $cc_payload->{id};
+ $cc_processor = $cc_payload->{processor} ||
+ $cc_args->{processor};
+ $cc_order_number = $cc_payload->{order_number} ||
+ $cc_payload->{invoice};
+ };
$logger->info("Credit card payment for user $user_id succeeded");
}
} else {
}
}
+ # Urgh, clean up this mega-function one day.
+ if ($cc_processor eq 'Stripe' and $approval_code and $cc_payload) {
+ $payment->expire_month($cc_payload->{card}{exp_month});
+ $payment->expire_year($cc_payload->{card}{exp_year});
+ $payment->cc_number($cc_payload->{card}{last4});
+ }
+
$payment->approval_code($approval_code) if $approval_code;
$payment->cc_order_number($cc_order_number) if $cc_order_number;
$payment->cc_type($cc_type) if $cc_type;
myopac_main_page = "payment_form";
last_chance = CGI.param("last_chance");
-%]
+
+ IF myopac_main_page == "payment_form" AND
+ ctx.get_org_setting(ctx.user.home_ou.id, 'credit.processor.stripe.enabled') AND ctx.get_org_setting(ctx.user.home_ou.id, 'credit.processor.default') == 'Stripe';
+ ctx.use_stripe = 1;
+ END %]
<h3 class="sr-only">[% l('Pay Fines') %]</h3>
[% IF ctx.fines.balance_owed <= 0 %]
<div>
"total is non-positive. We cannot process non-positive amounts.") %]
</div>
[% ELSE %]
-<div id="pay_fines_now">
+[% IF ctx.use_stripe %]
+<noscript>
+ [% l("Your browser does not have Javascript enabled, and we cannot " _
+ "process credit card payments without it. Please change your " _
+ "browser settings and try again.") %]
+</noscript>
+[% END %]
+<div id="pay_fines_now"[% IF ctx.use_stripe %] class="hide_me"[% END %]>
[% IF last_chance %]
<p><big>[% l("Are you sure you are ready to charge [_1] to your credit card?", money(ctx.fines.balance_owed)) %]</big></p>
<form action="[% ctx.opac_root %]/myopac/main_pay_init" method="post">
<a href="[% mkurl(ctx.opac_root _ '/myopac/main', {}, 1) %]">[% l('Cancel') %]</a>
[% ELSE %]
<form method="post" id="payment_form"
- [% IF use_stripe %]
+ [% IF ctx.use_stripe %]
onsubmit="return stripe_onsubmit();"
[% END %]
>
[% FOR xact IN CGI.param('xact_misc') %]
<input type="hidden" name="xact_misc" value="[% xact | html %]" />
[% END %]
- [% IF use_stripe %]
+ [% IF ctx.use_stripe %]
<input type="hidden" name="stripe_token" id="stripe_token" />
[% END %]
</tr>
<tr>
<td><label for="payment-credit-card">[% l('Credit Card #') %]</label></td>
- <td><input type="number" maxlength="16" id="payment-credit-card"
- [% IF use_stripe %]
+ <td><input type="text" maxlength="16" id="payment-credit-card"
+ [% IF ctx.use_stripe %]
data-stripe="number"
[% ELSE %]
name="number"
<tr>
<td><label for="payment-security-code">[% l('Security Code') %]</label></td>
<td>
- <input type="number" size="4" maxlength="5" id="payment-security-code"
- [% IF use_stripe %]
+ <input type="text" size="4" maxlength="5" id="payment-security-code"
+ [% IF ctx.use_stripe %]
data-stripe="cvc"
[% ELSE %]
name="cvv2"
<td><label for="payment-expire-month">[% l('Expiration Month') %]</label></td>
<td>
<select id="payment-expire-month"
- [% IF use_stripe %]
+ [% IF ctx.use_stripe %]
data-stripe="exp_month"
[% ELSE %]
name="expire_month"
<td><label for="payment-expire-year">[% l('Expiration Year') %]</label></td>
<td>
<select id="payment-expire-year"
- [% IF use_stripe %]
+ [%- IF ctx.use_stripe %]
data-stripe="exp_year"
[% ELSE %]
name="expire_year"
- [% END %]
+ [% END -%]
>
[% year = date.format(date.now, '%Y');
y = year;