From a897d37b2d5be9c6bb432e420eaabc4ebbcd6839 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Fri, 16 Apr 2021 16:03:10 +0200 Subject: [PATCH] improved footprint debug process --- .../api/payment_schedules_controller.rb | 2 +- app/controllers/api/stripe_controller.rb | 2 +- app/doc/open_api/v1/invoices_doc.rb | 1 + app/models/footprint_debug.rb | 28 ++++++++++++++ app/models/footprintable.rb | 20 +++++++--- app/models/history_value.rb | 10 +---- app/models/invoice.rb | 6 ++- app/models/invoice_item.rb | 1 + app/models/payment_schedule.rb | 5 ++- app/models/payment_schedule_item.rb | 2 +- app/models/subscription.rb | 1 + app/models/user.rb | 1 + app/services/footprint_service.rb | 37 ++++++++++--------- app/services/invoices_service.rb | 2 +- ...e_stripe_ids_to_payment_gateway_objects.rb | 16 ++++++++ lib/integrity/archive_helper.rb | 10 ++++- lib/tasks/fablab/maintenance.rake | 2 +- 17 files changed, 104 insertions(+), 42 deletions(-) diff --git a/app/controllers/api/payment_schedules_controller.rb b/app/controllers/api/payment_schedules_controller.rb index 901bb73b2..8192818da 100644 --- a/app/controllers/api/payment_schedules_controller.rb +++ b/app/controllers/api/payment_schedules_controller.rb @@ -53,7 +53,7 @@ class API::PaymentSchedulesController < API::ApiController def pay_item authorize @payment_schedule_item.payment_schedule - + # FIXME stripe_key = Setting.get('stripe_secret_key') stp_invoice = Stripe::Invoice.pay(@payment_schedule_item.stp_invoice_id, {}, { api_key: stripe_key }) PaymentScheduleItemWorker.new.perform(@payment_schedule_item.id) diff --git a/app/controllers/api/stripe_controller.rb b/app/controllers/api/stripe_controller.rb index afadfac8b..7d65a0406 100644 --- a/app/controllers/api/stripe_controller.rb +++ b/app/controllers/api/stripe_controller.rb @@ -30,7 +30,7 @@ class API::StripeController < API::PaymentsController currency: Setting.get('stripe_currency'), confirmation_method: 'manual', confirm: true, - customer: current_user.stp_customer_id + customer: current_user.stp_customer_id # FIXME }, { api_key: Setting.get('stripe_secret_key') } ) elsif params[:payment_intent_id].present? diff --git a/app/doc/open_api/v1/invoices_doc.rb b/app/doc/open_api/v1/invoices_doc.rb index d9c966cfc..e364c6982 100644 --- a/app/doc/open_api/v1/invoices_doc.rb +++ b/app/doc/open_api/v1/invoices_doc.rb @@ -16,6 +16,7 @@ class OpenAPI::V1::InvoicesDoc < OpenAPI::V1::BaseDoc description "Index of users' invoices, with optional pagination. Order by *created_at* descendant." param_group :pagination param :user_id, [Integer, Array], optional: true, desc: 'Scope the request to one or various users.' + # FIXME example <<-INVOICES # /open_api/v1/invoices?user_id=211&page=1&per_page=3 { diff --git a/app/models/footprint_debug.rb b/app/models/footprint_debug.rb index d79fc095c..70d02aa4d 100644 --- a/app/models/footprint_debug.rb +++ b/app/models/footprint_debug.rb @@ -2,4 +2,32 @@ # When a footprint is generated, the associated data is kept to allow further verifications class FootprintDebug < ApplicationRecord + # We try to rebuild the data, column by column, from the db object + # If any datum oes not match, we print it as ERROR + def format_data(item_id) + item = klass.constantize.find(item_id) + columns = FootprintService.footprint_columns(klass.constantize) + + result = [] + index = 0 + columns.each do |column| + col_data = item[column] + end_idx = index + col_data.to_s.length - 1 + + if data[index..end_idx] == col_data.to_s + # if the item data for the current column matches, save it into the results and move forward teh cursor + result.push(col_data.to_s) + index = end_idx + 1 + else + # if the item data for the current column does not matches, mark it as an error, display the next chars, but do not move the cursor + datum = data[index..end_idx] + datum = data[index..index + 5] if datum&.empty? + result.push "ERROR (#{datum}...)" + end + end + # the remaining data is the previous record checksum + result.push(data[index..-1]) + + result + end end diff --git a/app/models/footprintable.rb b/app/models/footprintable.rb index 3034a361e..37ca1bd49 100644 --- a/app/models/footprintable.rb +++ b/app/models/footprintable.rb @@ -8,16 +8,24 @@ class Footprintable < ApplicationRecord [] end - def check_footprint - footprint == compute_footprint + def footprint_children + [] end - def chain_record(sort_on = 'id') + def sort_on_field + 'id' + end + + def check_footprint + footprint_children.map(&:check_footprint).all? && footprint == compute_footprint + end + + def chain_record self.footprint = compute_footprint save! FootprintDebug.create!( footprint: footprint, - data: FootprintService.footprint_data(self.class, self, sort_on), + data: FootprintService.footprint_data(self.class, self), klass: self.class.name ) end @@ -28,7 +36,7 @@ class Footprintable < ApplicationRecord protected - def compute_footprint(sort_on = 'id') - FootprintService.compute_footprint(self.class, self, sort_on) + def compute_footprint + FootprintService.compute_footprint(self.class, self) end end diff --git a/app/models/history_value.rb b/app/models/history_value.rb index e224573b9..baaaecfb2 100644 --- a/app/models/history_value.rb +++ b/app/models/history_value.rb @@ -7,17 +7,11 @@ class HistoryValue < Footprintable after_create :chain_record - def chain_record - super('created_at') + def sort_on_field + 'created_at' end def user invoicing_profile.user end - - private - - def compute_footprint - super('created_at') - end end diff --git a/app/models/invoice.rb b/app/models/invoice.rb index 9aa320ab4..6c89a34a4 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -21,6 +21,7 @@ class Invoice < PaymentDocument has_one :avoir, class_name: 'Invoice', foreign_key: :invoice_id, dependent: :destroy has_one :payment_schedule_item + has_one :payment_gateway_object belongs_to :operator_profile, foreign_key: :operator_profile_id, class_name: 'InvoicingProfile' before_create :add_environment @@ -157,11 +158,12 @@ class Invoice < PaymentDocument res end - def check_footprint - invoice_items.map(&:check_footprint).all? && footprint == compute_footprint + def footprint_children + invoice_items end def paid_with_stripe? + # FIXME stp_payment_intent_id? || stp_invoice_id? || payment_method == 'stripe' end diff --git a/app/models/invoice_item.rb b/app/models/invoice_item.rb index 93d1c1213..90a696cfb 100644 --- a/app/models/invoice_item.rb +++ b/app/models/invoice_item.rb @@ -6,6 +6,7 @@ class InvoiceItem < Footprintable belongs_to :subscription has_one :invoice_item # associates invoice_items of an invoice to invoice_items of an Avoir + has_one :payment_gateway_object after_create :chain_record after_update :log_changes diff --git a/app/models/payment_schedule.rb b/app/models/payment_schedule.rb index 5f551a6ab..b7c282c4c 100644 --- a/app/models/payment_schedule.rb +++ b/app/models/payment_schedule.rb @@ -16,6 +16,7 @@ class PaymentSchedule < PaymentDocument belongs_to :reservation, foreign_type: 'Reservation', foreign_key: 'scheduled_id' has_many :payment_schedule_items + has_many :payment_gateway_object before_create :add_environment after_create :update_reference, :chain_record @@ -57,8 +58,8 @@ class PaymentSchedule < PaymentDocument File.binwrite(file, pdf) end - def check_footprint - payment_schedule_items.map(&:check_footprint).all? && footprint == compute_footprint + def footprint_children + payment_schedule_items end def post_save(setup_intent_id) diff --git a/app/models/payment_schedule_item.rb b/app/models/payment_schedule_item.rb index fcd022ab9..02300b488 100644 --- a/app/models/payment_schedule_item.rb +++ b/app/models/payment_schedule_item.rb @@ -14,7 +14,7 @@ class PaymentScheduleItem < Footprintable def payment_intent return unless payment_gateway_object - + # FIXME key = Setting.get('stripe_secret_key') stp_invoice = Stripe::Invoice.retrieve(stp_invoice_id, api_key: key) Stripe::PaymentIntent.retrieve(stp_invoice.payment_intent, api_key: key) diff --git a/app/models/subscription.rb b/app/models/subscription.rb index 91e8538dc..2b5c07f0c 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -8,6 +8,7 @@ class Subscription < ApplicationRecord belongs_to :statistic_profile has_one :payment_schedule, as: :scheduled, dependent: :destroy + has_one :payment_gateway_object has_many :invoices, as: :invoiced, dependent: :destroy has_many :offer_days, dependent: :destroy diff --git a/app/models/user.rb b/app/models/user.rb index 5d50283dc..4fda4b147 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -178,6 +178,7 @@ class User < ApplicationRecord end def stripe_customer + # FIXME Stripe::Customer.retrieve(stp_customer_id, api_key: Setting.get('stripe_secret_key')) end diff --git a/app/services/footprint_service.rb b/app/services/footprint_service.rb index a12d06036..aa0b95fa8 100644 --- a/app/services/footprint_service.rb +++ b/app/services/footprint_service.rb @@ -6,50 +6,53 @@ require 'integrity/checksum' class FootprintService class << self # Compute the footprint - # @param klass a class inheriting from Footprintable - # @param item an instance of the provided class - # @param sort_on the items in database by the provided criterion, to find the previous one - def compute_footprint(klass, item, sort_on = 'id') - Integrity::Checksum.text(FootprintService.footprint_data(klass, item, sort_on)) + # @param klass {Class} a class inheriting from Footprintable + # @param item {Footprintable} an instance of the provided class + def compute_footprint(klass, item) + Integrity::Checksum.text(FootprintService.footprint_data(klass, item)) end # Return the original data string used to compute the footprint - # @param klass a class inheriting from Footprintable - # @param item an instance of the provided class - # @param sort_on the items in database by the provided criterion, to find the previous one - def footprint_data(klass, item, sort_on = 'id') + # @param klass {Class} a class inheriting from Footprintable + # @param item {Footprintable} an instance of the provided class + # @param array {Boolean} if true, the result is return on the form of an array, otherwise a concatenated string is returned + def footprint_data(klass, item, array: false) raise TypeError unless item.is_a? klass + sort_on = item.sort_on_field previous = klass.where("#{sort_on} < ?", item[sort_on]) .order("#{sort_on} DESC") .limit(1) columns = FootprintService.footprint_columns(klass) - "#{columns.map { |c| comparable(item[c]) }.join}#{previous.first ? previous.first.footprint : ''}" + res = columns.map { |c| comparable(item[c]) }.push(previous.first ? previous.first.footprint : '') + array ? res.map(&:to_s) : res.join.to_s end # Return an ordered array of the columns used in the footprint computation - # @param klass a class inheriting from Footprintable + # @param klass {Class} a class inheriting from Footprintable def footprint_columns(klass) klass.columns.map(&:name).delete_if { |c| %w[footprint updated_at].concat(klass.columns_out_of_footprint).include? c } end # Logs a debugging message to help finding why a footprint is invalid - # @param klass a class inheriting from Footprintable - # @param item an instance of the provided class + # @param klass {Class} a class inheriting from Footprintable + # @param item {Footprintable} an instance of the provided class def debug_footprint(klass, item) columns = FootprintService.footprint_columns(klass) - current = FootprintService.footprint_data(klass, item) + current = FootprintService.footprint_data(klass, item, array: true) saved = FootprintDebug.find_by(footprint: item.footprint, klass: klass.name) - others = FootprintDebug.where('klass = ? AND data LIKE ?', klass, "#{item.id}%") + others = FootprintDebug.where('klass = ? AND data LIKE ? AND id != ?', klass, "#{item.id}%", saved.id) puts "Debug footprint for #{klass} [ id: #{item.id} ]" puts '-----------------------------------------' puts "columns: [ #{columns.join(', ')} ]" puts "current: #{current}" - puts " saved: #{saved&.data}" - puts "others possible matches: #{others.map(&:id)}" + puts " saved: #{saved.format_data(item.id)}" puts '-----------------------------------------' + puts "other possible matches IDs: #{others.map(&:id)}" + puts '-----------------------------------------' + item.footprint_children.map(&:debug_footprint) end private diff --git a/app/services/invoices_service.rb b/app/services/invoices_service.rb index 01060cc90..81116d2dc 100644 --- a/app/services/invoices_service.rb +++ b/app/services/invoices_service.rb @@ -83,7 +83,7 @@ class InvoicesService invoicing_profile: user.invoicing_profile, statistic_profile: user.statistic_profile, operator_profile_id: operator_profile_id, - stp_payment_intent_id: payment_id, + stp_payment_intent_id: payment_id, # FIXME payment_method: method ) diff --git a/db/migrate/20210416083610_migrate_stripe_ids_to_payment_gateway_objects.rb b/db/migrate/20210416083610_migrate_stripe_ids_to_payment_gateway_objects.rb index 77a8f90f9..d21bf4604 100644 --- a/db/migrate/20210416083610_migrate_stripe_ids_to_payment_gateway_objects.rb +++ b/db/migrate/20210416083610_migrate_stripe_ids_to_payment_gateway_objects.rb @@ -114,6 +114,19 @@ class MigrateStripeIdsToPaymentGatewayObjects < ActiveRecord::Migration[5.2] end end + ## USERS + puts 'Migrating users. This may take a while...' + User.order(:id).all.each do |u| + next unless u.stp_customer_id + + PaymentGatewayObject.create!( + item: u, + gateway_object_id: u.stp_customer_id, + gateway_object_type: 'Stripe::Customer' + ) + end + remove_column :users, :stp_customer_id + # chain all records InvoiceItem.order(:id).all.each(&:chain_record) Invoice.order(:id).all.each(&:chain_record) @@ -138,6 +151,7 @@ class MigrateStripeIdsToPaymentGatewayObjects < ActiveRecord::Migration[5.2] add_column :payment_schedules, :stp_subscription_id, :string add_column :payment_schedules, :stp_setup_intent_id, :string add_column :payment_schedule_items, :stp_invoice_id, :string + add_column :users, :stp_customer_id, :string [Plan, Machine, Space, Training].each do |klass| add_column klass.arel_table.name, :stp_product_id, :string end @@ -155,6 +169,8 @@ class MigrateStripeIdsToPaymentGatewayObjects < ActiveRecord::Migration[5.2] 'stp_invoice_item_id' when 'Stripe::PaymentIntent' 'stp_payment_intent_id' + when 'Stripe::Customer' + 'stp_customer_id' else raise "Unknown gateway_object_type #{pgo.gateway_object_type}" end diff --git a/lib/integrity/archive_helper.rb b/lib/integrity/archive_helper.rb index ddb31c17f..bece2a4ad 100644 --- a/lib/integrity/archive_helper.rb +++ b/lib/integrity/archive_helper.rb @@ -13,12 +13,18 @@ class Integrity::ArchiveHelper last_period = AccountingPeriod.order(start_at: :desc).first puts "Checking invoices footprints from #{last_period.end_at}. This may take a while..." Invoice.where('created_at > ?', last_period.end_at).order(:id).each do |i| - raise "Invalid footprint for invoice #{i.id}" unless i.check_footprint + next if i.check_footprint + + puts i.debug_footprint + raise "Invalid footprint for invoice #{i.id}" end else puts 'Checking all invoices footprints. This may take a while...' Invoice.order(:id).all.each do |i| - raise "Invalid footprint for invoice #{i.id}" unless i.check_footprint + next if i.check_footprint + + puts i.debug_footprint + raise "Invalid footprint for invoice #{i.id}" end end end diff --git a/lib/tasks/fablab/maintenance.rake b/lib/tasks/fablab/maintenance.rake index 0020666c6..4b0956e8b 100644 --- a/lib/tasks/fablab/maintenance.rake +++ b/lib/tasks/fablab/maintenance.rake @@ -102,7 +102,7 @@ namespace :fablab do klass.all.each do |item| FootprintDebug.create!( footprint: item.footprint, - data: FootprintService.footprint_data(klass, item, klass == 'HistoryValue' ? 'created_at' : 'id'), + data: FootprintService.footprint_data(klass, item), klass: klass ) end