rails 4 个应用的 Stripe 优惠券未反映在费用中或保存在数据库中

Stripe coupon not reflected in charge or saved in database for rails 4 app

我们已经让 Stripe 在我们的 rails 4 应用程序中使用 Mastering Modern Payments 方法进行订阅支付。我们现在正在尝试添加优惠券。我们想使用 Stripe 来管理优惠券(在 Stripe 仪表板中创建优惠券)。

问题是当用户付款时,优惠券并没有减少收取的金额,也没有与用户和订阅信息一起保存到应用程序数据库中。我们没有收到任何错误。

工作原理:我们在用户支付时在哈希中看到优惠券代码,优惠券是在 Stripe 中设置的。总体而言,用户可以注册并支付罚款。我们在 Devise 中执行两步创建用户,然后让他们付款。当他们支付订阅费用时,我们的应用程序就会激活。

我们知道这里缺少优惠券的一部分,但我们正在努力弄清楚那是什么以及应该如何实施?我在 rails 中读到有关创建虚拟属性的信息,所以也许我们可以为优惠券这样做,因为目前在用户 table.

中没有任何优惠券信息

当用户注册订阅时,输出如下所示(实际数据已替换为发布):

... Started POST "/subscriptions" for 127.0.0.1 at 2015-11-30 10:33:40 -0800
Processing by SubscriptionsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"XYZ", "plan_id"=>"5", "email_address"=>"test55@gmail.com", "coupon"=>"123", "stripeToken"=>"tok_xyz"}
  User Load (0.5ms)  SELECT  "users".* FROM "users" WHERE "users"."id" =   ORDER BY "users"."id" ASC LIMIT 1  [["id", 52]]
  Plan Load (0.3ms)  SELECT  "plans".* FROM "plans" WHERE "plans"."id" =  LIMIT 1  [["id", 5]]
  User Load (0.5ms)  SELECT  "users".* FROM "users" WHERE "users"."email" =  LIMIT 1  [["email", "test55@gmail.com"]]
   (0.3ms)  BEGIN
  SQL (0.4ms)  UPDATE "users" SET "stripe_customer_id" = , "updated_at" =  WHERE "users"."id" =   [["stripe_customer_id", "cus_7Rq9Kv4nGh6uD2"], ["updated_at", "2015-11-30 18:33:41.930863"], ["id", 52]]
  SQL (48.6ms)  INSERT INTO "subscriptions" ("plan_id", "user_id", "created_at", "updated_at") VALUES (, , , ) RETURNING "id"  [["plan_id", 5], ["user_id", 52], ["created_at", "2015-11-30 18:33:41.934364"], ["updated_at", "2015-11-30 18:33:41.934364"]]
DEPRECATION WARNING: `serialized_attributes` is deprecated without replacement, and will be removed in Rails 5.0. (called from serialized_attributes at /usr/local/rvm/gems/ruby-2.2.1/gems/activerecord-4.2.1/lib/active_record/attribute_methods/serialization.rb:56)...

用户和订阅的架构(优惠券是订阅中的一列 table):

schema.rb:

...
 create_table "subscriptions", force: :cascade do |t|
    t.date     "purchase_date"
    t.boolean  "active"
    t.datetime "created_at",    null: false
    t.datetime "updated_at",    null: false
    t.integer  "user_id"
    t.integer  "plan_id"
    t.string   "stripe_id"
    t.string   "coupon"
  end

  add_index "subscriptions", ["plan_id"], name: "index_subscriptions_on_plan_id", using: :btree
  add_index "subscriptions", ["user_id"], name: "index_subscriptions_on_user_id", using: :btree

  create_table "users", force: :cascade do |t|
    t.string   "email",                  default: "",    null: false
    t.string   "encrypted_password",     default: ""
    t.string   "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer  "sign_in_count",          default: 0,     null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string   "current_sign_in_ip"
    t.string   "last_sign_in_ip"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.string   "name"
    t.string   "confirmation_token"
    t.datetime "confirmed_at"
    t.datetime "confirmation_sent_at"
    t.string   "unconfirmed_email"
    t.integer  "role"
    t.string   "invitation_token"
    t.datetime "invitation_created_at"
    t.datetime "invitation_sent_at"
    t.datetime "invitation_accepted_at"
    t.integer  "invitation_limit"
    t.integer  "invited_by_id"
    t.string   "invited_by_type"
    t.integer  "invitations_count",      default: 0
    t.boolean  "terms_accepted",         default: false
    t.string   "phone_number"
    t.string   "plan_id"
    t.string   "employer"
    t.string   "stripe_customer_id"
  end

  add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
  add_index "users", ["invitation_token"], name: "index_users_on_invitation_token", unique: true, using: :btree
  add_index "users", ["invitations_count"], name: "index_users_on_invitations_count", using: :btree
  add_index "users", ["invited_by_id"], name: "index_users_on_invited_by_id", using: :btree
  add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
