Module: StripeMock::RequestHandlers::Helpers

Included in:
Instance
Defined in:
lib/stripe_mock/request_handlers/helpers/card_helpers.rb,
lib/stripe_mock/request_handlers/helpers/token_helpers.rb,
lib/stripe_mock/request_handlers/helpers/charge_helpers.rb,
lib/stripe_mock/request_handlers/helpers/coupon_helpers.rb,
lib/stripe_mock/request_handlers/helpers/search_helpers.rb,
lib/stripe_mock/request_handlers/helpers/bank_account_helpers.rb,
lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb,
lib/stripe_mock/request_handlers/helpers/external_account_helpers.rb

Constant Summary collapse

QUERYSTRING_PATTERN =

Only supports exact matches on a single field, e.g.

/\A(?<field>[\w\.]+)(\[['"](?<metadata_key>[^'"]*)['"]\])?:['"]?(?<value>[^'"]*)['"]?\z/

Instance Method Summary collapse

Instance Method Details

#add_card_to(type, type_id, params, objects) ⇒ Object



104
105
106
107
108
109
# File 'lib/stripe_mock/request_handlers/helpers/card_helpers.rb', line 104

def add_card_to(type, type_id, params, objects)
  resource = assert_existence type, type_id, objects[type_id]

  card = card_from_params(params[:card] || params[:source] || params[:external_accounts])
  add_card_to_object(type, card, resource)
end

#add_card_to_object(type, card, object, replace_current = false) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/stripe_mock/request_handlers/helpers/card_helpers.rb', line 37

def add_card_to_object(type, card, object, replace_current=false)
  card[type] = object[:id]
  cards_or_sources = object[:cards] || object[:sources] || object[:external_accounts]

  is_customer = object.has_key?(:sources)

  if replace_current && cards_or_sources[:data]
    cards_or_sources[:data].delete_if {|card| card[:id] == object[:default_card]}
    object[:default_card]   = card[:id] unless is_customer
    object[:default_source] = card[:id] if is_customer
    cards_or_sources[:data] = [card]
  else
    cards_or_sources[:total_count] = (cards_or_sources[:total_count] || 0) + 1
    (cards_or_sources[:data] ||= []) << card
  end

  object[:default_card]   = card[:id] if !is_customer && object[:default_card].nil?
  object[:default_source] = card[:id] if is_customer  && object[:default_source].nil?

  card
end

#add_coupon_to_object(object, coupon) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
# File 'lib/stripe_mock/request_handlers/helpers/coupon_helpers.rb', line 4

def add_coupon_to_object(object, coupon)
  discount_attrs = {}.tap do |attrs|
    attrs[object[:object]]         = object[:id]
    attrs[:coupon]                 = coupon
    attrs[:start]                  = Time.now.to_i
    attrs[:end]                    = (DateTime.now >> coupon[:duration_in_months].to_i).to_time.to_i if coupon[:duration] == 'repeating'
    attrs[:id]                     = new_id("di")
  end

  object[:discount] = Stripe::Discount.construct_from(discount_attrs)
  object
end

#add_external_account_to(type, type_id, params, objects) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/stripe_mock/request_handlers/helpers/external_account_helpers.rb', line 5

def (type, type_id, params, objects)
  resource = assert_existence type, type_id, objects[type_id]

  source =
    if params[:card]
      card_from_params(params[:card])
    elsif params[:bank_account]
      bank_from_params(params[:bank_account])
    else
      begin
        get_card_by_token(params[:external_account])
      rescue Stripe::InvalidRequestError
        bank_from_params(params[:external_account])
      end
    end
  (type, source, resource)
end

#add_external_account_to_object(type, source, object, replace_current = false) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/stripe_mock/request_handlers/helpers/external_account_helpers.rb', line 23

def (type, source, object, replace_current=false)
  source[type] = object[:id]
  accounts = object[:external_accounts]

  if replace_current && accounts[:data]
    accounts[:data].delete_if {|source| source[:id] == object[:default_source]}
    object[:default_source] = source[:id]
    accounts[:data] = [source]
  else
    accounts[:total_count] = (accounts[:total_count] || 0) + 1
    (accounts[:data] ||= []) << source
  end
  object[:default_source] = source[:id] if object[:default_source].nil?

  source
