<commit_before>CI.inner.Invoice = class Invoice extends CI.inner.Obj
  total: =>
    "$#{@amount_due / 100}"

  zeroize: (val) =>
    if val < 10
      "0" + val
    else
      val

  as_string: (timestamp) =>
    m = moment.unix(timestamp).utc()
    "#{m.year()}/#{@zeroize(m.month()+1)}/#{@zeroize(m.date())}"

  time_period: =>
    "#{@as_string(@period_start)} - #{@as_string(@period_end)}"

  invoice_date: =>
    "#{@as_string(@date)}"

CI.inner.Billing = class Billing extends CI.inner.Obj
  observables: =>
    stripeToken: null
    cardInfo: null
    invoices: []

    # old data
    oldPlan: null
    oldTotal: 0

    # metadata
    wizardStep: 1
    planFeatures: []
    loadingOrganizations: false

    # new data
    organizations: {}
    chosenPlan: null
    plans: []
    parallelism: 1
    concurrency: 1
    containers: 1
    payor: null
    special_price_p: null

  constructor: ->
    super

    @savedCardNumber = @komp =>
      return "" unless @cardInfo()
      "************" + @cardInfo().last4

    @wizardCompleted = @komp =>
      @wizardStep() > 3

    # Handles the plan templates we send
    @concurrency_p =  @komp =>
      @plans()[0] and (@plans()[0].type is "concurrency")

    @containers_p = @komp =>
      @plans()[0] and (@plans()[0].type is "containers")

    # Handles their current plan
    @chosen_plan_concurrency_p = @komp =>
      @chosenPlan() and (@chosenPlan().type is "concurrency")

    @chosen_plan_containers_p = @komp =>
      @chosenPlan() and ((@chosenPlan().type is "containers") or
        @chosenPlan().type is "trial")

    @total = @komp =>
      if @chosen_plan_concurrency_p()
        @calculateCost(@chosenPlan(), parseInt(@concurrency()), parseInt(@parallelism()))
      else if @chosen_plan_containers_p()
        @calculateCost(@chosenPlan(), parseInt(@containers()))

    @extra_containers = @komp =>
      if @chosen_plan_containers_p()
        Math.max(0, @containers() - @chosenPlan().free_containers())

  parallelism_option_text: (p) =>
    "#{p}-way ($#{@parallelism_cost(@chosenPlan(), p)})"

  concurrency_option_text: (c) =>
    "#{c} build#{if c > 1 then 's' else ''} at a time ($#{@concurrency_cost(@chosenPlan(), c)})"

  containers_option_text: (c) =>
    container_price = @chosenPlan().container_cost
    cost = @containerCost(@chosenPlan(), c)
    "#{c} containers ($#{cost})"


  parallelism_cost: (plan, p) =>
    Math.max(0, @calculateCost(plan, null, p) - @calculateCost(plan))

  # p2 > p1
  parallelism_cost_difference: (plan, p1, p2) =>
    @parallelism_cost(plan, p2) - @parallelism_cost(plan, p1)

  concurrency_cost: (plan, c) ->
    if plan.concurrency == "Unlimited"
      0
    else
      Math.max(0, @calculateCost(plan, c) - @calculateCost(plan))

  calculateCostConcurrency: (plan, concurrency, parallelism) ->
    c = concurrency or 0
    extra_c = Math.max(0, c - 1)

    p = parallelism or 1
    p = Math.max(p, 2)
    extra_p = (CI.math.log2 p) - 1
    extra_p = Math.max(0, extra_p)

    plan.price + (extra_c * 49) + (Math.round(extra_p * 99))

  containerCost: (plan, containers) ->
    c = Math.min(containers or 0, plan.max_containers())
    free_c = plan.free_containers()

    Math.max(0, (c - free_c) * plan.container_cost)

  calculateCostContainers: (plan, containers) =>
    plan.price + @containerCost(plan, containers)

  calculateCost: (plan, args...) =>
    unless plan
      0
    else
      if plan.type is "concurrency"
        @calculateCostConcurrency(plan, args...)
      else if plan.type is "containers"
        @calculateCostContainers(plan, args...)

  selectPlan: (plan, event) =>
    if plan.price?
      if @wizardCompleted()
        @oldPlan(@chosenPlan())
        @chosenPlan(plan)
        $("#confirmForm").modal({keyboard: false})
      else
        @createCard(plan, event)
    else
      VM.raiseIntercomDialog("I'd like ask about enterprise pricing...\n\n")

  cancelUpdate: (data, event) =>
    $('#confirmForm').modal('hide')
    @chosenPlan(@oldPlan())

  doUpdate: (data, event) =>
    @recordStripeTransaction event, null
    $('#confirmForm').modal('hide')
    if @wizardCompleted() # go to the speed nav
      # fight jQuery plugins with more jQuery
      $("#speed > a").click()

  ajaxSetCard: (event, token, type) =>
    $.ajax
      type: type
      url: "/api/v1/user/pay/card"
      event: event
      data: JSON.stringify
        token: token
      success: =>
        @loadExistingCard()

  stripeDefaults: () =>
    key: @stripeKey()
    name: "CircleCI"
    address: false
    email: VM.current_user().selected_email()

  createCard: (plan, event) =>
    vals =
      panelLabel: 'Add card',
      price: 100 * plan.price
      description: "#{plan.name} plan"
      token: (token) =>
        @chosenPlan(plan)
        @recordStripeTransaction event, token

    StripeCheckout.open($.extend @stripeDefaults(), vals)


  updateCard: (data, event) =>
    vals =
      panelLabel: 'Update card',
      token: (token) =>
        @ajaxSetCard(event, token.id, "PUT")

    StripeCheckout.open($.extend @stripeDefaults(), vals)


  load: (hash="small") =>
    unless @loaded
      @loadPlans()
      @loadPlanFeatures()
      @loadExistingCard()
      @loadInvoices()
      @loadExistingPlans()
      @loadOrganizations()
      @loadStripe()
      @loaded = true


  stripeKey: () =>
    switch renderContext.env
      when "production" then "pk_ZPBtv9wYtkUh6YwhwKRqL0ygAb0Q9"
      else '<KEY>'



  recordStripeTransaction: (event, stripeInfo) =>
    $('button').attr('disabled','disabled') # disable other buttons during payment
    $.ajax(
      url: "/api/v1/user/pay"
      event: event
      type: if stripeInfo then "POST" else "PUT"
      data: JSON.stringify
        token: stripeInfo
        plan: @chosenPlan().id

      success: () =>
        $('button').removeAttr('disabled') # payment done, reenable buttons
        @cardInfo(stripeInfo.card) if stripeInfo?
        @oldTotal(@total())

        # just to be sure
        @loadExistingCard()
        @loadInvoices()

        @advanceWizard()
        mixpanel.track("Paid")
    )
    false

  advanceWizard: =>
    @wizardStep(@wizardStep() + 1)

  closeWizard: =>
    @wizardStep(4)


  loadStripe: () =>
    $.getScript "https://js.stripe.com/v1/"
    if VM.ab().stripe_v3
      $.getScript "https://checkout.stripe.com/v3/checkout.js"
    else<commit_msg>Fix stripe_v3 abtest (did you know A/B tests are observables?)<commit_after>CI.inner.Invoice = class Invoice extends CI.inner.Obj
  total: =>
    "$#{@amount_due / 100}"

  zeroize: (val) =>
    if val < 10
      "0" + val
    else
      val

  as_string: (timestamp) =>
    m = moment.unix(timestamp).utc()
    "#{m.year()}/#{@zeroize(m.month()+1)}/#{@zeroize(m.date())}"

  time_period: =>
    "#{@as_string(@period_start)} - #{@as_string(@period_end)}"

  invoice_date: =>
    "#{@as_string(@date)}"

