Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/models/solidus_stripe/gateway.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def purchase(amount_in_cents, source, options = {})
end

# Voids a previously authorized transaction, releasing the funds that are on hold.
def void(payment_intent_id, _source, _options = {})
Comment thread
elia marked this conversation as resolved.
def void(payment_intent_id, _options = {})
check_payment_intent_id(payment_intent_id)

payment_intent = request do
Expand All @@ -133,7 +133,7 @@ def void(payment_intent_id, _source, _options = {})

# Refunds the provided amount on a previously captured transaction.
# TODO: check this method params twice.
def credit(amount_in_cents, _source, payment_intent_id, options = {})
def credit(amount_in_cents, payment_intent_id, options = {})
check_payment_intent_id(payment_intent_id)

payment = options[:originator].payment
Expand Down
92 changes: 92 additions & 0 deletions lib/solidus_stripe/testing_support/factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,49 @@
stripe_payment_method_id { "pm_#{SecureRandom.uuid.delete('-')}" }
end

factory :stripe_payment, parent: :payment do
association :payment_method, factory: :stripe_payment_method
amount { order.outstanding_balance }
response_code { "pi_#{SecureRandom.uuid.delete('-')}" }
state { 'checkout' }

transient do
stripe_payment_method_id { "pm_#{SecureRandom.uuid.delete('-')}" }
end

source {
create(
:stripe_payment_source,
payment_method: payment_method,
stripe_payment_method_id: stripe_payment_method_id
)
}

trait :authorized do
state { 'pending' }

after(:create) do |payment, _evaluator|
create(
:stripe_payment_log_entry,
:authorize,
source: payment
)
end
end

trait :captured do
state { 'completed' }

after(:create) do |payment, _evaluator|
create(
:stripe_payment_log_entry,
:autocapture,
source: payment
)
end
end
end

factory :stripe_payment_intent, class: 'SolidusStripe::PaymentIntent' do
association :order
association :payment_method, factory: :stripe_payment_method
Expand Down Expand Up @@ -49,6 +92,55 @@
end
end

factory :stripe_payment_log_entry, class: 'Spree::LogEntry' do
transient do
success { true }
message { nil }
response_code { source.response_code }
data { nil }
end

source { create(:payment) }

trait :authorize do
message { 'PaymentIntent was confirmed successfully' }
end

trait :capture do
message { 'Payment captured successfully' }
end

trait :autocapture do
message { 'PaymentIntent was confirmed and captured successfully' }
end

trait :void do
message { 'PaymentIntent was canceled successfully' }
end

trait :refund do
message { 'PaymentIntent was refunded successfully' }
end

trait :fail do
success { false }
message { 'PaymentIntent operation failed with a generic error' }
end

details {
YAML.safe_dump(
SolidusStripe::LogEntries.build_payment_log(
success: success,
message: message,
response_code: response_code,
data: data
),
permitted_classes: Spree::LogEntry.permitted_classes,
aliases: Spree::Config.log_entry_allow_aliases
)
}
end

factory :order_with_stripe_payment, parent: :order do
transient do
amount { 10 }
Expand Down
12 changes: 6 additions & 6 deletions spec/models/solidus_stripe/gateway_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
stripe_payment_intent = Stripe::PaymentIntent.construct_from(id: "pi_123")
allow(Stripe::PaymentIntent).to receive(:cancel).and_return(stripe_payment_intent)

result = gateway.void('pi_123', :source)
result = gateway.void('pi_123')

expect(Stripe::PaymentIntent).to have_received(:cancel).with('pi_123')
expect(result.params).to eq("data" => '{"id":"pi_123"}')
Expand All @@ -173,7 +173,7 @@
payment_method = build(:stripe_payment_method)
gateway = payment_method.gateway