end

#add_refund_to_charge(refund, charge) ⇒ Object



5
6
7
8
9
10
11
12
# File 'lib/stripe_mock/request_handlers/helpers/charge_helpers.rb', line 5

def add_refund_to_charge(refund, charge)
  refunds = charge[:refunds]
  refunds[:data] << refund
  refunds[:total_count] = refunds[:data].count

  charge[:amount_refunded] = refunds[:data].reduce(0) {|sum, r| sum + r[:amount].to_i }
  charge[:refunded] = true
end

#add_source_to(type, type_id, params, objects) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/stripe_mock/request_handlers/helpers/card_helpers.rb', line 85

def add_source_to(type, type_id, params, objects)
  resource = assert_existence type, type_id, objects[type_id]

  source =
    if params[:card]
      card_from_params(params[:card])
    elsif params[:bank_account]
      get_bank_by_token(params[:bank_account])
    else
      begin
        get_card_by_token(params[:source])
      rescue Stripe::InvalidRequestError
        get_bank_by_token(params[:source])
      end
    end
  source[:metadata].merge!(params[:metadata]) if params[:metadata]
  add_source_to_object(type, source, resource)
end

#add_source_to_object(type, source, object, replace_current = false) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/stripe_mock/request_handlers/helpers/card_helpers.rb', line 20

def add_source_to_object(type, source, object, replace_current=false)
  source[type] = object[:id]
  sources = object[:sources]

  if replace_current && sources[:data]
    sources[:data].delete_if {|source| source[:id] == object[:default_source]}
    object[:default_source] = source[:id]
    sources[:data] = [source]
  else
    sources[:total_count] = (sources[:total_count] || 0) + 1
    (sources[:data] ||= []) << source
  end
  object[:default_source] = source[:id] if object[:default_source].nil?

  source
end

#add_subscription_to_customer(cus, sub) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb', line 62

def add_subscription_to_customer(cus, sub)
  if sub[:trial_end].nil? || sub[:trial_end] == "now"
    id = new_id('ch')
    charges[id] = Data.mock_charge(
      :id => id,
      :customer => cus[:id],
      :amount => (sub[:plan] ? sub[:plan][:amount] : total_items_amount(sub[:items][:data]))
    )
  end

  if cus[:currency].nil?
    cus[:currency] = sub[:items][:data][0][:plan][:currency]
  elsif cus[:currency] != sub[:items][:data][0][:plan][:currency]
    raise Stripe::InvalidRequestError.new( "Can't combine currencies on a single customer. This customer has had a subscription, coupon, or invoice item with currency #{cus[:currency]}", 'currency', http_status: 400)
  end
  cus[:subscriptions][:total_count] = (cus[:subscriptions][:total_count] || 0) + 1
  cus[:subscriptions][:data].unshift sub
end

#bank_from_params(attrs_or_token) ⇒ Object



40
41
42
43
44
45
# File 'lib/stripe_mock/request_handlers/helpers/external_account_helpers.rb', line 40

def bank_from_params(attrs_or_token)
  if attrs_or_token.is_a? Hash
    attrs_or_token = generate_bank_token(attrs_or_token)
  end
  get_bank_by_token(attrs_or_token)
end

#card_from_params(attrs_or_token) ⇒ Object



118
119
120
121
122
123
124
# File 'lib/stripe_mock/request_handlers/helpers/card_helpers.rb', line 118

def card_from_params(attrs_or_token)
  if attrs_or_token.is_a? Hash
    attrs_or_token = generate_card_token(attrs_or_token)
  end
  card = get_card_by_token(attrs_or_token)
  validate_card(card)
end

#custom_subscription_params(plans, cus, options = {}) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb', line 30

