# frozen_string_literal: true

require 'spec_helper'
require 'stripe/payment_intent_validator'

RSpec.describe Stripe::PaymentIntentValidator do
  let(:payment_method) {
    create(:stripe_sca_payment_method, distributor_ids: [create(:distributor_enterprise).id],
                                       preferred_enterprise_id: create(:enterprise).id)
  }

  let(:year_valid) { Time.zone.now.year.next }

  describe "#call", :vcr, :stripe_version do
    let(:payment) {
      create(:payment, amount: payment_intent.amount, payment_method:,
                       response_code: payment_intent.id, source: pm_card)
    }
    let(:validator) { Stripe::PaymentIntentValidator.new(payment) }

    describe "as a guest" do
      context "when payment intent is valid" do
        shared_examples "payments intents" do |card_type, card_number|
          context "from #{card_type}" do
            let!(:pm_card) do
              Stripe::PaymentMethod.create({
                                             type: 'card',
                                             card: {
                                               number: card_number,
                                               exp_month: 12,
                                               exp_year: year_valid,
                                               cvc: '314',
                                             },
                                           })
            end
            let!(:payment_intent) do
              Stripe::PaymentIntent.create({
                                             amount: 100,
                                             currency: 'eur',
                                             payment_method: pm_card,
                                             payment_method_types: ['card'],
                                             capture_method: 'manual',
                                           })
            end
            let(:payment_intent_response_body) {
              [id: payment_intent.id, status: payment_intent.status]
            }

            before do
              Stripe::PaymentIntent.confirm(payment_intent.id)
            end
            it "returns payment intent id and does not raise" do
              expect {
                result = validator.call
                expect(result).to eq payment_intent_response_body
              }.not_to raise_error Stripe::StripeError
            end

            it "captures the payment" do
              expect(Stripe::PaymentIntent.retrieve(
                payment_intent.id
              ).status).to eq("requires_capture")

              Stripe::PaymentIntent.capture(payment_intent.id)

              expect(Stripe::PaymentIntent.retrieve(
                payment_intent.id
              ).status).to eq("succeeded")
            end
          end
        end

        context "valid non-3D credit cards are correctly handled" do
          it_behaves_like "payments intents", "Visa", 4_242_424_242_424_242
          it_behaves_like "payments intents", "Visa (debit)", 4_000_056_655_665_556
          it_behaves_like "payments intents", "Mastercard", 5_555_555_555_554_444
          it_behaves_like "payments intents", "Mastercard (2-series)", 2_223_003_122_003_222
          it_behaves_like "payments intents", "Mastercard (debit)", 5_200_828_282_828_210
          it_behaves_like "payments intents", "Mastercard (prepaid)", 5_105_105_105_105_100
          it_behaves_like "payments intents", "American Express",  378_282_246_310_005
          it_behaves_like "payments intents", "American Express",  371_449_635_398_431
          it_behaves_like "payments intents", "Discover", 6_011_111_111_111_117
          it_behaves_like "payments intents", "Discover", 6_011_000_990_139_424
          it_behaves_like "payments intents", "Discover (debit)", 6_011_981_111_111_113
          it_behaves_like "payments intents", "Diners Club", 3_056_930_009_020_004
          it_behaves_like "payments intents", "Diners Club (14-digit card)", 36_227_206_271_667
          it_behaves_like "payments intents", "BCcard and DinaCard", 6_555_900_000_604_105
          it_behaves_like "payments intents", "JCB", 3_566_002_020_360_505
          it_behaves_like "payments intents", "UnionPay", 6_200_000_000_000_005
          it_behaves_like "payments intents", "UnionPay (19-digit card)", 6_205_500_000_000_000_004
        end

        xcontext "valid 3D cards are correctly handled" do
          pending("updating spec to handle 3D2S cards")
          it_behaves_like "payments intents", "UnionPay (debit)", 6_200_000_000_000_047
        end
      end
      context "when payment intent is invalid" do
        shared_examples "payments intents" do |card_type, card_number, error_message|
          context "from #{card_type}" do
            let!(:pm_card) do
              Stripe::PaymentMethod.create({
                                             type: 'card',
                                             card: {
                                               number: card_number,
                                               exp_month: 12,
                                               exp_year: year_valid,
                                               cvc: '314',
                                             },
                                           })
            end
            let(:payment_intent) do
              Stripe::PaymentIntent.create({
                                             amount: 100,
                                             currency: 'eur',
                                             payment_method: pm_card,
                                             payment_method_types: ['card'],
                                             capture_method: 'manual',
                                           })
            end
            it "raises Stripe error with payment intent last_payment_error as message" do
              expect {
                Stripe::PaymentIntent.confirm(payment_intent.id)
              }.to raise_error Stripe::StripeError, error_message
            end
          end
        end
        context "invalid credit cards are correctly handled" do
          it_behaves_like "payments intents", "Generic decline", 4_000_000_000_000_002,
                          "Your card was declined."
          it_behaves_like "payments intents", "Insufficient funds decline", 4_000_000_000_009_995,
                          "Your card has insufficient funds."
          it_behaves_like "payments intents", "Lost card decline", 4_000_000_000_009_987,
                          "Your card was declined."
          it_behaves_like "payments intents", "Stolen card decline", 4_000_000_000_009_979,
                          "Your card was declined."
          it_behaves_like "payments intents", "Expired card decline", 4_000_000_000_000_069,
                          "Your card has expired."
          it_behaves_like "payments intents", "Incorrect CVC decline", 4_000_000_000_000_127,
                          "Your card's security code is incorrect."
          it_behaves_like "payments intents", "Processing error decline", 4_000_000_000_000_119,
                          "An error occurred while processing your card. Try again in a little bit."
          it_behaves_like "payments intents", "Exceeding velocity limit decline",
                          4_000_000_000_006_975,
                          %(Your card was declined for making repeated attempts too frequently
            or exceeding its amount limit.).squish
        end
      end
    end

    describe "as a Stripe customer" do
      context "when payment intent is valid" do
        let(:payment_method_id) { pm_card.id }
        let(:customer_id) { customer.id }
        let!(:user) { create(:user, email: "apple.customer@example.com") }
        let(:credit_card) { create(:credit_card, gateway_payment_profile_id: pm_card.id, user:) }
        let(:customer) do
          Stripe::Customer.create({
                                    name: 'Apple Customer',
                                    email: 'applecustomer@example.com',
                                  })
        end

        shared_examples "payments intents" do |card_type, card_number|
          context "from #{card_type}" do
            let!(:pm_card) do
              Stripe::PaymentMethod.create({
                                             type: 'card',
                                             card: {
                                               number: card_number,
                                               exp_month: 12,
                                               exp_year: year_valid,
                                               cvc: '314',
                                             },
                                           })
            end
            let!(:payment_intent) do
              Stripe::PaymentIntent.create({
                                             amount: 100,
                                             currency: 'eur',
                                             payment_method: pm_card,
                                             payment_method_types: ['card'],
                                             capture_method: 'manual',
                                             customer: customer.id,
                                             setup_future_usage: "off_session"
                                           })
            end
            let(:payment_intent_response_body) {
              [id: payment_intent.id, status: payment_intent.status]
            }

            before do
              credit_card.update_attribute :gateway_customer_profile_id, customer_id
              Stripe::PaymentIntent.confirm(payment_intent.id)
            end
            it "returns payment intent id and does not raise" do
              expect {
                result = validator.call
                expect(result).to eq payment_intent_response_body
              }.not_to raise_error Stripe::StripeError
            end

            it "captures the payment" do
              expect(Stripe::PaymentIntent.retrieve(
                payment_intent.id
              ).status).to eq("requires_capture")

              Stripe::PaymentIntent.capture(payment_intent.id)

              expect(Stripe::PaymentIntent.retrieve(
                payment_intent.id
              ).status).to eq("succeeded")
            end
          end
        end

        context "valid non-3D credit cards are correctly handled" do
          it_behaves_like "payments intents", "Visa", 4_242_424_242_424_242
          it_behaves_like "payments intents", "Visa (debit)", 4_000_056_655_665_556
          it_behaves_like "payments intents", "Mastercard", 5_555_555_555_554_444
          it_behaves_like "payments intents", "Mastercard (2-series)", 2_223_003_122_003_222
          it_behaves_like "payments intents", "Mastercard (debit)", 5_200_828_282_828_210
          it_behaves_like "payments intents", "Mastercard (prepaid)", 5_105_105_105_105_100
          it_behaves_like "payments intents", "American Express",  378_282_246_310_005
          it_behaves_like "payments intents", "American Express",  371_449_635_398_431
          it_behaves_like "payments intents", "Discover", 6_011_111_111_111_117
          it_behaves_like "payments intents", "Discover", 6_011_000_990_139_424
          it_behaves_like "payments intents", "Discover (debit)", 6_011_981_111_111_113
          it_behaves_like "payments intents", "Diners Club", 3_056_930_009_020_004
          it_behaves_like "payments intents", "Diners Club (14-digit card)", 36_227_206_271_667
          it_behaves_like "payments intents", "BCcard and DinaCard", 6_555_900_000_604_105
          it_behaves_like "payments intents", "JCB", 3_566_002_020_360_505
          it_behaves_like "payments intents", "UnionPay", 6_200_000_000_000_005
          it_behaves_like "payments intents", "UnionPay (19-digit card)", 6_205_500_000_000_000_004
        end

        xcontext "valid 3D cards are correctly handled" do
          pending("updating spec to handle 3D2S cards")
          it_behaves_like "payments intents", "UnionPay (debit)", 6_200_000_000_000_047
        end
      end
      context "when payment intent is invalid" do
        let(:payment_method_id) { pm_card.id }
        let(:customer_id) { customer.id }
        let!(:user) { create(:user, email: "apple.customer@example.com") }
        let(:credit_card) { create(:credit_card, gateway_payment_profile_id: pm_card.id, user:) }
        let(:customer) do
          Stripe::Customer.create({
                                    name: 'Apple Customer',
                                    email: 'applecustomer@example.com',
                                  })
        end

        shared_examples "payments intents" do |card_type, card_number, error_message|
          context "from #{card_type}" do
            let!(:pm_card) do
              Stripe::PaymentMethod.create({
                                             type: 'card',
                                             card: {
                                               number: card_number,
                                               exp_month: 12,
                                               exp_year: year_valid,
                                               cvc: '314',
                                             },
                                           })
            end
            let(:payment_intent) do
              Stripe::PaymentIntent.create({
                                             amount: 100,
                                             currency: 'eur',
                                             payment_method: pm_card,
                                             payment_method_types: ['card'],
                                             capture_method: 'manual',
                                             customer: customer.id,
                                             setup_future_usage: "off_session"
                                           })
            end
            it "raises Stripe error with payment intent last_payment_error as message" do
              expect {
                Stripe::PaymentIntent.confirm(payment_intent.id)
              }.to raise_error Stripe::StripeError, error_message
            end
          end
        end
        context "invalid credit cards are correctly handled" do
          it_behaves_like "payments intents", "Generic decline", 4_000_000_000_000_002,
                          "Your card was declined."
          it_behaves_like "payments intents", "Insufficient funds decline", 4_000_000_000_009_995,
                          "Your card has insufficient funds."
          it_behaves_like "payments intents", "Lost card decline", 4_000_000_000_009_987,
                          "Your card was declined."
          it_behaves_like "payments intents", "Stolen card decline", 4_000_000_000_009_979,
                          "Your card was declined."
          it_behaves_like "payments intents", "Expired card decline", 4_000_000_000_000_069,
                          "Your card has expired."
          it_behaves_like "payments intents", "Incorrect CVC decline", 4_000_000_000_000_127,
                          "Your card's security code is incorrect."
          it_behaves_like "payments intents", "Processing error decline", 4_000_000_000_000_119,
                          "An error occurred while processing your card. Try again in a little bit."
          it_behaves_like "payments intents", "Exceeding velocity limit decline",
                          4_000_000_000_006_975,
                          %(Your card was declined for making repeated attempts too frequently
            or exceeding its amount limit.).squish
        end
      end
    end
  end
end
