Skip to content

Commit

Permalink
close #162 allow payout to be optional for product
Browse files Browse the repository at this point in the history
  • Loading branch information
theachoem committed May 23, 2024
1 parent 09abd28 commit 74bc38c
Show file tree
Hide file tree
Showing 16 changed files with 185 additions and 38 deletions.
10 changes: 10 additions & 0 deletions app/controllers/spree/admin/payout_profile_products_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
module Spree
module Admin
class PayoutProfileProductsController < Spree::Admin::ResourceController
before_action :redirect_to_object, only: [:index]

belongs_to 'spree/product', find_by: :slug

def redirect_to_object
if parent.payout_profile_products.any?
redirect_to edit_object_url(parent.payout_profile_products.first)
else
redirect_to new_object_url
end
end

def model_class
Spree::PayoutProfileProduct
end
Expand Down
19 changes: 19 additions & 0 deletions app/models/spree/payout_profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,35 @@ class PayoutProfile < Base
before_save :ensure_default_exists_and_clear_vendor
before_destroy :confirm_destroyable

self.whitelisted_ransackable_attributes = %w[name vendor_id]

def self.default
Rails.cache.fetch("default_payout_account/#{self.name.underscore}") do
find_by(type: self.name, default: true)
end
end

def bank_name
'None'
end

def display_name
display_name = name

bank_info = [bank_name, bank_account_number].compact.join(" - ")
display_name += " (#{bank_info})" unless bank_info.empty?

display_name
end

def verified?
verified_at.present?
end

def receivable?
verified? && active?
end

def registered_in_bank?
true
end
Expand Down
5 changes: 5 additions & 0 deletions app/models/spree/payout_profiles/payway_v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ def allow_to_verify_with_bank?
preferred_api_key.present? &&
preferred_rsa_public_key.present?
end

# override
def bank_name
'ABA'
end
end
end
end
5 changes: 4 additions & 1 deletion app/models/vpago/line_item_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ def self.prepended(base)
base.has_many :payout_profiles, class_name: 'Spree::PayoutProfile', through: :product
base.has_many :active_payout_profiles, class_name: 'Spree::PayoutProfile', through: :product
base.has_many :active_payway_payout_profiles, class_name: 'Spree::PayoutProfile', through: :product

base.has_many :required_active_payout_profiles, class_name: 'Spree::PayoutProfile', through: :product
end

# considred required when there are any required profiles.
def required_payway_payout?
active_payway_payout_profiles.any?
required_active_payout_profiles.payway.exists?
end
end
end
Expand Down
9 changes: 8 additions & 1 deletion app/models/vpago/product_decorator.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
module Vpago
module ProductDecorator
def self.prepended(base)
base.scope :required, -> { where(optional: false) }
base.scope :optional, -> { where(optional: true) }

base.has_many :payout_profile_products, class_name: 'Spree::PayoutProfileProduct', inverse_of: :product
base.has_many :payout_profiles, class_name: 'Spree::PayoutProfile', through: :payout_profile_products, source: :payout_profile

base.has_many :active_payout_profiles, -> { verified.active }, class_name: 'Spree::PayoutProfile', through: :payout_profile_products, source: :payout_profile
base.has_many :active_payway_payout_profiles, -> { payway.verified.active }, class_name: 'Spree::PayoutProfile', through: :payout_profile_products, source: :payout_profile

