diff --git a/app/controllers/waste_carriers_engine/govpay_forms_controller.rb b/app/controllers/waste_carriers_engine/govpay_forms_controller.rb new file mode 100644 index 000000000..53d63a151 --- /dev/null +++ b/app/controllers/waste_carriers_engine/govpay_forms_controller.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module WasteCarriersEngine + class GovpayFormsController < ::WasteCarriersEngine::FormsController + include UnsubmittableForm + + def new + super(GovpayForm, "govpay_form") + + payment_info = prepare_for_payment + + if payment_info == :error + flash[:error] = I18n.t(".waste_carriers_engine.govpay_forms.new.setup_error") + go_back + else + redirect_to payment_info[:url] + end + end + + def payment_callback + find_or_initialize_transient_registration(params[:token]) + + if @transient_registration.finance_details.orders.first.govpay_status == "success" + Rails.logger.warn "Attempt to pay for an order with govpay_status already set to success" + respond_to_acceptable_payment(:success) + + else + @transient_registration.with_lock do + payment_status = GovpayCallbackService.new(params[:uuid]).run + + case payment_status + when :success, :pending + respond_to_acceptable_payment(payment_status) + else + respond_to_unsuccessful_payment(payment_status) + end + end + end + rescue ArgumentError + Rails.logger.warn "Govpay payment callback error: invalid payment uuid \"#{params[:uuid]}\"" + Airbrake.notify("Govpay callback error", "Invalid payment uuid \"#{params[:uuid]}\"") + flash[:error] = I18n.t(".waste_carriers_engine.govpay_forms.new.internal_error") + go_back + end + + private + + def prepare_for_payment + @transient_registration.prepare_for_payment(:govpay, current_user) + order = @transient_registration.finance_details.orders.first + govpay_service = GovpayPaymentService.new(@transient_registration, order, current_user) + govpay_service.prepare_for_payment + end + + def respond_to_acceptable_payment(action) + return unless valid_transient_registration? + + if action != :error + log_and_send_govpay_response(true, action) + @transient_registration.next! + redirect_to_correct_form + else + log_and_send_govpay_response(false, action) + flash[:error] = I18n.t(".waste_carriers_engine.govpay_forms.#{action}.invalid_response") + go_back + end + end + + def respond_to_unsuccessful_payment(action) + return unless valid_transient_registration? + + if action != :error + log_and_send_govpay_response(true, action) + flash[:error] = I18n.t(".waste_carriers_engine.govpay_forms.#{action}.message") + else + log_and_send_govpay_response(false, action) + flash[:error] = I18n.t(".waste_carriers_engine.govpay_forms.#{action}.invalid_response") + end + + go_back + end + + def valid_transient_registration? + setup_checks_pass? + end + + def log_and_send_govpay_response(is_valid, action) + valid_text = is_valid ? "Valid" : "Invalid" + title = "#{valid_text} Govpay response: #{action}" + + Rails.logger.debug [title, "Params:", params.to_json].join("\n") + Airbrake.notify(title, error_message: params) unless is_valid && action == :success + end + end +end diff --git a/app/controllers/waste_carriers_engine/registration_received_pending_govpay_payment_forms_controller.rb b/app/controllers/waste_carriers_engine/registration_received_pending_govpay_payment_forms_controller.rb new file mode 100644 index 000000000..2ef262e2a --- /dev/null +++ b/app/controllers/waste_carriers_engine/registration_received_pending_govpay_payment_forms_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module WasteCarriersEngine + class RegistrationReceivedPendingGovpayPaymentFormsController < ::WasteCarriersEngine::FormsController + include UnsubmittableForm + include CannotGoBackForm + + def new + return unless super( + RegistrationReceivedPendingGovpayPaymentForm, + "registration_received_pending_govpay_payment_form" + ) + + begin + @registration = RegistrationCompletionService.run(@transient_registration) + rescue StandardError => e + Airbrake.notify(e, reg_identifier: @transient_registration.reg_identifier) + Rails.logger.error e + end + end + end +end diff --git a/app/controllers/waste_carriers_engine/renewal_received_pending_govpay_payment_forms_controller.rb b/app/controllers/waste_carriers_engine/renewal_received_pending_govpay_payment_forms_controller.rb new file mode 100644 index 000000000..a51e838c4 --- /dev/null +++ b/app/controllers/waste_carriers_engine/renewal_received_pending_govpay_payment_forms_controller.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module WasteCarriersEngine + class RenewalReceivedPendingGovpayPaymentFormsController < ::WasteCarriersEngine::FormsController + include CannotGoBackForm + include UnsubmittableForm + + def new + super(RenewalReceivedPendingGovpayPaymentForm, "renewal_received_pending_govpay_payment_form") + end + end +end diff --git a/app/forms/waste_carriers_engine/govpay_form.rb b/app/forms/waste_carriers_engine/govpay_form.rb new file mode 100644 index 000000000..2ca201320 --- /dev/null +++ b/app/forms/waste_carriers_engine/govpay_form.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module WasteCarriersEngine + class GovpayForm < ::WasteCarriersEngine::BaseForm + def self.can_navigate_flexibly? + false + end + end +end diff --git a/app/forms/waste_carriers_engine/registration_received_pending_govpay_payment_form.rb b/app/forms/waste_carriers_engine/registration_received_pending_govpay_payment_form.rb new file mode 100644 index 000000000..5fb6e1f09 --- /dev/null +++ b/app/forms/waste_carriers_engine/registration_received_pending_govpay_payment_form.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module WasteCarriersEngine + class RegistrationReceivedPendingGovpayPaymentForm < ::WasteCarriersEngine::BaseForm + include CannotSubmit + + def self.can_navigate_flexibly? + false + end + end +end diff --git a/app/forms/waste_carriers_engine/renewal_received_pending_govpay_payment_form.rb b/app/forms/waste_carriers_engine/renewal_received_pending_govpay_payment_form.rb new file mode 100644 index 000000000..90c0f3786 --- /dev/null +++ b/app/forms/waste_carriers_engine/renewal_received_pending_govpay_payment_form.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module WasteCarriersEngine + class RenewalReceivedPendingGovpayPaymentForm < ::WasteCarriersEngine::BaseForm + include CannotSubmit + + def self.can_navigate_flexibly? + false + end + end +end diff --git a/app/models/concerns/waste_carriers_engine/can_have_registration_attributes.rb b/app/models/concerns/waste_carriers_engine/can_have_registration_attributes.rb index 7d0726213..4060649c0 100644 --- a/app/models/concerns/waste_carriers_engine/can_have_registration_attributes.rb +++ b/app/models/concerns/waste_carriers_engine/can_have_registration_attributes.rb @@ -113,12 +113,16 @@ def ad_contact_email? contact_email.blank? || contact_email == WasteCarriersEngine.configuration.assisted_digital_email end - def pending_worldpay_payment? + def pending_online_payment? return false unless finance_details.present? && finance_details.orders.present? && finance_details.orders.first.present? - WorldpayValidatorService.valid_world_pay_status?(:pending, finance_details.orders.first.world_pay_status) + if WasteCarriersEngine::FeatureToggle.active?(:govpay_payments) + GovpayValidatorService.valid_govpay_status?(:pending, finance_details.orders.first.govpay_status) + else + WorldpayValidatorService.valid_world_pay_status?(:pending, finance_details.orders.first.world_pay_status) + end end # Some business types should not have a company_no diff --git a/app/models/concerns/waste_carriers_engine/can_use_new_registration_workflow.rb b/app/models/concerns/waste_carriers_engine/can_use_new_registration_workflow.rb index 9ae07a4ed..8f346226a 100644 --- a/app/models/concerns/waste_carriers_engine/can_use_new_registration_workflow.rb +++ b/app/models/concerns/waste_carriers_engine/can_use_new_registration_workflow.rb @@ -66,12 +66,14 @@ module CanUseNewRegistrationWorkflow state :cards_form state :payment_summary_form state :worldpay_form + state :govpay_form state :confirm_bank_transfer_form state :registration_completed_form state :registration_received_pending_payment_form state :registration_received_pending_conviction_form state :registration_received_pending_worldpay_payment_form + state :registration_received_pending_govpay_payment_form # Transitions event :next do @@ -264,6 +266,9 @@ module CanUseNewRegistrationWorkflow # Payment & Completion transitions from: :cards_form, to: :payment_summary_form + transitions from: :payment_summary_form, to: :govpay_form, + if: :paying_by_card_govpay? + transitions from: :payment_summary_form, to: :worldpay_form, if: :paying_by_card? @@ -274,13 +279,15 @@ module CanUseNewRegistrationWorkflow # callback block, hence we went for `after` after: :set_metadata_route - transitions from: :worldpay_form, to: :registration_received_pending_worldpay_payment_form, - if: :pending_worldpay_payment?, + transitions from: :worldpay_form, + to: :registration_received_pending_worldpay_payment_form, + if: :pending_online_payment?, # TODO: This don't get triggered if in the `success` # callback block, hence we went for `after` after: :set_metadata_route - transitions from: :worldpay_form, to: :registration_received_pending_conviction_form, + transitions from: :worldpay_form, + to: :registration_received_pending_conviction_form, if: :conviction_check_required?, # TODO: This don't get triggered if in the `success` # callback block, hence we went for `after` @@ -290,6 +297,19 @@ module CanUseNewRegistrationWorkflow # TODO: This don't get triggered if in the `success` # callback block, hence we went for `after` after: :set_metadata_route + + transitions from: :govpay_form, + to: :registration_received_pending_govpay_payment_form, + if: :pending_online_payment?, + after: :set_metadata_route + + transitions from: :govpay_form, + to: :registration_received_pending_conviction_form, + if: :conviction_check_required?, + after: :set_metadata_route + + transitions from: :govpay_form, to: :registration_completed_form, + after: :set_metadata_route end # Transitions @@ -366,6 +386,10 @@ def paying_by_card? temp_payment_method == "card" end + def paying_by_card_govpay? + WasteCarriersEngine::FeatureToggle.active?(:govpay_payments) && temp_payment_method == "card" + end + def switch_to_lower_tier update_attributes(tier: WasteCarriersEngine::NewRegistration::LOWER_TIER) end diff --git a/app/models/concerns/waste_carriers_engine/can_use_renewing_registration_workflow.rb b/app/models/concerns/waste_carriers_engine/can_use_renewing_registration_workflow.rb index 3d64c2967..60640b830 100644 --- a/app/models/concerns/waste_carriers_engine/can_use_renewing_registration_workflow.rb +++ b/app/models/concerns/waste_carriers_engine/can_use_renewing_registration_workflow.rb @@ -52,12 +52,14 @@ module CanUseRenewingRegistrationWorkflow state :cards_form state :payment_summary_form state :worldpay_form + state :govpay_form state :confirm_bank_transfer_form state :renewal_complete_form state :renewal_received_pending_conviction_form state :renewal_received_pending_payment_form state :renewal_received_pending_worldpay_payment_form + state :renewal_received_pending_govpay_payment_form state :cannot_renew_type_change_form @@ -193,14 +195,17 @@ module CanUseRenewingRegistrationWorkflow # Payment & completion transitions from: :cards_form, to: :payment_summary_form + transitions from: :payment_summary_form, to: :govpay_form, + if: :paying_by_card_govpay? + transitions from: :payment_summary_form, to: :worldpay_form, if: :paying_by_card? transitions from: :payment_summary_form, to: :confirm_bank_transfer_form transitions from: :worldpay_form, to: :renewal_received_pending_worldpay_payment_form, - if: :pending_worldpay_payment?, - success: :send_renewal_pending_worldpay_payment_email, + if: :pending_online_payment?, + success: :send_renewal_pending_online_payment_email, # TODO: This don't get triggered if in the `success` # callback block, hence we went for `after` after: :set_metadata_route @@ -217,6 +222,19 @@ module CanUseRenewingRegistrationWorkflow # callback block, hence we went for `after` after: :set_metadata_route + transitions from: :govpay_form, to: :renewal_received_pending_govpay_payment_form, + if: :pending_online_payment?, + success: :send_renewal_pending_online_payment_email, + after: :set_metadata_route + + transitions from: :govpay_form, to: :renewal_received_pending_conviction_form, + if: :conviction_check_required?, + success: :send_renewal_pending_checks_email, + after: :set_metadata_route + + transitions from: :govpay_form, to: :renewal_complete_form, + after: :set_metadata_route + transitions from: :confirm_bank_transfer_form, to: :renewal_received_pending_payment_form, success: :send_renewal_received_pending_payment_email, # TODO: This don't get triggered if in the `success` @@ -279,6 +297,10 @@ def paying_by_card? temp_payment_method == "card" end + def paying_by_card_govpay? + WasteCarriersEngine::FeatureToggle.active?(:govpay_payments) && temp_payment_method == "card" + end + def use_trading_name? temp_use_trading_name == "yes" end @@ -287,8 +309,8 @@ def incorrect_company_data? temp_use_registered_company_details == "no" end - def send_renewal_pending_worldpay_payment_email - WasteCarriersEngine::Notify::RenewalPendingWorldpayPaymentEmailService.run(registration: self) + def send_renewal_pending_online_payment_email + WasteCarriersEngine::Notify::RenewalPendingOnlinePaymentEmailService.run(registration: self) rescue StandardError => e Airbrake.notify(e, registration_no: reg_identifier) if defined?(Airbrake) end diff --git a/app/models/waste_carriers_engine/finance_details.rb b/app/models/waste_carriers_engine/finance_details.rb index ac14730ae..06b461753 100644 --- a/app/models/waste_carriers_engine/finance_details.rb +++ b/app/models/waste_carriers_engine/finance_details.rb @@ -14,8 +14,7 @@ class FinanceDetails field :balance, type: Integer - validates :balance, - presence: true + validates :balance, presence: true def self.new_renewal_finance_details(transient_registration, method, current_user) user_email = current_user&.email || transient_registration.contact_email @@ -43,9 +42,7 @@ def zero_difference_balance def update_balance order_balance = orders.sum { |item| item[:total_amount] } - # Select payments where the type is not WORLDPAY, or if it is, the status is AUTHORISED - payment_balance = payments.any_of({ :payment_type.ne => "WORLDPAY" }, - world_pay_payment_status: "AUTHORISED").sum { |item| item[:amount] } + payment_balance = payments.except_online_not_authorised.sum { |item| item[:amount] } self.balance = order_balance - payment_balance end end diff --git a/app/models/waste_carriers_engine/order.rb b/app/models/waste_carriers_engine/order.rb index bb5774da6..0b1e83499 100644 --- a/app/models/waste_carriers_engine/order.rb +++ b/app/models/waste_carriers_engine/order.rb @@ -13,10 +13,13 @@ class Order field :orderCode, as: :order_code, type: String field :paymentMethod, as: :payment_method, type: String field :merchantId, as: :merchant_id, type: String + field :payment_uuid, type: String field :totalAmount, as: :total_amount, type: Integer field :currency, type: String field :dateCreated, as: :date_created, type: DateTime field :worldPayStatus, as: :world_pay_status, type: String + field :govpayId, as: :govpay_id, type: String + field :govpayStatus, as: :govpay_status, type: String field :dateLastUpdated, as: :date_last_updated, type: DateTime field :updatedByUser, as: :updated_by_user, type: String field :description, type: String @@ -78,12 +81,24 @@ def set_description self.description = generate_description end - def update_after_worldpay(status) - self.world_pay_status = status + def update_after_online_payment(status) + if WasteCarriersEngine::FeatureToggle.active?(:govpay_payments) + Rails.logger.debug "Updating order after online payment, status: #{status}" + self.govpay_status = status + else + self.world_pay_status = status + end self.date_last_updated = Time.current save! end + # Generate a uuid for the payment associated with this order, on demand + def payment_uuid + update_attributes!(payment_uuid: SecureRandom.uuid) unless self[:payment_uuid] + + self[:payment_uuid] + end + private def generate_description diff --git a/app/models/waste_carriers_engine/payment.rb b/app/models/waste_carriers_engine/payment.rb index 86455c8c3..c4c68450e 100644 --- a/app/models/waste_carriers_engine/payment.rb +++ b/app/models/waste_carriers_engine/payment.rb @@ -11,6 +11,8 @@ class Payment field :amount, type: Integer field :currency, type: String, default: "GBP" field :mac_code, type: String + field :uuid, type: String + field :govpay_id, type: String field :dateReceived, as: :date_received, type: Date field :dateEntered, as: :date_entered, type: DateTime field :dateReceived_year, as: :date_received_year, type: Integer @@ -18,28 +20,45 @@ class Payment field :dateReceived_day, as: :date_received_day, type: Integer field :registrationReference, as: :registration_reference, type: String field :worldPayPaymentStatus, as: :world_pay_payment_status, type: String + field :govpayPaymentStatus, as: :govpay_payment_status, type: String field :updatedByUser, as: :updated_by_user, type: String field :comment, type: String scope :refundable, -> { where(payment_type: { "$in" => RECEIVABLE_PAYMENT_TYPES }) } scope :reversible, -> { where(payment_type: { "$in" => RECEIVABLE_PAYMENT_TYPES }) } - def self.new_from_worldpay(order, user_email) + # Select payments where the type is not one of the online ones, or if it is, the status is AUTHORISED + scope :except_online_not_authorised, lambda { + where({ "$or" => [ + { payment_type: { "$nin" => %w[WORLDPAY GOVPAY] } }, + { world_pay_payment_status: "AUTHORISED" }, + { govpay_payment_status: "success" } + ] }) + } + + def self.new_from_online_payment(order, user_email) payment = Payment.new payment[:order_key] = order[:order_code] payment[:amount] = order[:total_amount] payment[:currency] = "GBP" - payment[:payment_type] = "WORLDPAY" - payment[:registration_reference] = "Worldpay" - payment[:comment] = "Paid via Worldpay" payment[:updated_by_user] = user_email payment.finance_details = order.finance_details - + if WasteCarriersEngine::FeatureToggle.active?(:govpay_payments) + payment[:payment_type] = "GOVPAY" + payment[:registration_reference] = "Govpay" + payment[:comment] = "Paid via Govpay" + payment[:uuid] = order.payment_uuid + payment[:govpay_id] = order.govpay_id + else + payment[:payment_type] = "WORLDPAY" + payment[:registration_reference] = "Worldpay" + payment[:comment] = "Paid via Worldpay" + end payment end - def self.new_from_non_worldpay(params, order) + def self.new_from_non_online_payment(params, order) payment = Payment.new payment[:amount] = params[:amount] @@ -60,9 +79,14 @@ def self.new_from_non_worldpay(params, order) payment end - def update_after_worldpay(params) - self.world_pay_payment_status = params[:paymentStatus] - self.mac_code = params[:mac] + def update_after_online_payment(params) + if WasteCarriersEngine::FeatureToggle.active?(:govpay_payments) + Rails.logger.debug "Updating payment after online payment, params: #{params}" + self.govpay_payment_status = params[:govpay_status] + else + self.world_pay_payment_status = params[:paymentStatus] + self.mac_code = params[:mac] + end self.date_received = Date.current self.date_entered = date_received diff --git a/app/services/concerns/waste_carriers_engine/can_send_govpay_request.rb b/app/services/concerns/waste_carriers_engine/can_send_govpay_request.rb new file mode 100644 index 000000000..b75dd7d36 --- /dev/null +++ b/app/services/concerns/waste_carriers_engine/can_send_govpay_request.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module WasteCarriersEngine + module CanSendGovpayRequest + extend ActiveSupport::Concern + + # rubocop:disable Metrics/BlockLength + included do + private + + def send_request(method, path, params = nil) + Rails.logger.debug "Sending request to Govpay, #{method}, path: #{path}, params: #{params}" + + response = nil + begin + response = RestClient::Request.execute( + method: method, + url: url(path), + payload: params.present? ? params.to_json : nil, + headers: { + "Authorization" => "Bearer #{bearer_token}", + "Content-Type" => "application/json" + } + ) + + Rails.logger.debug "Received response from Govpay: #{response}" + rescue StandardError => e + Rails.logger.error("Error sending request to govpay: #{e}") + Airbrake.notify(e, message: "Error on govpay request") + end + + response + end + + def url(path) + "#{Rails.configuration.govpay_url}#{path}" + end + + def bearer_token + @bearer_token ||= Rails.configuration.govpay_api_token + end + end + # rubocop:enable Metrics/BlockLength + end +end diff --git a/app/services/waste_carriers_engine/govpay_callback_service.rb b/app/services/waste_carriers_engine/govpay_callback_service.rb new file mode 100644 index 000000000..6ee4ef1c7 --- /dev/null +++ b/app/services/waste_carriers_engine/govpay_callback_service.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "rest-client" + +module WasteCarriersEngine + class GovpayCallbackService + + def initialize(payment_uuid) + @payment_uuid = payment_uuid + @transient_registration = transient_registration_by_payment_uuid + @order = order_by_payment_uuid + end + + def run + @govpay_payment_status = GovpayPaymentDetailsService.new(@payment_uuid).govpay_payment_status + + @response_type = GovpayPaymentDetailsService.response_type(@govpay_payment_status) + return :error unless govpay_response_validator(@govpay_payment_status).send("valid_#{@response_type}?".to_sym) + + case @response_type + when :success, :pending + update_payment_data + else + unsuccessful_payment + end + + @response_type + end + + private + + def transient_registration_by_payment_uuid + reg = TransientRegistration.find_by("financeDetails.orders.payment_uuid": @payment_uuid) + raise ArgumentError, "Transient registration not found for payment uuid #{@payment_uuid}" unless reg + + reg + end + + def order_by_payment_uuid + order = @transient_registration.finance_details.orders.find_by(payment_uuid: @payment_uuid) + raise ArgumentError, "Order not found for payment uuid #{@payment_uuid}" unless order + + order + end + + def unsuccessful_payment + @order.update_after_online_payment(@response_type) + end + + def update_payment_data + @order.update_after_online_payment(@response_type) + payment = Payment.new_from_online_payment(@order, user_email) + payment.update_after_online_payment(govpay_status: @response_type) + + @transient_registration.finance_details.update_balance + @transient_registration.finance_details.save! + end + + def govpay_response_validator(govpay_status) + GovpayValidatorService.new(@order, @payment_uuid, govpay_status) + end + + def user_email + @current_user&.email || @transient_registration.contact_email + end + end +end diff --git a/app/services/waste_carriers_engine/govpay_payment_details_service.rb b/app/services/waste_carriers_engine/govpay_payment_details_service.rb new file mode 100644 index 000000000..e8c17ecdc --- /dev/null +++ b/app/services/waste_carriers_engine/govpay_payment_details_service.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "rest-client" + +module WasteCarriersEngine + class GovpayPaymentDetailsService + include CanSendGovpayRequest + + def initialize(payment_uuid) + # Because orders are embedded in finance_details, we can't search directly on orders so we need to: + # 1. find the transient_registration which contains the order with this payment_uuid + # 2. within that transient_registration, find which order has that payment_uuid + transient_registration = TransientRegistration.find_by("financeDetails.orders.payment_uuid": payment_uuid) + @order = transient_registration.finance_details.orders.find_by(payment_uuid: payment_uuid) + rescue StandardError + raise ArgumentError, "Order not found for payment uuid \"#{payment_uuid}\"" + end + + # Payment status in Govpay terms + def govpay_payment_status + response = send_request(:get, "/payments/#{@order.govpay_id}") + response_json = JSON.parse(response.body) + + status = response_json&.dig("state", "status") || "error" + + # Special case: If failed, check whether this was because of a cancellation + status = "cancelled" if status == "failed" && response_json.dig("state", "code") == "P0030" + + status + rescue StandardError => e + Rails.logger.error "Failed to retrieve payment status: #{e}" + "error" + end + + # Payment status in application terms + def self.response_type(status) + { + "created" => :pending, + "started" => :pending, + "submitted" => :pending, + "cancelled" => :cancel, + "failed" => :failure, + nil => :error + }.freeze[status] || status.to_sym + end + end +end diff --git a/app/services/waste_carriers_engine/govpay_payment_service.rb b/app/services/waste_carriers_engine/govpay_payment_service.rb new file mode 100644 index 000000000..e2acdc723 --- /dev/null +++ b/app/services/waste_carriers_engine/govpay_payment_service.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require "rest-client" + +module WasteCarriersEngine + class GovpayPaymentService + include CanSendGovpayRequest + + def initialize(transient_registration, order, current_user) + @transient_registration = transient_registration + @order = order + @current_user = current_user + end + + def prepare_for_payment + response = send_request(:post, "/payments", payment_params) + response_json = JSON.parse(response.body) + + govpay_payment_id = response_json["payment_id"] + if govpay_payment_id.present? + @order.govpay_id = govpay_payment_id + @order.save! + { + payment: nil, # @payment, + url: govpay_redirect_url(response) + } + else + :error + end + end + + def payment_callback_url + host = Rails.configuration.host + path = WasteCarriersEngine::Engine.routes.url_helpers.payment_callback_govpay_forms_path( + token: @transient_registration.token, uuid: @order.payment_uuid + ) + + [host, path].join + end + + def govpay_redirect_url(response) + JSON.parse(response.body).dig("_links", "next_url", "href") + end + + private + + def payment_params + { + amount: @order.total_amount, + return_url: payment_callback_url, + reference: @order.order_code, + description: "Your Waste Carrier Registration #{@transient_registration.reg_identifier}" + } + end + end +end diff --git a/app/services/waste_carriers_engine/govpay_validator_service.rb b/app/services/waste_carriers_engine/govpay_validator_service.rb new file mode 100644 index 000000000..6bd920433 --- /dev/null +++ b/app/services/waste_carriers_engine/govpay_validator_service.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module WasteCarriersEngine + # Validates requests to record a successful or failed Govpay payment. + + # This should happen after a user attempts to make a payment on the Govpay site. They are then redirected to + # a callback route which checks the payment status and decides how to route them onwards. This service is to + # validate the payment status before we record that a payment has been made (or failed) and allow the user + # to continue. + class GovpayValidatorService + def initialize(order, payment_uuid, govpay_status) + @order = order + @payment_uuid = payment_uuid + @govpay_status = govpay_status + end + + def valid_success? + valid?(:success) + end + + def valid_failure? + valid?(:failure) + end + + def valid_pending? + valid?(:pending) + end + + def valid_cancel? + valid?(:cancel) + end + + def valid_error? + valid?(:error) + end + + def self.valid_govpay_status?(response_type, status) + @allowed_statuses ||= { + success: %w[success], + failure: %w[failed], + pending: %w[created started submitted], + cancel: %w[cancelled], + error: %w[error] + }.freeze + @allowed_statuses[response_type].include?(status) + end + + private + + def valid?(action) + valid_order? && valid_payment_uuid? && valid_status?(action) + end + + def valid_order? + return true if @order.present? + + Rails.logger.error "Invalid Govpay response: Cannot find matching order" + false + end + + def valid_payment_uuid? + unless @payment_uuid.present? + Rails.logger.error "Invalid Govpay response: Missing payment uuid" + return false + end + + transient_registration = TransientRegistration.find_by("financeDetails.orders.payment_uuid": @payment_uuid) + unless transient_registration.nil? + order = transient_registration.finance_details.orders.find_by(payment_uuid: @payment_uuid) + end + + return true if order.present? + + Rails.logger.error "Invalid Govpay response: Cannot find matching order for payment uuid #{@payment_uuid}" + false + end + + def valid_status?(response_type) + return true if GovpayValidatorService.valid_govpay_status?(response_type, @govpay_status) + + Rails.logger.error "Invalid Govpay response: #{@status} is not a valid payment status for #{response_type}" + false + end + end +end diff --git a/app/services/waste_carriers_engine/notify/registration_pending_worldpay_payment_email_service.rb b/app/services/waste_carriers_engine/notify/registration_pending_online_payment_email_service.rb similarity index 85% rename from app/services/waste_carriers_engine/notify/registration_pending_worldpay_payment_email_service.rb rename to app/services/waste_carriers_engine/notify/registration_pending_online_payment_email_service.rb index 748e1b55b..52b9f79d1 100644 --- a/app/services/waste_carriers_engine/notify/registration_pending_worldpay_payment_email_service.rb +++ b/app/services/waste_carriers_engine/notify/registration_pending_online_payment_email_service.rb @@ -2,7 +2,7 @@ module WasteCarriersEngine module Notify - class RegistrationPendingWorldpayPaymentEmailService < BaseSendEmailService + class RegistrationPendingOnlinePaymentEmailService < BaseSendEmailService private def notify_options diff --git a/app/services/waste_carriers_engine/notify/renewal_pending_worldpay_payment_email_service.rb b/app/services/waste_carriers_engine/notify/renewal_pending_online_payment_email_service.rb similarity index 85% rename from app/services/waste_carriers_engine/notify/renewal_pending_worldpay_payment_email_service.rb rename to app/services/waste_carriers_engine/notify/renewal_pending_online_payment_email_service.rb index 9b0d7c419..b883f80c0 100644 --- a/app/services/waste_carriers_engine/notify/renewal_pending_worldpay_payment_email_service.rb +++ b/app/services/waste_carriers_engine/notify/renewal_pending_online_payment_email_service.rb @@ -2,7 +2,7 @@ module WasteCarriersEngine module Notify - class RenewalPendingWorldpayPaymentEmailService < BaseSendEmailService + class RenewalPendingOnlinePaymentEmailService < BaseSendEmailService private def notify_options diff --git a/app/services/waste_carriers_engine/registration_completion_service.rb b/app/services/waste_carriers_engine/registration_completion_service.rb index ecdb6dc7e..a3e6f8e61 100644 --- a/app/services/waste_carriers_engine/registration_completion_service.rb +++ b/app/services/waste_carriers_engine/registration_completion_service.rb @@ -5,6 +5,7 @@ module WasteCarriersEngine class RegistrationCompletionService < BaseService attr_reader :transient_registration + # rubocop:disable Metrics/MethodLength def run(transient_registration) @transient_registration = transient_registration @@ -27,6 +28,7 @@ def run(transient_registration) begin RegistrationActivationService.run(registration: registration) + Rails.logger.info "Completed registration #{@transient_registration.reg_identifier}" rescue StandardError => e Airbrake.notify(e, reg_identifier: @transient_registration.reg_identifier) Rails.logger.error e @@ -35,6 +37,7 @@ def run(transient_registration) registration end + # rubocop:enable Metrics/MethodLength private @@ -78,8 +81,8 @@ def delete_transient_registration # In the case when the registration can be completed, the registration activation email is sent from # the RegistrationActivationService. def send_confirmation_email - if registration.pending_worldpay_payment? - send_worldpay_pending_payment_email + if registration.pending_online_payment? + send_online_pending_payment_email elsif registration.unpaid_balance? send_pending_payment_email elsif registration.conviction_check_required? @@ -93,8 +96,8 @@ def send_pending_payment_email Notify::RegistrationPendingPaymentEmailService.run(registration: registration) end - def send_worldpay_pending_payment_email - Notify::RegistrationPendingWorldpayPaymentEmailService.run(registration: registration) + def send_online_pending_payment_email + Notify::RegistrationPendingOnlinePaymentEmailService.run(registration: registration) end def send_pending_conviction_check_email diff --git a/app/services/waste_carriers_engine/worldpay_service.rb b/app/services/waste_carriers_engine/worldpay_service.rb index 8bc1fb5c6..10f363e85 100644 --- a/app/services/waste_carriers_engine/worldpay_service.rb +++ b/app/services/waste_carriers_engine/worldpay_service.rb @@ -62,7 +62,7 @@ def valid_unsuccessful_payment?(validation_method) worldpay_validator_service = WorldpayValidatorService.new(@order, @params) return false unless worldpay_validator_service.public_send(validation_method) - @order.update_after_worldpay(@params[:paymentStatus]) + @order.update_after_online_payment(@params[:paymentStatus]) true end @@ -93,13 +93,13 @@ def parse_response(response) end def new_payment_object(order) - Payment.new_from_worldpay(order, user_email) + Payment.new_from_online_payment(order, user_email) end def update_saved_data - payment = Payment.new_from_worldpay(@order, user_email) - payment.update_after_worldpay(@params) - @order.update_after_worldpay(@params[:paymentStatus]) + payment = Payment.new_from_online_payment(@order, user_email) + payment.update_after_online_payment(@params) + @order.update_after_online_payment(@params[:paymentStatus]) @transient_registration.finance_details.update_balance @transient_registration.finance_details.save! diff --git a/app/views/waste_carriers_engine/registration_received_pending_govpay_payment_forms/new.html.erb b/app/views/waste_carriers_engine/registration_received_pending_govpay_payment_forms/new.html.erb new file mode 100644 index 000000000..7aea13819 --- /dev/null +++ b/app/views/waste_carriers_engine/registration_received_pending_govpay_payment_forms/new.html.erb @@ -0,0 +1,35 @@ +
+
+ +
+

