【问题标题】:Change Credit Card Information (Stripe)更改信用卡信息(条纹)
【发布时间】:2013-12-02 16:11:37
【问题描述】:

我终于知道如何使用本教程实施 Stripes 每月计费。 http://railscasts.com/episodes/288-billing-with-stripe

到目前为止,用户可以使用 Stripe 创建和删除他们的订阅。

但用户创建订阅后如何更改其信用卡信息

这是我的带有评论和问题的代码。请帮助新的 Rails。 :)

控制器

class SubscriptionsController < ApplicationController

  def new
    plan = Plan.find(params[:plan_id])
    @subscription = plan.subscriptions.build
    @subscription.user_id = current_user.id
  end

  def create
    @subscription = Subscription.new(params[:subscription])
    if @subscription.save_with_payment
      redirect_to @subscription, :notice => "Thank you for subscribing!"
    else
      render :new
    end
  end

  def update
    @subscription = current_user.subscription
     if @subscription.save
      redirect_to edit_subscription_path, :success => 'Updated Card.' 
    else
      flash.alert = 'Unable to update card.'
      render :edit 
    end
  end

end

模型

class Subscription < ActiveRecord::Base
  attr_accessible :plan_id, :user_id, :email, :stripe_customer_token, :last_4_digits,
              :card_token, :card_name, :exp_month, :exp_year, :stripe_card_token

  attr_accessor :stripe_card_token

  belongs_to :plan
  belongs_to :user

  def save_with_payment
    if valid?
      save_with_stripe_payment
    end
  end

  def save_with_stripe_payment
    customer = Stripe::Customer.create(card: stripe_card_token, email: email, plan: plan_id, description: "Unlimited Comics")
    self.stripe_customer_token = customer.id
    self.card_token = customer.cards.data.first["id"]
    self.card_name = customer.cards.data.first["type"]
    self.exp_month = customer.cards.data.first["exp_month"]
    self.exp_year = customer.cards.data.first["exp_year"]
    self.last_4_digits = customer.cards.data.first["last4"]
    save!
  rescue Stripe::InvalidRequestError => e
    logger.error "Stripe error while creating customer: #{e.message}"
    errors.add :base, "There was a problem with your credit card."
    false
  end

  def update_card
    customer = Stripe::Customer.retrieve(stripe_customer_token)
    card = customer.cards.retrieve(card_token)

    *** This Update works, but how do I pass a new Credit Card Number, Expiration Date etc.
    card.name = "My new name"
    customer.save 
  rescue Stripe::StripeError => e 
    logger.error "Stripe Error: " + e.message 
    errors.add :base, "#{e.message}." 
    false
  end

end

观看次数

<%= form_for @subscription do |f| %>
  <%= render 'shared/error_messages', object: f.object %>

  <%= f.hidden_field :plan_id %>
  <%= f.hidden_field :user_id %>
  <%= f.hidden_field :stripe_card_token %>

  <h4>Change Credit Card</h4>

   <div class="field">
     <%= label_tag :card_number, "Credit Card Number" %>
     <%= text_field_tag :card_number, nil, name: nil %>
   </div>
   <div class="field">
     <%= label_tag :card_code, "Security Code on Card (CVV)" %>
     <%= text_field_tag :card_code, nil, name: nil %>
   </div>

   <div class="field">
     <%= label_tag :card_month, "Card Expiration" %>
     <%= select_month nil, {add_month_numbers: true}, {name: nil, id: "card_month"} %>
     <%= select_year nil, {start_year: Date.today.year, end_year: Date.today.year+15}, {name: nil, id: "card_year"} %>
   </div>

   <%= f.submit "Change Credit Card", :class => "btn btn-primary" %>

<% end %>

路线

App::Application.routes.draw do
  resources :subscriptions
end

架构

create_table "subscriptions", :force => true do |t|
  t.integer  "plan_id"
  t.integer  "user_id"
  t.string   "email"
  t.string   "card_name"
  t.string   "exp_month"
  t.string   "exp_year"
  t.string   "card_token"
  t.string   "stripe_customer_token"
  t.string   "last_4_digits"
  t.datetime "created_at",                     :null => false
  t.datetime "updated_at",                     :null => false
end