# required
base.has_many :required_payout_profile_products, -> { required }, class_name: 'Spree::PayoutProfileProduct', inverse_of: :product
base.has_many :required_active_payout_profiles, -> { verified.active }, class_name: 'Spree::PayoutProfile', through: :payout_profile_products, source: :payout_profile
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<%= content_tag :li, class: 'nav-item' do %>
<%= link_to_with_icon 'wallet2.svg',
Spree.t(:payout_profile_products),
Spree.t(:payout),
admin_product_payout_profile_products_url(@product),
class: "nav-link #{'active' if current == :payout_profile_products}" %>
<% end if can?(:admin, Spree::PayoutProfileProduct) %>
<% end if can?(:admin, Spree::PayoutProfileProduct) %>
27 changes: 25 additions & 2 deletions app/views/spree/admin/payout_profile_products/_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
<%= f.field_container :payout_profile_id do %>
<%= f.label :payout_profile_id, Spree.t(:payout_profile) %>
<%= f.collection_select :payout_profile_id, @product.vendor.payout_profiles, :id, :name, { prompt: Spree.t('match_choices.none') }, class: 'form-control select2' %>
<%= f.collection_select :payout_profile_id, @product.vendor.payout_profiles, :id, :display_name, { prompt: Spree.t('match_choices.none') }, class: 'form-control select2' %>
<%= f.error_message_on :payout_profile_id %>
<% end %>
<% end %>

<%= render partial: 'payout_profile_info_card', locals: { payout_profile: f.object.payout_profile } if f.object.payout_profile.present? %>

<%= f.field_container :optional, class: ["form-group card card-body"] do %>
<% default = object.class.columns_hash['optional'].default %>

<strong><%= Spree.t(:optional) %></strong>
<div class="radio my-2 form-check">
<%= f.radio_button :optional, true, class: 'form-check-input' %>
<%= f.label :optional_true, Spree.t(:say_yes) + (default == 'true' ? ' (default)' : ''), class: 'form-check-label' %>
<small class="form-text text-muted">
<%= "User can buy this product with other payment methods beside #{f.object.payout_profile.bank_name}, but amount will be sent to #{Spree::Store.default.name} account" %>
</small>
</div>

<div class="radio my-2 form-check">
<%= f.radio_button :optional, false, class: 'form-check-input' %>
<%= f.label :optional_false, Spree.t(:say_no) + (default == 'false' ? ' (default)' : ''), class: 'form-check-label' %>
<small class="form-text text-muted">
<%= "User can only buy this product with #{f.object.payout_profile.bank_name} payment methods" %>
</small>
</div>
<% end if f.object.payout_profile.present? %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<ul class="list-group mb-3">
<li class="list-group-item d-flex justify-content-between align-items-center">
<%= Spree.t(:bank_account_number) %>
<strong>
<%= button_link_to payout_profile&.bank_account_number, edit_admin_payout_profile_path(payout_profile), icon: 'edit.svg' %>
</strong>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
<%= Spree.t(:bank) %>
<span><%= payout_profile&.bank_name %></span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
<%= Spree.t(:active) %>
<span><%= active_badge(payout_profile&.active) %></span>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
<%= Spree.t(:verified_at) %>
<% if payout_profile&.verified? %>
<span><%= pretty_time(payout_profile&.verified_at) %></span>
<% else %>
<span><%= active_badge(false) %></span>
<% end %>
</li>
</ul>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<% if payout_profile.present? && !payout_profile.receivable? %>
<div class="alert alert-warning mb-3 d-flex justify-content-between align-items-center">
Profile is not verified or is inactive. Please verify and mark it as active to receive payout amount.
<%= button_link_to Spree.t(:verify), spree.edit_admin_payout_profile_path(payout_profile), class: "btn-warning", icon: 'send.svg' %>
</div>
<% elsif payout_profile&.receivable? %>
<div class="alert alert-success mb-3">
<%= svg_icon name: "tick.svg", classes: 'mr-2', width: '16', height: '16' %>
This product is now eligible for payout
</div>
<% end %>
18 changes: 18 additions & 0 deletions app/views/spree/admin/payout_profile_products/edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<%= render partial: 'spree/admin/shared/product_tabs', locals: { current: :payout_profile_products } %>
<%= render partial: 'spree/admin/shared/error_messages', locals: { target: @payout_profile_product } %>

<%= render partial: 'payout_profile_status', locals: { payout_profile: @payout_profile_product.payout_profile } %>

