require File.dirname(__FILE__) + "/../spec_helper"

describe Braintree::Subscription do
  TrialPlan = {
    :description => "Plan for integration tests -- with trial",
    :id => "integration_trial_plan",
    :price => BigDecimal.new("43.21"),
    :trial_period => true,
    :trial_duration => 2,
    :trial_duration_unit => Braintree::Subscription::TrialDurationUnit::Day
  }

  TriallessPlan = {
    :description => "Plan for integration tests -- without a trial",
    :id => "integration_trialless_plan",
    :price => BigDecimal.new("12.34"),
    :trial_period => false
  }

  before(:each) do
    @credit_card = Braintree::Customer.create!(
      :credit_card => {
        :number => Braintree::Test::CreditCardNumbers::Visa,
        :expiration_date => "05/2010"
      }
    ).credit_cards[0]
  end

  describe "self.create" do
    it "is successful with a miniumum of params" do
      result = Braintree::Subscription.create(
        :payment_method_token => @credit_card.token,
        :plan_id => TriallessPlan[:id]
      )

      date_format = /^\d{4}\D\d{1,2}\D\d{1,2}$/
      result.success?.should == true
      result.subscription.id.should =~ /^\w{6}$/
      result.subscription.status.should == Braintree::Subscription::Status::Active
      result.subscription.plan_id.should == "integration_trialless_plan"

      result.subscription.first_billing_date.should match(date_format)
      result.subscription.next_billing_date.should match(date_format)
      result.subscription.billing_period_start_date.should match(date_format)
      result.subscription.billing_period_end_date.should match(date_format)

      result.subscription.failure_count.should == 0
      result.subscription.payment_method_token.should == @credit_card.token
    end

    it "can set the id" do
      new_id = rand(36**9).to_s(36)
      result = Braintree::Subscription.create(
        :payment_method_token => @credit_card.token,
        :plan_id => TriallessPlan[:id],
        :id => new_id
      )

      date_format = /^\d{4}\D\d{1,2}\D\d{1,2}$/
      result.success?.should == true
      result.subscription.id.should == new_id
    end

    context "merchant_account_id" do
      it "defaults to the default merchant account if no merchant_account_id is provided" do
        result = Braintree::Subscription.create(
          :payment_method_token => @credit_card.token,
          :plan_id => TriallessPlan[:id]
        )

        result.success?.should == true
        result.subscription.merchant_account_id.should == SpecHelper::DefaultMerchantAccountId
      end

      it "allows setting the merchant_account_id" do
        result = Braintree::Subscription.create(
          :payment_method_token => @credit_card.token,
          :plan_id => TriallessPlan[:id],
          :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId
        )

        result.success?.should == true
        result.subscription.merchant_account_id.should == SpecHelper::NonDefaultMerchantAccountId
      end
    end

    context "trial period" do
      context "defaults to the plan's trial period settings" do
        it "with no trial" do
          result = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          )

          result.subscription.trial_period.should == false
          result.subscription.trial_duration.should == nil
          result.subscription.trial_duration_unit.should == nil
        end

        it "with a trial" do
          result = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TrialPlan[:id]
          )

          result.subscription.trial_period.should == true
          result.subscription.trial_duration.should == 2
          result.subscription.trial_duration_unit.should == Braintree::Subscription::TrialDurationUnit::Day
        end

        it "can alter the trial period params" do
          result = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TrialPlan[:id],
            :trial_duration => 5,
            :trial_duration_unit => Braintree::Subscription::TrialDurationUnit::Month
          )

          result.subscription.trial_period.should == true
          result.subscription.trial_duration.should == 5
          result.subscription.trial_duration_unit.should == Braintree::Subscription::TrialDurationUnit::Month
        end

        it "can override the trial_period param" do
          result = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TrialPlan[:id],
            :trial_period => false
          )

          result.subscription.trial_period.should == false
        end

        it "doesn't create a transaction if there's a trial period" do
          result = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TrialPlan[:id]
          )

          result.subscription.transactions.size.should == 0
        end
      end

      context "no trial period" do
        it "creates a transaction if no trial period" do
          result = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          )

          result.subscription.transactions.size.should == 1
          result.subscription.transactions.first.should be_a(Braintree::Transaction)
          result.subscription.transactions.first.amount.should == TriallessPlan[:price]
          result.subscription.transactions.first.type.should == Braintree::Transaction::Type::Sale
          result.subscription.transactions.first.subscription_id.should == result.subscription.id
        end

        it "does not create the subscription and returns the transaction if the transaction is not successful" do
          result = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id],
            :price => Braintree::Test::TransactionAmounts::Decline
          )

          result.success?.should be_false
          result.transaction.status.should == Braintree::Transaction::Status::ProcessorDeclined
          result.message.should == "Do Not Honor"
        end
      end

      context "price" do
        it "defaults to the plan's price" do
          result = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TrialPlan[:id]
          )

          result.subscription.price.should == TrialPlan[:price]
        end

        it "can be overridden" do
          result = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TrialPlan[:id],
            :price => 98.76
          )

          result.subscription.price.should == BigDecimal.new("98.76")
        end
      end
    end

    context "validation errors" do
      it "has validation errors on id" do
        result = Braintree::Subscription.create(
          :payment_method_token => @credit_card.token,
          :plan_id => TrialPlan[:id],
          :id => "invalid token"
        )
        result.success?.should == false
        result.errors.for(:subscription).on(:id)[0].message.should == "ID is invalid (use only letters, numbers, '-', and '_')."
      end

      it "has validation errors on duplicate id" do
        duplicate_token = "duplicate_token_#{rand(36**8).to_s(36)}"
        result = Braintree::Subscription.create(
          :payment_method_token => @credit_card.token,
          :plan_id => TrialPlan[:id],
          :id => duplicate_token
        )
        result.success?.should == true

        result = Braintree::Subscription.create(
          :payment_method_token => @credit_card.token,
          :plan_id => TrialPlan[:id],
          :id => duplicate_token
        )
        result.success?.should == false
        result.errors.for(:subscription).on(:id)[0].message.should == "ID has already been taken."
      end

      it "trial duration required" do
        result = Braintree::Subscription.create(
          :payment_method_token => @credit_card.token,
          :plan_id => TrialPlan[:id],
          :trial_period => true,
          :trial_duration => nil
        )
        result.success?.should == false
        result.errors.for(:subscription).on(:trial_duration)[0].message.should == "Trial Duration is required."
      end

      it "trial duration unit required" do
        result = Braintree::Subscription.create(
          :payment_method_token => @credit_card.token,
          :plan_id => TrialPlan[:id],
          :trial_period => true,
          :trial_duration => 2,
          :trial_duration_unit => nil
        )
        result.success?.should == false
        result.errors.for(:subscription).on(:trial_duration_unit)[0].message.should == "Trial Duration Unit is invalid."
      end
    end
  end

  describe "self.find" do
    it "finds a subscription" do
      result = Braintree::Subscription.create(
        :payment_method_token => @credit_card.token,
        :plan_id => TriallessPlan[:id]
      )
      result.success?.should == true

      Braintree::Subscription.find(result.subscription.id).should == result.subscription
    end

    it "raises Braintree::NotFoundError if it cannot find" do
      expect {
        Braintree::Subscription.find('noSuchSubscription')
      }.to raise_error(Braintree::NotFoundError, 'subscription with id "noSuchSubscription" not found')
    end
  end

  describe "self.update" do
    before(:each) do
      @subscription = Braintree::Subscription.create(
        :payment_method_token => @credit_card.token,
        :price => 54.32,
        :plan_id => TriallessPlan[:id]
      ).subscription
    end

    it "allows changing the merchant_account_id" do
      result = Braintree::Subscription.update(@subscription.id,
        :merchant_account_id => SpecHelper::NonDefaultMerchantAccountId
      )

      result.success?.should == true
      result.subscription.merchant_account_id.should == SpecHelper::NonDefaultMerchantAccountId
    end

    it "allows changing the payment_method_token" do
      new_credit_card = Braintree::CreditCard.create!(
        :customer_id => @credit_card.customer_id,
        :number => Braintree::Test::CreditCardNumbers::Visa,
        :expiration_date => "05/2010"
      )

      result = Braintree::Subscription.update(@subscription.id,
        :payment_method_token => new_credit_card.token
      )

      result.subscription.payment_method_token.should == new_credit_card.token
    end

    context "when successful" do
      it "returns a success response with the updated subscription if valid" do
        new_id = rand(36**9).to_s(36)
        result = Braintree::Subscription.update(@subscription.id,
          :id => new_id,
          :price => 9999.88,
          :plan_id => TrialPlan[:id]
        )

        result.success?.should == true
        result.subscription.id.should =~ /#{new_id}/
        result.subscription.plan_id.should == TrialPlan[:id]
        result.subscription.price.should == BigDecimal.new("9999.88")
      end

      it "prorates if there is a charge (because merchant has proration option enabled in control panel)" do
        result = Braintree::Subscription.update(@subscription.id,
          :price => @subscription.price.to_f + 1
        )

        result.success?.should == true
        result.subscription.price.to_f.should == @subscription.price.to_f + 1
        result.subscription.transactions.size.should == @subscription.transactions.size + 1
      end

      it "doesn't prorate if price decreases" do
        result = Braintree::Subscription.update(@subscription.id,
          :price => @subscription.price.to_f - 1
        )

        result.success?.should == true
        result.subscription.price.to_f.should == @subscription.price.to_f - 1
        result.subscription.transactions.size.should == @subscription.transactions.size
      end
    end

    context "when unsuccessful" do
      before(:each) do
        @subscription = Braintree::Subscription.create(
          :payment_method_token => @credit_card.token,
          :plan_id => TrialPlan[:id]
        ).subscription
      end

      it "raises NotFoundError if the subscription can't be found" do
        expect {
          Braintree::Subscription.update(rand(36**9).to_s(36),
            :price => 58.20
          )
        }.to raise_error(Braintree::NotFoundError)
      end

      it "has validation errors on id" do
        result = Braintree::Subscription.update(@subscription.id, :id => "invalid token")
        result.success?.should == false
        result.errors.for(:subscription).on(:id)[0].code.should == Braintree::ErrorCodes::Subscription::TokenFormatIsInvalid
      end

      it "has a price" do
        result = Braintree::Subscription.update(@subscription.id, :price => "")
        result.success?.should == false
        result.errors.for(:subscription).on(:price)[0].code.should == Braintree::ErrorCodes::Subscription::PriceCannotBeBlank
      end

      it "has a properly formatted price" do
        result = Braintree::Subscription.update(@subscription.id, :price => "9.2.1 apples")
        result.success?.should == false
        result.errors.for(:subscription).on(:price)[0].code.should == Braintree::ErrorCodes::Subscription::PriceFormatIsInvalid
      end

      it "has validation errors on duplicate id" do
        duplicate_id = "new_id_#{rand(36**6).to_s(36)}"
        duplicate = Braintree::Subscription.create(
          :payment_method_token => @credit_card.token,
          :plan_id => TrialPlan[:id],
          :id => duplicate_id
        )
        result = Braintree::Subscription.update(
          @subscription.id,
          :id => duplicate_id
        )
        result.success?.should == false
        result.errors.for(:subscription).on(:id)[0].code.should == Braintree::ErrorCodes::Subscription::IdIsInUse
      end

      it "cannot update a canceled subscription" do
        subscription = Braintree::Subscription.create(
          :payment_method_token => @credit_card.token,
          :price => 54.32,
          :plan_id => TriallessPlan[:id]
        ).subscription

        result = Braintree::Subscription.cancel(subscription.id)
        result.success?.should == true

        result = Braintree::Subscription.update(subscription.id,
          :price => 123.45
        )
        result.success?.should == false
        result.errors.for(:subscription)[0].code.should == Braintree::ErrorCodes::Subscription::CannotEditCanceledSubscription
      end
    end
  end


  describe "self.cancel" do
    it "returns a success response with the updated subscription if valid" do
      subscription = Braintree::Subscription.create(
        :payment_method_token => @credit_card.token,
        :price => 54.32,
        :plan_id => TriallessPlan[:id]
      ).subscription

      result = Braintree::Subscription.cancel(subscription.id)
      result.success?.should == true
      result.subscription.status.should == Braintree::Subscription::Status::Canceled
    end

    it "returns a validation error if record not found" do
      expect {
        r = Braintree::Subscription.cancel('noSuchSubscription')
      }.to raise_error(Braintree::NotFoundError, 'subscription with id "noSuchSubscription" not found')
    end

    it "cannot be canceled if already canceled" do
      subscription = Braintree::Subscription.create(
        :payment_method_token => @credit_card.token,
        :price => 54.32,
        :plan_id => TriallessPlan[:id]
      ).subscription

      result = Braintree::Subscription.cancel(subscription.id)
      result.success?.should == true
      result.subscription.status.should == Braintree::Subscription::Status::Canceled

      result = Braintree::Subscription.cancel(subscription.id)
      result.success?.should == false
      result.errors.for(:subscription)[0].code.should == "81905"
    end
  end

  describe "self.search" do
    context "search_fields" do
      it "correctly returns a result with no matches" do
        collection = Braintree::Subscription.search do |search|
          search.plan_id.is "not_a_real_plan_id"
        end

        collection.maximum_size.should == 0
      end

      context "is statement" do
        it "returns resource collection with matching results" do
          trialless_subscription = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          trial_subscription = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TrialPlan[:id]
          ).subscription

          collection = Braintree::Subscription.search do |search|
            search.plan_id.is TriallessPlan[:id]
          end

          collection.should include(trialless_subscription)
          collection.should_not include(trial_subscription)
        end
      end

      context "is_not statement" do
        it "returns resource collection without matching results" do
          trialless_subscription = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          trial_subscription = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TrialPlan[:id]
          ).subscription

          collection = Braintree::Subscription.search do |search|
            search.plan_id.is_not TriallessPlan[:id]
          end

          collection.should_not include(trialless_subscription)
          collection.should include(trial_subscription)
        end
      end

      context "ends_with statement" do
        it "returns resource collection with matching results" do
          trialless_subscription = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          trial_subscription = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TrialPlan[:id]
          ).subscription

          collection = Braintree::Subscription.search do |search|
            search.plan_id.ends_with "trial_plan"
          end

          collection.should include(trial_subscription)
          collection.should_not include(trialless_subscription)
        end
      end

      context "starts_with statement" do
        it "returns resource collection with matching results" do
          trialless_subscription = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          trial_subscription = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TrialPlan[:id]
          ).subscription

          collection = Braintree::Subscription.search do |search|
            search.plan_id.starts_with "integration_trial_p"
          end

          collection.should include(trial_subscription)
          collection.should_not include(trialless_subscription)
        end
      end

      context "contains statement" do
        it "returns resource collection with matching results" do
          trialless_subscription = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          trial_subscription = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TrialPlan[:id]
          ).subscription

          collection = Braintree::Subscription.search do |search|
            search.plan_id.contains "trial_p"
          end

          collection.should include(trial_subscription)
          collection.should_not include(trialless_subscription)
        end
      end
    end

    context "multiple_value_fields" do
      context "in" do
        it "matches all values if none are specified" do
          subscription1 = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          subscription2 = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          Braintree::Subscription.cancel(subscription2.id)

          collection = Braintree::Subscription.search do |search|
            search.plan_id.is TriallessPlan[:id]
          end

          collection.should include(subscription1)
          collection.should include(subscription2)
        end

        it "returns only matching results" do
          subscription1 = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          subscription2 = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          Braintree::Subscription.cancel(subscription2.id)

          collection = Braintree::Subscription.search do |search|
            search.status.in Braintree::Subscription::Status::Active
          end

          collection.should include(subscription1)
          collection.should_not include(subscription2)
        end

        it "returns only matching results given an argument list" do
          subscription1 = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          subscription2 = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          Braintree::Subscription.cancel(subscription2.id)

          collection = Braintree::Subscription.search do |search|
            search.status.in Braintree::Subscription::Status::Active, Braintree::Subscription::Status::Canceled
          end

          collection.should include(subscription1)
          collection.should include(subscription2)
        end

        it "returns only matching results given an array" do
          subscription1 = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          subscription2 = Braintree::Subscription.create(
            :payment_method_token => @credit_card.token,
            :plan_id => TriallessPlan[:id]
          ).subscription

          Braintree::Subscription.cancel(subscription2.id)

          collection = Braintree::Subscription.search do |search|
            search.status.in [Braintree::Subscription::Status::Active, Braintree::Subscription::Status::Canceled]
          end

          collection.should include(subscription1)
          collection.should include(subscription2)
        end
      end
    end

    it "returns multiple results" do
      (110 - Braintree::Subscription.search.maximum_size).times do
        Braintree::Subscription.create(:payment_method_token => @credit_card.token, :plan_id => TriallessPlan[:id])
      end

      collection = Braintree::Subscription.search
      collection.maximum_size.should > 100

      subscriptions_ids = collection.map {|t| t.id }.uniq.compact
      subscriptions_ids.size.should == collection.maximum_size
    end

  end

  describe "self.retry_charge" do
    it "is successful with only subscription id" do
      subscription = Braintree::Subscription.search do |search|
        search.status.in Braintree::Subscription::Status::PastDue
      end.first

      result = Braintree::Subscription.retry_charge(subscription.id)

      result.success?.should == true
      transaction = result.transaction

      transaction.amount.should == subscription.price
      transaction.processor_authorization_code.should_not be_nil
      transaction.type.should == Braintree::Transaction::Type::Sale
      transaction.status.should == Braintree::Transaction::Status::Authorized
    end

    it "is successful with subscription id and amount" do
      subscription = Braintree::Subscription.search do |search|
        search.status.in Braintree::Subscription::Status::PastDue
      end.first

      result = Braintree::Subscription.retry_charge(subscription.id, Braintree::Test::TransactionAmounts::Authorize)

      result.success?.should == true
      transaction = result.transaction

      transaction.amount.should == BigDecimal.new(Braintree::Test::TransactionAmounts::Authorize)
      transaction.processor_authorization_code.should_not be_nil
      transaction.type.should == Braintree::Transaction::Type::Sale
      transaction.status.should == Braintree::Transaction::Status::Authorized
    end
  end
end