【问题讨论】:

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


    【解决方案1】:

    感谢@TimSullivan 的说明,此答案已更新

    我必须在我的应用程序中做同样的事情。 Stripe 不允许更新卡号。这给您留下了两个选择:1)创建一张新卡并删除原卡; 2)创建一张新卡,将其设置为default_card并保留原卡。我选择了第二条路线。

    我是这样做的:

    models/subscriber.rb

    def update_card(subscriber, stripe_card_token)
      customer = Stripe::Customer.retrieve(subscriber.stripe_customer_token)
      card = customer.sources.create(card: stripe_card_token)
      card.save
      customer.default_source = card.id
      customer.save
    rescue Stripe::InvalidRequestError => e
      logger.error "Stripe error while updating card info: #{e.message}"
      errors.add :base, "#{e.message}"
      false
    end
    

    controllers/subscribers_controller.rb

    def edit_card
      @subscriber = current_subscriber
    end
    
    def update_card
      @subscriber = current_subscriber
      if @subscriber.update_card(@subscriber, params[:stripe_card_token])
        flash[:success] = 'Saved. Your card information has been updated.'
        redirect_to @subscriber
      else
        flash[:warning] = 'Stripe reported an error while updating your card. Please try again.'
        redirect_to @subscriber
      end
    end
    

    views/subscribers/edit_card.html.erb

    <%= form_for @subscriber, url: update_card_path, html: { class: 'update_subscriber' } do |f| %>
      <div class='form-group'>
        <%= label_tag        :number, 'Card Number', class: 'col-sm-3 control-label' %>
        <div class='col-sm-9'>
          <%= text_field_tag :number, nil, class: 'form-control', placeholder: 'We accept Visa, MasterCard, AMEX and Discover' %>
        </div>
      </div>
    
      <div class='form-group'>
        <%= label_tag        :cvc, 'Security Code', class: 'col-sm-3 control-label' %>
        <div class='col-sm-9'>
          <%= text_field_tag :cvc, nil, class: 'form-control', placeholder: 'The code on the back of your card (CVC)' %>
        </div>
      </div>
    
      <div class='form-group'>
        <%= label_tag        :exp_month, 'Expiration Date', class: 'col-sm-3 control-label' %>
        <div class='col-sm-5'>
          <%= select_month   nil, { add_month_numbers: true }, { id: 'exp_month', class: 'form-control btm-space' } %>
        </div>
        <div class='col-sm-4'>
          <%= select_year    nil, { start_year: Date.today.year, end_year: Date.today.year+15 }, { id: 'exp_year', class: 'form-control btm-space' } %>
        </div>
      </div>
    
      <div class='form-group'>
        <div class='col-sm-4 col-sm-offset-8'>
          <%= submit_tag 'Update', class: 'btn btn-success btn-block' %>
        </div>
      </div>
    
      <%= f.hidden_field :stripe_card_token %>
    <% end %>
    

    config/routes.rb

    match '/edit_card',   to: 'subscribers#edit_card',   via: 'get'
    match '/update_card', to: 'subscribers#update_card', via: 'post'
    

    app/js/update_card.js

    var subscription;
    
    jQuery(function() {
      Stripe.setPublishableKey($('meta[name="stripe-key"]').attr('content'));
      return subscription.setUpForm();
    });
    
    subscription = {
      setUpForm: function() {
        return $('.update_subscriber').submit(function() {
          $('input[type="submit"]').attr('disabled', true);
          if ($('#card_number').length) {
            subscription.updateCard();
            return false;
          } else {
            return true;
          }
        });
      },
      updateCard: function() {
        var card;
        card = {
          number: $('#card_number').val(),
          cvc: $('#card_code').val(),
          expMonth: $('#card_month').val(),
          expYear: $('#card_year').val()
        };
        return Stripe.createToken(card, subscription.handleStripeResponse);
      },
      handleStripeResponse: function(status, response) {
        if (status === 200) {
          $('#subscriber_stripe_card_token').val(response.id);
          return $('.update_subscriber')[0].submit();
        } else {
          $('#stripe_error').text(response.error.message);
          return $('input[type="submit"]').attr('disabled', false);
        }
      }
    };
    

    希望这会有所帮助。

    【讨论】:

    • 这是一种非常不安全的更新卡的方法,因为它会将卡的详细信息发送到您自己的服务器,如果卡被黑客入侵,您将承担责任。使用 stripe.js 生成令牌,如下面我的回答所示。
    • @TimSullivan 你 100% 正确。那是一个巨大的缺陷。谢谢你指出这一点。我已经更新了我的答案。
    • 如何获取表格上的旧卡号?你在哪里加载?
    • cards 集合现在是 sources; customer.sources 和 customer.default_source。
    【解决方案2】:

    此处接受的答案使用起来非常不安全。它围绕 Stripe 鼓励的安全性进行了终结。您自己的服务器应该从不获取信用卡详细信息。

    您应该instead be using Stripe.js to pass the card to Stripe directly(类似于您在链接到的 Railscast 中已经完成的操作)并检索令牌,然后根据令牌创建卡片。

    def update_card(subscriber, stripe_token)
      customer = Stripe::Customer.retrieve(subscriber.stripe_customer_token)
      card = customer.cards.create(card: stripe_token)
      card.save
      customer.default_card = card.id
      customer.save
    rescue Stripe::InvalidRequestError => e
      logger.error "Stripe error while updating card info: #{e.message}"
      errors.add :base, "#{e.message}"
      false
    end
    

    【讨论】:

    • 如果使用 Stripe API > 2015-02-18 它是 Customer->sources->data,而不是 Customer->card。 “data”是一个存储所有支付来源的数组。
    • 从不是一种误导性陈述。并非所有集成都使用 Javascript 来处理付款(或卡上的任何其他操作)。虽然鼓励不要这样做,但如果您符合 PCI 标准,这也不是错误的方法。
    • 如果您遇到了符合 PCI 的麻烦,您当然可以找到比使用 Stripe 更便宜的信用卡处理方式。对于 99.99% 的实际情况,“从不”是正确的,但可以肯定的是,只要您符合 PCI 标准,就可以使用信用卡号为所欲为。
    【解决方案3】:

    您可以像这样更新信用卡详细信息(PHP 代码)

    1. 首先收集卡详细信息和客户条带 ID(您必须在创建此客户时将其保存在数据库中)。
    2. 创建 Stripe 令牌并将其转发到服务器,如下所示:

       Stripe.setPublishableKey('pk_test_6pRNASCoBOKtIshFeQd4XMUh');
        function stripeResponseHandler(status, response) {
        var $form = $('#payment-form');
      
        if (response.error) {
          // Show the errors on the form
          $form.find('.payment-errors').text(response.error.message);
          $form.find('button').prop('disabled', false);
        } else {
          // response contains id and card, which contains additional card details
          var token = response.id;
          alert('The Token Is: '+token);
          // Insert the token into the form so it gets submitted to the server
          $form.append($('<input type="hidden" name="stripeToken" />').val(token));
          // and submit
          $form.get(0).submit();
        }
      };
      
        jQuery(function($) {
        $('#payment-form').submit(function(event) {
          var $form = $(this);
      
          // Disable the submit button to prevent repeated clicks
          $form.find('button').prop('disabled', true);
      
          Stripe.card.createToken($form, stripeResponseHandler);
      
          // Prevent the form from submitting with the default action
          return false;
        });
      });
      
    3. 在服务器中,您可以像这样更新详细信息:

        $customer_id = trim($_REQUEST['cus_select']);     
        $query = "SELECT cus_stripe_id FROM customer_details_tbl WHERE id=$customer_id";      
        $customer_query = mysqli_query($db_conn,$query);
        $row_customer = mysqli_fetch_row($customer_query);
        $cus_stripe_id = $row_customer[0];
      
         Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
      
         $error = '';
         $success = '';
         try {
          if (!isset($_POST['stripeToken']))
            throw new Exception("The Stripe Token was not generated correctly");
          $customer = Stripe_Customer::retrieve($cus_stripe_id);
          $customer->source = $_POST['stripeToken'];
          $customer->save();
          echo "Card Details Updated Successfully";
         }
         catch (Exception $e) {
          $error = $e->getMessage();
          echo $error;
         }
       }
      

    就是这样。这将用新的详细信息覆盖现有卡。

    【讨论】:

      【解决方案4】:

      2015-02-18 卡和 default_card 属性不再返回客户。您现在应该分别使用 sources 和 default_source。 customer.card.* 和 customer.bank_account.* webhook 现在命名为 customer.source.*。如果您只有与客户关联的卡(而不是其他类型的支付来源),那么您可以像使用旧属性一样使用新属性。如果您有多种类型的支付来源,则来源列表包含异构对象,您可以检查每个来源的对象属性以确定其格式。旧 API 版本同时返回新属性和旧属性。

      【讨论】:

      • 我不明白你为什么要这样辩解
      【解决方案5】:

      像 Tim Sullivan 建议的那样使用 Stripe.js。这是他为更新的 Stripe API 更新的代码。

      def update_card(subscriber, stripe_token)
        customer = Stripe::Customer.retrieve({CUSTOMER_ID})
        card = customer.sources.create(card: params[:stripeToken])
        customer.default_source = card.id
        customer.save
      rescue Stripe::InvalidRequestError => e
        logger.error "Stripe error while updating card info: #{e.message}"
        errors.add :base, "#{e.message}"
        false
      end
      

      【讨论】:

        【解决方案6】:

        所以 Stripe 的 ruby​​ 库有 update method,您已经有 card.name,但对于到期日期,您可以使用 card.exp_monthcard.exp_year,查看 update method 了解所有参数。

        您会注意到那里没有更新卡号,所以我的建议是让用户创建另一张卡,如果他们想更改他们的信用卡号,然后更新客户以将该卡作为他们的默认信用卡(查看 stripe ruby​​ 库文档中的 create cardupdate a customer)。

        希望对你有帮助

        【讨论】:

        • 我将如何继续在我的控制器中创建新卡?我是否也需要将每张卡片存储在我的数据库中...对不起,我在使用 API 和 Stripe 方面还很陌生
        • 我会创建一个类似于 save_with_stripe_payment 的方法,只是你不需要创建一个新客户,这次只需要一张新卡,你可以选择存储每张卡,也可以保存最新的一,stripe api 可以让您获取客户随身携带的所有卡片stripe.com/docs/api/ruby#list_cards,以防您没有保存所有卡片,希望对您有所帮助
        • 我相信使用 Stripe 的全部意义在于您不会将客户的信用卡保存在本地数据库中
        猜你喜欢
        • 2015-10-16
        • 2021-01-25
        • 2019-02-25
        • 2021-05-04
        • 2016-10-22
        • 2015-06-26
        • 2019-02-15
        • 2021-02-20
        • 2020-11-01
        相关资源
        最近更新 更多