如何使用Ruby on Rails在Stripe中测试未付费订阅?

时间:2014-07-18 00:54:05

标签: ruby-on-rails rspec stripe-payments

我正在尝试在我的Rails应用程序中测试该方案,其中客户允许订阅转为“未付款”(通常是因为该卡过期并且在Stripe重试收费的两周内未更新)并且是最后到处更新卡并重新激活帐户。我相信我的逻辑是正确的(更新卡并支付每张未付的发票),但我希望能够测试它,或者更好的是,写一些RSpec测试(功能测试和可能的控制器测试)。 问题是,我无法弄清楚如何创建一个订阅“未付”的模拟情况。(我想我可以用过期的卡创建一堆帐户并等待两周到测试,但这不是一个可接受的解决方案。我甚至无法仅为“测试”上下文更改订阅重试设置以加快处理速度。)我找到stripe-ruby-mock但我无法手动设置状态订阅。

这是我尝试过的:

plan = Stripe::Plan.create(id: 'test')
  customer = Stripe::Customer.create(id: 'test_customer', card: 'tk', plan: 'test')

  sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id)
  sub.status = 'unpaid'
  sub.save
  sub = customer.subscriptions.retrieve(customer.subscriptions.data.first.id)
  expect(sub.status).to eq 'unpaid'

这是stripe-ruby-mock的结果:

Failure/Error: expect(sub.status).to eq 'unpaid'

   expected: "unpaid"
        got: "active"

2 个答案:

答案 0 :(得分:8)

Stripe的推荐程序是:

  1. 使用卡4000000000000341设置客户("将此卡附加到客户对象将成功,但尝试向客户收费将失败。")

  2. 向客户提供订阅,但试用日期为今天/明天。

  3. 等待

  4. 第三步很烦人,但它会完成工作。

答案 1 :(得分:2)

考虑到@ VoteyDisciple的建议,我已经通过一些方法在RSpec中合理地自动化了(根据具体情况,合理地')。我使用VCR来捕获API调用(针对测试条带环境),这是必不可少的,因为这意味着只有在第一次记录测试时才会发生sleep调用。

使用注释来表示通过Stripe的API完成的行为,因为我的实现很容易被我的项目所吸引,并且没那么有用。

VCR.use_cassette('retry') do |cassette|
  # Clear out existing Stripe data: customers, coupons, plans.
  # This is so the test is reliably repeatable.
  Stripe::Customer.all.each &:delete
  Stripe::Coupon.all.each &:delete
  Stripe::Plan.all.each &:delete

  # Create a plan, in my case it has the id 'test'.
  Stripe::Plan.create(
    id:             'test',
    amount:         100_00,
    currency:       'AUD',
    interval:       'month',
    interval_count: 1,
    name:           'RSpec Test'
  )

  # Create a customer
  customer = Stripe::Customer.create email: 'test@test.test'
  token    = card_token cassette, '4000000000000341'
  # Add the card 4000000000000341 to the customer
  customer.sources.create token: 'TOKEN for 0341'
  # Create a subscription with a trial ending in two seconds.
  subscription = customer.subscriptions.create(
    plan:      'test',
    trial_end: 2.seconds.from_now.to_i
  )

  # Wait for Stripe to create a proper invoice. I wish this
  # was unavoidable, but I don't think it is.
  sleep 180 if cassette.recording?

  # Grab the invoice that actually has a dollar value.
  # There's an invoice for the trial, and we don't care about that.
  invoice = customer.invoices.detect { |invoice| invoice.total > 0 }
  # Force Stripe to attempt payment for the first time (instead
  # of waiting for hours).
  begin
    invoice.pay
  rescue Stripe::CardError
    # Expecting this to fail.
  end

  invoice.refresh
  expect(invoice.paid).to eq(false)
  expect(invoice.attempted).to eq(true)

  # Add a new (valid) card to the customer.
  token = card_token cassette, '4242424242424242'
  card  = customer.sources.create token: token
  # and set it as the default
  customer.default_source = card.id
  customer.save

  # Run the code in your app that retries the payment, which
  # essentially invokes invoice.pay again.
  # THIS IS FOR YOU TO IMPLEMENT

  # And now we can check that the invoice wass successfully paid
  invoice.refresh
  expect(invoice.paid).to eq(true)
end

以自动方式获取卡片令牌是一个全新的复杂领域。我所得到的可能对其他人不起作用,但这里是红宝石方法,呼唤幻影(你需要发送录像机对象):

def card_token(cassette, card = '4242424242424242')
  return 'tok_my_default_test_token' unless cassette.recording?

  token = `phantomjs --ignore-ssl-errors=true --ssl-protocol=any ./spec/fixtures/stripe_tokens.js #{ENV['STRIPE_PUBLISH_KEY']} #{card}`.strip
  raise "Unexpected token: #{token}" unless token[/^tok_/]

  token
end

phantomjs正在运行的javascript文件(stripe_tokens.js)包含:

var page   = require('webpage').create(),
    system = require('system');

var key  = system.args[1],
    card = system.args[2];

page.onCallback = function(data) {
  console.log(data);
  phantom.exit();
};

page.open('spec/fixtures/stripe_tokens.html', function(status) {
  if (status == 'success') {
    page.evaluate(function(key, card) {
      Stripe.setPublishableKey(key);
      Stripe.card.createToken(
        {number: card, cvc: "123", exp_month: "12", exp_year: "2019"},
        function(status, response) { window.callPhantom(response.id) }
      );
    }, key, card);
  }
});

最后,涉及的HTML文件(stripe_tokens.html)非常简单:

<html>
  <head>
    <script type="text/javascript" src="https://js.stripe.com/v2/"></script>
  </head>
  <body></body>
</html>

将所有这些放在一起,而且,它可能会起作用!它适用于我们的应用程序:)