...

优惠券在创建订阅服务对象中:

create_subscription.rb

class CreateSubscription
  def self.call(plan, email_address, token)
    user, raw_token = CreateUser.call(email_address)

    subscription = Subscription.new(
        plan: plan,
        user: user
    )

    begin
      stripe_sub = nil
      if user.stripe_customer_id.blank?
        customer = Stripe::Customer.create(
            source: token,
            email: user.email,
            plan: plan.stripe_id,
            coupon: subscription.coupon,
        )
        user.stripe_customer_id = customer.id
        user.save!
        stripe_sub = customer.subscriptions.first
      else
        customer = Stripe::Customer.retrieve(user.stripe_customer_id)
        stripe_sub = customer.subscriptions.create(
            plan: plan.stripe_id
        )
      end

      subscription.stripe_id = stripe_sub.id

      subscription.save!
    rescue Stripe::StripeError => e
      subscription.errors[:base] << e.message
    end

    subscription
  end
end

优惠券在支付页面:

app/views/subscriptions/new.html.haml

= content_for :header do
    %script{src: 'https://js.stripe.com/v2/', type: 'text/javascript'}

    :javascript
        $(function(){
            Stripe.setPublishableKey("#{Rails.configuration.stripe[:publishable_key]}");
        });

    %script{src: '/subscriptions.js', type: 'text/javascript'}

- unless @subscription.errors.blank?
    = @subscription.errors.full_messages.to_sentence
%h2
    Subscribing to #{@plan.name}
= form_for @subscription, html: { id: 'payment-form' } do |f|
    %input{name: 'plan_id', type: 'hidden', value: @plan.id}/
    %span.payment-errors
    .form-row
        %label
            %span Email Address
            %input{name: 'email_address', size: '20', type: 'email', value: @user.email}
    .form-row
        %label
            %span Card Number
            %input{size: '20', type: 'text', data: {stripe: 'number'}}
    .form-row
        %label
            %span CVC
            %input{size: '4', type: 'text', data: {stripe: 'cvc'}}
    .form-row
        %label
            %span Expiration (MM/YYYY)
            %input{size: '2', type: 'text', data: {stripe: 'exp-month'}}
        %span /
        %input{size: '4', type: 'text', data: {stripe: 'exp-year'}}
    .form-row
        %label
            %span Promo code, if any
            %input{name: 'coupon', size: '20', type: 'text', value: @coupon}
    %button{type: 'submit', class: 'btn btn-lg btn-primary'} Pay Now

我们的订阅模式是这样的:

subscription.rb:

    class Subscription < ActiveRecord::Base
      belongs_to :user
      belongs_to :plan

      has_paper_trail

      def inactive?
        active ? false : true
      end

  def active?
    active
  end
end

我们如何在费用中反映 Stripe 优惠券并将其保存在我们的应用程序数据库中?谢谢。

您的 CreateSubscription 服务不提供传递优惠券代码的途径。

您需要为此添加一个新参数,例如。

class CreateSubscription
  def self.call(plan, email_address, token, coupon = nil)
    # ...
    subscription = Subscription.new(
      plan: plan,
      user: user,
      coupon: coupon
    )
    # ...
  end
end

此外,对于您提供的代码,还有一些其他事项需要注意:

customer = Stripe::Customer.create(
  source: token,
  email: user.email,
  plan: plan.stripe_id,
  coupon: subscription.coupon
)

Stipe 允许您对客户和订阅设置 coupon。计费时,应用于订阅的折扣会覆盖在客户范围内应用的折扣。参见 https://stripe.com/docs/api#subscription_object

因此,如果您的客户取消订阅,或有其他(多个)订阅。根据您的优惠券设置,他们也可能会获得折扣。

因此,根据您希望发生的情况,您可能只想将优惠券传递给 Stripe::Subscription 而不是传递给 Stripe::Customer