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
相关产品推荐
相关产品推荐