expect { gateway.void(nil, :source) }.to raise_error(
expect { gateway.void(nil) }.to raise_error(
ArgumentError,
/missing payment_intent_id/
)
Expand All @@ -183,7 +183,7 @@
payment_method = build(:stripe_payment_method)
gateway = payment_method.gateway

expect { gateway.void("invalid", :source) }.to raise_error(
expect { gateway.void("invalid") }.to raise_error(
ArgumentError,
/payment intent id has the wrong format/
)
Expand All @@ -197,7 +197,7 @@
refund = instance_double(Stripe::Refund, to_json: '{foo: "re_123"}')
allow(Stripe::Refund).to receive(:create).and_return(refund)

result = gateway.credit(123_45, :source, 'pi_123', currency: 'USD', originator: instance_double(
result = gateway.credit(123_45, 'pi_123', currency: 'USD', originator: instance_double(
Spree::Refund,
payment: payment
))
Expand All @@ -213,7 +213,7 @@
payment_method = build(:stripe_payment_method)
gateway = payment_method.gateway

expect { gateway.credit(:amount, :source, nil) }.to raise_error(
expect { gateway.credit(:amount, nil) }.to raise_error(
ArgumentError,
/missing payment_intent_id/
)
Expand All @@ -223,7 +223,7 @@
payment_method = build(:stripe_payment_method)
gateway = payment_method.gateway

expect { gateway.credit(:amount, :source, "invalid") }.to raise_error(
expect { gateway.credit(:amount, "invalid") }.to raise_error(
ArgumentError,
/payment intent id has the wrong format/
)
Expand Down
177 changes: 177 additions & 0 deletions spec/support/solidus_stripe/backend_test_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# frozen_string_literal: true

module SolidusStripe::BackendTestHelper
def payment_method
# Memoize the payment method id to avoid fetching it multiple times
@payment_method ||= create(:stripe_payment_method)
end

def order
@order ||= create(:completed_order_with_totals, line_items_price: 50)
end

# Stripe-related helper methods for creating and fetching Stripe objects

def create_stripe_payment_method(card_number)
payment_method.gateway.request do
Stripe::PaymentMethod.create({
type: 'card',
card: {
number: card_number,
exp_month: 12,
exp_year: (Time.zone.now.year + 1),
cvc: '123',
},
})
end
end

def create_stripe_payment_intent(stripe_payment_method_id)
payment_method.gateway.request do
Stripe::PaymentIntent.create(
amount: (order.outstanding_balance * 100).to_i,
capture_method: 'manual',
confirm: true,
currency: 'usd',
payment_method: stripe_payment_method_id
)
end
end

def fetch_stripe_refund(payment)
payment_method.gateway.request do
Stripe::Refund.list(payment_intent: payment.response_code).data.first
end
end

def fetch_stripe_payment_intent(payment)
payment_method.gateway.request do
Stripe::PaymentIntent.retrieve(payment.response_code)
end
end

# Navigation helper methods for interacting with the admin panel

def visit_payments_page
visit "/admin/orders/#{order.number}/payments"
end

def visit_payment_page(payment)
visit_payments_page
click_on payment.number
end

# Action helper methods for performing operations on payments

def create_authorized_payment(opts = {})
stripe_payment_method = create_stripe_payment_method(opts[:card_number] || '4242424242424242')
payment_intent = create_stripe_payment_intent(stripe_payment_method.id)

create(:stripe_payment,
:authorized,
order: order,
response_code: payment_intent.id,
stripe_payment_method_id: stripe_payment_method.id,
payment_method: payment_method)
end

def create_captured_payment(card_number: '4242424242424242')
payment = create_authorized_payment(card_number: card_number)
payment.capture!
payment
end

def refund_payment
refund_reason = create :refund_reason
click_icon(:"mail-reply") # Refund icon style reference in solidus_backend
within '.new_refund' do
select refund_reason.name, from: 'Reason'
click_button 'Refund'
end
end

def partially_refund_payment(refund_reason, amount)
click_icon(:"mail-reply") # Refund icon style reference in solidus_backend
within '.new_refund' do
select refund_reason.name, from: 'Reason'
fill_in 'Amount', with: amount
click_button 'Refund'
end
end

def void_payment
click_icon(:void)
end

def capture_payment
click_icon(:capture)
end

def cancel_order
visit "/admin/orders/#{order.number}/edit"
accept_alert do
click_on 'Cancel'
end
end

# Helper methods for checking expected outcomes and states

def expects_page_to_display_successfully_captured_payment(payment)
expect(page).to have_content('Payment Updated')
expect(payment.reload.state).to eq('completed')
expect(payment.capture_events.first.amount).to eq(payment.amount)
end

def expects_page_to_display_successfully_refunded_payment(payment)
expect(page).to have_content('Refund has been successfully created!')
expect(payment).to be_fully_refunded
end

def expects_page_to_display_successfully_partially_refunded_payment(payment, amount)
expect(page).to have_content('Refund has been successfully created!')
expect(payment.reload.state).to eq('completed')
expect(payment.refunds.first.amount).to eq(amount)
end

def expects_page_to_display_successfully_voided_payment(payment)
expect(page).to have_content('Payment Updated')
expect(payment.reload.state).to eq('void')
end

def expects_page_to_display_successfully_canceled_order_payment(payment)
expect(page).to have_content('Order canceled')
expect(payment.reload.state).to eq('void')
end

def expects_page_to_display_capture_fail_message(payment, message)
expect(page).to have_content(message)
expect(payment.reload.state).to eq('failed')
end

def expects_page_to_display_payment(payment)
click_on 'Payments'
expect(page).to have_content(payment.number)
end

def expects_page_to_display_log_messages(log_messages = [])
expect(page).to have_content('Log Entries')
log_messages.each do |log_message|
expect(page).to have_content(log_message)
end
end

def expects_payment_to_be_refunded_on_stripe(payment, amount)
stripe_refund = fetch_stripe_refund(payment)
expect(stripe_refund.amount).to eq(amount * 100)
end

def expects_payment_to_be_voided_on_stripe(payment)
stripe_payment_intent = fetch_stripe_payment_intent(payment)
expect(stripe_payment_intent.status).to eq('canceled')
end

def expects_payment_to_have_correct_capture_amount_on_stripe(payment, amount)
stripe_payment_intent = fetch_stripe_payment_intent(payment)
expect(stripe_payment_intent.amount_received).to eq(amount * 100)
end
end
Loading