Ask customer about saving card details used in checkout

This commit is contained in:
Rob Harrington
2017-07-31 12:46:41 +10:00
parent f317cee9e9
commit 3b78179dea
6 changed files with 128 additions and 71 deletions

View File

@@ -74,6 +74,7 @@ Darkswarm.factory 'Checkout', ($injector, CurrentOrder, ShippingMethods, StripeJ
year: @secrets.card.exp_year
first_name: @order.bill_address.firstname
last_name: @order.bill_address.lastname
save_requested_by_customer: @secrets.save_requested_by_customer
}
munged_order

View File

@@ -4,6 +4,10 @@ Spree::CreditCard.class_eval do
# Obviously can be removed once we are using strong params
attr_accessible :cc_type, :last_digits
# For holding customer preference in memory
attr_accessible :save_requested_by_customer
attr_writer :save_requested_by_customer
# Should be able to remove once we reach Spree v2.2.0
# https://github.com/spree/spree/commit/411010f3975c919ab298cb63962ee492455b415c
belongs_to :payment_method
@@ -15,4 +19,8 @@ Spree::CreditCard.class_eval do
def has_payment_profile? # rubocop:disable Style/PredicateName
gateway_customer_profile_id.present? || gateway_payment_profile_id.present?
end
def save_requested_by_customer?
!!@save_requested_by_customer
end
end

View File

@@ -94,6 +94,7 @@ module Spree
def create_payment_profile
return unless source.is_a?(CreditCard)
return unless source.try(:save_requested_by_customer?)
return unless source.number || source.gateway_payment_profile_id
return unless source.gateway_customer_profile_id.nil?
payment_method.create_profile(self)

View File

@@ -10,5 +10,10 @@
%div{ ng: { if: '!secrets.selected_card' } }
= render "spree/checkout/payment/gateway", payment_method: payment_method
.row
.small-12.columns.text-right
= check_box_tag 'secrets.save_requested_by_customer'
= label_tag 'secrets.save_requested_by_customer', t('.remember_this_card')
:javascript
Stripe.setPublishableKey("#{ENV['STRIPE_INSTANCE_PUBLISHABLE_KEY']}")

View File

@@ -2161,6 +2161,7 @@ Please follow the instructions there to make your enterprise visible on the Open
enter_new_card: Enter details for a new card
used_saved_card: "Use a saved card:"
or_enter_new_card: "Or, enter details for a new card:"
remember_this_card: Remember this card?
date_picker:
format: ! '%Y-%m-%d'
js_format: 'yy-mm-dd'

View File

