You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Stripe自定义应用手续费代码优化与全面测试咨询

Stripe月额外手续费功能:测试方案与代码优化

功能背景

我已完成Stripe手续费相关功能开发,目前运行看似正常,但支付环节至关重要,必须确保功能完全准确。需求规则为:仅向指定候选人收取「每周期首笔捐款」的额外手续费——例如2023年11月24日收到首笔捐款时收取额外手续费,当月(或该周期内)后续捐款按正常标准收费;2023年12月24日后的首笔捐款再次收取额外手续费。

现有代码

contribution_form.rb

puts "starting else block  ----->>#{amount_cents}" 
applicable_fee = candidate.determine_fee(amount_cents)
Stripe::Charge.create({
  amount: amount_cents,
  currency: candidate.candidate_country[candidate.country.to_s.to_sym][:currency],
  source: stripe_token,
  application_fee_amount: applicable_fee,
  # application_fee_amount: ((amount_cents * ((candidate.merchant_rate.to_f * 100) + 2.9) / 100) + 25).to_i,
  statement_descriptor_suffix: "#{get_statement_descriptor.to_s.upcase}",
  on_behalf_of: candidate.stripe_gateway_id,
  transfer_data: {
    destination: candidate.stripe_gateway_id,
  },
}, stripe_version: '2019-12-03',)

candidate.rb

def determine_fee(amount_cents)
  amount = ((amount_cents * ((merchant_rate.to_f * 100) + 2.9) / 100) + 25).to_i
  return amount unless id ==  3954 
  if fee_paid_on["started_date"].nil? || (next_start_date(fee_paid_on["started_date"].to_datetime, true) <= Time.zone.now)
     amount = ((amount_cents * ((merchant_rate.to_f * 100) + 2.9) / 100) + 25 + 799).to_i
    fee_paid_details
  else 
    amount = ((amount_cents * ((merchant_rate.to_f * 100) + 2.9) / 100) + 25).to_i 
    fee_paid_details
  end
  amount
end 

def next_start_date(start_date, flag = false)
  puts "next start date ----->>#{start_date}"
  return start_date + 1.month if flag 

  t = Time.zone.now
  start_date.month != t.month ? Time.zone.parse("#{start_date.day}/#{t.month}/#{t.year}") : start_date + 1.month
end  
#only updating start date and setting it as per next month
def  fee_paid_details 
  started_date =  fee_paid_on["started_date"]
  new_started_date = (next_start_date(started_date.to_datetime) < Time.zone.now ? started_date.to_datetime : next_start_date(started_date.to_datetime)) if started_date
  update_columns(
    fee_paid_on: {
      "started_date" => started_date.nil? ? Time.zone.now : new_started_date,
      "last_paid_on" => Time.zone.now 
    }
  )
end 

一、全面测试方案

核心场景覆盖

  • 首次捐款:候选人无历史捐款记录,发起首笔捐款,验证:
    • 手续费包含额外799分
    • fee_paid_on的started_date和last_paid_on均设为当前时间
  • 同周期后续捐款:在首次捐款的30天周期内发起第二笔捐款,验证:
    • 手续费为正常标准(无额外799分)
    • last_paid_on更新为当前时间,started_date保持不变
  • 跨周期首笔捐款:超过首次捐款日30天后发起捐款,验证:
    • 再次收取额外手续费
    • started_date更新为当前日期,last_paid_on同步更新
  • 周期临界日捐款:在首次捐款日满30天的当天发起捐款,验证是否触发额外手续费,以及日期字段更新是否正确

边界场景测试

  • 月底跨月:比如首次捐款为1月31日,2月28日(非闰年)发起捐款,验证手续费规则和日期字段是否正常处理
  • 极端金额:用最小金额(如100分)、最大金额测试手续费计算,避免浮点数精度误差
  • 非目标候选人:验证ID非3954的候选人始终只收取正常手续费,不会触发额外逻辑

测试实施方式

  • 单元测试:用Rspec+Timecop模拟不同时间节点,直接调用determine_fee和fee_paid_details,断言手续费金额、数据库字段的正确性
  • 集成测试:调用Stripe测试API,模拟完整支付流程,验证实际创建的Charge中application_fee_amount符合预期
  • 手动回归:在测试环境复现所有场景,核对Stripe后台费用记录与系统内数据一致性

二、代码优化方案

1. 移除硬编码候选人ID

新增requires_monthly_extra_fee布尔字段替代id == 3954,后续无需改代码即可调整目标候选人:

# 迁移文件
add_column :candidates, :requires_monthly_extra_fee, :boolean, default: false

2. 提取重复计算逻辑

把重复的基础手续费计算抽成私有方法,降低维护成本:

private

def base_fee(amount_cents)
  ((amount_cents * ((merchant_rate.to_f * 100) + 2.9) / 100) + 25).to_i
end

def extra_fee_amount
  799 # 可移至配置文件,方便后续调整
end

3. 简化日期计算逻辑

优化next_start_date,处理月底跨月的边界情况,逻辑更清晰:

def next_start_date(start_date, use_monthly_interval = false)
  return start_date.advance(months: 1) if use_monthly_interval

  current_month = Time.zone.now.beginning_of_month
  if start_date.month != current_month.month
    # 当月无对应日期时自动取月底
    target_date = Time.zone.parse("#{current_month.year}-#{current_month.month}-#{start_date.day}") rescue current_month.end_of_month
    target_date
  else
    start_date.advance(months: 1)
  end
end

4. 重构determine_fee方法

简化逻辑,提升可读性:

def determine_fee(amount_cents)
  return base_fee(amount_cents) unless requires_monthly_extra_fee

  current_started_date = fee_paid_on["started_date"]&.to_datetime
  should_charge_extra = current_started_date.nil? || next_start_date(current_started_date, true) <= Time.zone.now

  fee = should_charge_extra ? base_fee(amount_cents) + extra_fee_amount : base_fee(amount_cents)
  fee_paid_details
  fee
end

5. 优化fee_paid_details方法

减少重复调用,逻辑更直观:

def fee_paid_details
  current_started_date = fee_paid_on["started_date"]&.to_datetime

  new_started_date = if current_started_date.nil?
                       Time.zone.now
                     else
                       next_date = next_start_date(current_started_date)
                       next_date <= Time.zone.now ? current_started_date : next_date
                     end

  update_columns(
    fee_paid_on: {
      "started_date" => new_started_date,
      "last_paid_on" => Time.zone.now
    }
  )
end

6. 其他优化点

  • 移除所有puts调试语句,避免生产环境冗余日志
  • 新增after_initialize回调,确保fee_paid_on默认是Hash类型,避免空指针:
    after_initialize :set_default_fee_paid_on
    
    private
    
    def set_default_fee_paid_on
      self.fee_paid_on ||= {}
    end
    

内容的提问来源于stack exchange,提问作者shashank barman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 08:01:33