我一直在开发Stripe Webhook处理程序,以根据值创建/更新记录。
这不是很难,如果它像下面这样简单;
StripeEvent.configure do |events|
events.subscribe 'charge.succeeded' do |event|
charge = event.data.object
StripeMailer.receipt(charge).deliver
StripeMailer.admin_charge_succeeded(charge).deliver
end
end
但是,如果我需要有条件地存储数据,那可能会有点麻烦。
在这里,我提取了每个Webhook处理程序并定义了类似stripe_handlers/blahblah_handler.rb
。
class InvoicePaymentFailed
def call(event)
invoice_obj = event.data.object
charge_obj = retrieve_charge_obj_of(invoice_obj)
invoice = Invoice.find_by(stripe_invoice_id: charge_obj[:invoice])
# common execution for subscription
invoice.account.subscription.renew_billing_period(start_at: invoice_obj[:period_start], end_at: invoice_obj[:period_end])
case invoice.state
when 'pending'
invoice.fail!(:processing,
amount_due: invoice[:amount_due],
error: {
code: charge_obj[:failure_code],
message: charge_obj[:failure_message]
})
when 'past_due'
invoice.failed_final_attempt!
end
invoice.next_attempt_at = Utils.unix_time_to_utc(invoice_obj[:next_payment_attempt].to_i)
invoice.attempt_count = invoice_obj[:attempt_count].to_i
invoice.save
end
private
def retrieve_charge_obj_of(invoice)
charge_obj = Stripe::Charge.retrieve(id: invoice.charge)
return charge_obj
rescue Stripe::InvalidRequestError, Stripe::AuthenticationError, Stripe::APIConnectionError, Stripe::StripeError => e
logger.error e
logger.error e.backtrace.join("\n")
end
end
end
我只是想知道如何干掉这个Webhook处理程序。
是否有一些最佳做法可以接受这个或任何想法?
答案 0 :(得分:1)
我建议在retrieve_charge_obj_of
中重新引发异常,因为稍后您将获得nil
引用异常,这会产生误导。 (原样,您也可以让异常冒出来,让专用的错误处理系统进行抢救,记录并返回有意义的500
错误。)
一个。如果您不想要返回500,那么您有一个错误b / c retrieve_charge_obj_of
将在拯救异常后返回nil 。如果charge_obj
为nil
,那么此服务将提升NPE,从而产生500.
如果invoice_obj[:next_payment_attempt]
可以是!present?
(blank?
),那么Utils.unix_time_to_utc(invoice_obj[:next_payment_attempt].to_i)
应该是什么意思?
一个。如果是nil
,false
或''
,则#to_i
会返回0
- 是否有意? ([]
/ {}
也是blank?
但会加注)
从概念上讲,这个处理程序需要在Invoice
上发出状态转换,所以这个逻辑的一部分可以放在模型中:
class Invoice < ApplicationRecord
# this method is "internal" to your application, so incoming params should be already "clean"
def mark_payment_failed!(err_code, err_msg, attempt_count, next_payment_at)
transaction do # payment processing usually needs to be transactional
case self.state
when 'pending'
err = { code: err_code, message: err_msg }
self.fail!(:processing, amount_due: self.amount_due, error: err)
when 'past_due'
self.failed_final_attempt!
else
ex_msg = "some useful data #{state} #{err_code}"
raise InvalidStateTransition, ex_msg
end
self.next_attempt_at = next_payment_at
self.attempt_count = attempt_count
self.save
end
end
class InvalidStateTransition < StandardError; end
end
注意:我建议在状态和状态之前使用正式的状态机实现(例如state_machine)。过渡失控。
数据提取,验证和转换应该在处理程序中进行(这就是“处理程序”的用途),它们应该在应用程序中更深入地进行之前发生。在采取任何行动之前,最好及早发现错误,并尽早停止执行。
我看到还有一些其他边缘情况没有真正处理过。