<% content_for :page_actions do %>
<%= button_link_to Spree.t(:add_profiles), admin_payout_profiles_url(q: { vendor_id_eq: @product.vendor_id }), icon: 'arrow-right-circle.svg' %>
<% end %>

<%= form_for @payout_profile_product, url: object_url do |f| %>
<fieldset>
<%= render partial: 'form', locals: { f: f } %>
<div class="form-actions" data-hook="buttons">
<%= button Spree.t('actions.update'), 'save.svg', 'submit', { class: 'btn-success', data: { disable_with: "#{ Spree.t(:saving) }..." } } %>
<%= link_to_with_icon 'delete.svg', Spree.t(:delete), object_url(@payout_profile_product), { class: 'btn btn-danger float-right', data: { confirm: Spree.t(:are_you_sure), method: :delete } } %>
</div>
</fieldset>
<% end %>
17 changes: 15 additions & 2 deletions app/views/spree/admin/payout_profile_products/new.html.erb
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
<%= render partial: 'spree/admin/shared/product_tabs', locals: { current: :payout_profile_products } %>
<%= render partial: 'spree/admin/shared/error_messages', locals: { target: @payout_profile_product } %>

<% content_for :page_actions do %>
<%= button_link_to Spree.t(:add_profiles) + " (#{@product.vendor&.name})", admin_payout_profiles_url(q: { vendor_id_eq: @product.vendor_id }), icon: 'arrow-right-circle.svg' %>
<% end if @product.vendor.payout_profiles.any? %>

<% if @product.vendor.payout_profiles.none? %>
<div class="alert alert-warning mb-3 d-flex justify-content-between align-items-center">
To set up payouts for this product, make sure the vendor has a payout account linked
<%= button_link_to Spree.t(:add_profiles) + " (#{@product.vendor&.name})", new_admin_payout_profile_url(vendor_id: @product.vendor_id), class: "btn-warning", icon: 'arrow-right-circle.svg' %>
</div>
<% end %>

<%= form_for @payout_profile_product, url: collection_url do |f| %>
<fieldset>
<%= render partial: 'form', locals: { f: f } %>
<%= render partial: 'spree/admin/shared/new_resource_links' %>
<div class="form-actions" data-hook="buttons">
<%= button Spree.t('actions.create'), 'save.svg', 'submit', {class: 'btn-success', data: { disable_with: "#{ Spree.t(:saving) }..." }} %>
<div>
</fieldset>
<% end %>
<% end if @product.vendor.payout_profiles.any? %>
19 changes: 19 additions & 0 deletions app/views/spree/admin/payout_profiles/_filter.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<%= search_form_for [:admin, @search] do |f| %>
<div data-hook="admin_payout_profile_search" class="row">
<div class="col-12 col-lg-6">
<div class="form-group">
<%= f.label :name_cont, Spree.t(:name) %>
<%= f.text_field :name_cont, size: 15, class: "form-control js-quick-search-target js-filterable" %>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="form-group">
<%= f.label :vendor_id_eq, Spree.t(:vendor) %>
<%= f.select :vendor_id_eq, Spree::Vendor.all.map{|t| [t.name , t.id]}, { include_blank: true}, class: 'form-control js-filterable' %>
</div>
</div>
</div>
<div class="form-actions">
<%= button Spree.t(:search), 'search.svg' %>
</div>
<% end %>
2 changes: 1 addition & 1 deletion app/views/spree/admin/payout_profiles/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

<%= f.field_container :vendor do %>
<%= f.label :vendor, Spree.t(:vendors) %>
<%= f.collection_select :vendor_id, Spree::Vendor.all, :id, :name, { prompt: Spree.t('match_choices.none') }, class: 'form-control select2' %>
<%= f.collection_select :vendor_id, Spree::Vendor.all, :id, :name, { prompt: Spree.t('match_choices.none'), selected: f.object.vendor_id || params[:vendor_id] }, class: 'form-control select2' %>
<%= f.error_message_on :vendor_id %>
<% end unless f.object.default? %>

