Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify ownership of payment_source when creating WalletPaymentSource #3007

Merged
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
2 changes: 1 addition & 1 deletion backend/spec/features/admin/orders/payments_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@

context "user existing card" do
let!(:cc) do
create(:credit_card, payment_method: payment_method, gateway_customer_profile_id: "BGS-RFRE")
create(:credit_card, payment_method: payment_method, gateway_customer_profile_id: "BGS-RFRE", user: order.user)
end

before do
Expand Down
36 changes: 26 additions & 10 deletions core/app/models/spree/wallet_payment_source.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
# frozen_string_literal: true

class Spree::WalletPaymentSource < ActiveRecord::Base
belongs_to :user, class_name: Spree::UserClassHandle.new, foreign_key: 'user_id', inverse_of: :wallet_payment_sources
belongs_to :payment_source, polymorphic: true, inverse_of: :wallet_payment_sources
module Spree
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please change the namespace/inheritance stuff into one or more other commits, so they are independently documented? Also, why are these change needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's to follow the format used in the other model files.

class WalletPaymentSource < Spree::Base
belongs_to :user, class_name: Spree::UserClassHandle.new, foreign_key: 'user_id', inverse_of: :wallet_payment_sources
belongs_to :payment_source, polymorphic: true, inverse_of: :wallet_payment_sources

validates_presence_of :user
validates_presence_of :payment_source
validates_presence_of :user
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer validates :user, presence: true. Not a big deal though

validates_presence_of :payment_source
validates :user_id, uniqueness: {
scope: [:payment_source_type, :payment_source_id],
message: :payment_source_already_exists
kennyadsl marked this conversation as resolved.
Show resolved Hide resolved
}

validate :check_for_payment_source_class
validate :check_for_payment_source_class
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing the reasoning for this is to mimic what happens in the Spree::Wallet#add method? I'm a fan prepending validation methods with validate so you know the purpose of that method is to validate and if called will add to the object errors. validate_payment_source_class

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skukx agree but these validations were already there, they have just been indented due to the module change. I think that if we want to change them we need to open another PR and deprecate existing methods.

validate :validate_payment_source_ownership

private
private

def check_for_payment_source_class
if !payment_source.is_a?(Spree::PaymentSource)
errors.add(:payment_source, :has_to_be_payment_source_class)
def check_for_payment_source_class
if !payment_source.is_a?(Spree::PaymentSource)
errors.add(:payment_source, :has_to_be_payment_source_class)
end
end

def validate_payment_source_ownership
return unless payment_source.present?

if payment_source.respond_to?(:user_id) &&
payment_source.user_id != user_id
errors.add(:payment_source, :not_owned_by_user)
end
end
end
end
3 changes: 3 additions & 0 deletions core/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,9 @@ en:
attributes:
payment_source:
has_to_be_payment_source_class: has to be a Spree::PaymentSource
not_owned_by_user: does not belong to user
user_id:
payment_source_already_exists: already has the Spree::PaymentSource in their wallet
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a very human friendly error message. Could we use

"already has this payment source in their wallet"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming @ericsaupe took inspiration from the message above. I think we could fix both in this PR.

models:
spree/address:
one: Address
Expand Down
10 changes: 3 additions & 7 deletions core/spec/models/spree/order/checkout_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ def assert_state_changed(order, from, to)

before do
user = create(:user, email: 'spree@example.org', bill_address: user_bill_address)
default_credit_card.update(user: user)
wallet_payment_source = user.wallet.add(default_credit_card)
user.wallet.default_wallet_payment_source = wallet_payment_source
order.user = user
Expand Down Expand Up @@ -497,12 +498,7 @@ def assert_state_changed(order, from, to)

context "with a payment in the pending state" do
let(:order) { create :order_ready_to_complete }
let(:payment) { create :payment, state: "pending", amount: order.total }

before do
order.payments = [payment]
order.save!
end
let(:payment) { create :payment, order: order, state: "pending", amount: order.total }

it "allows the order to complete" do
expect { order.complete! }.
Expand Down Expand Up @@ -542,7 +538,7 @@ def assert_state_changed(order, from, to)
order.user = FactoryBot.create(:user)
order.store = FactoryBot.create(:store)
order.email = 'spree@example.org'
order.payments << FactoryBot.create(:payment)
order.payments << FactoryBot.create(:payment, order: order)

# make sure we will actually capture a payment
allow(order).to receive_messages(payment_required?: true)
Expand Down
39 changes: 34 additions & 5 deletions core/spec/models/spree/wallet_payment_source_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
subject { Spree::WalletPaymentSource }

describe "validation" do
let(:user) { create(:user) }

context 'with a non-PaymentSource model' do
with_model 'NonPaymentSource', scope: :all do
model do
Expand All @@ -19,7 +21,7 @@
it "errors when `payment_source` is not a `Spree::PaymentSource`" do
wallet_payment_source = Spree::WalletPaymentSource.new(
payment_source: payment_source,
user: create(:user)
user: user
)

expect(wallet_payment_source).not_to be_valid
Expand All @@ -29,18 +31,45 @@
end
end

it "is invalid if `payment_source` is already in the user's wallet" do
credit_card = create(:credit_card, user: user)
Spree::WalletPaymentSource.create(
payment_source: credit_card,
user: user
)
wallet_payment_source = subject.new(
payment_source: credit_card,
user: user
)
expect(wallet_payment_source).not_to be_valid
expect(wallet_payment_source.errors.messages).to eq(
{ user_id: ["already has the Spree::PaymentSource in their wallet"] }
)
end

it "is invalid when `payment_source` is not owned by the user" do
wallet_payment_source = subject.new(
payment_source: create(:credit_card),
user: user
)
expect(wallet_payment_source).not_to be_valid
expect(wallet_payment_source.errors.messages).to eq(
{ payment_source: ["does not belong to user"] }
)
end

it "is valid with a `credit_card` as `payment_source`" do
valid_attrs = {
payment_source: create(:credit_card),
user: create(:user)
payment_source: create(:credit_card, user: user),
user: user
}
expect(subject.new(valid_attrs)).to be_valid
end

it "is valid with `store_credit` as `payment_source`" do
valid_attrs = {
payment_source: create(:store_credit),
user: create(:user)
payment_source: create(:store_credit, user: user),
user: user
}
expect(subject.new(valid_attrs)).to be_valid
end
Expand Down
2 changes: 1 addition & 1 deletion core/spec/models/spree/wallet_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
end

context 'with a wallet payment source that does not belong to the wallet' do
let(:other_wallet_credit_card) { other_wallet.add(credit_card) }
let(:other_wallet_credit_card) { other_wallet.add(other_credit_card) }
let(:other_wallet) { Spree::Wallet.new(other_user) }
let(:other_credit_card) { create(:credit_card, user_id: other_user.id) }
let(:other_user) { create(:user) }
Expand Down