Skip to content

Commit

Permalink
Merge pull request #972 from sul-dlss/cybersource-userid
Browse files Browse the repository at this point in the history
Send user UUIDs to CyberSource for payment tracking
  • Loading branch information
jcoyne authored Oct 4, 2023
2 parents 1f28330 + 4c7321d commit bce13e3
Show file tree
Hide file tree
Showing 13 changed files with 143 additions and 233 deletions.
9 changes: 4 additions & 5 deletions app/controllers/payments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ def create
#
# POST /payments/accept
def accept
ils_client.pay_fines(user: cybersource_response.user,
amount: cybersource_response.amount,
session_id: cybersource_response.session_id)
ils_client.pay_fines(user_id: cybersource_response.user_id,
amount: cybersource_response.amount)

redirect_to fines_path, flash: {
success: (t 'mylibrary.fine_payment.accept_html', amount: cybersource_response.amount)
Expand All @@ -67,11 +66,11 @@ def payments_json_response
end

def create_payment_params
params.permit(%I[reason billseq amount session_id user group])
params.permit(%I[user_id amount])
end

def cybersource_request
Cybersource::PaymentRequest.new(**params.permit(:user, :amount, :session_id).to_h.symbolize_keys).sign!
Cybersource::PaymentRequest.new(**create_payment_params.to_h.symbolize_keys).sign!
end

def cybersource_response
Expand Down
20 changes: 7 additions & 13 deletions app/models/cybersource/payment_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@ module Cybersource
class PaymentRequest
include Enumerable

attr_reader :user, :amount, :session_id, :signature, :signed_date_time
attr_reader :user_id, :amount, :signature, :signed_date_time

# Set of fields we use to generate the signature and that Cybersource verifies
REQUEST_SIGNED_FIELDS = %i[access_key profile_id transaction_uuid signed_date_time
locale transaction_type reference_number amount currency unsigned_field_names
signed_field_names merchant_defined_data1].freeze
locale transaction_type reference_number amount currency
unsigned_field_names signed_field_names].freeze

def initialize(user:, amount:, session_id:)
@user = user
def initialize(user_id:, amount:)
@user_id = user_id
@amount = amount
@session_id = session_id
@signed_fields = REQUEST_SIGNED_FIELDS
@unsigned_fields = []
@signed_date_time, @signature = nil
Expand Down Expand Up @@ -81,14 +80,9 @@ def transaction_uuid
@transaction_uuid ||= SecureRandom.uuid
end

# Used to check if the user has a payment pending by comparing to a cookie
def reference_number
@session_id
end

# Passed back to us by Cybersource and used to look up the user in the ILS
def merchant_defined_data1
@user
def reference_number
@user_id
end

# Matches us with configuration on the Cybersource side; differs for dev/test/prod
Expand Down
8 changes: 2 additions & 6 deletions app/models/cybersource/payment_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,12 @@ def signed_data
@signed_data = @signed_fields.index_with { |field| @fields[field] }
end

def user
@fields['req_merchant_defined_data1']
def user_id
@fields['req_reference_number']
end

def amount
@fields['req_amount']
end

def session_id
@fields['req_reference_number']
end
end
end
6 changes: 0 additions & 6 deletions app/models/folio/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ def key
record['id']
end

# TODO: remove; unused after migration off of Symphony
# Also update the pay all button template not to render it
def sequence
nil
end

def patron_key
record.dig('loan', 'proxyUserId') || record['userId']
end
Expand Down
6 changes: 3 additions & 3 deletions app/services/folio_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,14 @@ def find_patron_by_barcode(barcode, patron_info: true)
# Mark all of a user's fines (accounts) as having been paid
# The payment will show as being made from the 'Online' service point
# rubocop:disable Metrics/MethodLength
def pay_fines(user:, amount:, session_id:)
patron = find_patron_by_barcode(user)
def pay_fines(user_id:, amount:)
patron = Patron.find(user_id)
payload = {
accountIds: patron.fines.map(&:key),
paymentMethod: 'Credit card',
amount: amount,
userName: 'libsys_admin',
transactionInfo: session_id,
transactionInfo: user_id,
servicePointId: Settings.folio.online_service_point_id,
notifyPatron: true
}
Expand Down
9 changes: 2 additions & 7 deletions app/views/fines/_pay_all_button.html.erb
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
<% fines = patron_or_group.fines %>
<% amount = fines.sum(&:owed) %>
<% amount = patron_or_group.fines.sum(&:owed) %>
<% if amount.positive? %>
<div>
<% if patron_or_group.can_pay_fines? %>
<%= form_tag payments_path, method: :post do %>
<%= hidden_field_tag :reason, fines.map(&:status).join(',') %>
<%= hidden_field_tag :billseq, fines.map(&:sequence).values_at(0, -1).join('-') %>
<%= hidden_field_tag :user_id, patron_or_group.key %>
<%= hidden_field_tag :amount, format('%.2f', amount) %>
<%= hidden_field_tag :session_id, SecureRandom.hex %>
<%= hidden_field_tag :group, patron_or_group.is_a?(Symphony::Group) ? 'G' : '' %>
<%= hidden_field_tag :user, patron_or_group.barcode %>
<%= button_tag class: 'btn btn-md btn-info', type: 'submit', data: { 'pay-button' => true } do %>
<%= sul_icon :'sharp-payment-24px' %>
<span>Pay <%= number_to_currency(amount) %> now</span>
Expand Down
4 changes: 2 additions & 2 deletions spec/controllers/fines_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
require 'rails_helper'

RSpec.describe FinesController do
let(:mock_patron) { instance_double(Folio::Patron, group?: false, barcode: '1234') }
let(:mock_patron) { instance_double(Folio::Patron, group?: false, key: '513a9054-5897-11ee-8c99-0242ac120002') }