@@ -35,83 +35,124 @@ describe "Submitting Stripe Connect charge requests", type: :request do
context "when a new card is submitted" do
let(:store_response_mock) { { status: 200, body: JSON.generate(id: customer_id, default_card: card_id, sources: { data: [{id: "1"}] }) } }
let(:charge_response_mock) { { status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) } }
let(:token_response_mock) { { status: 200, body: JSON.generate(id: new_token) } }
let(:charge_response_mock) { { status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) } }
before do
# Saves the card against the user
stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/customers")
.with(:body => { card: token, email: order.email})
.to_return(store_response_mock)
# Requests a token from the newly saved card
stub_request(:post, "https://api.stripe.com/v1/tokens")
.with(:body => { card: card_id, customer: customer_id})
.to_return(token_response_mock)
# Charges the card
stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/charges")
.with(:body => {"amount" => "1234", "card" => new_token, "currency" => "aud", "description" => "Spree Order ID: #{order.number}", "payment_user_agent" => "Stripe/v1 ActiveMerchantBindings/1.63.0"})
.to_return(charge_response_mock)
end
context "and the store, token and charge requests are successful" do
it "should process the payment, and stores the card/customer details" do
put update_checkout_path, params
json_response = JSON.parse(response.body)
expect(json_response["path"]).to eq spree.order_path(order)
expect(order.payments.completed.count).to be 1
card = order.payments.completed.first.source
expect(card.gateway_customer_profile_id).to eq customer_id
expect(card.gateway_payment_profile_id).to eq card_id
expect(card.cc_type).to eq "visa"
expect(card.last_digits).to eq "4242"
expect(card.first_name).to eq "Jill"
expect(card.last_name).to eq "Jeffreys"
end
end
context "when the store request returns an error message" do
let(:store_response_mock) { { status: 402, body: JSON.generate(error: { message: "Bup-bow..."}) } }
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq I18n.t(:spree_gateway_error_flash_for_checkout, error: 'Bup-bow...')
expect(order.payments.completed.count).to be 0
end
end
context "when the charge request returns an error message" do
let(:charge_response_mock) { { status: 402, body: JSON.generate(error: { message: "Bup-bow..."}) } }
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq I18n.t(:payment_processing_failed)
expect(order.payments.completed.count).to be 0
end
end
context "when the token request returns an error message" do
let(:token_response_mock) { { status: 402, body: JSON.generate(error: { message: "Bup-bow..."}) } }
let(:charge_response_mock) { { status: 402, body: JSON.generate(error: { message: "Bup-bow..."}) } }
context "and the user doesn't request that the card is saved for later" do
before do
# Attempts to charge the card without a token, which will return an error
# Charges the card
stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/charges")
.with(:body => {"amount" => "1234", "currency" => "aud", "description" => "Spree Order ID: #{order.number}", "payment_user_agent" => "Stripe/v1 ActiveMerchantBindings/1.63.0"})
.with(:body => {"amount" => "1234", "card" => token, "currency" => "aud", "description" => "Spree Order ID: #{order.number}", "payment_user_agent" => "Stripe/v1 ActiveMerchantBindings/1.63.0"})
.to_return(charge_response_mock)
end
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq I18n.t(:payment_processing_failed)
expect(order.payments.completed.count).to be 0
context "and the charge request is successful" do
it "should process the payment without storing card details" do
put update_checkout_path, params
json_response = JSON.parse(response.body)
expect(json_response["path"]).to eq spree.order_path(order)
expect(order.payments.completed.count).to be 1
card = order.payments.completed.first.source
expect(card.gateway_customer_profile_id).to eq nil
expect(card.gateway_payment_profile_id).to eq token
expect(card.cc_type).to eq "visa"
expect(card.last_digits).to eq "4242"
expect(card.first_name).to eq "Jill"
expect(card.last_name).to eq "Jeffreys"
end
end
context "when the charge request returns an error message" do
let(:charge_response_mock) { { status: 402, body: JSON.generate(error: { message: "Bup-bow..."}) } }
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq I18n.t(:payment_processing_failed)
expect(order.payments.completed.count).to be 0
end
end
end
context "and the customer requests that the card is saved for later" do
before do
params[:order][:payments_attributes][0][:source_attributes][:save_requested_by_customer] = '1'
# Saves the card against the user
stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/customers")
.with(:body => { card: token, email: order.email})
.to_return(store_response_mock)
# Requests a token from the newly saved card
stub_request(:post, "https://api.stripe.com/v1/tokens")
.with(:body => { card: card_id, customer: customer_id})
.to_return(token_response_mock)
# Charges the card
stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/charges")
.with(:body => {"amount" => "1234", "card" => new_token, "currency" => "aud", "description" => "Spree Order ID: #{order.number}", "payment_user_agent" => "Stripe/v1 ActiveMerchantBindings/1.63.0"})
.to_return(charge_response_mock)
end
context "and the store, token and charge requests are successful" do
it "should process the payment, and stores the card/customer details" do
put update_checkout_path, params
json_response = JSON.parse(response.body)
expect(json_response["path"]).to eq spree.order_path(order)
expect(order.payments.completed.count).to be 1
card = order.payments.completed.first.source
expect(card.gateway_customer_profile_id).to eq customer_id
expect(card.gateway_payment_profile_id).to eq card_id
expect(card.cc_type).to eq "visa"
expect(card.last_digits).to eq "4242"
expect(card.first_name).to eq "Jill"
expect(card.last_name).to eq "Jeffreys"
end
end
context "when the store request returns an error message" do
let(:store_response_mock) { { status: 402, body: JSON.generate(error: { message: "Bup-bow..."}) } }
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq I18n.t(:spree_gateway_error_flash_for_checkout, error: 'Bup-bow...')
expect(order.payments.completed.count).to be 0
end
end
context "when the charge request returns an error message" do
let(:charge_response_mock) { { status: 402, body: JSON.generate(error: { message: "Bup-bow..."}) } }
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq I18n.t(:payment_processing_failed)
expect(order.payments.completed.count).to be 0
end
end
context "when the token request returns an error message" do
let(:token_response_mock) { { status: 402, body: JSON.generate(error: { message: "Bup-bow..."}) } }
let(:charge_response_mock) { { status: 402, body: JSON.generate(error: { message: "Bup-bow..."}) } }
before do
# Attempts to charge the card without a token, which will return an error
stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/charges")
.with(:body => {"amount" => "1234", "currency" => "aud", "description" => "Spree Order ID: #{order.number}", "payment_user_agent" => "Stripe/v1 ActiveMerchantBindings/1.63.0"})
.to_return(charge_response_mock)
end
it "should not process the payment" do
put update_checkout_path, params
expect(response.status).to be 400
json_response = JSON.parse(response.body)
expect(json_response["flash"]["error"]).to eq I18n.t(:payment_processing_failed)
expect(order.payments.completed.count).to be 0
end
end
end
end
@@ -131,8 +172,8 @@ describe "Submitting Stripe Connect charge requests", type: :request do
)
end
let(:charge_response_mock) { { status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) } }
let(:token_response_mock) { { status: 200, body: JSON.generate(id: new_token) } }
let(:charge_response_mock) { { status: 200, body: JSON.generate(id: "ch_1234", object: "charge", amount: 2000) } }
before do
params[:order][:existing_card] = credit_card.id