diff --git a/Gemfile b/Gemfile index c20a4d5..c4baf61 100755 --- a/Gemfile +++ b/Gemfile @@ -270,3 +270,5 @@ group :development, :test do gem 'spring' gem 'spring-watcher-listen' end + +gem "rqrcode", "~> 3.2" diff --git a/Gemfile.lock b/Gemfile.lock index fa99e92..a45d34b 100755 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -188,6 +188,7 @@ GEM byebug (11.1.3) childprocess (5.1.0) logger (~> 1.5) + chunky_png (1.4.0) climate_control (1.2.0) coderay (1.1.3) commonmarker (0.23.10) @@ -768,6 +769,10 @@ GEM nokogiri rexml (3.4.4) rotp (6.3.0) + rqrcode (3.2.0) + chunky_png (~> 1.0) + rqrcode_core (~> 2.0) + rqrcode_core (2.1.0) rspec-core (3.13.0) rspec-support (~> 3.13.0) rspec-expectations (3.13.2) @@ -1127,6 +1132,7 @@ DEPENDENCIES responders (>= 3.1.1) rest-client reverse_markdown + rqrcode (~> 3.2) rspec-rails (>= 6.1.5) rspec_junit_formatter rubocop diff --git a/app/controllers/public/api/v1/captain/inter_webhooks_controller.rb b/app/controllers/public/api/v1/captain/inter_webhooks_controller.rb new file mode 100644 index 0000000..f667457 --- /dev/null +++ b/app/controllers/public/api/v1/captain/inter_webhooks_controller.rb @@ -0,0 +1,69 @@ +class Public::Api::V1::Captain::InterWebhooksController < ApplicationController + skip_before_action :verify_authenticity_token + + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def create + payload = JSON.parse(request.body.read) + pix_data = payload['pix'] + + if pix_data.blank? + render json: { message: 'Ignored: No pix data' }, status: :ok + return + end + + txid = pix_data['txid'] + e2eid = pix_data['endToEndId'] + + # Idempotency Check + existing_charge = ::Captain::PixCharge.find_by(e2eid: e2eid) + if existing_charge + render json: { message: 'Already processed' }, status: :ok + return + end + + # Find Charge + charge = ::Captain::PixCharge.find_by(txid: txid) + unless charge + render json: { message: 'Charge not found' }, status: :ok # Return 200 to satisfy Inter retry policy + return + end + + # Update Charge + charge.update!( + status: 'paid', + e2eid: e2eid, + paid_at: Time.current, + raw_webhook_payload: payload + ) + + # Update Reservation + charge.reservation.update!(payment_status: 'paid') + + # Notify Chat + notify_chat(charge.reservation) + + render json: { message: 'Received' }, status: :ok + rescue StandardError => e + Rails.logger.error "Webhook Error: #{e.message}" + render json: { error: e.message }, status: :unprocessable_entity + end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength + + private + + def notify_chat(reservation) + return unless reservation.conversation_id + + conversation = Conversation.find(reservation.conversation_id) + + Messages::CreateService.new( + conversation: conversation, + params: { + content: "✅ Pagamento confirmado! Sua reserva ##{reservation.id} na unidade #{reservation.captain_unit.name} está garantida.", + message_type: :outgoing + } + ).perform + rescue StandardError => e + Rails.logger.error "Failed to notify chat: #{e.message}" + end +end diff --git a/app/javascript/captain_booking/App.vue b/app/javascript/captain_booking/App.vue new file mode 100644 index 0000000..f303daf --- /dev/null +++ b/app/javascript/captain_booking/App.vue @@ -0,0 +1,945 @@ + + +