let(:fines) do
[
instance_double(Folio::Account, key: '1', sequence: '1', owed: '12', status: 'BADCHECK')
instance_double(Folio::Account, key: '1', owed: '12', status: 'BADCHECK')
]
end

Expand Down
13 changes: 7 additions & 6 deletions spec/controllers/payments_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

RSpec.describe PaymentsController do
let(:user) { { username: 'somesunetid', patron_key: '513a9054-5897-11ee-8c99-0242ac120002' } }
let(:mock_patron) { instance_double(Folio::Patron, group?: false, barcode: '1234', payments: payments) }
let(:mock_patron) { instance_double(Folio::Patron, group?: false, key: user[:patron_key], payments: payments) }
let(:mock_client) { instance_double(FolioClient, ping: true, pay_fines: nil) }
let(:mock_graphql_client_response) do
[
Expand Down Expand Up @@ -55,7 +55,7 @@

describe '#create' do
before do
post :create, params: { user: 'u', session_id: 's', group: 'g', amount: 'a' }
post :create, params: { user_id: '513a9054-5897-11ee-8c99-0242ac120002', amount: '10.00' }
end

it 'renders a form to send to cybersource' do
Expand All @@ -65,9 +65,10 @@

describe '#accept' do
let(:cybersource_response) do
instance_double(Cybersource::PaymentResponse, user: '123', amount: '10.00',
session_id: 'session_this_is_the_one',
valid?: true, payment_success?: true)
instance_double(Cybersource::PaymentResponse, user_id: '513a9054-5897-11ee-8c99-0242ac120002',
amount: '10.00',
valid?: true,
payment_success?: true)
end

before do
Expand All @@ -77,7 +78,7 @@
it 'updates the payment in the ILS' do
post :accept
expect(mock_client).to have_received(:pay_fines)
.with(user: '123', amount: '10.00', session_id: 'session_this_is_the_one')
.with(user_id: '513a9054-5897-11ee-8c99-0242ac120002', amount: '10.00')
end

it 'redirects to fines page' do
Expand Down
10 changes: 3 additions & 7 deletions spec/models/cybersource/payment_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,19 @@

RSpec.describe Cybersource::PaymentRequest do
subject(:request_params) do
described_class.new(user: '1234567890', amount: '100.00', session_id: '3672f43945ec20cf2966525aeb7691e4').sign!.to_h
described_class.new(user_id: '0340214b-5492-472d-b634-c5c115639465', amount: '100.00').sign!.to_h
end

before do
allow(Cybersource::Security).to receive(:secret_key).and_return('very_secret')
end

it 'stores the user barcode as merchant defined data' do
expect(request_params[:merchant_defined_data1]).to eq('1234567890')
end

it 'stores the amount of total charges' do
expect(request_params[:amount]).to eq('100.00')
end

it 'stores the session id as the transaction reference number' do
expect(request_params[:reference_number]).to eq('3672f43945ec20cf2966525aeb7691e4')
it 'stores the user id as the transaction reference number' do
expect(request_params[:reference_number]).to eq('0340214b-5492-472d-b634-c5c115639465')
end

it 'signs the transaction parameters' do
Expand Down
18 changes: 6 additions & 12 deletions spec/models/cybersource/payment_response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,29 @@
let(:signature) do
Cybersource::Security.generate_signature(
{
req_merchant_defined_data1: '1234567890',
req_amount: '100.00',
req_reference_number: '3672f43945ec20cf2966525aeb7691e4'
req_reference_number: '0340214b-5492-472d-b634-c5c115639465'
}
)
end
let(:decision) { 'ACCEPT' }
let(:params) do
ActionController::Parameters.new(req_merchant_defined_data1: '1234567890',
req_amount: '100.00',
req_reference_number: '3672f43945ec20cf2966525aeb7691e4',
signed_field_names: 'req_merchant_defined_data1,req_amount,req_reference_number',
ActionController::Parameters.new(req_amount: '100.00',
req_reference_number: '0340214b-5492-472d-b634-c5c115639465',
signed_field_names: 'req_amount,req_reference_number',
unsigned_field_names: '',
signature: signature,
decision: decision)
end

it 'parses the user barcode from the merchant defined data' do
expect(cybersource_response.user).to eq('1234567890')
it 'parses the user id from the merchant defined data' do
expect(cybersource_response.user_id).to eq('0340214b-5492-472d-b634-c5c115639465')
end

it 'parses the amount of total charges' do
expect(cybersource_response.amount).to eq('100.00')
end

it 'parses the session id from the transaction reference number' do
expect(cybersource_response.session_id).to eq('3672f43945ec20cf2966525aeb7691e4')
end

it 'validates that the transaction is signed and accepted' do
expect { cybersource_response.validate! }.not_to raise_error
end
Expand Down
6 changes: 4 additions & 2 deletions spec/views/fines/_pay_all_button.html.erb_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
RSpec.describe 'fines/_pay_all_button' do
subject(:output) { Capybara.string(rendered) }

let(:patron) { instance_double(Symphony::Patron, barcode: '1', fines: fines, can_pay_fines?: true) }
let(:fines) { [instance_double(Symphony::Fine, owed: 3, status: 'A', sequence: '1')] }
let(:patron) do
instance_double(Folio::Patron, key: '513a9054-5897-11ee-8c99-0242ac120002', fines: fines, can_pay_fines?: true)
end
let(:fines) { [instance_double(Folio::Account, owed: 3)] }

before do
without_partial_double_verification do
Expand Down
Loading

0 comments on commit bce13e3

Please sign in to comment.