+ <%= t(".heading") %> +

+
+ <%= t(".highlight_text") %>
+ <%= @registration.reg_identifier %> +
+
+ +

<%= t(".paragraph_1", email: @registration.contact_email) %>

+ + <% unless WasteCarriersEngine.configuration.host_is_back_office? %> +

<%= t(".paragraph_2", email: @registration.email_to_send_receipt) %>

+ <% end %> + +

<%= t(".subheading_1") %>

+

<%= t(".paragraph_3") %>

+ +
+

<%= t(".paragraph_4") %>

+
+ + <%= render "shared/contact_environment_agency" %> + + <%= render "shared/registration_checks" %> + + <%= render "shared/registration_finished_button" %> + + <%= render "shared/link_to_survey" %> +
+
diff --git a/app/views/waste_carriers_engine/renewal_received_pending_govpay_payment_forms/new.html.erb b/app/views/waste_carriers_engine/renewal_received_pending_govpay_payment_forms/new.html.erb new file mode 100644 index 000000000..56a4ea0ee --- /dev/null +++ b/app/views/waste_carriers_engine/renewal_received_pending_govpay_payment_forms/new.html.erb @@ -0,0 +1,53 @@ +
+
+ <% if @renewal_received_pending_govpay_payment_form.errors.any? %> + +

