mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-11 18:26:50 +00:00
Inspecting 1509 files
.........................................................................................................C.........................................C......................................................................................................................................................................................................................................................................................................................................................................................................................................................................C.......................................................................................................................................................................................................................................................................................................................................................................................................................C.........................................................................................................................................................................CCC........................................C......................................................................................C......................................................................................................................................................................................................
Offenses:
app/controllers/split_checkout_controller.rb:5:33: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
class SplitCheckoutController < ::BaseController
^^
app/controllers/webhook_endpoints_controller.rb:3:36: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
class WebhookEndpointsController < ::BaseController
^^
config.ru:5:9: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
require ::File.expand_path('config/environment', __dir__)
^^
spec/helpers/checkout_helper_spec.rb:168:68: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
let!(:tax_rate) { create(:tax_rate, amount: 0.1, calculator: ::Calculator::DefaultTax.new) }
^^
spec/models/spree/order_spec.rb:619:25: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
let(:fee_handler) { ::OrderFeesHandler.new(subject) }
^^
spec/models/spree/payment_method_spec.rb:150:51: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
calculator: ::Calculator::FlatRate.new(preferred_amount: 10))
^^
spec/models/spree/payment_method_spec.rb:154:54: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
calculator: ::Calculator::FlatPercentItemTotal
^^
spec/models/spree/payment_spec.rb:429:49: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
create(:payment_method, calculator: ::Calculator::FlatRate.new(preferred_amount: 10))
^^
spec/models/spree/payment_spec.rb:1002:11: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10)
^^
spec/models/spree/payment_spec.rb:1039:28: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
let(:calculator) { ::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) }
^^
spec/queries/complete_visible_orders_spec.rb:12:31: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
let(:order_permissions) { ::Permissions::Order.new(user, filter_canceled) }
^^
spec/services/paypal_items_builder_spec.rb:34:37: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
calculator: ::Calculator::DefaultTax.new)
^^
spec/services/paypal_items_builder_spec.rb:38:37: C: [Corrected] Style/RedundantConstantBase: Remove redundant ::.
calculator: ::Calculator::DefaultTax.new)
^^
1509 files inspected, 13 offenses detected, 13 offenses corrected
1132 lines
41 KiB
Ruby
1132 lines
41 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
describe Spree::Payment do
|
|
context 'original specs from Spree' do
|
|
before { Stripe.api_key = "sk_test_12345" }
|
|
let(:order) { create(:order) }
|
|
let(:shop) { create(:enterprise) }
|
|
let(:payment_method) {
|
|
payment_method = create(:stripe_sca_payment_method,
|
|
distributor_ids: [create(:distributor_enterprise).id],
|
|
preferred_enterprise_id: shop.id)
|
|
allow(payment_method).to receive(:source_required) { true }
|
|
allow(payment_method).to receive(:payment) { true }
|
|
payment_method
|
|
}
|
|
|
|
let(:card) {
|
|
create(:credit_card)
|
|
}
|
|
|
|
let(:payment) do
|
|
payment = create(:payment)
|
|
payment.source = card
|
|
payment.order = order
|
|
payment.payment_method = payment_method
|
|
payment.response_code = "12345"
|
|
payment
|
|
end
|
|
|
|
let(:amount_in_cents) { payment.amount.to_f * 100 }
|
|
|
|
let(:success_response) do
|
|
double('success_response', success?: true,
|
|
authorization: '123',
|
|
avs_result: { 'code' => 'avs-code' },
|
|
cvv_result: { code: nil, message: nil })
|
|
end
|
|
|
|
let(:failed_response) { double('gateway_response', success?: false) }
|
|
|
|
let(:payment_authorised) {
|
|
payment_intent(payment.amount, "requires_capture")
|
|
}
|
|
let(:payment_canceled) {
|
|
payment_intent(payment.amount, "canceled")
|
|
}
|
|
let(:payment_refunded) {
|
|
payment_intent(payment.amount, "refunded")
|
|
}
|
|
let(:capture_successful) {
|
|
payment_intent(payment.amount, "succeeded")
|
|
}
|
|
|
|
context "extends LocalizedNumber" do
|
|
subject { build_stubbed(:payment) }
|
|
it_behaves_like "a model using the LocalizedNumber module", [:amount]
|
|
end
|
|
|
|
context 'validations' do
|
|
it "returns useful error messages when source is invalid" do
|
|
payment = build_stubbed(:payment, source: Spree::CreditCard.new)
|
|
expect(payment).not_to be_valid
|
|
cc_errors = payment.errors['Credit Card']
|
|
expect(cc_errors).to include("Number can't be blank")
|
|
expect(cc_errors).to include("Month is not a number")
|
|
expect(cc_errors).to include("Year is not a number")
|
|
expect(cc_errors).to include("Verification Value can't be blank")
|
|
end
|
|
end
|
|
|
|
context "creating a new payment alongside other incomplete payments" do
|
|
let(:order) { create(:order_with_totals) }
|
|
let!(:incomplete_payment) { create(:payment, order:, state: "pending") }
|
|
let(:new_payment) { create(:payment, order:, state: "checkout") }
|
|
|
|
it "invalidates other incomplete payments on the order" do
|
|
new_payment
|
|
|
|
expect(incomplete_payment.reload.state).to eq "invalid"
|
|
end
|
|
end
|
|
|
|
# Regression test for https://github.com/spree/spree/pull/2224
|
|
context 'failure' do
|
|
it 'should transition to failed from pending state' do
|
|
payment.state = 'pending'
|
|
payment.failure
|
|
expect(payment.state).to eql('failed')
|
|
end
|
|
|
|
it 'should transition to failed from processing state' do
|
|
payment.state = 'processing'
|
|
payment.failure
|
|
expect(payment.state).to eql('failed')
|
|
end
|
|
end
|
|
|
|
context 'invalidate' do
|
|
it 'should transition from checkout to invalid' do
|
|
payment.state = 'checkout'
|
|
payment.invalidate
|
|
expect(payment.state).to eq('invalid')
|
|
end
|
|
end
|
|
|
|
context "processing" do
|
|
before do
|
|
allow(payment).to receive(:record_response)
|
|
allow(card).to receive(:has_payment_profile?).and_return(true)
|
|
allow(payment).to receive(:update_order).and_return(true)
|
|
allow(payment).to receive(:create_payment_profile).and_return(true)
|
|
end
|
|
|
|
context "#process!" do
|
|
it "should call purchase!" do
|
|
payment = build_stubbed(:payment, payment_method:)
|
|
expect(payment).to receive(:purchase!)
|
|
payment.process!
|
|
end
|
|
|
|
it "should make the state 'processing'" do
|
|
stub_request(:get, "https://api.stripe.com/v1/payment_intents/12345").
|
|
to_return(status: 200, body: payment_authorised)
|
|
stub_request(:post, "https://api.stripe.com/v1/payment_intents/12345/capture").
|
|
to_return(status: 200, body: capture_successful)
|
|
expect(payment).to receive(:started_processing!)
|
|
payment.save!
|
|
payment.process!
|
|
end
|
|
|
|
it "should invalidate if payment method doesnt support source" do
|
|
expect(payment.payment_method).to receive(:supports?)
|
|
.with(payment.source).and_return(false)
|
|
expect { payment.process! }.to raise_error(Spree::Core::GatewayError)
|
|
expect(payment.state).to eq('invalid')
|
|
end
|
|
|
|
context "the payment is already authorized" do
|
|
before do
|
|
allow(payment).to receive(:response_code) { "pi_123" }
|
|
end
|
|
|
|
it "should call purchase" do
|
|
expect(payment).to receive(:purchase!)
|
|
payment.process!
|
|
end
|
|
end
|
|
end
|
|
|
|
context "#process_offline when payment is already authorized" do
|
|
before do
|
|
allow(payment).to receive(:response_code) { "pi_123" }
|
|
end
|
|
|
|
it "should call capture if the payment is already authorized" do
|
|
expect(payment).to receive(:capture!)
|
|
expect(payment).to_not receive(:purchase!)
|
|
payment.process_offline!
|
|
end
|
|
end
|
|
|
|
context "#authorize" do
|
|
before do
|
|
allow(payment_method).to receive(:authorize) { success_response }
|
|
end
|
|
it "should call authorize on the gateway with the payment amount" do
|
|
expect(payment.payment_method).to receive(:authorize).with(
|
|
amount_in_cents, card, anything
|
|
).and_return(success_response)
|
|
payment.authorize!
|
|
end
|
|
|
|
it "should call authorize on the gateway with the currency code" do
|
|
allow(payment).to receive(:currency) { "GBP" }
|
|
expect(payment.payment_method).to receive(:authorize).with(
|
|
amount_in_cents, card, hash_including({ currency: "GBP" })
|
|
).and_return(success_response)
|
|
payment.authorize!
|
|
end
|
|
|
|
it "should log the response" do
|
|
payment.authorize!
|
|
expect(payment).to have_received(:record_response)
|
|
end
|
|
|
|
context "when gateway does not match the environment" do
|
|
it "should raise an exception" do
|
|
allow(payment_method).to receive(:environment) { "foo" }
|
|
expect { payment.authorize! }.to raise_error(Spree::Core::GatewayError)
|
|
end
|
|
end
|
|
|
|
context "if successful" do
|
|
before do
|
|
expect(payment.payment_method).to receive(:authorize).with(
|
|
amount_in_cents, card, anything
|
|
).and_return(success_response)
|
|
end
|
|
|
|
it "should store the response_code, avs_response and cvv_response fields" do
|
|
payment.authorize!
|
|
expect(payment.response_code).to eq('123')
|
|
expect(payment.avs_response).to eq('avs-code')
|
|
end
|
|
|
|
it "should make payment pending" do
|
|
expect(payment).to receive(:pend!)
|
|
payment.authorize!
|
|
end
|
|
end
|
|
|
|
context "authorization is required" do
|
|
before do
|
|
allow(success_response).to receive(:cvv_result) {
|
|
{ 'code' => "123",
|
|
'message' => "https://stripe.com/redirect" }
|
|
}
|
|
expect(payment.payment_method).to receive(:authorize).with(
|
|
amount_in_cents, card, anything
|
|
).and_return(success_response)
|
|
end
|
|
|
|
it "should move the payment to requires_authorization" do
|
|
expect(payment).to receive(:require_authorization!)
|
|
payment.authorize!
|
|
end
|
|
end
|
|
|
|
context "if unsuccessful" do
|
|
it "should mark payment as failed" do
|
|
allow(payment_method).to receive(:authorize).and_return(failed_response)
|
|
expect(payment).to receive(:failure)
|
|
expect(payment).to_not receive(:pend)
|
|
expect {
|
|
payment.authorize!
|
|
}.to raise_error(Spree::Core::GatewayError)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "purchase" do
|
|
before do
|
|
stub_request(:get, "https://api.stripe.com/v1/payment_intents/12345").
|
|
to_return(status: 200, body: payment_authorised)
|
|
stub_request(:post, "https://api.stripe.com/v1/payment_intents/12345/capture").
|
|
to_return(status: 200, body: capture_successful)
|
|
end
|
|
it "should call purchase on the gateway with the payment amount" do
|
|
expect(payment_method).to receive(:purchase).with(amount_in_cents, card,
|
|
anything).and_return(success_response)
|
|
payment.purchase!
|
|
end
|
|
|
|
it "should log the response" do
|
|
payment.purchase!
|
|
expect(payment).to have_received(:record_response)
|
|
end
|
|
|
|
context "when gateway does not match the environment" do
|
|
it "should raise an exception" do
|
|
allow(payment_method).to receive(:environment) { "foo" }
|
|
expect { payment.purchase! }.to raise_error(Spree::Core::GatewayError)
|
|
end
|
|
end
|
|
|
|
context "if successful" do
|
|
before do
|
|
expect(payment.payment_method).to receive(:purchase).with(
|
|
amount_in_cents, card, anything
|
|
).and_return(success_response)
|
|
end
|
|
|
|
it "should store the response_code and avs_response" do
|
|
payment.purchase!
|
|
expect(payment.response_code).to eq('123')
|
|
expect(payment.avs_response).to eq('avs-code')
|
|
end
|
|
|
|
it "should make payment complete" do
|
|
expect(payment).to receive(:complete!)
|
|
payment.purchase!
|
|
end
|
|
end
|
|
|
|
context "if unsuccessful" do
|
|
it "should make payment failed" do
|
|
allow(payment_method).to receive(:purchase).and_return(failed_response)
|
|
expect(payment).to receive(:failure)
|
|
expect(payment).not_to receive(:pend)
|
|
expect { payment.purchase! }.to raise_error(Spree::Core::GatewayError)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "#capture" do
|
|
before do
|
|
allow(payment).to receive(:complete).and_return(true)
|
|
end
|
|
|
|
context "when payment is pending" do
|
|
before do
|
|
payment.state = 'pending'
|
|
end
|
|
|
|
context "if successful" do
|
|
before do
|
|
expect(payment.payment_method).to receive(:capture).and_return(success_response)
|
|
end
|
|
|
|
it "should make payment complete" do
|
|
expect(payment).to receive(:complete)
|
|
payment.capture!
|
|
end
|
|
|
|
it "should store the response_code" do
|
|
allow(payment_method).to receive(:capture).and_return(success_response)
|
|
payment.capture!
|
|
expect(payment.response_code).to eq('123')
|
|
end
|
|
end
|
|
|
|
context "if unsuccessful" do
|
|
it "should not make payment complete" do
|
|
allow(payment_method).to receive(:capture).and_return(failed_response)
|
|
expect(payment).to receive(:failure)
|
|
expect(payment).to_not receive(:complete)
|
|
expect { payment.capture! }.to raise_error(Spree::Core::GatewayError)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Regression test for #2119
|
|
context "when payment is completed" do
|
|
it "should do nothing" do
|
|
payment = build_stubbed(:payment, :completed)
|
|
expect(payment).to_not receive(:complete)
|
|
expect(payment.payment_method).to_not receive(:capture)
|
|
expect(payment.log_entries).to_not receive(:create)
|
|
payment.capture!
|
|
end
|
|
end
|
|
end
|
|
|
|
context "#void" do
|
|
before do
|
|
payment.response_code = '123'
|
|
payment.state = 'pending'
|
|
end
|
|
|
|
context "when profiles are supported" do
|
|
it "should call payment_enterprise.void with the payment's response_code" do
|
|
allow(payment_method).to receive(:payment_profiles_supported) { true }
|
|
expect(payment_method).to receive(:void).with('123', card,
|
|
anything).and_return(success_response)
|
|
payment.void_transaction!
|
|
end
|
|
end
|
|
|
|
context "when profiles are not supported" do
|
|
it "should call payment_gateway.void with the payment's response_code" do
|
|
allow(payment_method).to receive(:payment_profiles_supported) { false }
|
|
expect(payment_method).to receive(:void).with('123', card,
|
|
anything).and_return(success_response)
|
|
payment.void_transaction!
|
|
end
|
|
end
|
|
|
|
it "should log the response" do
|
|
stub_request(:get, "https://api.stripe.com/v1/payment_intents/123").
|
|
to_return(status: 200, body: payment_authorised)
|
|
stub_request(:post, "https://api.stripe.com/v1/payment_intents/123/cancel").
|
|
to_return(status: 200, body: payment_canceled)
|
|
payment.void_transaction!
|
|
expect(payment).to have_received(:record_response)
|
|
end
|
|
|
|
context "when payment_method does not match the environment" do
|
|
it "should raise an exception" do
|
|
payment = build_stubbed(:payment, payment_method:)
|
|
allow(payment_method).to receive(:environment) { "foo" }
|
|
expect { payment.void_transaction! }.to raise_error(Spree::Core::GatewayError)
|
|
end
|
|
end
|
|
|
|
context "if successful" do
|
|
let(:mocked_response) {
|
|
instance_double(ActiveMerchant::Billing::Response, authorization: '12345',
|
|
success?: true)
|
|
}
|
|
|
|
before do
|
|
allow(payment_method).to receive(:void).and_return(mocked_response)
|
|
end
|
|
|
|
it "should update the response_code with the authorization from the gateway" do
|
|
stub_request(:get, "https://api.stripe.com/v1/payment_intents/abc").
|
|
to_return(status: 200, body: payment_authorised)
|
|
stub_request(:post, "https://api.stripe.com/v1/payment_intents/abc/cancel").
|
|
to_return(status: 200, body: payment_canceled)
|
|
# Change it to something different
|
|
payment.response_code = 'abc'
|
|
payment.void_transaction!
|
|
expect(payment.response_code).to eq('12345')
|
|
end
|
|
end
|
|
|
|
context "if unsuccessful" do
|
|
it "should not void the payment" do
|
|
allow(payment_method).to receive(:void).and_return(failed_response)
|
|
expect(payment).to_not receive(:void)
|
|
expect { payment.void_transaction! }.to raise_error(Spree::Core::GatewayError)
|
|
end
|
|
end
|
|
|
|
# Regression test for #2119
|
|
context "if payment is already voided" do
|
|
it "should not void the payment" do
|
|
payment = build_stubbed(:payment, payment_method:, state: 'void')
|
|
expect(payment.payment_method).to_not receive(:void)
|
|
payment.void_transaction!
|
|
end
|
|
end
|
|
|
|
context "if payment has any adjustment" do
|
|
let!(:order) { create(:order) }
|
|
let!(:payment_method) {
|
|
create(:payment_method, calculator: Calculator::FlatRate.new(preferred_amount: 10))
|
|
}
|
|
|
|
it "should create another adjustment and revoke the previous one" do
|
|
payment = create(:payment, order:, payment_method:)
|
|
expect(order.all_adjustments.payment_fee.eligible.length).to eq(1)
|
|
|
|
payment.void_transaction!
|
|
expect(order.all_adjustments.payment_fee.eligible.length).to eq(0)
|
|
|
|
payment = create(:payment, order:, payment_method:)
|
|
expect(order.all_adjustments.payment_fee.eligible.length).to eq(1)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "#credit" do
|
|
before do
|
|
payment.state = 'completed'
|
|
payment.response_code = '123'
|
|
end
|
|
|
|
context "when outstanding_balance is less than payment amount" do
|
|
before do
|
|
allow(payment.order).to receive(:outstanding_balance) { 10 }
|
|
allow(payment).to receive(:credit_allowed) { 1000 }
|
|
end
|
|
|
|
it "should call credit on the gateway with the credit amount and response_code" do
|
|
expect(payment_method).to receive(:credit).with(1000, card, '123',
|
|
anything).and_return(success_response)
|
|
payment.credit!
|
|
end
|
|
end
|
|
|
|
context "if payment method has any payment fees" do
|
|
before do
|
|
expect(payment.order).to receive(:outstanding_balance).at_least(:once) { 10 }
|
|
expect(payment).to receive(:credit_allowed) { 200 }
|
|
end
|
|
|
|
it "should not applied any transaction fees" do
|
|
stub_request(:get, "https://api.stripe.com/v1/payment_intents/123").
|
|
to_return(status: 200, body: payment_authorised)
|
|
stub_request(:post, "https://api.stripe.com/v1/charges/ch_1234/refunds").
|
|
to_return(status: 200, body: payment_refunded)
|
|
payment.credit!
|
|
expect(payment.adjustment.finalized?).to eq(false)
|
|
expect(order.all_adjustments.payment_fee.length).to eq(0)
|
|
end
|
|
end
|
|
|
|
context "when outstanding_balance is equal to payment amount" do
|
|
before do
|
|
allow(payment.order).to receive(:outstanding_balance) { payment.amount }
|
|
end
|
|
|
|
it "should call credit on the gateway with the credit amount and response_code" do
|
|
expect(payment_method).to receive(:credit).with(
|
|
amount_in_cents, card, '123', anything
|
|
).and_return(success_response)
|
|
payment.credit!
|
|
end
|
|
end
|
|
|
|
context "when outstanding_balance is greater than payment amount" do
|
|
before do
|
|
allow(payment.order).to receive(:outstanding_balance) { 101 }
|
|
end
|
|
|
|
it "should call credit on the gateway with original payment amount and response_code" do
|
|
expect(payment_method).to receive(:credit).with(
|
|
amount_in_cents.to_f, card, '123', anything
|
|
).and_return(success_response)
|
|
payment.credit!
|
|
end
|
|
end
|
|
|
|
it "should log the response" do
|
|
stub_request(:get, "https://api.stripe.com/v1/payment_intents/123").
|
|
to_return(status: 200, body: payment_authorised)
|
|
stub_request(:post, "https://api.stripe.com/v1/charges/ch_1234/refunds").
|
|
to_return(status: 200, body: payment_refunded)
|
|
payment.credit!
|
|
expect(payment).to have_received(:record_response)
|
|
end
|
|
|
|
context "when gateway does not match the environment" do
|
|
it "should raise an exception" do
|
|
payment = build_stubbed(:payment, payment_method:)
|
|
allow(payment_method).to receive(:environment) { "foo" }
|
|
expect { payment.credit! }.to raise_error(Spree::Core::GatewayError)
|
|
end
|
|
end
|
|
|
|
context "when response is successful" do
|
|
it "should create an offsetting payment" do
|
|
stub_request(:get, "https://api.stripe.com/v1/payment_intents/123").
|
|
to_return(status: 200, body: payment_authorised)
|
|
stub_request(:post, "https://api.stripe.com/v1/charges/ch_1234/refunds").
|
|
to_return(status: 200, body: payment_refunded)
|
|
expect(Spree::Payment).to receive(:create!)
|
|
payment.credit!
|
|
end
|
|
|
|
it "resulting payment should have correct values" do
|
|
stub_request(:get, "https://api.stripe.com/v1/payment_intents/123").
|
|
to_return(status: 200, body: payment_authorised)
|
|
stub_request(:post, "https://api.stripe.com/v1/charges/ch_1234/refunds").
|
|
to_return(status: 200, body: payment_refunded)
|
|
allow(payment.order).to receive(:new_outstanding_balance) { 100 }
|
|
allow(payment).to receive(:credit_allowed) { 10 }
|
|
|
|
offsetting_payment = payment.credit!
|
|
expect(offsetting_payment.amount.to_f).to eq(-10)
|
|
expect(offsetting_payment).to be_completed
|
|
expect(offsetting_payment.response_code).to eq('12345')
|
|
expect(offsetting_payment.source).to eq(payment)
|
|
end
|
|
|
|
context 'and the source payment card is expired' do
|
|
let(:card) do
|
|
Spree::CreditCard.new(month: 12, year: 1995, number: '4111111111111111',
|
|
verification_value: '123')
|
|
end
|
|
|
|
let(:successful_response) do
|
|
ActiveMerchant::Billing::Response.new(true, "Yay!")
|
|
end
|
|
|
|
it 'lets the new payment to be saved' do
|
|
stub_request(:get, "https://api.stripe.com/v1/payment_intents/123").
|
|
to_return(status: 200, body: payment_authorised)
|
|
stub_request(:post, "https://api.stripe.com/v1/charges/ch_1234/refunds").
|
|
to_return(status: 200, body: payment_refunded)
|
|
allow(payment.order).to receive(:new_outstanding_balance) { 100 }
|
|
allow(payment).to receive(:credit_allowed) { 10 }
|
|
|
|
offsetting_payment = payment.credit!
|
|
|
|
expect(offsetting_payment).to be_valid
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when response is unsuccessful" do
|
|
it "should not create a payment" do
|
|
allow(payment_method).to receive(:credit).and_return(failed_response)
|
|
expect(Spree::Payment).not_to receive(:create)
|
|
expect { payment.credit! }.to raise_error(Spree::Core::GatewayError)
|
|
end
|
|
end
|
|
|
|
context "when already processing" do
|
|
it "should return nil without trying to process the source" do
|
|
payment = build_stubbed(:payment)
|
|
payment.state = 'processing'
|
|
|
|
expect(payment).to_not receive(:authorize!)
|
|
expect(payment).to_not receive(:purchase!)
|
|
expect(payment.process!).to be_nil
|
|
end
|
|
end
|
|
|
|
context "with source required" do
|
|
context "raises an error if no source is specified" do
|
|
specify do
|
|
payment = build_stubbed(:payment, source: nil, payment_method:)
|
|
expect {
|
|
payment.process!
|
|
}.to raise_error(Spree::Core::GatewayError, Spree.t(:payment_processing_failed))
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with source optional" do
|
|
context "raises no error if source is not specified" do
|
|
specify do
|
|
payment = build_stubbed(:payment, source: nil, payment_method:)
|
|
allow(payment.payment_method).to receive(:source_required?) { false }
|
|
expect { payment.process! }.not_to raise_error
|
|
end
|
|
end
|
|
end
|
|
|
|
context "#credit_allowed" do
|
|
it "is the difference between offsets total and payment amount" do
|
|
payment = build_stubbed(:payment, amount: 100)
|
|
allow(payment).to receive(:offsets_total) { 0 }
|
|
expect(payment.credit_allowed).to eq(100)
|
|
allow(payment).to receive(:offsets_total) { 80 }
|
|
expect(payment.credit_allowed).to eq(20)
|
|
end
|
|
end
|
|
|
|
context "#can_credit?" do
|
|
it "is true if credit_allowed > 0" do
|
|
payment = build_stubbed(:payment)
|
|
allow(payment).to receive(:credit_allowed) { 100 }
|
|
expect(payment.can_credit?).to be true
|
|
end
|
|
it "is false if credit_allowed is 0" do
|
|
payment = build_stubbed(:payment)
|
|
allow(payment).to receive(:credit_allowed) { 0 }
|
|
expect(payment.can_credit?).to be false
|
|
end
|
|
end
|
|
|
|
context "#credit" do
|
|
context "when amount <= credit_allowed" do
|
|
it "makes the state processing" do
|
|
stub_request(:get, "https://api.stripe.com/v1/payment_intents/12345").
|
|
to_return(status: 200, body: payment_authorised)
|
|
stub_request(:post, "https://api.stripe.com/v1/charges/ch_1234/refunds").
|
|
to_return(status: 200, body: payment_refunded)
|
|
payment.payment_method.name = 'Gateway'
|
|
payment.payment_method.distributors << create(:distributor_enterprise)
|
|
payment.payment_method.save!
|
|
|
|
payment.order = create(:order)
|
|
|
|
payment.state = 'completed'
|
|
allow(payment).to receive(:credit_allowed) { 10 }
|
|
payment.partial_credit(10)
|
|
expect(payment).to be_processing
|
|
end
|
|
it "calls credit on the source with the payment and amount" do
|
|
payment.state = 'completed'
|
|
allow(payment).to receive(:credit_allowed) { 10 }
|
|
expect(payment).to receive(:credit!).with(10)
|
|
payment.partial_credit(10)
|
|
end
|
|
end
|
|
context "when amount > credit_allowed" do
|
|
it "should not call credit on the source" do
|
|
payment = build_stubbed(:payment)
|
|
payment.state = 'completed'
|
|
allow(payment).to receive(:credit_allowed) { 10 }
|
|
payment.partial_credit(20)
|
|
expect(payment).to be_completed
|
|
end
|
|
end
|
|
end
|
|
|
|
context "#save" do
|
|
context "completed payments" do
|
|
it "updates order payment total" do
|
|
payment = create(:payment, :completed, amount: 100, order:)
|
|
expect(order.payment_total).to eq payment.amount
|
|
end
|
|
end
|
|
|
|
context "non-completed payments" do
|
|
it "doesn't update order payment total" do
|
|
expect {
|
|
create(:payment, amount: 100, order:)
|
|
}.not_to change { order.payment_total }
|
|
end
|
|
end
|
|
|
|
context 'when the payment was completed but now void' do
|
|
let(:payment) { create(:payment, :completed, amount: 100, order:) }
|
|
|
|
it 'updates order payment total' do
|
|
payment.void
|
|
expect(order.payment_total).to eq 0
|
|
end
|
|
end
|
|
|
|
context "completed orders" do
|
|
let(:order_updater) { OrderManagement::Order::Updater.new(order) }
|
|
|
|
before { allow(order).to receive(:completed?) { true } }
|
|
|
|
it "updates payment_state and shipments" do
|
|
expect(OrderManagement::Order::Updater).to receive(:new).with(order).
|
|
and_return(order_updater)
|
|
|
|
expect(order_updater).to receive(:after_payment_update).with(kind_of(Spree::Payment)).
|
|
and_call_original
|
|
|
|
expect(order_updater).to receive(:update_payment_state)
|
|
expect(order_updater).to receive(:update_shipment_state)
|
|
create(:payment, amount: 100, order:)
|
|
end
|
|
end
|
|
|
|
context "when profiles are supported" do
|
|
before do
|
|
allow(payment_method).to receive(:payment_profiles_supported?) { true }
|
|
allow(payment.source).to receive(:has_payment_profile?) { false }
|
|
end
|
|
|
|
context "when there is an error connecting to the gateway" do
|
|
it "should call gateway_error " do
|
|
pending '[Spree build] Failing spec'
|
|
message = double("gateway_error")
|
|
connection_error = ActiveMerchant::ConnectionError.new(message, nil)
|
|
expect(payment_method).to receive(:create_profile).and_raise(connection_error)
|
|
expect do
|
|
Spree::Payment.create(
|
|
amount: 100,
|
|
order:,
|
|
source: card,
|
|
payment_method:
|
|
)
|
|
end.should raise_error(Spree::Core::GatewayError)
|
|
end
|
|
end
|
|
|
|
context "when successfully connecting to the gateway" do
|
|
it "should create a payment profile" do
|
|
payment_method.name = 'Gateway'
|
|
payment_method.distributors << create(:distributor_enterprise)
|
|
payment_method.save!
|
|
|
|
payment.payment_method = payment_method
|
|
payment.source.save_requested_by_customer = true
|
|
|
|
expect(payment_method).to receive(:create_profile)
|
|
|
|
Spree::Payment.create(
|
|
amount: 100,
|
|
order: create(:order),
|
|
source: card,
|
|
payment_method:
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when profiles are not supported" do
|
|
before do
|
|
allow(payment_method).to receive(:payment_profiles_supported?) { false }
|
|
end
|
|
|
|
it "should not create a payment profile" do
|
|
payment_method.name = 'Gateway'
|
|
payment_method.distributors << create(:distributor_enterprise)
|
|
payment_method.save!
|
|
|
|
expect(payment_method).to_not receive :create_profile
|
|
payment = Spree::Payment.create(
|
|
amount: 100,
|
|
order: create(:order),
|
|
source: card,
|
|
payment_method:
|
|
)
|
|
end
|
|
end
|
|
|
|
context 'when the payment was completed but now void' do
|
|
let(:payment) { create(:payment, :completed, amount: 100, order:) }
|
|
|
|
it 'updates order payment total' do
|
|
payment.void
|
|
expect(order.payment_total).to eq 0
|
|
end
|
|
end
|
|
end
|
|
|
|
context "#build_source" do
|
|
it "should build the payment's source" do
|
|
params = { amount: 100, payment_method:,
|
|
source_attributes: {
|
|
expiry: "1 / 99",
|
|
number: '1234567890123',
|
|
verification_value: '123'
|
|
} }
|
|
|
|
payment = Spree::Payment.new(params)
|
|
expect(payment).to be_valid
|
|
expect(payment.source).not_to be_nil
|
|
end
|
|
|
|
it "errors when payment source not valid" do
|
|
params = { amount: 100, payment_method:,
|
|
source_attributes: { expiry: "1 / 12" } }
|
|
|
|
payment = Spree::Payment.new(params)
|
|
expect(payment).not_to be_valid
|
|
expect(payment.source).not_to be_nil
|
|
expect(payment.source.errors[:number]).not_to be_empty
|
|
expect(payment.source.errors[:verification_value]).not_to be_empty
|
|
end
|
|
end
|
|
|
|
context "#currency" do
|
|
it "returns the order currency" do
|
|
payment = build_stubbed(:payment, order: build_stubbed(:order, currency: "ABC"))
|
|
expect(payment.currency).to eq("ABC")
|
|
end
|
|
end
|
|
|
|
context "#display_amount" do
|
|
it "returns a Spree::Money for this amount" do
|
|
payment = build_stubbed(:payment)
|
|
expect(payment.display_amount).to eq(Spree::Money.new(payment.amount))
|
|
end
|
|
end
|
|
|
|
# Regression test for #2216
|
|
context "#gateway_options" do
|
|
before do
|
|
allow(order).to receive(:last_ip_address) { "192.168.1.1" }
|
|
end
|
|
|
|
it "contains an IP" do
|
|
expect(payment.gateway_options[:ip]).to eq(order.last_ip_address)
|
|
end
|
|
end
|
|
|
|
context "#set_unique_identifier" do
|
|
# Regression test for Spree #1998
|
|
it "sets a unique identifier on create" do
|
|
payment.run_callbacks(:create)
|
|
expect(payment.identifier).not_to be_blank
|
|
expect(payment.identifier.size).to eq(8)
|
|
expect(payment.identifier).to be_a(String)
|
|
end
|
|
|
|
# Regression test for Spree #3733
|
|
it "does not regenerate the identifier on re-save" do
|
|
payment.save
|
|
old_identifier = payment.identifier
|
|
payment.save
|
|
expect(payment.identifier).to eq old_identifier
|
|
end
|
|
|
|
context "other payment exists" do
|
|
let(:other_payment) {
|
|
payment_method.name = 'Gateway'
|
|
payment_method.distributors << create(:distributor_enterprise)
|
|
payment_method.save!
|
|
|
|
payment = Spree::Payment.new
|
|
payment.source = card
|
|
payment.order = create(:order)
|
|
payment.payment_method = payment_method
|
|
payment
|
|
}
|
|
|
|
before { other_payment.save! }
|
|
|
|
it "doesn't set duplicate identifier" do
|
|
allow(payment).to receive(:generate_identifier).and_return(other_payment.identifier)
|
|
allow(payment).to receive(:generate_identifier).and_call_original
|
|
|
|
payment.run_callbacks(:create)
|
|
|
|
expect(payment.identifier).not_to be_blank
|
|
expect(payment.identifier).not_to eq(other_payment.identifier)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "available actions" do
|
|
context "for most gateways" do
|
|
let(:payment) { build_stubbed(:payment, source: build_stubbed(:credit_card)) }
|
|
|
|
it "can capture and void" do
|
|
expect(payment.actions).to match_array %w(capture void)
|
|
end
|
|
|
|
describe "when a payment has been taken" do
|
|
before do
|
|
allow(payment).to receive(:state) { 'completed' }
|
|
allow(payment).to receive(:order) { double(:order, payment_state: 'credit_owed') }
|
|
end
|
|
|
|
it "can void and credit" do
|
|
expect(payment.actions).to match_array %w(void credit)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "refund!" do
|
|
let(:payment) { create(:payment) }
|
|
let(:success) { double(success?: true, authorization: 'abc123') }
|
|
let(:failure) { double(success?: false) }
|
|
|
|
it "always checks the environment" do
|
|
allow(payment.payment_method).to receive(:refund) { success }
|
|
expect(payment).to receive(:check_environment)
|
|
payment.refund!
|
|
end
|
|
|
|
describe "calculating refund amount" do
|
|
let(:payment) { build_stubbed(:payment) }
|
|
|
|
it "returns the parameter amount when given" do
|
|
expect(payment.send(:calculate_refund_amount, 123)).to be === 123.0
|
|
end
|
|
|
|
it "refunds up to the value of the payment when the outstanding balance is larger" do
|
|
allow(payment).to receive(:credit_allowed) { 123 }
|
|
allow(payment).to receive(:order) { double(:order, outstanding_balance: 1000) }
|
|
expect(payment.send(:calculate_refund_amount)).to eq(123)
|
|
end
|
|
|
|
it "refunds up to the outstanding balance of the order when the payment is larger" do
|
|
allow(payment).to receive(:credit_allowed) { 1000 }
|
|
allow(payment).to receive(:order) { double(:order, outstanding_balance: 123) }
|
|
expect(payment.send(:calculate_refund_amount)).to eq(123)
|
|
end
|
|
end
|
|
|
|
describe "performing refunds" do
|
|
before do
|
|
allow(payment).to receive(:calculate_refund_amount) { 123 }
|
|
expect(payment.payment_method).to receive(:refund).and_return(success)
|
|
end
|
|
|
|
it "performs the refund without payment profiles" do
|
|
allow(payment.payment_method).to receive(:payment_profiles_supported?) { false }
|
|
payment.refund!
|
|
end
|
|
|
|
it "performs the refund with payment profiles" do
|
|
allow(payment.payment_method).to receive(:payment_profiles_supported?) { true }
|
|
payment.refund!
|
|
end
|
|
end
|
|
|
|
it "records the response" do
|
|
allow(payment).to receive(:calculate_refund_amount) { 123 }
|
|
allow(payment.payment_method).to receive(:refund).and_return(success)
|
|
expect(payment).to receive(:record_response).with(success)
|
|
payment.refund!
|
|
end
|
|
|
|
it "records a payment on success" do
|
|
allow(payment).to receive(:calculate_refund_amount) { 123 }
|
|
allow(payment.payment_method).to receive(:refund).and_return(success)
|
|
allow(payment).to receive(:record_response)
|
|
|
|
expect do
|
|
payment.refund!
|
|
end.to change(Spree::Payment, :count).by(1)
|
|
|
|
p = Spree::Payment.last
|
|
expect(p.order).to eq(payment.order)
|
|
expect(p.source).to eq(payment)
|
|
expect(p.payment_method).to eq(payment.payment_method)
|
|
expect(p.amount).to eq(-123)
|
|
expect(p.response_code).to eq(success.authorization)
|
|
expect(p.state).to eq('completed')
|
|
end
|
|
|
|
it "logs the error on failure" do
|
|
allow(payment).to receive(:calculate_refund_amount) { 123 }
|
|
allow(payment.payment_method).to receive(:refund).and_return(failure)
|
|
allow(payment).to receive(:record_response)
|
|
expect(payment).to receive(:gateway_error).with(failure)
|
|
payment.refund!
|
|
end
|
|
end
|
|
|
|
describe "applying transaction fees" do
|
|
let!(:order) { create(:order) }
|
|
let!(:line_item) { create(:line_item, order:, quantity: 3, price: 5.00) }
|
|
|
|
before do
|
|
order.reload.update_order!
|
|
end
|
|
|
|
context "when order-based calculator" do
|
|
let!(:shop) { create(:enterprise) }
|
|
let!(:payment_method) { create(:payment_method, calculator:) }
|
|
let!(:calculator) do
|
|
Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10)
|
|
end
|
|
|
|
context "when order complete" do
|
|
let!(:order) { create(:completed_order_with_totals, distributor: shop) }
|
|
let!(:variant) { order.line_items.first.variant }
|
|
let!(:inventory_item) { create(:inventory_item, enterprise: shop, variant:) }
|
|
|
|
it "creates adjustment" do
|
|
payment = create(:payment, order:, payment_method:,
|
|
amount: order.total)
|
|
expect(payment.adjustment).to be_present
|
|
expect(payment.adjustment.amount).not_to eq(0)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'OFN specs from previously decorated model' do
|
|
describe "applying transaction fees" do
|
|
let!(:order) { create(:order) }
|
|
let!(:line_item) { create(:line_item, order:, quantity: 3, price: 5.00) }
|
|
|
|
before do
|
|
order.reload.update_order!
|
|
end
|
|
|
|
context "to Stripe payments" do
|
|
let(:shop) { create(:enterprise) }
|
|
let(:payment_method) {
|
|
create(:stripe_sca_payment_method, distributor_ids: [create(:distributor_enterprise).id],
|
|
preferred_enterprise_id: shop.id)
|
|
}
|
|
let(:payment) {
|
|
create(:payment, order:, payment_method:, amount: order.total)
|
|
}
|
|
let(:calculator) { Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) }
|
|
|
|
before do
|
|
payment_method.calculator = calculator
|
|
payment_method.save!
|
|
|
|
allow(order).to receive(:pending_payments) { [payment] }
|
|
end
|
|
|
|
context "when the payment fails" do
|
|
let(:failed_response) {
|
|
ActiveMerchant::Billing::Response.new(false, "This is an error message")
|
|
}
|
|
|
|
before do
|
|
allow(payment_method).to receive(:purchase) { failed_response }
|
|
end
|
|
|
|
it "makes the transaction fee ineligible and finalizes it" do
|
|
# Decided to wrap the save process in order.process_payments!
|
|
# since that is the context it is usually performed in
|
|
order.process_payments!
|
|
expect(order.payments.count).to eq 1
|
|
expect(order.payments).to include payment
|
|
expect(payment.state).to eq "failed"
|
|
expect(payment.adjustment.eligible?).to be false
|
|
expect(payment.adjustment.finalized?).to be true
|
|
expect(order.all_adjustments.payment_fee.count).to eq 1
|
|
expect(order.all_adjustments.payment_fee.eligible).to_not include payment.adjustment
|
|
end
|
|
end
|
|
|
|
context "when the payment information is invalid" do
|
|
before do
|
|
allow(payment_method).to receive(:supports?) { false }
|
|
end
|
|
|
|
it "makes the transaction fee ineligible and finalizes it" do
|
|
# Decided to wrap the save process in order.process_payments!
|
|
# since that is the context it is usually performed in
|
|
order.process_payments!
|
|
expect(order.payments.count).to eq 1
|
|
expect(order.payments).to include payment
|
|
expect(payment.state).to eq "invalid"
|
|
expect(payment.adjustment.eligible?).to be false
|
|
expect(payment.adjustment.finalized?).to be true
|
|
expect(order.all_adjustments.payment_fee.count).to eq 1
|
|
expect(order.all_adjustments.payment_fee.eligible).to_not include payment.adjustment
|
|
end
|
|
end
|
|
|
|
context "when the payment is processed successfully" do
|
|
let(:successful_response) { ActiveMerchant::Billing::Response.new(true, "Yay!") }
|
|
|
|
before do
|
|
allow(payment_method).to receive(:purchase) { successful_response }
|
|
end
|
|
|
|
it "creates an appropriate adjustment" do
|
|
# Decided to wrap the save process in order.process_payments!
|
|
# since that is the context it is usually performed in
|
|
order.process_payments!
|
|
expect(order.payments.count).to eq 1
|
|
expect(order.payments).to include payment
|
|
expect(payment.state).to eq "completed"
|
|
expect(payment.adjustment.eligible?).to be true
|
|
expect(order.all_adjustments.payment_fee.count).to eq 1
|
|
expect(order.all_adjustments.payment_fee.eligible).to include payment.adjustment
|
|
expect(payment.adjustment.amount).to eq 1.5
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#clear_authorization_url" do
|
|
let(:payment) { create(:payment, cvv_response_message: "message") }
|
|
|
|
it "removes the cvv_response_message" do
|
|
payment.clear_authorization_url
|
|
expect(payment.cvv_response_message).to eq(nil)
|
|
end
|
|
end
|
|
|
|
describe "#complete" do
|
|
let(:payment) { build(:payment, state: "processing") }
|
|
|
|
it "sets :captured_at to the current time" do
|
|
payment.complete
|
|
expect(payment.captured_at).to be_present
|
|
end
|
|
end
|
|
end
|