def custom_subscription_params(plans, cus, options = {})
  verify_trial_end(options[:trial_end]) if options[:trial_end]

  plan = plans.first if plans.size == 1

  now = Time.now.utc.to_i
  created_time = options[:created] || now
  start_time = options[:current_period_start] || now
  params = { customer: cus[:id], current_period_start: start_time, created: created_time }
  params.merge!({ :plan => (plans.size == 1 ? plans.first : nil) })
  keys_to_merge = /application_fee_percent|quantity|metadata|tax_percent|billing|days_until_due|default_tax_rates|pending_invoice_item_interval|default_payment_method|collection_method/
  params.merge! options.select {|k,v| k =~ keys_to_merge}

  if options[:cancel_at_period_end] == true
    params.merge!(cancel_at_period_end: true, canceled_at: now)
  elsif options[:cancel_at_period_end] == false
    params.merge!(cancel_at_period_end: false, canceled_at: nil)
  end

  # TODO: Implement coupon logic

  if (((plan && plan[:trial_period_days]) || 0) == 0 && options[:trial_end].nil?) || options[:trial_end] == "now"
    end_time = options[:billing_cycle_anchor] || get_ending_time(start_time, plan)
    params.merge!({status: 'active', current_period_end: end_time, trial_start: nil, trial_end: nil, billing_cycle_anchor: options[:billing_cycle_anchor] || created_time})
  else
    end_time = options[:trial_end] || (Time.now.utc.to_i + plan[:trial_period_days]*86400)
    params.merge!({status: 'trialing', current_period_end: end_time, trial_start: start_time, trial_end: end_time, billing_cycle_anchor: options[:billing_cycle_anchor] || created_time})
  end

  params
end

#delete_card_from(type, type_id, card_id, objects) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/stripe_mock/request_handlers/helpers/card_helpers.rb', line 66

def delete_card_from(type, type_id, card_id, objects)
  resource = assert_existence type, type_id, objects[type_id]

  assert_existence :card, card_id, get_card(resource, card_id)

  card = { id: card_id, deleted: true }
  cards_or_sources = resource[:cards] || resource[:sources] || resource[:external_accounts]
  cards_or_sources[:data].reject!{|cc|
    cc[:id] == card[:id]
  }

  is_customer = resource.has_key?(:sources)
  new_default = cards_or_sources[:data].count > 0 ? cards_or_sources[:data].first[:id] : nil
  resource[:default_card]   = new_default unless is_customer
  resource[:sources][:total_count] = cards_or_sources[:data].count if is_customer
  resource[:default_source] = new_default if is_customer
  card
end

#delete_coupon_from_object(object) ⇒ Object



17
18
19
20
# File 'lib/stripe_mock/request_handlers/helpers/coupon_helpers.rb', line 17

def delete_coupon_from_object(object)
  object[:discount] = nil
  object
end

#delete_subscription_from_customer(cus, subscription) ⇒ Object



81
82
83
84
85
86
# File 'lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb', line 81

def delete_subscription_from_customer(cus, subscription)
  cus[:subscriptions][:data].reject!{|sub|
    sub[:id] == subscription[:id]
  }
  cus[:subscriptions][:total_count] -=1
end

#exact_match?(actual:, expected:) ⇒ Boolean

Returns:

  • (Boolean)


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/stripe_mock/request_handlers/helpers/search_helpers.rb', line 42

def exact_match?(actual:, expected:)
  # allow comparisons of integers
  if actual.respond_to?(:to_i) && actual.to_i == actual
    expected = expected.to_i
  end
  # allow comparisons of boolean
  case expected
  when "true"
    expected = true
  when "false"
    expected = false
  end

  actual == expected
end

#field_value(resource, field:) ⇒ Object



58
59
60
61
62
63
64
# File 'lib/stripe_mock/request_handlers/helpers/search_helpers.rb', line 58

def field_value(resource, field:)
  value = resource
  field.split('.').each do |segment|
    value = value[segment.to_sym]
  end
  value
end

#filter_by_timestamp(subscriptions, field:, value:) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb', line 129

def filter_by_timestamp(subscriptions, field:, value:)
  if value.is_a?(Hash)
    operator_mapping = { gt: :>, gte: :>=, lt: :<, lte: :<= }
    subscriptions.filter do |sub|
      sub[field].public_send(operator_mapping[value.keys[0]], value.values[0])
    end
  else
    subscriptions.filter do |sub|
      sub[field] == value
    end
  end
end

#generate_bank_token(bank_params = {}) ⇒ Object