Expand Down
8 changes: 6 additions & 2 deletions app/views/spree/admin/payout_profiles/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
<% end %>

<% content_for :page_actions do %>
<%= button_link_to Spree.t(:new), new_object_url, class: "btn-success", icon: 'add.svg' %>
<%= button_link_to Spree.t(:new), new_object_url(vendor_id: params.dig(:q, :vendor_id_eq)), class: "btn-success", icon: 'add.svg' %>
<% end if can? :create, Spree::PayoutProfile %>

<% content_for :table_filter do %>
<% render partial: 'filter' %>
<% end %>

<% if @payout_profiles.any? %>
<div class="table-responsive border rounded bg-white mb-3">
<table class="table">
Expand Down Expand Up @@ -52,7 +56,7 @@
<% else %>
<div class="text-center no-objects-found m-5">
<%= Spree.t(:no_resource_found, resource: plural_resource_name(Spree::PayoutProfile)) %>,
<%= link_to(Spree.t(:add_one), new_object_url) if can? :create, Spree::PayoutProfile %>!
<%= link_to(Spree.t(:add_one), new_object_url(vendor_id: params.dig(:q, :vendor_id_eq))) if can? :create, Spree::PayoutProfile %>!
</div>
<% end %>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddOptionalSperePayoutProfileProducts < ActiveRecord::Migration[7.0]
def change
add_column :spree_payout_profile_products, :optional, :boolean, null: false, default: false, if_not_exists: true
end
end
40 changes: 13 additions & 27 deletions spec/models/spree/line_item_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,30 @@

RSpec.describe Spree::LineItem, type: :model do
it { should have_many(:payout_profiles).class_name('Spree::PayoutProfile').through(:product) }
it { should have_many(:required_active_payout_profiles).class_name('Spree::PayoutProfile').through(:product) }

describe '#required_payway_payout?' do
let(:payout_profiles) { [payout_product] }
let(:product) { create(:product, payout_profiles: payout_profiles) }
let(:line_item) { create(:line_item, product: product) }
context 'when there are required_active_payout_profiles in product' do
let(:payout_product) { build(:payway_payout_profile, active: true, verified_at: DateTime.current)}
let(:product) { create(:product, required_active_payout_profiles: [payout_product]) }
let(:line_item) { create(:line_item, product: product) }

context 'when have active & verified payout profile in product' do
let(:payout_product) { create(:payway_payout_profile, active: true, verified_at: DateTime.current)}

it 'return true' do
expect(line_item.active_payway_payout_profiles).to eq [payout_product]
expect(product.required_active_payout_profiles.payway.exists?).to be true
expect(line_item.required_active_payout_profiles.payway.exists?).to be true

expect(line_item.required_payway_payout?).to be true
end
end

context 'when have verified, but not active payout profile in product' do
let(:payout_product) { create(:payway_payout_profile, active: false, verified_at: DateTime.current)}

it 'return false' do
expect(line_item.active_payway_payout_profiles).to eq []
expect(line_item.required_payway_payout?).to be false
end
end
context 'when there are no required_active_payout_profiles in product' do
let(:product) { create(:product, required_active_payout_profiles: []) }
let(:line_item) { create(:line_item, product: product) }

context 'when have active, but not verified payout profile in product' do
let(:payout_product) { create(:payway_payout_profile, active: false, verified_at: DateTime.current)}

it 'return false' do
expect(line_item.active_payway_payout_profiles).to eq []
expect(line_item.required_payway_payout?).to be false
end
end
expect(product.required_active_payout_profiles.payway.exists?).to be false
expect(line_item.required_active_payout_profiles.payway.exists?).to be false

context 'when does not have active & verified payout profile in product' do
let(:payout_profiles) { [] }

it 'return false' do
expect(line_item.active_payway_payout_profiles).to eq []
expect(line_item.required_payway_payout?).to be false
end
end
Expand Down

0 comments on commit 74bc38c

Please sign in to comment.