CI.inner.Billing = class Billing extends CI.inner.Obj
  observables: =>
    stripeToken: null
    cardInfo: null
    invoices: []

    # old data
    oldPlan: null
    oldTotal: 0

    # metadata
    wizardStep: 1
    planFeatures: []
    loadingOrganizations: false

    # new data
    organizations: {}
    chosenPlan: null
    plans: []
    parallelism: 1
    concurrency: 1
    containers: 1
    payor: null
    special_price_p: null

  constructor: ->
    super

    @savedCardNumber = @komp =>
      return "" unless @cardInfo()
      "************" + @cardInfo().last4

    @wizardCompleted = @komp =>
      @wizardStep() > 3

    # Handles the plan templates we send
    @concurrency_p =  @komp =>
      @plans()[0] and (@plans()[0].type is "concurrency")

    @containers_p = @komp =>
      @plans()[0] and (@plans()[0].type is "containers")

    # Handles their current plan
    @chosen_plan_concurrency_p = @komp =>
      @chosenPlan() and (@chosenPlan().type is "concurrency")

    @chosen_plan_containers_p = @komp =>
      @chosenPlan() and ((@chosenPlan().type is "containers") or
        @chosenPlan().type is "trial")

    @total = @komp =>
      if @chosen_plan_concurrency_p()
        @calculateCost(@chosenPlan(), parseInt(@concurrency()), parseInt(@parallelism()))
      else if @chosen_plan_containers_p()
        @calculateCost(@chosenPlan(), parseInt(@containers()))

    @extra_containers = @komp =>
      if @chosen_plan_containers_p()
        Math.max(0, @containers() - @chosenPlan().free_containers())

  parallelism_option_text: (p) =>
    "#{p}-way ($#{@parallelism_cost(@chosenPlan(), p)})"

  concurrency_option_text: (c) =>
    "#{c} build#{if c > 1 then 's' else ''} at a time ($#{@concurrency_cost(@chosenPlan(), c)})"

  containers_option_text: (c) =>
    container_price = @chosenPlan().container_cost
    cost = @containerCost(@chosenPlan(), c)
    "#{c} containers ($#{cost})"


  parallelism_cost: (plan, p) =>
    Math.max(0, @calculateCost(plan, null, p) - @calculateCost(plan))

  # p2 > p1
  parallelism_cost_difference: (plan, p1, p2) =>
    @parallelism_cost(plan, p2) - @parallelism_cost(plan, p1)

  concurrency_cost: (plan, c) ->
    if plan.concurrency == "Unlimited"
      0
    else
      Math.max(0, @calculateCost(plan, c) - @calculateCost(plan))

  calculateCostConcurrency: (plan, concurrency, parallelism) ->
    c = concurrency or 0
    extra_c = Math.max(0, c - 1)

    p = parallelism or 1
    p = Math.max(p, 2)
    extra_p = (CI.math.log2 p) - 1
    extra_p = Math.max(0, extra_p)

    plan.price + (extra_c * 49) + (Math.round(extra_p * 99))

  containerCost: (plan, containers) ->
    c = Math.min(containers or 0, plan.max_containers())
    free_c = plan.free_containers()

    Math.max(0, (c - free_c) * plan.container_cost)

  calculateCostContainers: (plan, containers) =>
    plan.price + @containerCost(plan, containers)

  calculateCost: (plan, args...) =>
    unless plan
      0
    else
      if plan.type is "concurrency"
        @calculateCostConcurrency(plan, args...)
      else if plan.type is "containers"
        @calculateCostContainers(plan, args...)

  selectPlan: (plan, event) =>
    if plan.price?
      if @wizardCompleted()
        @oldPlan(@chosenPlan())
        @chosenPlan(plan)
        $("#confirmForm").modal({keyboard: false})
      else
        @createCard(plan, event)
    else
      VM.raiseIntercomDialog("I'd like ask about enterprise pricing...\n\n")

  cancelUpdate: (data, event) =>
    $('#confirmForm').modal('hide')
    @chosenPlan(@oldPlan())

  doUpdate: (data, event) =>
    @recordStripeTransaction event, null
    $('#confirmForm').modal('hide')
    if @wizardCompleted() # go to the speed nav
      # fight jQuery plugins with more jQuery
      $("#speed > a").click()

  ajaxSetCard: (event, token, type) =>
    $.ajax
      type: type
      url: "/api/v1/user/pay/card"
      event: event
      data: JSON.stringify
        token: token
      success: =>
        @loadExistingCard()

  stripeDefaults: () =>
    key: @stripeKey()
    name: "CircleCI"
    address: false
    email: VM.current_user().selected_email()

  createCard: (plan, event) =>
    vals =
      panelLabel: 'Add card',
      price: 100 * plan.price
      description: "#{plan.name} plan"
      token: (token) =>
        @chosenPlan(plan)
        @recordStripeTransaction event, token

    StripeCheckout.open($.extend @stripeDefaults(), vals)


  updateCard: (data, event) =>
    vals =
      panelLabel: 'Update card',
      token: (token) =>
        @ajaxSetCard(event, token.id, "PUT")

    StripeCheckout.open($.extend @stripeDefaults(), vals)


  load: (hash="small") =>
    unless @loaded
      @loadPlans()
      @loadPlanFeatures()
      @loadExistingCard()
      @loadInvoices()
      @loadExistingPlans()
      @loadOrganizations()
      @loadStripe()
      @loaded = true


  stripeKey: () =>
    switch renderContext.env
      when "production" then "<KEY>"
      else '<KEY>'



  recordStripeTransaction: (event, stripeInfo) =>
    $('button').attr('disabled','disabled') # disable other buttons during payment
    $.ajax(
      url: "/api/v1/user/pay"
      event: event
      type: if stripeInfo then "POST" else "PUT"
      data: JSON.stringify
        token: stripeInfo
        plan: @chosenPlan().id

      success: () =>
        $('button').removeAttr('disabled') # payment done, reenable buttons
        @cardInfo(stripeInfo.card) if stripeInfo?
        @oldTotal(@total())

        # just to be sure
        @loadExistingCard()
        @loadInvoices()

        @advanceWizard()
        mixpanel.track("Paid")
    )
    false

  advanceWizard: =>
    @wizardStep(@wizardStep() + 1)

  closeWizard: =>
    @wizardStep(4)


  loadStripe: () =>
    $.getScript "https://js.stripe.com/v1/"
    if VM.ab().stripe_v3()
      $.getScript "https://checkout.stripe.com/v3/checkout.js"
    else
{'fraction_non_alphanumeric': 0.15481040426198683, 'fraction_numerical': 0.008931369476653087, 'mean_word_length': 2.8530033202535465, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 98, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7185146', 'n_tokens_mistral': 4441, 'n_tokens_neox': 4300, 'n_words': 930}