5
6
7
8
9
10
# File 'lib/stripe_mock/request_handlers/helpers/token_helpers.rb', line 5

def generate_bank_token(bank_params = {})
  token = new_id 'btok'
  bank_params[:id] = new_id 'bank_account'
  @bank_tokens[token] = Data. bank_params
  token
end

#generate_card_token(card_params = {}) ⇒ Object



12
13
14
15
16
17
# File 'lib/stripe_mock/request_handlers/helpers/token_helpers.rb', line 12

def generate_card_token(card_params = {})
  token = new_id 'tok'
  card_params[:id] = new_id 'cc'
  @card_tokens[token] = Data.mock_card symbolize_names(card_params)
  token
end

#get_bank_by_token(token) ⇒ Object



19
20
21
22
23
24
25
# File 'lib/stripe_mock/request_handlers/helpers/token_helpers.rb', line 19

def get_bank_by_token(token)
  if token.nil? || @bank_tokens[token].nil?
    Data.
  else
    @bank_tokens.delete(token)
  end
end

#get_card(object, card_id, class_name = 'Customer') ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/stripe_mock/request_handlers/helpers/card_helpers.rb', line 5

def get_card(object, card_id, class_name='Customer')
  cards = object[:cards] || object[:sources] || object[:external_accounts]
  card = cards[:data].find{|cc| cc[:id] == card_id }
  if card.nil?
    if class_name == 'Recipient'
      msg = "#{class_name} #{object[:id]} does not have a card with ID #{card_id}"
      raise Stripe::InvalidRequestError.new(msg, 'card', http_status: 404)
    else
      msg = "There is no source with ID #{card_id}"
      raise Stripe::InvalidRequestError.new(msg, 'id', http_status: 404)
    end
  end
  card
end

#get_card_by_token(token) ⇒ Object



27
28
29
30
31
32
33
34
35
# File 'lib/stripe_mock/request_handlers/helpers/token_helpers.rb', line 27

def get_card_by_token(token)
  if token.nil? || @card_tokens[token].nil?
    # TODO: Make this strict
    msg = "Invalid token id: #{token}"
    raise Stripe::InvalidRequestError.new(msg, 'tok', http_status: 404)
  else
    @card_tokens.delete(token)
  end
end

#get_card_or_bank_by_token(token) ⇒ Object



37
38
39
40
# File 'lib/stripe_mock/request_handlers/helpers/token_helpers.rb', line 37

def get_card_or_bank_by_token(token)
  token_id = token['id'] || token
  @card_tokens[token_id] || @bank_tokens[token_id] || raise(Stripe::InvalidRequestError.new("Invalid token id: #{token_id}", 'tok', http_status: 404))
end

#get_customer_subscription(customer, sub_id) ⇒ Object



5
6
7
# File 'lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb', line 5

def get_customer_subscription(customer, sub_id)
  customer[:subscriptions][:data].find{|sub| sub[:id] == sub_id }
end

#get_ending_time(start_time, plan, intervals = 1) ⇒ Object

