【问题标题】:How can I make the transaction rollback if the API call fails and vice versa?如果 API 调用失败,我如何使事务回滚,反之亦然?
【发布时间】:2014-03-22 02:08:48
【问题描述】:

我想要两件事:

a) 我希望只有在 API 调用成功时才能在数据库中保存记录

b) 我只想在 db 记录保存成功的情况下执行 API 调用

目标是使本地(数据库中)存储的数据与 Stripe 上的数据保持一致。

@payment = Payment.new(...)

begin
  Payment.transaction do
    @payment.save!

    stripe_customer = Stripe::Customer.retrieve(manager.customer_id)

    charge = Stripe::Charge.create(
      amount:   @plan.amount_in_cents,
      currency: 'usd',
      customer: stripe_customer.id
    )
  end
# https://stripe.com/docs/api#errors
rescue Stripe::CardError, Stripe::InvalidRequestError, Stripe::APIError => error
  @payment.errors.add :base, 'There was a problem processing your credit card. Please try again.'
  render :new
rescue => error
  render :new
else
  redirect_to dashboard_root_path, notice: 'Thank you. Your payment is being processed.'
end

上面的下面会起作用,因为如果记录(第 5 行)没有保存,其余的代码就不会执行。

但是如果我需要在 API 调用后保存 @payment 对象怎么办,因为我需要为 @payment 对象分配 API 结果中的值。举个例子:

@payment = Payment.new(...)

begin
  Payment.transaction do
    stripe_customer = Stripe::Customer.retrieve(manager.customer_id)

    charge = Stripe::Charge.create(
      amount:   @plan.amount_in_cents,
      currency: 'usd',
      customer: stripe_customer.id
    )

    @payment.payment_id = charge[:id]

    @payment.activated_at = Time.now.utc
    @payment.save!
  end
# https://stripe.com/docs/api#errors
rescue Stripe::CardError, Stripe::InvalidRequestError, Stripe::APIError => error
  @payment.errors.add :base, 'There was a problem processing your credit card. Please try again.'
  render :new
rescue => error
  render :new
else
  redirect_to dashboard_root_path, notice: 'Thank you. Your payment is being processed.'
end

您注意到@payment.save! 发生在 API 调用之后。这可能是一个问题,因为 API 调用在数据库尝试保存记录之前运行。这可能意味着 API 调用成功,但数据库提交失败。

对这个场景有什么想法/建议吗?

【问题讨论】:

    标签: ruby-on-rails ruby ruby-on-rails-4 transactions stripe-payments


    【解决方案1】:

    您不能同时执行 API => DB 和 DB => API(听起来像是无限的执行条件),至少我无法想象如何实现此工作流程。我了解您的数据一致性需求,因此我建议:

    • 检查记录是否有效@payment.valid?(可能使用valid_without_payment?之类的自定义方法)
    • 运行 api 调用
    • 仅当 api 调用成功时才保存记录(使用payment_id

    或者:

    • 不使用payment_id保存记录
    • 运行 api 调用
    • 如果调用成功,使用payment_id(api 响应)更新记录
    • 定期(cron)运行任务(脚本)以检查不一致的实例(where(payment_id: nil))并将其删除

    我认为这两个选项都可以接受,并且您的数据将保持一致。

    【讨论】:

    • 对,但我想在 API 调用之后而不是之前检查 @payment.valid?,因为我需要分配 payment_id。即便如此,API 调用仍然会在保存数据库记录之前先执行,这违背了目标
    • 你想调用api然后保存到db(API => DB),同时保存到db然后调用api(DB => API)。我认为这是不可能的。所以这就是为什么我建议:1)检查是否有效(可能使用像valid_without_payment?这样的自定义方法),2)调用api和3)只有当api返回ok时才保存(使用api响应中的payment_id)。
    • 如果保存(使用 payment_id)失败怎么办?
    • 如果付款有效且 api 成功(返回有效 id),则使用 payment_id 保存不应失败。您无法同时在 B 之前实现 A 和在 A 之前实现 B,至少我无法想象如何实现。一种不同的方法可能类似于您的第一个代码块:1)保存记录(没有 payment_id),2)调用 api 和 3)使用 payment_id 更新记录。您可以补充此逻辑,定期运行 rake 任务 (cron) 以检查不一致的记录(没有 payment_id)并将其删除。
    • 这是否有意义validates :payment_id, presence: true,并在 before_validation 回调中运行 api 调用?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-17
    • 1970-01-01
    • 2011-01-01
    • 2021-11-06
    • 2022-06-14
    • 1970-01-01
    相关资源
    最近更新 更多