diff --git a/app/controllers/orders_payment_methods_controller.rb b/app/controllers/orders_payment_methods_controller.rb index 9c736ad..7253cbb 100644 --- a/app/controllers/orders_payment_methods_controller.rb +++ b/app/controllers/orders_payment_methods_controller.rb @@ -9,8 +9,6 @@ def create if @order_payment_method.valid? @order_payment_method.save! - @order.reload - @order.process_payment! render json: @order_payment_method, status: :created else diff --git a/app/controllers/payment_methods_controller.rb b/app/controllers/payment_methods_controller.rb index 2b183d4..27d707a 100644 --- a/app/controllers/payment_methods_controller.rb +++ b/app/controllers/payment_methods_controller.rb @@ -5,7 +5,7 @@ def index @payment_methods = PaymentMethod.page(pagination_params[:page]).per(pagination_params[:limit]) with_pagination_options @payment_methods do |options| - render json: PaymentMethodsSerializer.new(@payment_methods, options), status: :ok + render json: PaymentMethodSerializer.new(@payment_methods, options), status: :ok end end end diff --git a/app/controllers/payments_controller.rb b/app/controllers/payments_controller.rb new file mode 100644 index 0000000..96ef004 --- /dev/null +++ b/app/controllers/payments_controller.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class PaymentsController < ApplicationController + before_action :authenticate + + def create + @order = Order.find params[:order_id] + @payment = @order.build_payment create_params + + if @payment.valid? + @payment.save! + @order.reload + @order.process_payment! + + render json: PaymentSerializer.new(@payment), status: :created + + else + render json: @payment.errors, status: :unprocessable_entity + end + end + + private + + def create_params + params.require(:payment).permit(:amount) + end +end diff --git a/app/models/order.rb b/app/models/order.rb index 1e48ac6..0273dd9 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -14,10 +14,11 @@ class Order < ApplicationRecord belongs_to :shipping_address has_one :orders_payment_methods, class_name: 'OrdersPaymentMethod', dependent: :destroy has_one :payment_method, through: :orders_payment_methods + has_one :payment, dependent: :destroy has_many :items, class_name: 'OrderItem', dependent: :destroy - validates :cart, cart_items: true + validates :cart, cart_items: true, if: proc { |order| order.cart.present? } aasm :status do state :pending, initial: true @@ -35,8 +36,7 @@ class Order < ApplicationRecord state :void event :process_payment do - transitions from: :pending, to: :payment_processing, guard: :verify_payment_method? - transitions from: :pending, to: :order_packaging + transitions from: :delivered, to: :payment_processing, guard: :payment_received? end event :confirm_payment do @@ -56,11 +56,11 @@ def pay_on_delivery? payment_method.name == PAYMENT_METHODS[:pay_on_delivery] end - def verify_payment_method? - !pay_on_delivery? + def payment_received? + pay_on_delivery? && payment.present? end def payment_confirmed? - return true if pay_on_delivery? + false end end diff --git a/app/models/payment.rb b/app/models/payment.rb index 40527c4..4bc209d 100644 --- a/app/models/payment.rb +++ b/app/models/payment.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Payment < ApplicationRecord + belongs_to :order has_one :payments_payment_methods, class_name: 'PaymentsPaymentMethod', dependent: :destroy has_one :payment_method, through: :payments_payment_methods end diff --git a/app/serializers/payment_methods_serializer.rb b/app/serializers/payment_method_serializer.rb similarity index 76% rename from app/serializers/payment_methods_serializer.rb rename to app/serializers/payment_method_serializer.rb index 7fe4efb..9af2f51 100644 --- a/app/serializers/payment_methods_serializer.rb +++ b/app/serializers/payment_method_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class PaymentMethodsSerializer +class PaymentMethodSerializer include JSONAPI::Serializer attributes :name, :description diff --git a/app/serializers/payment_serializer.rb b/app/serializers/payment_serializer.rb new file mode 100644 index 0000000..2630f2e --- /dev/null +++ b/app/serializers/payment_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class PaymentSerializer + include JSONAPI::Serializer + + attributes :amount, :order_id + + has_one :payment_method +end diff --git a/config/routes.rb b/config/routes.rb index e2d2288..8e9cc65 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do + get 'payments/create' post 'auth', to: 'auth#create' resources :users do resources :shipping_addresses, only: %i[create index] @@ -16,5 +17,6 @@ resources :payment_methods, only: :index resources :orders, only: :show do resources :orders_payment_methods, only: :create, as: :payment_methods + resources :payments, only: :create end end diff --git a/db/migrate/20230121042740_add_order_to_payments.rb b/db/migrate/20230121042740_add_order_to_payments.rb new file mode 100644 index 0000000..9b7ce59 --- /dev/null +++ b/db/migrate/20230121042740_add_order_to_payments.rb @@ -0,0 +1,5 @@ +class AddOrderToPayments < ActiveRecord::Migration[6.0] + def change + add_reference :payments, :order, null: false, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 6086bc9..3d383fb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_01_20_141624) do +ActiveRecord::Schema.define(version: 2023_01_21_042740) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -164,6 +164,8 @@ t.decimal "amount", precision: 8, scale: 2 t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.bigint "order_id", null: false + t.index ["order_id"], name: "index_payments_on_order_id" end create_table "payments_payment_methods", force: :cascade do |t| @@ -274,6 +276,7 @@ add_foreign_key "orders", "shipping_addresses" add_foreign_key "orders_payment_methods", "orders" add_foreign_key "orders_payment_methods", "payment_methods" + add_foreign_key "payments", "orders" add_foreign_key "payments_payment_methods", "payment_methods" add_foreign_key "payments_payment_methods", "payments" add_foreign_key "products", "brands" diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb index b2e0657..38c595e 100644 --- a/spec/models/order_spec.rb +++ b/spec/models/order_spec.rb @@ -12,6 +12,7 @@ context 'Associations' do specify { should have_one(:payment_method).through :orders_payment_methods } + specify { should have_one(:payment) } specify { should belong_to(:shipping_address) } end end diff --git a/spec/models/payment_spec.rb b/spec/models/payment_spec.rb index e180bdb..b2198a0 100644 --- a/spec/models/payment_spec.rb +++ b/spec/models/payment_spec.rb @@ -5,5 +5,6 @@ RSpec.describe Payment, type: :model do context 'Associations' do specify { should have_one(:payment_method).through :payments_payment_methods } + specify { should belong_to(:order) } end end diff --git a/spec/requests/orders/payments/create_spec.rb b/spec/requests/orders/payments/create_spec.rb new file mode 100644 index 0000000..0ea4912 --- /dev/null +++ b/spec/requests/orders/payments/create_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'POST /orders/:order_id/payments', type: :request do + let(:user) { create :user } + let(:cart) { create :cart, :with_items, user: user, items_count: 1 } + let(:order) { create :order, cart: cart } + let(:payment_method) { create :payment_method, name: 'Pay on Delivery' } + + context 'When unauthorized' do + before { post order_payments_path(order), params: {} } + + it_should_behave_like 'unauthorized request' + end + + context 'With pay on delivery payment method' do + let(:cart) { create :cart, user: user } + let(:product) { create :product, :with_master, stock: 10 } + let(:product2) { create :product, :with_master, stock: 1 } + let(:cart_item1) { create :cart_item, product: product, sku: product.master.sku, cart: cart } + let(:cart_item2) { create :cart_item, product: product2, sku: product2.master.sku, cart: cart } + let(:amount) { cart.items.select(:amount).collect(&:amount).reduce(&:+).to_f } + + before do + cart.items << [cart_item1, cart_item2] + order.payment_method = payment_method + order.status = :delivered + order.save! + post order_payments_path(order), params: { payment: { amount: amount } }, + headers: authorization_header(user.email) + end + + it_behaves_like 'created resource request' + + it 'returns payment data' do + expect(response.parsed_body['data']['type']).to eq 'payment' + expect(response.parsed_body['data']['attributes']['order_id']).to eql order.id + expect(response.parsed_body['data']['attributes']['amount'].to_f).to eql amount.to_f + end + + it 'updates order status to payment processing' do + order.reload + expect(order.status).to eql :payment_processing.to_s + end + end +end diff --git a/spec/requests/payments_spec.rb b/spec/requests/payments_spec.rb new file mode 100644 index 0000000..2375f8b --- /dev/null +++ b/spec/requests/payments_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Payments', type: :request do + describe 'GET /create' do + it 'returns http success' do + get '/payments/create' + expect(response).to have_http_status(:success) + end + end +end