‘intervals` is set to 1 when calculating current_period_end from current_period_start & plan `intervals` is set to 2 when calculating Stripe::Invoice.upcoming end from current_period_start & plan



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb', line 90

def get_ending_time(start_time, plan, intervals = 1)
  return start_time unless plan

  interval = plan[:interval] || plan.dig(:recurring, :interval)
  interval_count = plan[:interval_count] || plan.dig(:recurring, :interval_count) || 1
  case interval
  when "week"
    start_time + (604800 * (interval_count) * intervals)
  when "month"
    (Time.at(start_time).to_datetime >> ((interval_count) * intervals)).to_time.to_i
  when "year"
    (Time.at(start_time).to_datetime >> (12 * intervals)).to_time.to_i # max period is 1 year
  else
    start_time
  end
end

#resolve_subscription_changes(subscription, plans, customer, options = {}) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb', line 9

def resolve_subscription_changes(subscription, plans, customer, options = {})
  subscription.merge!(custom_subscription_params(plans, customer, options))
  items = options[:items]
  items = items.values if items.respond_to?(:values)
  subscription[:items][:data] = plans.map do |plan|
    matching_item = items && items.detect { |item| [item[:price], item[:plan]].include? plan[:id] }
    if matching_item
      matching_item[:quantity] ||= 1
      matching_item[:id] ||= new_id('si')
      params = matching_item.merge(plan: plan)
      params[:price] = plan if plan[:object] == "price"
      Data.mock_subscription_item(params)
    else
      params = { plan: plan, id: new_id('si') }
      params[:price] = plan if plan[:object] == "price"
      Data.mock_subscription_item(params)
    end
  end
  subscription
end

#retrieve_object_cards(type, type_id, objects) ⇒ Object



59
60
61
62
63
64
# File 'lib/stripe_mock/request_handlers/helpers/card_helpers.rb', line 59

def retrieve_object_cards(type, type_id, objects)
  resource = assert_existence type, type_id, objects[type_id]
  cards = resource[:cards] || resource[:sources] || resource[:external_accounts]

  Data.mock_list_object(cards[:data])
end

#search_results(all_values, querystring, fields: [], resource_name:) ⇒ Object

Raises:

  • (Stripe::InvalidRequestError)


10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/stripe_mock/request_handlers/helpers/search_helpers.rb', line 10

def search_results(all_values, querystring, fields: [], resource_name:)
  values = all_values.dup
  query_match = QUERYSTRING_PATTERN.match(querystring)
  raise Stripe::InvalidRequestError.new(
    'We were unable to parse your search query.' \
      ' Try using the format `metadata["key"]:"value"` to query for metadata or key:"value" to query for other fields.',
    nil,
    http_status: 400,
  ) unless query_match

  case query_match[:field]
  when *fields
    values = values.select { |resource|
      exact_match?(actual: field_value(resource, field: query_match[:field]), expected: query_match[:value])
    }
  when "metadata"
    values = values.select { |resource|
      resource[:metadata] &&
        exact_match?(actual: resource[:metadata][query_match[:metadata_key].to_sym], expected: query_match[:value])
    }
  else
    raise Stripe::InvalidRequestError.new(
      "Field `#{query_match[:field]}` is an unsupported search field for resource `#{resource_name}`." \
        " See http://stripe.com/docs/search#query-fields-for-#{resource_name.gsub('_', '-')} for a list of supported fields.",
      nil,
      http_status: 400,
    )
  end

  values
end

#total_items_amount(items) ⇒ Object



119
120
121
122
123
124
125
126
127
# File 'lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb', line 119

def total_items_amount(items)
  total = 0
  items.each do |item|
    quantity = item[:quantity] || 1
    amount = item[:plan][:unit_amount] || item[:plan][:amount]
    total += quantity * amount
  end
  total
end

#validate_card(card) ⇒ Object



111
112
113
114
115
116
# File 'lib/stripe_mock/request_handlers/helpers/card_helpers.rb', line 111

def validate_card(card)
  [:exp_month, :exp_year].each do |field|
    card[field] = card[field].to_i
  end
  card
end

#verify_bank_account(object, bank_account_id, class_name = 'Customer') ⇒ Object



5
6
7
8
9
10
11
# File 'lib/stripe_mock/request_handlers/helpers/bank_account_helpers.rb', line 5

def (object, , class_name='Customer')
  bank_accounts = object[:external_accounts] || object[:bank_accounts] || object[:sources]
   = bank_accounts[:data].find{|acc| acc[:id] ==  }
  return if .nil?
  ['status'] = 'verified'
  
end

#verify_trial_end(trial_end) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
# File 'lib/stripe_mock/request_handlers/helpers/subscription_helpers.rb', line 107

def verify_trial_end(trial_end)
  if trial_end != "now"
    if !trial_end.is_a? Integer
      raise Stripe::InvalidRequestError.new('Invalid timestamp: must be an integer', nil, http_status: 400)
    elsif trial_end < Time.now.utc.to_i
      raise Stripe::InvalidRequestError.new('Invalid timestamp: must be an integer Unix timestamp in the future', nil, http_status: 400)
    elsif trial_end > Time.now.utc.to_i + 31557600*5 # five years
      raise Stripe::InvalidRequestError.new('Invalid timestamp: can be no more than five years in the future', nil, http_status: 400)
    end
  end
end