diff --git a/app/controllers/api/subscriptions_controller.rb b/app/controllers/api/subscriptions_controller.rb index 2e1b1d6db..871f58294 100644 --- a/app/controllers/api/subscriptions_controller.rb +++ b/app/controllers/api/subscriptions_controller.rb @@ -20,7 +20,9 @@ class API::SubscriptionsController < API::ApiController @subscription = Subscription.new(subscription_params) is_subscribe = Subscriptions::Subscribe.new(current_user.invoicing_profile.id, user_id) - .pay_and_save(@subscription, coupon: coupon_params[:coupon_code], invoice: true) + .pay_and_save(@subscription, coupon: coupon_params[:coupon_code], + invoice: true, + schedule: params[:subcription][:payment_schedule]) if is_subscribe render :show, status: :created, location: @subscription diff --git a/app/models/payment_schedule.rb b/app/models/payment_schedule.rb index 3cf06a745..5b7131cea 100644 --- a/app/models/payment_schedule.rb +++ b/app/models/payment_schedule.rb @@ -8,4 +8,10 @@ class PaymentSchedule < ApplicationRecord belongs_to :coupon belongs_to :invoicing_profile belongs_to :operator_profile, foreign_key: :operator_profile_id, class_name: 'InvoicingProfile' + + after_create :create_stripe_subscription + + def create_stripe_subscription + StripeWorker.perform_async(:create_stripe_subscription, id) + end end diff --git a/app/models/subscription.rb b/app/models/subscription.rb index fcd0fa327..2f9a517d6 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -20,7 +20,7 @@ class Subscription < ApplicationRecord # @param invoice if true then only the subscription is payed, without reservation # if false then the subscription is payed with reservation - def save_with_payment(operator_profile_id, invoice = true, coupon_code = nil, payment_intent_id = nil) + def save_with_payment(operator_profile_id, invoice = true, coupon_code = nil, payment_intent_id = nil, schedule = nil) return false unless valid? set_expiration_date @@ -33,16 +33,30 @@ class Subscription < ApplicationRecord # debit wallet wallet_transaction = debit_user_wallet - invoc = generate_invoice(operator_profile_id, coupon_code, payment_intent_id) + payment = if schedule + generate_schedule(operator_profile_id, coupon_code, payment_intent_id) + else + generate_invoice(operator_profile_id, coupon_code, payment_intent_id) + end + if wallet_transaction - invoc.wallet_amount = @wallet_amount_debit - invoc.wallet_transaction_id = wallet_transaction.id + payment.wallet_amount = @wallet_amount_debit + payment.wallet_transaction_id = wallet_transaction.id end - invoc.save + payment.save end true end + def generate_schedule(operator_profile_id, coupon_code = nil, payment_intent_id = nil) + operator = InvoicingProfile.find(operator_profile_id)&.user + method = operator&.admin? || (operator&.manager? && operator != user) ? nil : 'stripe' + coupon = Coupon.find_by(code: coupon_code) unless coupon_code.nil? + + schedule = PaymentScheduleService.new.create(self, plan.amount, coupon, operator, method) + + end + def generate_invoice(operator_profile_id, coupon_code = nil, payment_intent_id = nil) coupon_id = nil total = plan.amount diff --git a/app/pdfs/pdf/invoice.rb b/app/pdfs/pdf/invoice.rb index b559cc293..aa6beccc2 100644 --- a/app/pdfs/pdf/invoice.rb +++ b/app/pdfs/pdf/invoice.rb @@ -199,8 +199,8 @@ class PDF::Invoice < Prawn::Document elsif cp.type == 'amount_off' # refunds of invoices with cash coupons: we need to ventilate coupons on paid items if invoice.is_a?(Avoir) - paid_items = invoice.invoice.invoice_items.select{ |ii| ii.amount.positive? }.length - refund_items = invoice.invoice_items.select{ |ii| ii.amount.positive? }.length + paid_items = invoice.invoice.invoice_items.select { |ii| ii.amount.positive? }.length + refund_items = invoice.invoice_items.select { |ii| ii.amount.positive? }.length discount = ((invoice.coupon.amount_off / paid_items) * refund_items) / 100.00 else diff --git a/app/services/payment_schedule_service.rb b/app/services/payment_schedule_service.rb index ec632f8a0..aad762e79 100644 --- a/app/services/payment_schedule_service.rb +++ b/app/services/payment_schedule_service.rb @@ -41,4 +41,17 @@ class PaymentScheduleService end { payment_schedule: ps, items: items } end + + def create(subscription, total, coupon, operator, payment_method) + schedule = compute(subscription.plan, total, coupon) + ps = schedule[:payment_schedule] + items = schedule[:items] + + ps.scheduled = subscription + ps.payment_method = payment_method + ps.operator_profile_id = operator + items.each do |item| + item.payment_schedule = ps + end + end end diff --git a/app/services/subscriptions/subscribe.rb b/app/services/subscriptions/subscribe.rb index 5bb1d9d0c..fa664f48c 100644 --- a/app/services/subscriptions/subscribe.rb +++ b/app/services/subscriptions/subscribe.rb @@ -9,11 +9,11 @@ class Subscriptions::Subscribe @operator_profile_id = operator_profile_id end - def pay_and_save(subscription, coupon: nil, invoice: nil, payment_intent_id: nil) + def pay_and_save(subscription, coupon: nil, invoice: nil, payment_intent_id: nil, schedule: nil) return false if user_id.nil? subscription.statistic_profile_id = StatisticProfile.find_by(user_id: user_id).id - subscription.save_with_payment(operator_profile_id, invoice, coupon, payment_intent_id) + subscription.save_with_payment(operator_profile_id, invoice, coupon, payment_intent_id, schedule) end def extend_subscription(subscription, new_expiration_date, free_days) diff --git a/app/workers/stripe_worker.rb b/app/workers/stripe_worker.rb index 2640c2603..5fc244ad0 100644 --- a/app/workers/stripe_worker.rb +++ b/app/workers/stripe_worker.rb @@ -73,4 +73,32 @@ class StripeWorker ) plan.update_columns(stp_price_id: price.id) end + + def create_stripe_subscription(payment_schedule_id, first_invoice_items) + payment_schedule = PaymentSchedule.find(payment_schedule_id) + + items = [] + first_invoice_items.each do |fii| + # TODO, fill this prices with real data + price = Stripe::Price.create({ + unit_amount: 2000, + currency: 'eur', + recurring: { interval: 'month' }, + product_data: { + name: 'lorem ipsum' + } + }, + { api_key: Setting.get('stripe_secret_key') }) + items.push(price: price[:id]) + end + Stripe::Subscription.create({ + customer: payment_schedule.invoicing_profile.user.stp_customer_id, + cancel_at: payment_schedule.scheduled.expiration_date, + promotion_code: payment_schedule.coupon&.code, + add_invoice_items: items, + items: [ + { price: payment_schedule.scheduled.plan.stp_price_id } + ] + }, { api_key: Setting.get('stripe_secret_key') }) + end end diff --git a/db/migrate/20201027100746_create_payment_schedules.rb b/db/migrate/20201027100746_create_payment_schedules.rb index eb38e3647..8b3692afc 100644 --- a/db/migrate/20201027100746_create_payment_schedules.rb +++ b/db/migrate/20201027100746_create_payment_schedules.rb @@ -16,7 +16,7 @@ class CreatePaymentSchedules < ActiveRecord::Migration[5.2] t.string :footprint t.string :environment t.belongs_to :invoicing_profile, foreign_key: true - t.references :operator_profile_id, foreign_key: { to_table: 'invoicing_profiles' } + t.references :operator_profile, foreign_key: { to_table: 'invoicing_profiles' } t.timestamps end diff --git a/db/structure.sql b/db/structure.sql index c036d3886..bef8501a1 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1513,7 +1513,7 @@ CREATE TABLE public.payment_schedules ( footprint character varying, environment character varying, invoicing_profile_id bigint, - operator_profile_id_id bigint, + operator_profile_id bigint, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL ); @@ -4554,10 +4554,10 @@ CREATE INDEX index_payment_schedules_on_invoicing_profile_id ON public.payment_s -- --- Name: index_payment_schedules_on_operator_profile_id_id; Type: INDEX; Schema: public; Owner: - +-- Name: index_payment_schedules_on_operator_profile_id; Type: INDEX; Schema: public; Owner: - -- -CREATE INDEX index_payment_schedules_on_operator_profile_id_id ON public.payment_schedules USING btree (operator_profile_id_id); +CREATE INDEX index_payment_schedules_on_operator_profile_id ON public.payment_schedules USING btree (operator_profile_id); -- @@ -5378,6 +5378,14 @@ ALTER TABLE ONLY public.projects_machines ADD CONSTRAINT fk_rails_88b280c24c FOREIGN KEY (machine_id) REFERENCES public.machines(id); +-- +-- Name: payment_schedules fk_rails_8b73dd8d7d; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.payment_schedules + ADD CONSTRAINT fk_rails_8b73dd8d7d FOREIGN KEY (operator_profile_id) REFERENCES public.invoicing_profiles(id); + + -- -- Name: availability_tags fk_rails_8cb4e921f7; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -5442,14 +5450,6 @@ ALTER TABLE ONLY public.projects_themes ADD CONSTRAINT fk_rails_b021a22658 FOREIGN KEY (theme_id) REFERENCES public.themes(id); --- --- Name: payment_schedules fk_rails_b38f5b39f6; Type: FK CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.payment_schedules - ADD CONSTRAINT fk_rails_b38f5b39f6 FOREIGN KEY (operator_profile_id_id) REFERENCES public.invoicing_profiles(id); - - -- -- Name: statistic_profiles fk_rails_bba64e5eb9; Type: FK CONSTRAINT; Schema: public; Owner: - --