<%= t(".error_heading") %>

+ + + + <% else %> + <%= form_for(@renewal_received_pending_govpay_payment_form) do |f| %> + <%= render partial: "waste_carriers_engine/shared/error_summary", locals: { f: f } %> + +
+

+ <%= t(".heading") %> +

+
+ <%= t(".highlight_text") %>
+ <%= @renewal_received_pending_govpay_payment_form.reg_identifier %> +
+
+ +

<%= t(".subheading") %>

+ +

<%= t(".paragraph_1", + email: @renewal_received_pending_govpay_payment_form.transient_registration.contact_email) %>

+ +

<%= t(".paragraph_2") %>

+ + <% unless WasteCarriersEngine.configuration.host_is_back_office? %> +

<%= t(".paragraph_3", email: @renewal_received_pending_govpay_payment_form.transient_registration.email_to_send_receipt) %>

+ <% end %> + +
+

<%= t(".paragraph_4") %>

+
+ + <%= render "shared/contact_environment_agency" %> + + <%= render "shared/registration_checks" %> + + <%= render "shared/link_to_survey" %> + + <%= render "shared/registration_finished_button" %> + <% end %> + + <% end %> +
+
diff --git a/config/locales/forms/registration_received_pending_govpay_payment_forms/en.yml b/config/locales/forms/registration_received_pending_govpay_payment_forms/en.yml new file mode 100644 index 000000000..3969ad8b2 --- /dev/null +++ b/config/locales/forms/registration_received_pending_govpay_payment_forms/en.yml @@ -0,0 +1,12 @@ +en: + waste_carriers_engine: + registration_received_pending_govpay_payment_forms: + new: + title: We're processing your payment + heading: We're processing your payment + highlight_text: "Please use this registration number if you contact us:" + paragraph_1: We've sent a confirmation email to %{email}. + paragraph_2: A payment confirmation will be sent to %{email}. + subheading_1: We have received your application + paragraph_3: We're currently processing your payment and will let you know when it has been received. + paragraph_4: You're not legally entitled to operate as a waste carrier until we have confirmed your registration. diff --git a/config/locales/forms/renewal_received_pending_govpay_payment_forms/en.yml b/config/locales/forms/renewal_received_pending_govpay_payment_forms/en.yml new file mode 100644 index 000000000..b4fd50f7f --- /dev/null +++ b/config/locales/forms/renewal_received_pending_govpay_payment_forms/en.yml @@ -0,0 +1,23 @@ +en: + waste_carriers_engine: + renewal_received_pending_govpay_payment_forms: + new: + title: Application received + heading: Application received + highlight_text: Your registration number is still + subheading: Processing your payment + paragraph_1: We have sent a confirmation email to %{email}. + paragraph_2: We are currently processing your payment and will let you know when it has been received. + paragraph_3: A payment confirmation will be sent to %{email}. + paragraph_4: Your registration will not be renewed until we have confirmed your registration. + error_heading: Something is wrong + next_button: Finished + activemodel: + errors: + models: + waste_carriers_engine/renewal_received_pending_worldpay_payment_form: + attributes: + reg_identifier: + invalid_format: The registration ID is not in a valid format + no_registration: There is no registration matching this ID + renewal_in_progress: This renewal is already in progress diff --git a/config/routes.rb b/config/routes.rb index ae3b59639..77833a440 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -44,6 +44,11 @@ path: "registration-received-pending-worldpay-payment", path_names: { new: "" } + resources :registration_received_pending_govpay_payment_forms, + only: :new, + path: "registration-received-pending-govpay-payment", + path_names: { new: "" } + resources :registration_completed_forms, only: :new, path: "registration-completed", @@ -416,6 +421,16 @@ on: :collection end + resources :govpay_forms, + only: %i[new create], + path: "govpay", + path_names: { new: "" } do + get "payment_callback/:uuid", + to: "govpay_forms#payment_callback", + as: "payment_callback", + on: :collection + end + resources :confirm_bank_transfer_forms, only: %i[new create], path: "confirm-bank-transfer", @@ -441,6 +456,11 @@ path: "renewal-received-pending-worldpay-payment", path_names: { new: "" } + resources :renewal_received_pending_govpay_payment_forms, + only: %i[new create], + path: "renewal-received-pending-govpay-payment", + path_names: { new: "" } + resources :cannot_renew_type_change_forms, only: %i[new create], path: "cannot-renew-type-change", diff --git a/spec/cassettes/notify_renewal_pending_worldpay_payment_sends_an_email.yml b/spec/cassettes/notify_renewal_pending_online_payment_sends_an_email.yml similarity index 52% rename from spec/cassettes/notify_renewal_pending_worldpay_payment_sends_an_email.yml rename to spec/cassettes/notify_renewal_pending_online_payment_sends_an_email.yml index 4e6cbbb7f..cdbe4a320 100644 --- a/spec/cassettes/notify_renewal_pending_worldpay_payment_sends_an_email.yml +++ b/spec/cassettes/notify_renewal_pending_online_payment_sends_an_email.yml @@ -5,7 +5,7 @@ http_interactions: uri: https://api.notifications.service.gov.uk/v2/notifications/email body: encoding: UTF-8 - string: '{"email_address":"foo@example.com","template_id":"3da098e3-3db2-4c99-8e96-ed9d1a8ef227","personalisation":{"reg_identifier":"CBDU2","registration_type":"carrier, + string: '{"email_address":"foo@example.com","template_id":"3da098e3-3db2-4c99-8e96-ed9d1a8ef227","personalisation":{"reg_identifier":"CBDU1","registration_type":"carrier, broker and dealer"}}' headers: User-Agent: @@ -23,41 +23,48 @@ http_interactions: code: 201 message: Created headers: + Content-Type: + - application/json + Content-Length: + - '1011' + Connection: + - keep-alive + Date: + - Tue, 17 May 2022 16:12:38 GMT Access-Control-Allow-Headers: - Content-Type,Authorization Access-Control-Allow-Methods: - GET,PUT,POST,DELETE Access-Control-Allow-Origin: - "*" - Content-Type: - - application/json - Date: - - Fri, 30 Apr 2021 09:50:52 GMT Server: - - nginx + - gunicorn Strict-Transport-Security: - - max-age=31536000; includeSubdomains + - max-age=31536000; includeSubDomains; preload X-B3-Spanid: - - c465aec99043f401 + - 30d987ef7f19714d X-B3-Traceid: - - c465aec99043f401 + - 390284b76f3e756830d987ef7f19714d X-Vcap-Request-Id: - - 0f5e0b56-7790-4565-5e31-51fe31ea6e77 - Content-Length: - - '1050' - Connection: - - keep-alive + - f048d391-2715-432b-5b5f-f5c9668459f3 + X-Cache: + - Miss from cloudfront + Via: + - 1.1 57f9250ef620b33bc5b87625f8d36f5e.cloudfront.net (CloudFront) + X-Amz-Cf-Pop: + - LHR62-C3 + X-Amz-Cf-Id: + - QLyOQdLghQ3wFGgev4XfSWwjfye2BHTAufKPiFfZtRRUmWrXKVJKaw== body: encoding: UTF-8 string: '{"content":{"body":"#Your application to renew your waste carrier, broker and dealer registration has been received\r\n\r\nYour registration - number is still\r\nCBDU2 \r\n\r\n#What happens next\r\n\r\nWe''ll check your - details and let you know whether your renewal application has been successful. - We aim to do this within 10 working days.\r\n\r\n*If you have enquiries please - contact the Environment Agency helpline: 03708 506506\r\n*This is an automated - email, please do not reply","from_email":"waste.carriers.registration.service@notifications.service.gov.uk","subject":"Your - application to renew waste carriers registration CBDU2 has been received"},"id":"1513cc3d-9776-4bf6-9e22-463e3aa0a82c","reference":null,"scheduled_for":null,"template":{"id":"3da098e3-3db2-4c99-8e96-ed9d1a8ef227","uri":"https://api.notifications.service.gov.uk/services/25cb6b94-8ce7-485b-918a-559f3b18f69c/templates/3da098e3-3db2-4c99-8e96-ed9d1a8ef227","version":5},"uri":"https://api.notifications.service.gov.uk/v2/notifications/1513cc3d-9776-4bf6-9e22-463e3aa0a82c"} + number is still\r\nCBDU1 \r\n\r\nWe are currently processing your payment. + We''ll email you to let you know when your renewal application has been successful.\r\n\r\n*If + you have enquiries please contact the Environment Agency helpline: 03708 506506\r\n*This + is an automated email, please do not reply","from_email":"waste.carriers.registration.service@notifications.service.gov.uk","subject":"Your + application to renew waste carriers registration CBDU1 has been received"},"id":"5511c9ae-0ae6-4c0b-bc36-e72c8d48430d","reference":null,"scheduled_for":null,"template":{"id":"3da098e3-3db2-4c99-8e96-ed9d1a8ef227","uri":"https://api.notifications.service.gov.uk/services/25cb6b94-8ce7-485b-918a-559f3b18f69c/templates/3da098e3-3db2-4c99-8e96-ed9d1a8ef227","version":7},"uri":"https://api.notifications.service.gov.uk/v2/notifications/5511c9ae-0ae6-4c0b-bc36-e72c8d48430d"} ' - recorded_at: Fri, 30 Apr 2021 09:50:52 GMT -recorded_with: VCR 6.0.0 + recorded_at: Tue, 17 May 2022 16:12:37 GMT +recorded_with: VCR 6.1.0 diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb index d51ea7ce3..d3ed55c93 100644 --- a/spec/dummy/config/application.rb +++ b/spec/dummy/config/application.rb @@ -86,6 +86,11 @@ class Application < Rails::Application config.worldpay_password = ENV["WCRS_WORLDPAY_ECOM_PASSWORD"] config.worldpay_macsecret = ENV["WCRS_WORLDPAY_ECOM_MACSECRET"] + # Govpay + config.govpay_url = ENV["WCRS_GOVPAY_URL"] || "https://publicapi.payments.service.gov.uk" + config.govpay_merchant_code = ENV["WCRS_GOVPAY_MERCHANT_CODE"] + config.govpay_api_token = ENV["WCRS_GOVPAY_API_TOKEN"] + # Emails config.email_service_name = "Waste Carriers Registration Service" config.email_service_email = ENV["WCRS_EMAIL_SERVICE_EMAIL"] diff --git a/spec/factories/finance_details.rb b/spec/factories/finance_details.rb index 346c3d7a9..39df3caa0 100644 --- a/spec/factories/finance_details.rb +++ b/spec/factories/finance_details.rb @@ -14,6 +14,12 @@ orders { [build(:order, :has_pending_worldpay_status)] } end + trait :has_pending_govpay_order do + has_required_data + + orders { [build(:order, :has_pending_govpay_status)] } + end + trait :has_order do orders { [build(:order, :has_required_data)] } end diff --git a/spec/factories/new_registration.rb b/spec/factories/new_registration.rb index 38f11e118..0a1deb861 100644 --- a/spec/factories/new_registration.rb +++ b/spec/factories/new_registration.rb @@ -31,6 +31,10 @@ finance_details { build(:finance_details, :has_pending_worldpay_order) } end + trait :has_pending_govpay_status do + finance_details { build(:finance_details, :has_pending_govpay_order) } + end + trait :has_required_lower_tier_data do location { "england" } declared_convictions { "no" } diff --git a/spec/factories/order.rb b/spec/factories/order.rb index aa9b7d7f4..c9ee3ef5f 100644 --- a/spec/factories/order.rb +++ b/spec/factories/order.rb @@ -16,6 +16,12 @@ world_pay_status { "SENT_FOR_AUTHORISATION" } end + trait :has_pending_govpay_status do + has_required_data + + govpay_status { "created" } + end + trait :has_copy_cards_item do date_created { Time.now } diff --git a/spec/fixtures/files/govpay/create_payment_created_response.json b/spec/fixtures/files/govpay/create_payment_created_response.json new file mode 100644 index 000000000..63d3d4fab --- /dev/null +++ b/spec/fixtures/files/govpay/create_payment_created_response.json @@ -0,0 +1,24 @@ +{ + "created_date": "2020-03-03T16:17:19.554Z", + "state": { + "status": "created", + "finished": false + }, + "_links": { + "self": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/hu20sqlact5260q2nanm0q8u93", + "method": "GET" + }, + "next_url": { + "href": "https://www.payments.service.gov.uk/secure/bb0a272c-8eaf-468d-b3xf-ae5e000d2231", + "method": "GET" + } + }, + "amount": 14500, + "reference" : "12345", + "description": "Pay your council tax", + "return_url": "https://your.service.gov.uk/completed", + "payment_id": "hu20sqlact5260q2nanm0q8u93", + "payment_provider": "worldpay", + "provider_id": "10987654321" + } \ No newline at end of file diff --git a/spec/fixtures/files/govpay/create_payment_error_response.json b/spec/fixtures/files/govpay/create_payment_error_response.json new file mode 100644 index 000000000..3d2f0b67c --- /dev/null +++ b/spec/fixtures/files/govpay/create_payment_error_response.json @@ -0,0 +1,5 @@ +{ + "field": "amount", + "code": "P0101", + "description": "Missing mandatory attribute: amount" +} \ No newline at end of file diff --git a/spec/fixtures/files/govpay/get_payment_response_cancelled.json b/spec/fixtures/files/govpay/get_payment_response_cancelled.json new file mode 100644 index 000000000..c38bb5451 --- /dev/null +++ b/spec/fixtures/files/govpay/get_payment_response_cancelled.json @@ -0,0 +1,61 @@ +{ + "amount": 10501, + "description": "Waste carrier registration upper tier", + "reference": "12345", + "language": "en", + "metadata": { + "ledger_code": "AB100", + "an_internal_reference_number": 200 + }, + "email": "sherlock.holmes@example.com", + "state": { + "status": "failed", + "finished": true, + "message": "Payment was cancelled by the user", + "code": "P0030" + }, + "payment_id": "cjgt8i57kavb8jfufhjlh129fm", + "payment_provider": "sandbox", + "created_date": "2022-05-18T11:46:54.697Z", + "refund_summary": { + "status": "unavailable", + "amount_available": 10501, + "amount_submitted": 0 + }, + "settlement_summary": {}, + "card_details": { + "last_digits_card_number": "5100", + "first_digits_card_number": "510510", + "cardholder_name": "Sherlock Holmes", + "expiry_date": "01/24", + "billing_address": { + "line1": "221 Baker Street", + "line2": "Flat b", + "postcode": "NW1 6XE", + "city": "London", + "country": "GB" + }, + "card_brand": "Mastercard", + "card_type": "debit" + }, + "delayed_capture": false, + "moto": false, + "provider_id": "9fda0405-dc56-4d18-bf53-26167a91a1ac", + "return_url": "https://some-wcr-env.defra.gov.uk/completed", + "authorisation_mode": "web", + "card_brand": "Mastercard", + "_links": { + "self": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cjgt8i57kavb8jfufhjlh129fm", + "method": "GET" + }, + "events": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cjgt8i57kavb8jfufhjlh129fm/events", + "method": "GET" + }, + "refunds": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cjgt8i57kavb8jfufhjlh129fm/refunds", + "method": "GET" + } + } +} \ No newline at end of file diff --git a/spec/fixtures/files/govpay/get_payment_response_created.json b/spec/fixtures/files/govpay/get_payment_response_created.json new file mode 100644 index 000000000..db63489d1 --- /dev/null +++ b/spec/fixtures/files/govpay/get_payment_response_created.json @@ -0,0 +1,73 @@ +{ + "amount": 10501, + "description": "Waste carrier registration upper tier", + "reference": "12345", + "language": "en", + "metadata": { + "ledger_code": "AB100", + "an_internal_reference_number": 200 + }, + "email": "sherlock.holmes@example.com", + "state": { + "status": "created", + "finished": false + }, + "payment_id": "cjgt8i57kavb8jfufhjlh129fm", + "payment_provider": "sandbox", + "created_date": "2022-05-18T11:46:54.697Z", + "refund_summary": { + "status": "pending", + "amount_available": 10501, + "amount_submitted": 0 + }, + "settlement_summary": {}, + "card_details": { + "last_digits_card_number": null, + "first_digits_card_number": null, + "cardholder_name": "Sherlock Holmes", + "expiry_date": null, + "billing_address": { + "line1": "221 Baker Street", + "line2": "Flat b", + "postcode": "NW1 6XE", + "city": "London", + "country": "GB" + }, + "card_brand": "", + "card_type": null + }, + "delayed_capture": false, + "moto": false, + "return_url": "https://some-wcr-env.defra.gov.uk/completed", + "authorisation_mode": "web", + "_links": { + "self": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cjgt8i57kavb8jfufhjlh129fm", + "method": "GET" + }, + "next_url": { + "href": "https://www.payments.service.gov.uk/secure/c3b8908b-68df-478a-ad94-6a724e614e46", + "method": "GET" + }, + "next_url_post": { + "type": "application/x-www-form-urlencoded", + "params": { + "chargeTokenId": "c3b8908b-68df-478a-ad94-6a724e614e46" + }, + "href": "https://www.payments.service.gov.uk/secure", + "method": "POST" + }, + "events": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cjgt8i57kavb8jfufhjlh129fm/events", + "method": "GET" + }, + "refunds": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cjgt8i57kavb8jfufhjlh129fm/refunds", + "method": "GET" + }, + "cancel": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cjgt8i57kavb8jfufhjlh129fm/cancel", + "method": "POST" + } + } +} \ No newline at end of file diff --git a/spec/fixtures/files/govpay/get_payment_response_failure.json b/spec/fixtures/files/govpay/get_payment_response_failure.json new file mode 100644 index 000000000..adaf15077 --- /dev/null +++ b/spec/fixtures/files/govpay/get_payment_response_failure.json @@ -0,0 +1,9 @@ +{ + "amount": 2000, + "state": { + "status": "failed", + "finished": true, + "message": "Payment expired", + "code": "P0020" + } + } diff --git a/spec/fixtures/files/govpay/get_payment_response_not_found.json b/spec/fixtures/files/govpay/get_payment_response_not_found.json new file mode 100644 index 000000000..6cf279db3 --- /dev/null +++ b/spec/fixtures/files/govpay/get_payment_response_not_found.json @@ -0,0 +1,4 @@ +{ + "code": "P0200", + "description": "Not found" +} \ No newline at end of file diff --git a/spec/fixtures/files/govpay/get_payment_response_submitted.json b/spec/fixtures/files/govpay/get_payment_response_submitted.json new file mode 100644 index 000000000..1f5660fd9 --- /dev/null +++ b/spec/fixtures/files/govpay/get_payment_response_submitted.json @@ -0,0 +1,75 @@ +{ + "amount": 10501, + "description": "Waste carrier registration upper tier", + "reference": "12345", + "language": "en", + "metadata": { + "ledger_code": "AB100", + "an_internal_reference_number": 200 + }, + "email": "sherlock.holmes@example.com", + "state": { + "status": "submitted", + "finished": false + }, + "payment_id": "cjgt8i57kavb8jfufhjlh129fm", + "payment_provider": "sandbox", + "created_date": "2022-05-18T11:46:54.697Z", + "refund_summary": { + "status": "pending", + "amount_available": 10501, + "amount_submitted": 0 + }, + "settlement_summary": {}, + "card_details": { + "last_digits_card_number": "5100", + "first_digits_card_number": "510510", + "cardholder_name": "Sherlock Holmes", + "expiry_date": "01/24", + "billing_address": { + "line1": "221 Baker Street", + "line2": "Flat b", + "postcode": "NW1 6XE", + "city": "London", + "country": "GB" + }, + "card_brand": "Mastercard", + "card_type": "debit" + }, + "delayed_capture": false, + "moto": false, + "provider_id": "9fda0405-dc56-4d18-bf53-26167a91a1ac", + "return_url": "https://some-wcr-env.defra.gov.uk/completed", + "authorisation_mode": "web", + "card_brand": "Mastercard", + "_links": { + "self": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cjgt8i57kavb8jfufhjlh129fm", + "method": "GET" + }, + "next_url": { + "href": "https://www.payments.service.gov.uk/secure/18a144e1-f6d8-4d73-a11a-a45187da9138", + "method": "GET" + }, + "next_url_post": { + "type": "application/x-www-form-urlencoded", + "params": { + "chargeTokenId": "18a144e1-f6d8-4d73-a11a-a45187da9138" + }, + "href": "https://www.payments.service.gov.uk/secure", + "method": "POST" + }, + "events": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cjgt8i57kavb8jfufhjlh129fm/events", + "method": "GET" + }, + "refunds": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cjgt8i57kavb8jfufhjlh129fm/refunds", + "method": "GET" + }, + "cancel": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cjgt8i57kavb8jfufhjlh129fm/cancel", + "method": "POST" + } + } +} \ No newline at end of file diff --git a/spec/fixtures/files/govpay/get_payment_response_success.json b/spec/fixtures/files/govpay/get_payment_response_success.json new file mode 100644 index 000000000..8beca9bd1 --- /dev/null +++ b/spec/fixtures/files/govpay/get_payment_response_success.json @@ -0,0 +1,62 @@ +{ + "amount": 10501, + "description": "Waste carrier registration upper tier", + "reference": "12345", + "language": "en", + "metadata": { + "ledger_code": "AB100", + "an_internal_reference_number": 200 + }, + "email": "sherlock.holmes@example.com", + "state": { + "status": "success", + "finished": true + }, + "payment_id": "cnnffa1e6s3u9a6n24u2cp527d", + "payment_provider": "sandbox", + "created_date": "2022-05-18T11:52:13.669Z", + "refund_summary": { + "status": "available", + "amount_available": 10501, + "amount_submitted": 0 + }, + "settlement_summary": { + "capture_submit_time": "2022-05-18T11:52:39.172Z", + "captured_date": "2022-05-18" + }, + "card_details": { + "last_digits_card_number": "5100", + "first_digits_card_number": "510510", + "cardholder_name": "Sherlock Holmes", + "expiry_date": "01/24", + "billing_address": { + "line1": "221 Baker Street", + "line2": "Flat b", + "postcode": "NW1 6XE", + "city": "London", + "country": "GB" + }, + "card_brand": "Mastercard", + "card_type": "debit" + }, + "delayed_capture": false, + "moto": false, + "provider_id": "9bb0c2c1-d0c5-4a63-8945-f4240e06f8ae", + "return_url": "https://some-wcr-env.defra.gov.uk/completed", + "authorisation_mode": "web", + "card_brand": "Mastercard", + "_links": { + "self": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cnnffa1e6s3u9a6n24u2cp527d", + "method": "GET" + }, + "events": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cnnffa1e6s3u9a6n24u2cp527d/events", + "method": "GET" + }, + "refunds": { + "href": "https://publicapi.payments.service.gov.uk/v1/payments/cnnffa1e6s3u9a6n24u2cp527d/refunds", + "method": "GET" + } + } +} \ No newline at end of file diff --git a/spec/fixtures/files/request_to_worldpay.xml b/spec/fixtures/files/worldpay/request_to_worldpay.xml similarity index 100% rename from spec/fixtures/files/request_to_worldpay.xml rename to spec/fixtures/files/worldpay/request_to_worldpay.xml diff --git a/spec/fixtures/files/response_from_worldpay.xml b/spec/fixtures/files/worldpay/response_from_worldpay.xml similarity index 100% rename from spec/fixtures/files/response_from_worldpay.xml rename to spec/fixtures/files/worldpay/response_from_worldpay.xml diff --git a/spec/fixtures/worldpay_initial_request.xml b/spec/fixtures/files/worldpay/worldpay_initial_request.xml similarity index 100% rename from spec/fixtures/worldpay_initial_request.xml rename to spec/fixtures/files/worldpay/worldpay_initial_request.xml diff --git a/spec/fixtures/worldpay_initial_request_invalid.xml b/spec/fixtures/files/worldpay/worldpay_initial_request_invalid.xml similarity index 100% rename from spec/fixtures/worldpay_initial_request_invalid.xml rename to spec/fixtures/files/worldpay/worldpay_initial_request_invalid.xml diff --git a/spec/fixtures/worldpay_redirect.xml b/spec/fixtures/files/worldpay/worldpay_redirect.xml similarity index 100% rename from spec/fixtures/worldpay_redirect.xml rename to spec/fixtures/files/worldpay/worldpay_redirect.xml diff --git a/spec/models/waste_carriers_engine/new_registration_workflow/govpay_form_spec.rb b/spec/models/waste_carriers_engine/new_registration_workflow/govpay_form_spec.rb new file mode 100644 index 000000000..c3cbef6b9 --- /dev/null +++ b/spec/models/waste_carriers_engine/new_registration_workflow/govpay_form_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "rails_helper" + +module WasteCarriersEngine + RSpec.describe NewRegistration do + before { allow(WasteCarriersEngine::FeatureToggle).to receive(:active?).with(:govpay_payments).and_return(true) } + + subject { build(:new_registration, workflow_state: "govpay_form") } + + describe "#workflow_state" do + context ":govpay_form state transitions" do + context "on next" do + include_examples "has next transition", next_state: "registration_completed_form" + + context "when there are pending convictions" do + subject { build(:new_registration, :requires_conviction_check, workflow_state: "govpay_form") } + + include_examples "has next transition", next_state: "registration_received_pending_conviction_form" + end + + context "when there is a pending govpay payment" do + subject { build(:new_registration, :has_pending_govpay_status, workflow_state: "govpay_form") } + + include_examples "has next transition", next_state: "registration_received_pending_govpay_payment_form" + end + end + end + end + end +end diff --git a/spec/models/waste_carriers_engine/new_registration_workflow/payment_summary_form_spec.rb b/spec/models/waste_carriers_engine/new_registration_workflow/payment_summary_form_spec.rb index c5c4eec28..3f7b5bda5 100644 --- a/spec/models/waste_carriers_engine/new_registration_workflow/payment_summary_form_spec.rb +++ b/spec/models/waste_carriers_engine/new_registration_workflow/payment_summary_form_spec.rb @@ -9,10 +9,17 @@ module WasteCarriersEngine describe "#workflow_state" do context ":payment_summary_form state transitions" do context "on next" do - context "when the user choose to pay by card" do + context "when the user chooses to pay by card" do subject { build(:new_registration, workflow_state: "payment_summary_form", temp_payment_method: "card") } - include_examples "has next transition", next_state: "worldpay_form" + context "and Worldpay payments are enabled" do + include_examples "has next transition", next_state: "worldpay_form" + end + + context "and Govpay payments are enabled" do + before { allow(WasteCarriersEngine::FeatureToggle).to receive(:active?).with(:govpay_payments).and_return(true) } + include_examples "has next transition", next_state: "govpay_form" + end end include_examples "has next transition", next_state: "confirm_bank_transfer_form" diff --git a/spec/models/waste_carriers_engine/order_spec.rb b/spec/models/waste_carriers_engine/order_spec.rb index 1fcb89f0d..a9c9a0a97 100644 --- a/spec/models/waste_carriers_engine/order_spec.rb +++ b/spec/models/waste_carriers_engine/order_spec.rb @@ -154,12 +154,12 @@ module WasteCarriersEngine end end - describe "update_after_worldpay" do + describe "update_after_online_payment" do let(:finance_details) { transient_registration.prepare_for_payment(:worldpay, current_user) } let(:order) { finance_details.orders.first } it "copies the worldpay status to the order" do - order.update_after_worldpay("AUTHORISED") + order.update_after_online_payment("AUTHORISED") expect(order.world_pay_status).to eq("AUTHORISED") end @@ -168,10 +168,30 @@ module WasteCarriersEngine # Wipe the date first so we know the value has been added order.update_attributes(date_last_updated: nil) - order.update_after_worldpay("AUTHORISED") + order.update_after_online_payment("AUTHORISED") expect(order.date_last_updated).to eq(Time.new(2004, 8, 15, 16, 23, 42)) end end end + + describe "#payment_uuid" do + let(:transient_registration) { build(:renewing_registration, :has_required_data, :has_finance_details) } + let(:order) { described_class.new(finance_details: transient_registration.finance_details) } + + context "with no pre-existing uuid" do + it "generates and saves a uuid" do + expect(order[:payment_uuid]).to be_nil + expect(order.payment_uuid).to be_present + expect(order[:payment_uuid]).to be_present + end + end + + context "with a pre-existing uuid" do + it "returns the existing uuid" do + uuid = order.payment_uuid + expect(order.payment_uuid).to eq uuid + end + end + end end end diff --git a/spec/models/waste_carriers_engine/payment_spec.rb b/spec/models/waste_carriers_engine/payment_spec.rb index 8d298b562..e386b4e40 100644 --- a/spec/models/waste_carriers_engine/payment_spec.rb +++ b/spec/models/waste_carriers_engine/payment_spec.rb @@ -57,9 +57,33 @@ module WasteCarriersEngine expect(result).to_not include(refund_payment) end end + + describe ".except_online_not_authorised" do + let(:transient_registration) { build(:renewing_registration, :has_required_data, :has_finance_details) } + let(:cash_payment) { WasteCarriersEngine::Payment.new(payment_type: "CASH") } + let(:worldpay_payment_authorised) { WasteCarriersEngine::Payment.new(payment_type: "WORLDPAY", world_pay_payment_status: "AUTHORISED") } + let(:worldpay_payment_refused) { WasteCarriersEngine::Payment.new(payment_type: "WORLDPAY", world_pay_payment_status: "REFUSED") } + let(:govpay_payment_authorised) { WasteCarriersEngine::Payment.new(payment_type: "GOVPAY", govpay_payment_status: "success") } + let(:govpay_payment_refused) { WasteCarriersEngine::Payment.new(payment_type: "GOVPAY", govpay_payment_status: "failed") } + + before do + transient_registration.finance_details.payments << cash_payment << worldpay_payment_authorised << govpay_payment_authorised << govpay_payment_refused + transient_registration.save + transient_registration.reload + end + + it "returns the expected payments only" do + result = transient_registration.finance_details.payments.except_online_not_authorised + expect(result).to include(cash_payment) + expect(result).to include(worldpay_payment_authorised) + expect(result).not_to include(worldpay_payment_refused) + expect(result).to include(govpay_payment_authorised) + expect(result).not_to include(govpay_payment_refused) + end + end end - describe "new_from_worldpay" do + describe "new_from_online_payment" do before do Timecop.freeze(Time.new(2018, 1, 1)) do transient_registration.prepare_for_payment(:worldpay, current_user) @@ -67,7 +91,7 @@ module WasteCarriersEngine end let(:order) { transient_registration.finance_details.orders.first } - let(:payment) { Payment.new_from_worldpay(order, current_user.email) } + let(:payment) { Payment.new_from_online_payment(order, current_user.email) } it "should set the correct order_key" do expect(payment.order_key).to eq("1514764800") @@ -98,7 +122,7 @@ module WasteCarriersEngine end end - describe "new_from_non_worldpay" do + describe "new_from_non_online_payment" do before do Timecop.freeze(Time.new(2018, 1, 1)) do transient_registration.prepare_for_payment(:worldpay, current_user) @@ -120,7 +144,7 @@ module WasteCarriersEngine end let(:order) { transient_registration.finance_details.orders.first } - let(:payment) { Payment.new_from_non_worldpay(params, order) } + let(:payment) { Payment.new_from_non_online_payment(params, order) } it "should set the correct amount" do expect(payment.amount).to eq(params[:amount]) @@ -175,14 +199,14 @@ module WasteCarriersEngine end end - describe "update_after_worldpay" do + describe "update_after_online_payment" do let(:order) { transient_registration.finance_details.orders.first } - let(:payment) { Payment.new_from_worldpay(order, current_user.email) } + let(:payment) { Payment.new_from_online_payment(order, current_user.email) } before do Timecop.freeze(Time.new(2018, 3, 4)) do transient_registration.prepare_for_payment(:worldpay, current_user) - payment.update_after_worldpay(paymentStatus: "AUTHORISED", mac: "foo") + payment.update_after_online_payment({ paymentStatus: "AUTHORISED", mac: "foo" }) end end diff --git a/spec/models/waste_carriers_engine/renewing_registration_spec.rb b/spec/models/waste_carriers_engine/renewing_registration_spec.rb index d636074d8..1c877cf8b 100644 --- a/spec/models/waste_carriers_engine/renewing_registration_spec.rb +++ b/spec/models/waste_carriers_engine/renewing_registration_spec.rb @@ -31,7 +31,7 @@ module WasteCarriersEngine context "when transitioning from worldpay_form to renewal_complete_form successfully" do it "set the transient registration metadata route" do expect(renewing_registration).to receive(:set_metadata_route).once - expect(renewing_registration).to receive(:pending_worldpay_payment?).and_return(false) + expect(renewing_registration).to receive(:pending_online_payment?).and_return(false) expect(renewing_registration).to receive(:conviction_check_required?).and_return(false) renewing_registration.update_attributes(workflow_state: :worldpay_form) @@ -42,7 +42,7 @@ module WasteCarriersEngine context "when transitioning from worldpay_form to renewal_received_pending_conviction_form succesfully" do it "set the transient registration metadata route" do expect(renewing_registration).to receive(:set_metadata_route).once - expect(renewing_registration).to receive(:pending_worldpay_payment?).and_return(false) + expect(renewing_registration).to receive(:pending_online_payment?).and_return(false) expect(renewing_registration).to receive(:conviction_check_required?).and_return(true) renewing_registration.update_attributes(workflow_state: :worldpay_form) diff --git a/spec/models/waste_carriers_engine/renewing_registration_workflow/govpay_form_spec.rb b/spec/models/waste_carriers_engine/renewing_registration_workflow/govpay_form_spec.rb new file mode 100644 index 000000000..40de4fbcf --- /dev/null +++ b/spec/models/waste_carriers_engine/renewing_registration_workflow/govpay_form_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "rails_helper" + +module WasteCarriersEngine + RSpec.describe RenewingRegistration, type: :model do + subject do + build(:renewing_registration, + :has_required_data, + :has_conviction_search_result, + :has_key_people, + :has_paid_balance, + workflow_state: "govpay_form") + end + + describe "#workflow_state" do + context ":govpay_form state transitions" do + context "on next" do + context "when a conviction check is not required" do + before do + allow(subject).to receive(:conviction_check_required?).and_return(false) + end + + context "when there is no pending Govpay payment" do + before do + allow(subject).to receive(:pending_online_payment?).and_return(false) + end + + include_examples "has next transition", next_state: "renewal_complete_form" + + it "does not send a confirmation email after the 'next' event" do + expect(Notifications::Client).not_to receive(:new) + + subject.next! + end + end + + context "when there is a pending Govpay payment" do + before do + allow(subject).to receive(:pending_online_payment?).and_return(true) + end + + include_examples "has next transition", next_state: "renewal_received_pending_govpay_payment_form" + + it "sends a confirmation email after the 'next' event" do + expect(Notify::RenewalPendingOnlinePaymentEmailService) + .to receive(:run) + .with(registration: subject) + .once + + subject.next! + end + end + end + + context "when a conviction check is required" do + before do + allow(subject).to receive(:conviction_check_required?).and_return(true) + end + + include_examples "has next transition", next_state: "renewal_received_pending_conviction_form" + + it "sends a confirmation email after the 'next' event" do + expect(Notify::RenewalPendingChecksEmailService) + .to receive(:run) + .with(registration: subject) + .once + + subject.next! + end + end + end + end + end + end +end diff --git a/spec/models/waste_carriers_engine/renewing_registration_workflow/payment_summary_form_spec.rb b/spec/models/waste_carriers_engine/renewing_registration_workflow/payment_summary_form_spec.rb index b195ff23c..ce38fe474 100644 --- a/spec/models/waste_carriers_engine/renewing_registration_workflow/payment_summary_form_spec.rb +++ b/spec/models/waste_carriers_engine/renewing_registration_workflow/payment_summary_form_spec.rb @@ -18,7 +18,14 @@ module WasteCarriersEngine context "when paying by card" do let(:temp_payment_method) { "card" } - include_examples "has next transition", next_state: "worldpay_form" + context "and Worldpay payments are enabled" do + include_examples "has next transition", next_state: "worldpay_form" + end + + context "and Govpay payments are enabled" do + before { allow(WasteCarriersEngine::FeatureToggle).to receive(:active?).with(:govpay_payments).and_return(true) } + include_examples "has next transition", next_state: "govpay_form" + end end context "when paying by bank transfer" do diff --git a/spec/models/waste_carriers_engine/renewing_registration_workflow/renewal_received_pending_govpay_payment_form_spec.rb b/spec/models/waste_carriers_engine/renewing_registration_workflow/renewal_received_pending_govpay_payment_form_spec.rb new file mode 100644 index 000000000..33222d719 --- /dev/null +++ b/spec/models/waste_carriers_engine/renewing_registration_workflow/renewal_received_pending_govpay_payment_form_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "rails_helper" + +module WasteCarriersEngine + RSpec.describe RenewalReceivedPendingGovpayPaymentForm, type: :model do + describe "#workflow_state" do + it_behaves_like "a fixed final state", + current_state: :renewal_received_pending_govpay_payment_form, + factory: :renewing_registration + end + end +end diff --git a/spec/models/waste_carriers_engine/renewing_registration_workflow/renewal_received_pending_payment_form_spec.rb b/spec/models/waste_carriers_engine/renewing_registration_workflow/renewal_received_pending_payment_form_spec.rb index d6686797a..83e14e266 100644 --- a/spec/models/waste_carriers_engine/renewing_registration_workflow/renewal_received_pending_payment_form_spec.rb +++ b/spec/models/waste_carriers_engine/renewing_registration_workflow/renewal_received_pending_payment_form_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" module WasteCarriersEngine - RSpec.describe RenewingRegistration, type: :model do + RSpec.describe RenewalReceivedPendingPaymentForm, type: :model do describe "#workflow_state" do it_behaves_like "a fixed final state", current_state: :renewal_received_pending_payment_form, diff --git a/spec/models/waste_carriers_engine/renewing_registration_workflow/renewal_received_pending_worldpay_payment_form_spec.rb b/spec/models/waste_carriers_engine/renewing_registration_workflow/renewal_received_pending_worldpay_payment_form_spec.rb index b699a137a..cdf318805 100644 --- a/spec/models/waste_carriers_engine/renewing_registration_workflow/renewal_received_pending_worldpay_payment_form_spec.rb +++ b/spec/models/waste_carriers_engine/renewing_registration_workflow/renewal_received_pending_worldpay_payment_form_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" module WasteCarriersEngine - RSpec.describe RenewingRegistration, type: :model do + RSpec.describe RenewalReceivedPendingWorldpayPaymentForm, type: :model do describe "#workflow_state" do it_behaves_like "a fixed final state", current_state: :renewal_received_pending_worldpay_payment_form, diff --git a/spec/models/waste_carriers_engine/renewing_registration_workflow/worldpay_form_spec.rb b/spec/models/waste_carriers_engine/renewing_registration_workflow/worldpay_form_spec.rb index 6d25efd06..ec08fac8f 100644 --- a/spec/models/waste_carriers_engine/renewing_registration_workflow/worldpay_form_spec.rb +++ b/spec/models/waste_carriers_engine/renewing_registration_workflow/worldpay_form_spec.rb @@ -23,7 +23,7 @@ module WasteCarriersEngine context "when there is no pending WorldPay payment" do before do - allow(subject).to receive(:pending_worldpay_payment?).and_return(false) + allow(subject).to receive(:pending_online_payment?).and_return(false) end include_examples "has next transition", next_state: "renewal_complete_form" @@ -41,13 +41,13 @@ module WasteCarriersEngine context "when there is a pending WorldPay payment" do before do - allow(subject).to receive(:pending_worldpay_payment?).and_return(true) + allow(subject).to receive(:pending_online_payment?).and_return(true) end include_examples "has next transition", next_state: "renewal_received_pending_worldpay_payment_form" it "sends a confirmation email after the 'next' event" do - expect(Notify::RenewalPendingWorldpayPaymentEmailService) + expect(Notify::RenewalPendingOnlinePaymentEmailService) .to receive(:run) .with(registration: subject) .once diff --git a/spec/models/waste_carriers_engine/transient_registration_spec.rb b/spec/models/waste_carriers_engine/transient_registration_spec.rb index 29a399891..065b3e4a5 100644 --- a/spec/models/waste_carriers_engine/transient_registration_spec.rb +++ b/spec/models/waste_carriers_engine/transient_registration_spec.rb @@ -116,7 +116,7 @@ module WasteCarriersEngine end end - describe "#pending_worldpay_payment?" do + describe "#pending_online_payment?" do context "when the renewal has an order" do before do transient_registration.finance_details = build(:finance_details, :has_order) @@ -128,7 +128,7 @@ module WasteCarriersEngine end it "returns true" do - expect(transient_registration.pending_worldpay_payment?).to eq(true) + expect(transient_registration.pending_online_payment?).to eq(true) end end @@ -138,7 +138,7 @@ module WasteCarriersEngine end it "returns false" do - expect(transient_registration.pending_worldpay_payment?).to eq(false) + expect(transient_registration.pending_online_payment?).to eq(false) end end end @@ -149,7 +149,7 @@ module WasteCarriersEngine end it "returns false" do - expect(transient_registration.pending_worldpay_payment?).to eq(false) + expect(transient_registration.pending_online_payment?).to eq(false) end end end diff --git a/spec/requests/waste_carriers_engine/govpay_forms_spec.rb b/spec/requests/waste_carriers_engine/govpay_forms_spec.rb new file mode 100644 index 000000000..6d004b66b --- /dev/null +++ b/spec/requests/waste_carriers_engine/govpay_forms_spec.rb @@ -0,0 +1,276 @@ +# frozen_string_literal: true + +require "webmock/rspec" +require "rails_helper" + +module WasteCarriersEngine + RSpec.describe "GovpayForms", type: :request do + let(:govpay_host) { "https://publicapi.payments.service.gov.uk" } + let(:order) { transient_registration.finance_details.orders.first } + let(:order_key) { "#{Rails.configuration.govpay_merchant_code}^#{order.order_code}" } + + before do + allow(Rails.configuration).to receive(:govpay_url).and_return(govpay_host) + allow(Rails.configuration).to receive(:govpay_merchant_code).and_return("some_merchant_code") + allow(Rails.configuration).to receive(:govpay_api_token).and_return("some_token") + end + + # TODO: Remove this when the feature flag is no longer required + # before { allow(WasteCarriersEngine::FeatureToggle).to receive(:active?).with(:govpay_payments).and_return(true) } + + context "when a valid user is signed in" do + let(:user) { create(:user) } + before(:each) do + sign_in(user) + end + + context "when a valid transient registration exists" do + let(:transient_registration) do + create(:renewing_registration, + :has_required_data, + :has_addresses, + :has_conviction_search_result, + :has_key_people, + account_email: user.email, + workflow_state: "govpay_form", + workflow_history: ["payment_summary_form"]) + end + let(:order) { transient_registration.finance_details.orders.first } + let(:token) { transient_registration[:token] } + + describe "#new" do + + before do + stub_request(:any, /.*#{govpay_host}.*/).to_return( + status: 200, + body: File.read("./spec/fixtures/files/govpay/get_payment_response_created.json") + ) + end + + it "creates a new finance_details" do + get new_govpay_form_path(token) + expect(transient_registration.reload.finance_details).to be_present + end + + it "redirects to govpay" do + get new_govpay_form_path(token) + expect(response.location).to include("https://www.payments.service.gov.uk") + end + + it "populates govpay_id on the order" do + get new_govpay_form_path(token) + expect(transient_registration.reload.finance_details.orders[0].govpay_id).to be_present + end + + context "when the transient_registration is a new registration" do + let(:transient_registration) do + create(:new_registration, + :has_addresses, + contact_email: user.email, + workflow_state: "govpay_form", + temp_cards: 2) + end + + it "creates a new finance_details" do + get new_govpay_form_path(token) + expect(transient_registration.reload.finance_details).to be_present + end + end + + context "when there is an error setting up the govpay url" do + before do + allow_any_instance_of(GovpayPaymentService).to receive(:prepare_for_payment).and_return(:error) + end + + it "redirects to payment_summary_form" do + get new_govpay_form_path(token) + expect(response).to redirect_to(new_payment_summary_form_path(token)) + end + end + end + + describe "#payment_callback" do + let(:govpay_host) { "https://publicapi.payments.service.gov.uk/v1" } + + before do + allow(WasteCarriersEngine::FeatureToggle).to receive(:active?).with(:govpay_payments).and_return(true) + allow(WasteCarriersEngine::FeatureToggle).to receive(:active?).with(:use_extended_grace_window).and_return(true) + allow(Rails.configuration).to receive(:govpay_url).and_return(govpay_host) + allow(Rails.configuration).to receive(:metadata_route).and_return("ASSISTED_DIGITAL") + stub_request(:any, %r{.*#{govpay_host}/payments}).to_return( + status: 200, + body: File.read("./spec/fixtures/files/govpay/get_payment_response_#{govpay_status}.json") + ) + transient_registration.prepare_for_payment(:govpay, user) + GovpayPaymentService.new(transient_registration, order, user).prepare_for_payment + end + + context "when govpay status is success" do + let(:govpay_status) { "success" } + + context "when the payment_uuid is valid and the balance is paid" do + + it "adds a new payment to the registration" do + expect { get payment_callback_govpay_forms_path(token, order.payment_uuid) } + .to change { transient_registration.reload.finance_details.payments.count }.from(0).to(1) + end + + it "redirects to renewal_complete_form" do + get payment_callback_govpay_forms_path(token, order.payment_uuid) + + expect(response).to redirect_to(new_renewal_complete_form_path(token)) + end + + it "updates the metadata route" do + expect(transient_registration.reload.metaData.route).to be_nil + + get payment_callback_govpay_forms_path(token, order.payment_uuid) + + expect(transient_registration.reload.metaData.route).to eq("ASSISTED_DIGITAL") + end + + it "is idempotent" do + expect do + get payment_callback_govpay_forms_path(token, order.payment_uuid) + get payment_callback_govpay_forms_path(token, order.payment_uuid) + transient_registration.reload + end.to change { transient_registration.finance_details.payments.count }.from(0).to(1) + end + + context "when it has been flagged for conviction checks" do + before { transient_registration.conviction_sign_offs = [build(:conviction_sign_off)] } + + it "updates the transient registration metadata attributes from application configuration" do + expect(transient_registration.reload.metaData.route).to be_nil + + get payment_callback_govpay_forms_path(token, order.payment_uuid) + + expect(transient_registration.reload.metaData.route).to eq("ASSISTED_DIGITAL") + end + + it "redirects to renewal_received_pending_conviction_form" do + get payment_callback_govpay_forms_path(token, order.payment_uuid) + + expect(response).to redirect_to(new_renewal_received_pending_conviction_form_path(token)) + end + + context "when the mailer fails" do + before do + allow(Rails.configuration.action_mailer).to receive(:raise_delivery_errors).and_return(true) + allow_any_instance_of(ActionMailer::MessageDelivery).to receive(:deliver_now).and_raise(StandardError) + end + + it "does not raise an error" do + expect { get payment_callback_govpay_forms_path(token, order.payment_uuid) }.to_not raise_error + end + end + end + end + + context "when the payment uuid is invalid" do + before do + stub_request(:any, %r{.*#{govpay_host}/payments}).to_return( + status: 200, + body: File.read("./spec/fixtures/files/govpay/get_payment_response_not_found.json") + ) + end + + it "does not create a payment" do + get payment_callback_govpay_forms_path(token, "invalid_uuid") + expect(transient_registration.reload.finance_details.payments.first).to be_nil + end + + it "redirects to payment_summary_form" do + get payment_callback_govpay_forms_path(token, "invalid_uuid") + expect(response).to redirect_to(new_payment_summary_form_path(token)) + end + end + end + + context "for pending govpay statuses" do + + RSpec.shared_examples "payment is pending" do + + context "when the payment uuid is valid" do + before do + allow_any_instance_of(RenewingRegistration).to receive(:pending_online_payment?).and_return(true) + end + + it "redirects to renewal_received_pending_govpay_payment_form" do + get payment_callback_govpay_forms_path(token, order.payment_uuid) + expect(response).to redirect_to(new_renewal_received_pending_govpay_payment_form_path(token)) + end + end + + context "when the payment uuid is invalid" do + it "redirects to payment_summary_form" do + get payment_callback_govpay_forms_path(token, "invalid_payment_uuid") + expect(response).to redirect_to(new_payment_summary_form_path(token)) + end + end + end + + context "when govpay status is created" do + let(:govpay_status) { "created" } + it_behaves_like "payment is pending" + end + + context "when govpay status is submitted" do + let(:govpay_status) { "submitted" } + it_behaves_like "payment is pending" + end + end + + context "for unsuccessful govpay statuses" do + + RSpec.shared_examples "payment is unsuccessful" do + + context "when the payment uuid is valid" do + it "redirects to payment_summary_form" do + get payment_callback_govpay_forms_path(token, order.payment_uuid) + expect(response).to redirect_to(new_payment_summary_form_path(token)) + end + end + + context "when the payment uuid is invalid" do + it "redirects to payment_summary_form" do + get payment_callback_govpay_forms_path(token, "invalid_payment_uuid") + expect(response).to redirect_to(new_payment_summary_form_path(token)) + end + end + end + + context "when govpay status is cancel" do + let(:govpay_status) { "cancelled" } + it_behaves_like "payment is unsuccessful" + end + + context "failure" do + let(:govpay_status) { "failure" } + it_behaves_like "payment is unsuccessful" + end + + context "error" do + let(:govpay_status) { "not_found" } + it_behaves_like "payment is unsuccessful" + end + end + + context "for an invalid success status" do + before { allow(GovpayValidatorService).to receive(:valid_govpay_status?).and_return(false) } + + let(:govpay_status) { "success" } + it_behaves_like "payment is unsuccessful" + end + + context "for an invalid failure status" do + before { allow(GovpayValidatorService).to receive(:valid_govpay_status?).and_return(false) } + + let(:govpay_status) { "cancelled" } + it_behaves_like "payment is unsuccessful" + end + end + end + end + end +end diff --git a/spec/requests/waste_carriers_engine/registration_received_pending_govpay_payment_forms_spec.rb b/spec/requests/waste_carriers_engine/registration_received_pending_govpay_payment_forms_spec.rb new file mode 100644 index 000000000..91e540b38 --- /dev/null +++ b/spec/requests/waste_carriers_engine/registration_received_pending_govpay_payment_forms_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require "rails_helper" + +module WasteCarriersEngine + RSpec.describe "RegistrationReceivedPendingGovpayPaymentForm", type: :request do + describe "GET new_registration_received_pending_govpay_payment_form_path" do + context "when no new registration exists" do + it "redirects to the invalid page" do + get new_registration_received_pending_govpay_payment_form_path("wibblewobblejellyonaplate") + + expect(response).to redirect_to(page_path("invalid")) + end + end + + context "when a valid new registration exists" do + let(:transient_registration) do + create( + :new_registration, + :has_required_data, + workflow_state: "registration_received_pending_govpay_payment_form" + ) + end + + context "when the workflow_state is correct" do + it "returns a 200 status, renders the :new template, creates a new registration and deletes the transient registration" do + reg_identifier = transient_registration.reg_identifier + new_registrations_count = WasteCarriersEngine::NewRegistration.count + + get new_registration_received_pending_govpay_payment_form_path(transient_registration.token) + + registration = WasteCarriersEngine::Registration.find_by(reg_identifier: reg_identifier) + + expect(response).to have_http_status(200) + expect(response).to render_template(:new) + expect(registration).to be_valid + expect(WasteCarriersEngine::NewRegistration.count).to eq(new_registrations_count - 1) + end + end + + context "when the workflow_state is not correct" do + before do + transient_registration.update_attributes(workflow_state: "payment_summary_form") + end + + it "redirects to the correct page and does not creates a new registration nor delete the transient object" do + new_registrations_count = WasteCarriersEngine::NewRegistration.count + + get new_registration_received_pending_govpay_payment_form_path(transient_registration.token) + + registration_scope = WasteCarriersEngine::Registration.where(reg_identifier: transient_registration.reg_identifier) + + expect(response).to redirect_to(new_payment_summary_form_path(transient_registration.token)) + expect(WasteCarriersEngine::NewRegistration.count).to eq(new_registrations_count) + expect(registration_scope).to be_empty + end + end + + context "when the registration completion service fails" do + let(:the_error) { StandardError.new("Oops!") } + + before do + allow(RegistrationCompletionService) + .to receive(:run) + .with(transient_registration) + .and_raise(the_error) + + # Airbrake may receive notifications other than the specific one used in the spec below + allow(Airbrake).to receive(:notify) + end + + it "logs the exception" do + expect(Airbrake) + .to receive(:notify) + .with(the_error, { reg_identifier: transient_registration.reg_identifier }) + + begin + get new_registration_received_pending_govpay_payment_form_path(transient_registration.token) + rescue ActionView::Template::Error + # Capture the exception raised in the view + end + end + end + end + end + end +end diff --git a/spec/requests/waste_carriers_engine/renewal_received_pending_govpay_payment_forms_spec.rb b/spec/requests/waste_carriers_engine/renewal_received_pending_govpay_payment_forms_spec.rb new file mode 100644 index 000000000..7c12d0e43 --- /dev/null +++ b/spec/requests/waste_carriers_engine/renewal_received_pending_govpay_payment_forms_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "rails_helper" + +module WasteCarriersEngine + RSpec.describe "RenewalReceivedPendingGovpayPaymentForms", type: :request do + describe "GET new_renewal_received_pending_govpay_payment_form_path" do + context "when a valid user is signed in" do + let(:user) { create(:user) } + before(:each) do + sign_in(user) + end + + context "when no renewing registration exists" do + it "redirects to the invalid page" do + get new_renewal_received_pending_govpay_payment_form_path("wibblewobblejellyonaplate") + + expect(response).to redirect_to(page_path("invalid")) + end + end + + context "when a valid renewing registration exists" do + let(:transient_registration) do + create( + :renewing_registration, + :has_unpaid_balance, + workflow_state: "renewal_received_pending_govpay_payment_form", + account_email: user.email + ) + end + + context "when the workflow_state is correct" do + it "returns a 200 status and renders the :new template" do + get new_renewal_received_pending_govpay_payment_form_path(transient_registration.token) + + expect(response).to have_http_status(200) + expect(response).to render_template(:new) + end + end + + context "when the workflow_state is not correct" do + before do + transient_registration.update_attributes(workflow_state: "payment_summary_form") + end + + it "redirects to the correct page" do + get new_renewal_received_pending_govpay_payment_form_path(transient_registration.token) + expect(response).to redirect_to(new_payment_summary_form_path(transient_registration.token)) + end + end + end + end + end + end +end diff --git a/spec/requests/waste_carriers_engine/worldpay_forms_spec.rb b/spec/requests/waste_carriers_engine/worldpay_forms_spec.rb index 5080bf243..1ca3dbbcf 100644 --- a/spec/requests/waste_carriers_engine/worldpay_forms_spec.rb +++ b/spec/requests/waste_carriers_engine/worldpay_forms_spec.rb @@ -31,7 +31,7 @@ module WasteCarriersEngine before do stub_request(:any, /.*#{host}.*/).to_return( status: 200, - body: File.read("./spec/fixtures/worldpay_redirect.xml") + body: File.read("./spec/fixtures/files/worldpay/worldpay_redirect.xml") ) end @@ -213,7 +213,7 @@ module WasteCarriersEngine context "when the params are valid" do before do allow_any_instance_of(WorldpayService).to receive(:valid_pending?).and_return(true) - allow_any_instance_of(RenewingRegistration).to receive(:pending_worldpay_payment?).and_return(true) + allow_any_instance_of(RenewingRegistration).to receive(:pending_online_payment?).and_return(true) end it "redirects to renewal_received_pending_payment_form" do diff --git a/spec/services/waste_carriers_engine/govpay_callback_service_spec.rb b/spec/services/waste_carriers_engine/govpay_callback_service_spec.rb new file mode 100644 index 000000000..6fb131c56 --- /dev/null +++ b/spec/services/waste_carriers_engine/govpay_callback_service_spec.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +require "webmock/rspec" +require "rails_helper" + +module WasteCarriersEngine + RSpec.describe GovpayCallbackService do + let(:govpay_host) { "https://publicapi.payments.service.gov.uk" } + let(:transient_registration) do + create(:renewing_registration, + :has_required_data, + :has_overseas_addresses, + :has_finance_details, + temp_cards: 0) + end + let(:current_user) { build(:user) } + let(:order) { transient_registration.finance_details.orders.first } + + before do + allow(WasteCarriersEngine::FeatureToggle).to receive(:active?).with(:govpay_payments).and_return(true) + allow(Rails.configuration).to receive(:govpay_url).and_return(govpay_host) + allow(Rails.configuration).to receive(:renewal_charge).and_return(10_500) + transient_registration.prepare_for_payment(:govpay, current_user) + order.govpay_id = "a_govpay_id" + order.save! + end + + let(:govpay_callback_service) { GovpayCallbackService.new(order.payment_uuid) } + + describe "#run" do + + RSpec.shared_examples "acceptable payment" do |response_type| + context "when the status is valid" do + before { allow_any_instance_of(GovpayValidatorService).to receive("valid_#{response_type}?".to_sym).and_return(true) } + + it "returns #{response_type}" do + expect(govpay_callback_service.run).to eq(response_type) + end + + it "updates the payment status" do + govpay_callback_service.run + expect(transient_registration.reload.finance_details.payments.first.govpay_payment_status).to eq(response_type.to_s) + end + + it "updates the payment govpay_id" do + govpay_callback_service.run + expect(transient_registration.reload.finance_details.payments.first.govpay_id).not_to be_nil + end + + it "updates the order govpay_status" do + govpay_callback_service.run + expect(transient_registration.reload.finance_details.orders.first.govpay_status).to eq(response_type.to_s) + end + + it "updates the order govpay_id" do + govpay_callback_service.run + expect(transient_registration.reload.finance_details.orders.first.govpay_id).not_to be_nil + end + end + + context "when the status is invalid" do + before { allow_any_instance_of(GovpayValidatorService).to receive("valid_#{response_type}?".to_sym).and_return(false) } + + it "returns an error" do + expect(govpay_callback_service.run).to eq(:error) + end + + it "does not update the order" do + unmodified_order = transient_registration.finance_details.orders.first + govpay_callback_service.run + expect(transient_registration.reload.finance_details.orders.first).to eq(unmodified_order) + end + + it "does not create a payment" do + govpay_callback_service.run + expect(transient_registration.reload.finance_details.payments.count).to eq(0) + end + end + end + + context "success" do + before { allow_any_instance_of(GovpayPaymentDetailsService).to receive(:govpay_payment_status).and_return("success") } + + it_behaves_like "acceptable payment", :success + + it "updates the balance" do + govpay_callback_service.run + expect(transient_registration.reload.finance_details.balance).to eq(0) + end + end + + context "created" do + before { allow_any_instance_of(GovpayPaymentDetailsService).to receive(:govpay_payment_status).and_return("created") } + it_behaves_like "acceptable payment", :pending + end + + context "submitted" do + before { allow_any_instance_of(GovpayPaymentDetailsService).to receive(:govpay_payment_status).and_return("submitted") } + it_behaves_like "acceptable payment", :pending + end + + RSpec.shared_examples "unsuccessful payment" do |response_type| + + context "when the status is valid" do + before do + allow_any_instance_of(GovpayValidatorService).to receive("valid_#{response_type}?".to_sym).and_return(true) + end + + it "returns #{response_type}" do + expect(govpay_callback_service.run).to eq(response_type) + end + + it "updates the order status" do + govpay_callback_service.run + expect(transient_registration.reload.finance_details.orders.first.govpay_status).to eq(response_type.to_s) + end + end + + context "when the status is invalid" do + before do + allow_any_instance_of(GovpayValidatorService).to receive("valid_#{response_type}?".to_sym).and_return(false) + end + + it "returns an error" do + expect(govpay_callback_service.run).to eq(:error) + end + + it "does not update the order" do + unmodified_order = transient_registration.finance_details.orders.first + govpay_callback_service.run + expect(transient_registration.reload.finance_details.orders.first).to eq(unmodified_order) + end + + it "does not create a payment" do + govpay_callback_service.run + expect(transient_registration.reload.finance_details.payments.count).to eq(0) + end + end + end + + context "failed" do + before { allow_any_instance_of(GovpayPaymentDetailsService).to receive(:govpay_payment_status).and_return("failed") } + it_behaves_like "unsuccessful payment", :failure + end + + context "cancelled" do + before { allow_any_instance_of(GovpayPaymentDetailsService).to receive(:govpay_payment_status).and_return("cancelled") } + it_behaves_like "unsuccessful payment", :cancel + end + + context "error" do + before { allow_any_instance_of(GovpayPaymentDetailsService).to receive(:govpay_payment_status).and_return("error") } + it_behaves_like "unsuccessful payment", :error + end + end + end +end diff --git a/spec/services/waste_carriers_engine/govpay_payment_details_service_spec.rb b/spec/services/waste_carriers_engine/govpay_payment_details_service_spec.rb new file mode 100644 index 000000000..dd5a328d9 --- /dev/null +++ b/spec/services/waste_carriers_engine/govpay_payment_details_service_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require "webmock/rspec" +require "rails_helper" + +module WasteCarriersEngine + RSpec.describe GovpayPaymentDetailsService do + let(:govpay_host) { "https://publicapi.payments.service.gov.uk" } + before do + allow(WasteCarriersEngine::FeatureToggle).to receive(:active?).with(:govpay_payments).and_return(true) + allow(Rails.configuration).to receive(:govpay_url).and_return(govpay_host) + end + + let(:transient_registration) do + create(:renewing_registration, + :has_required_data, + :has_overseas_addresses, + :has_finance_details, + temp_cards: 0) + end + let(:current_user) { build(:user) } + + before do + allow(Rails.configuration).to receive(:renewal_charge).and_return(10_500) + + transient_registration.prepare_for_payment(:govpay, current_user) + end + + subject { GovpayPaymentDetailsService.new(transient_registration.finance_details.orders.first.payment_uuid) } + + describe "govpay_payment_status" do + + context "with an invalid payment uuid" do + it "raises an exception" do + expect { GovpayPaymentDetailsService.new("bad_uuid") }.to raise_exception(ArgumentError) + end + end + + context "with a valid payment uuid" do + shared_examples "expected status is returned" do |govpay_status, expected_status| + let(:response_fixture) { "get_payment_response_#{govpay_status}.json" } + + it "returns #{expected_status}" do + expect(subject.govpay_payment_status).to eq expected_status + end + end + + before do + stub_request(:get, /.*#{govpay_host}.*/).to_return( + status: 200, + body: File.read("./spec/fixtures/files/govpay/#{response_fixture}") + ) + end + + it_behaves_like "expected status is returned", "created", "created" + + it_behaves_like "expected status is returned", "submitted", "submitted" + + it_behaves_like "expected status is returned", "success", "success" + + it_behaves_like "expected status is returned", "cancelled", "cancelled" + + it_behaves_like "expected status is returned", "not_found", "error" + end + end + + describe "payment_status" do + + shared_examples "maps to the expected status" do |govpay_status, response_type| + it "returns the correct status" do + expect(GovpayPaymentDetailsService.response_type(govpay_status)).to eq response_type + end + end + + it_behaves_like "maps to the expected status", "created", :pending + + it_behaves_like "maps to the expected status", "submitted", :pending + + it_behaves_like "maps to the expected status", "success", :success + + it_behaves_like "maps to the expected status", "failed", :failure + + it_behaves_like "maps to the expected status", "cancelled", :cancel + + it_behaves_like "maps to the expected status", nil, :error + end + end +end diff --git a/spec/services/waste_carriers_engine/govpay_payment_service_spec.rb b/spec/services/waste_carriers_engine/govpay_payment_service_spec.rb new file mode 100644 index 000000000..2130bfd52 --- /dev/null +++ b/spec/services/waste_carriers_engine/govpay_payment_service_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require "webmock/rspec" +require "rails_helper" + +module WasteCarriersEngine + RSpec.describe GovpayPaymentService do + let(:govpay_host) { "https://publicapi.payments.service.gov.uk" } + before do + allow(WasteCarriersEngine::FeatureToggle).to receive(:active?).with(:govpay_payments).and_return(true) + allow(Rails.configuration).to receive(:govpay_url).and_return(govpay_host) + end + + let(:transient_registration) do + create(:renewing_registration, + :has_required_data, + :has_overseas_addresses, + :has_finance_details, + temp_cards: 0) + end + let(:current_user) { build(:user) } + + before do + allow(Rails.configuration).to receive(:renewal_charge).and_return(10_500) + + transient_registration.prepare_for_payment(:govpay, current_user) + end + + let(:order) { transient_registration.finance_details.orders.first } + + let(:govpay_service) { GovpayPaymentService.new(transient_registration, order, current_user) } + + before do + stub_request(:any, /.*#{govpay_host}.*/).to_return( + status: 200, + body: File.read("./spec/fixtures/files/govpay/create_payment_created_response.json") + ) + end + + describe "prepare_for_payment" do + context "when the request is valid" do + let(:root) { Rails.configuration.wcrs_renewals_url } + let(:reg_id) { transient_registration.reg_identifier } + + it "returns a link" do + url = govpay_service.prepare_for_payment[:url] + # expect the value from the payment response file fixture + expect(url).to eq("https://www.payments.service.gov.uk/secure/bb0a272c-8eaf-468d-b3xf-ae5e000d2231") + end + + # Including this test because the Worldpay equivalent does create a new payment + it "does not create a new payment" do + expect { govpay_service.prepare_for_payment }.not_to change { transient_registration.finance_details.payments.length } + end + end + + context "when the request is invalid" do + before do + stub_request(:any, /.*#{govpay_host}.*/).to_return( + status: 200, + body: File.read("./spec/fixtures/files/govpay/create_payment_error_response.json") + ) + end + + it "returns :error" do + expect(govpay_service.prepare_for_payment).to eq(:error) + end + end + end + + describe "#payment_callback_url" do + let(:callback_host) { Faker::Internet.url } + + before do + allow(Rails.configuration).to receive(:host).and_return(callback_host) + end + + subject { govpay_service.payment_callback_url } + + context "when the order does not exist" do + + before { transient_registration.finance_details.orders = [] } + + it "raises an exception" do + expect { subject }.to raise_error(StandardError) + end + end + + context "when the order exists" do + + it "the callback url includes the base path" do + expect(subject).to start_with(callback_host) + end + + it "the callback url includes the payment uuid" do + expect(subject).to include(TransientRegistration.first.finance_details.orders.first.payment_uuid) + end + end + end + end +end diff --git a/spec/services/waste_carriers_engine/govpay_validator_service_spec.rb b/spec/services/waste_carriers_engine/govpay_validator_service_spec.rb new file mode 100644 index 000000000..be479c828 --- /dev/null +++ b/spec/services/waste_carriers_engine/govpay_validator_service_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require "rails_helper" + +module WasteCarriersEngine + RSpec.describe GovpayValidatorService do + let(:transient_registration) do + create(:renewing_registration, + :has_required_data, + :has_overseas_addresses, + :has_finance_details, + temp_cards: 0) + end + let(:payment) { Payment.new_from_online_payment(transient_registration.finance_details.orders.first, nil) } + let(:order) { transient_registration.finance_details.orders.first } + let(:govpay_validator_service) { GovpayValidatorService.new(order, payment.uuid, govpay_status) } + let(:govpay_host) { "https://publicapi.payments.service.gov.uk" } + + before do + allow(WasteCarriersEngine::FeatureToggle).to receive(:active?).with(:govpay_payments).and_return(true) + allow(Rails.configuration).to receive(:govpay_url).and_return(govpay_host) + allow(Rails.configuration).to receive(:renewal_charge).and_return(10_500) + end + + shared_examples "valid and invalid Govpay status" do |method, valid_status, invalid_status| + context "when the payment status is valid" do + let(:govpay_status) { valid_status } + + it "returns true" do + expect(govpay_validator_service.public_send(method)).to eq(true) + end + end + + context "when the payment status is invalid" do + let(:govpay_status) { invalid_status } + + it "returns false" do + expect(govpay_validator_service.public_send(method)).to eq(false) + end + end + end + + describe "#valid_success?" do + let(:govpay_status) { "success" } + context "when the govpay status is valid" do + + it "returns true" do + expect(govpay_validator_service.valid_success?).to eq(true) + end + end + + context "when the govpay status is not valid" do + + let(:govpay_status) { "failed" } + it "returns false" do + expect(govpay_validator_service.valid_success?).to eq(false) + end + end + + context "when the order is not present" do + let(:order) { nil } + + it "returns false" do + expect(govpay_validator_service.valid_success?).to eq(false) + end + end + + context "when the payment_uuid is not present" do + let(:govpay_validator_service) { GovpayValidatorService.new(order, nil, govpay_status) } + + it "returns false" do + expect(govpay_validator_service.valid_success?).to eq(false) + end + end + + context "when the payment_uuid is invalid" do + let(:govpay_validator_service) { GovpayValidatorService.new(order, "bad_payment_uuid", govpay_status) } + + it "returns false" do + expect(govpay_validator_service.valid_success?).to eq(false) + end + end + end + + describe "#valid_failure?" do + it_behaves_like "valid and invalid Govpay status", "valid_failure?", "failed" + end + + describe "#valid_pending?" do + it_behaves_like "valid and invalid Govpay status", "valid_pending?", "created" + end + + describe "#valid_cancel?" do + it_behaves_like "valid and invalid Govpay status", "valid_cancel?", "cancelled" + end + + describe "#valid_error?" do + it_behaves_like "valid and invalid Govpay status", "valid_error?", "error" + end + + describe "#valid_govpay_status?" do + it "returns true when the status matches the values for the response type" do + expect(described_class.valid_govpay_status?(:success, "success")).to eq(true) + end + + it "returns false when the status does not match the values for the response type" do + expect(described_class.valid_govpay_status?(:success, "FOO")).to eq(false) + end + end + end +end diff --git a/spec/services/waste_carriers_engine/notify/registration_pending_worldpay_payment_email_service_spec.rb b/spec/services/waste_carriers_engine/notify/registration_pending_online_payment_email_service_spec.rb similarity index 95% rename from spec/services/waste_carriers_engine/notify/registration_pending_worldpay_payment_email_service_spec.rb rename to spec/services/waste_carriers_engine/notify/registration_pending_online_payment_email_service_spec.rb index f6a078cca..7797a2f34 100644 --- a/spec/services/waste_carriers_engine/notify/registration_pending_worldpay_payment_email_service_spec.rb +++ b/spec/services/waste_carriers_engine/notify/registration_pending_online_payment_email_service_spec.rb @@ -4,7 +4,7 @@ module WasteCarriersEngine module Notify - RSpec.describe RegistrationPendingWorldpayPaymentEmailService do + RSpec.describe RegistrationPendingOnlinePaymentEmailService do let(:template_id) { "c4296e7b-dac6-4b59-906e-2c509271626f" } let(:registration) { create(:registration, :has_required_data) } diff --git a/spec/services/waste_carriers_engine/notify/renewal_pending_worldpay_payment_email_service_spec.rb b/spec/services/waste_carriers_engine/notify/renewal_pending_online_payment_email_service_spec.rb similarity index 90% rename from spec/services/waste_carriers_engine/notify/renewal_pending_worldpay_payment_email_service_spec.rb rename to spec/services/waste_carriers_engine/notify/renewal_pending_online_payment_email_service_spec.rb index 6edbd252c..2b9cf5b24 100644 --- a/spec/services/waste_carriers_engine/notify/renewal_pending_worldpay_payment_email_service_spec.rb +++ b/spec/services/waste_carriers_engine/notify/renewal_pending_online_payment_email_service_spec.rb @@ -4,7 +4,7 @@ module WasteCarriersEngine module Notify - RSpec.describe RenewalPendingWorldpayPaymentEmailService do + RSpec.describe RenewalPendingOnlinePaymentEmailService do let(:template_id) { "3da098e3-3db2-4c99-8e96-ed9d1a8ef227" } let(:registration) { create(:registration, :has_required_data) } @@ -35,7 +35,7 @@ module Notify end subject do - VCR.use_cassette("notify_renewal_pending_worldpay_payment_sends_an_email") do + VCR.use_cassette("notify_renewal_pending_online_payment_sends_an_email") do described_class.run(registration: registration) end end diff --git a/spec/services/waste_carriers_engine/registration_completion_service_spec.rb b/spec/services/waste_carriers_engine/registration_completion_service_spec.rb index f375c7094..763cc2ab9 100644 --- a/spec/services/waste_carriers_engine/registration_completion_service_spec.rb +++ b/spec/services/waste_carriers_engine/registration_completion_service_spec.rb @@ -129,14 +129,14 @@ module WasteCarriersEngine transient_registration.save end - it "sends a pending worldpay confirmation email with notify" do - allow(Notify::RegistrationPendingWorldpayPaymentEmailService) + it "sends a pending online payment confirmation email with notify" do + allow(Notify::RegistrationPendingOnlinePaymentEmailService) .to receive(:run) .and_call_original registration = described_class.run(transient_registration) - expect(Notify::RegistrationPendingWorldpayPaymentEmailService) + expect(Notify::RegistrationPendingOnlinePaymentEmailService) .to have_received(:run) .with(registration: registration) .once @@ -146,7 +146,7 @@ module WasteCarriersEngine before do the_error = StandardError.new("Oops!") - allow(Notify::RegistrationPendingWorldpayPaymentEmailService) + allow(Notify::RegistrationPendingOnlinePaymentEmailService) .to receive(:run) .and_raise(the_error) diff --git a/spec/services/waste_carriers_engine/worldpay_service_spec.rb b/spec/services/waste_carriers_engine/worldpay_service_spec.rb index db1352e36..604594bda 100644 --- a/spec/services/waste_carriers_engine/worldpay_service_spec.rb +++ b/spec/services/waste_carriers_engine/worldpay_service_spec.rb @@ -103,7 +103,7 @@ module WasteCarriersEngine stub_request(:any, /.*#{host}.*/).to_return( status: 200, - body: File.read("./spec/fixtures/worldpay_initial_request.xml") + body: File.read("./spec/fixtures/files/worldpay/worldpay_initial_request.xml") ) end @@ -125,7 +125,7 @@ module WasteCarriersEngine stub_request(:any, /.*#{host}.*/).to_return( status: 200, - body: File.read("./spec/fixtures/worldpay_initial_request_invalid.xml") + body: File.read("./spec/fixtures/files/worldpay/worldpay_initial_request_invalid.xml") ) end diff --git a/spec/services/waste_carriers_engine/worldpay_xml_service_spec.rb b/spec/services/waste_carriers_engine/worldpay_xml_service_spec.rb index 7ec7c6df5..aefbfd9ce 100644 --- a/spec/services/waste_carriers_engine/worldpay_xml_service_spec.rb +++ b/spec/services/waste_carriers_engine/worldpay_xml_service_spec.rb @@ -30,7 +30,7 @@ module WasteCarriersEngine describe "#build_xml" do it "returns correctly-formatted XML" do - xml = File.read("./spec/fixtures/files/request_to_worldpay.xml") + xml = File.read("./spec/fixtures/files/worldpay/request_to_worldpay.xml") expect(worldpay_xml_service.build_xml).to eq(xml) end