Skip to content

Commit

Permalink
Merge pull request #305 from spark-solutions/use-stripe-elements
Browse files Browse the repository at this point in the history
Add support for Stripe Elements and Apple Pay (via Payment Request API)
  • Loading branch information
damianlegawiec authored Dec 11, 2018
2 parents 7aea916 + 0412072 commit 36ef368
Show file tree
Hide file tree
Showing 14 changed files with 405 additions and 18 deletions.
10 changes: 10 additions & 0 deletions app/models/spree/gateway/stripe_apple_pay_gateway.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Spree
class Gateway::StripeApplePayGateway < Gateway::StripeGateway
preference :country_code, :string, default: 'US'
preference :domain_verification_certificate, :text

def method_type
'stripe_apple_pay'
end
end
end
7 changes: 7 additions & 0 deletions app/models/spree/gateway/stripe_elements_gateway.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Spree
class Gateway::StripeElementsGateway < Gateway::StripeGateway
def method_type
'stripe_elements'
end
end
end
15 changes: 15 additions & 0 deletions app/models/spree/order_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module SpreeGateway
module ApplePayOrderDecorator
def confirmation_required?
return false if paid_with_apple_pay?

super
end

def paid_with_apple_pay?
payments.valid.any?(&:apple_pay?)
end
end
end

Spree::Order.prepend SpreeGateway::ApplePayOrderDecorator
9 changes: 9 additions & 0 deletions app/models/spree/payment_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module SpreeGateway
module ApplePayPaymentDecorator
def apple_pay?
payment_method.is_a? Spree::Gateway::StripeApplePayGateway
end
end
end

Spree::Payment.prepend SpreeGateway::ApplePayPaymentDecorator
6 changes: 6 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# we need to add this route to the root Rails application because Spree can
# be mounted to some path eg. /shop and Apple Pay expects to access this file
# via https://example.com/.well-known/apple-developer-merchantid-domain-association
Rails.application.routes.draw do
get '/.well-known/apple-developer-merchantid-domain-association' => 'spree/apple_pay_domain_verification#show'
end
11 changes: 11 additions & 0 deletions lib/controllers/spree/apple_pay_domain_verification_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Spree
class ApplePayDomainVerificationController < Spree::BaseController
def show
gateway = Spree::Gateway::StripeApplePayGateway.active.last

raise ActiveRecord::RecordNotFound unless gateway

render plain: gateway.preferred_domain_verification_certificate
end
end
end
11 changes: 7 additions & 4 deletions lib/spree_gateway/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class Engine < Rails::Engine
app.config.spree.payment_methods << Spree::Gateway::SecurePayAU
app.config.spree.payment_methods << Spree::Gateway::SpreedlyCoreGateway
app.config.spree.payment_methods << Spree::Gateway::StripeGateway
app.config.spree.payment_methods << Spree::Gateway::StripeElementsGateway
app.config.spree.payment_methods << Spree::Gateway::StripeApplePayGateway
app.config.spree.payment_methods << Spree::Gateway::UsaEpayTransaction
app.config.spree.payment_methods << Spree::Gateway::Worldpay
end
Expand All @@ -40,9 +42,9 @@ def self.activate
'lib/assets/javascripts/spree/frontend/spree_gateway.js',
'lib/assets/stylesheets/spree/frontend/spree_gateway.css',
]
Dir.glob(File.join(File.dirname(__FILE__), "../../controllers/frontend/*/*_decorator*.rb")) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
end
end
Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/spree/*_decorator*.rb')) do |c|
Rails.application.config.cache_classes ? require(c) : load(c)
end
end

Expand All @@ -58,8 +60,9 @@ def self.frontend_available?
paths["app/views"] << "lib/views/backend"
end

paths['app/controllers'] << 'lib/controllers'

if self.frontend_available?
paths["app/controllers"] << "lib/controllers/frontend"
paths["app/views"] << "lib/views/frontend"
end

Expand Down
16 changes: 2 additions & 14 deletions lib/views/frontend/spree/checkout/payment/_stripe.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
}
};

$(document).ready(function() {
Spree.ready(function() {
Spree.stripePaymentMethod.prepend("<div id='stripeError' class='errorExplanation alert alert-danger' style='display:none'></div>");
return $('#checkout_form_payment [data-hook=buttons]').click(function() {
var expiration, params;
Expand All @@ -78,16 +78,4 @@
});
</script>

<%- if @order.has_checkout_step?('address') -%>
<script>
Spree.stripeAdditionalInfo = {
name: "<%= @order.bill_address.full_name %>",
address_line1: "<%= @order.bill_address.address1 %>",
address_line2: "<%= @order.bill_address.address2 %>",
address_city: "<%= @order.bill_address.city %>",
address_state: "<%= @order.bill_address.state_text %>",
address_zip: "<%= @order.bill_address.zipcode %>",
address_country: "<%= @order.bill_address.country %>"
}
</script>
<%- end -%>
<%= render 'spree/checkout/payment/stripe_additional_info', bill_address: @order.bill_address %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<% bill_address ||= @order.bill_address %>
<%- if @order.has_checkout_step?('address') -%>
<script>
Spree.stripeAdditionalInfo = {
name: "<%= bill_address.full_name %>",
address_line1: "<%= bill_address.address1 %>",
address_line2: "<%= bill_address.address2 %>",
address_city: "<%= bill_address.city %>",
address_state: "<%= bill_address.state_text %>",
address_zip: "<%= bill_address.zipcode %>",
address_country: "<%= bill_address.country %>"
}
</script>
<%- end -%>
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<% param_prefix = "payment_source[#{payment_method.id}]" %>

<div id="payment-request-button">
<!-- A Stripe Element will be inserted here. -->
</div>

<script type="text/javascript" src="https://js.stripe.com/v3/"></script>

<script>
Spree.stripeApplePayPaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
var stripeApplePay = Stripe("<%= payment_method.preferred_publishable_key %>");
var elements = stripeApplePay.elements();

var paymentRequest = stripeApplePay.paymentRequest({
country: '<%= payment_method.preferred_country_code.try(:upcase) %>',
currency: '<%= @order.currency.downcase %>',
displayItems: [
<% @order.line_items.each do |line_item| %>
{
label: '<%= line_item.name %> x <%= line_item.quantity %>',
amount: <%= Spree::Money.new(line_item.total, currency: line_item.currency).amount_in_cents %>
},
<% end %>
<% if @order.tax_total != 0 %>
{
label: '<%= Spree.t(:tax) %>',
amount: <%= Spree::Money.new(@order.tax_total, currency: @order.currency).amount_in_cents %>
},
<% end %>
<% if @order.shipment_total != 0 %>
{
label: '<%= Spree.t(:shipment) %>',
amount: <%= Spree::Money.new(@order.shipment_total, currency: @order.currency).amount_in_cents %>
}
<% end %>
],
total: {
label: '<%= Spree.t(:total) %>',
amount: <%= Spree::Money.new(@order.total, currency: @order.currency).amount_in_cents %>
},
requestPayerName: false,
requestPayerEmail: false,
requestPayerPhone: false
});

var prButton = elements.create('paymentRequestButton', {
paymentRequest: paymentRequest,
});

// Check the availability of the Payment Request API first.
paymentRequest.canMakePayment().then(function(result) {
if (result) {
prButton.mount('#payment-request-button');
} else {
document.getElementById('payment-request-button').style.display = 'none';
}
});

function addCreditCardFieldToForm(form, name, value) {
var hiddenInput = document.createElement('input');

hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', name);
hiddenInput.setAttribute('value', value);
form.appendChild(hiddenInput);
};

paymentRequest.on('token', function(ev) {
var form = document.getElementById('checkout_form_payment');
var token = ev.token;
if (ev.payerName) {
var payerName = ev.payerName
} else if (Spree.stripeAdditionalInfo) {
var payerName = Spree.stripeAdditionalInfo.name
}
addCreditCardFieldToForm(form, '<%= param_prefix %>[gateway_payment_profile_id]', token.id);
addCreditCardFieldToForm(form, '<%= param_prefix %>[number]', token.card.last4);
addCreditCardFieldToForm(form, '<%= param_prefix %>[month]', token.card.exp_month);
addCreditCardFieldToForm(form, '<%= param_prefix %>[year]', token.card.exp_year);
addCreditCardFieldToForm(form, '<%= param_prefix %>[name]', payerName);
ev.complete('success');
form.submit();
});

Spree.ready(function() {
Spree.stripeApplePayPaymentMethod.prepend("<div id='stripeApplePayError' class='errorExplanation alert alert-danger' style='display:none'></div>");
var form = document.getElementById('checkout_form_payment');
form.addEventListener('submit', function(e) {
if (Spree.stripeApplePayPaymentMethod.is(':visible')) {
$('#stripeApplePayError').hide();
e.preventDefault();
}
});
});
</script>

<%= render 'spree/checkout/payment/stripe_additional_info' %>
104 changes: 104 additions & 0 deletions lib/views/frontend/spree/checkout/payment/_stripe_elements.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<% param_prefix = "payment_source[#{payment_method.id}]" %>
<div class="well clearfix">
<p class="field">
<%= label_tag "name_on_card_#{payment_method.id}" do %>
<%= Spree.t(:name_on_card) %><abbr class="required" title="required">*</abbr>
<% end %>
<%= text_field_tag "#{param_prefix}[name]", "#{@order.bill_address_firstname} #{@order.bill_address_lastname}", { id: "name_on_card_#{payment_method.id}", class: 'form-control required'} %>
</p>
<div class="form-row">
<%= label_tag "card_number" do %>
<%= Spree.t(:card_number) %><abbr class="required" title="required">*</abbr>
<% end %>
<div class="form-control required cardNumber">
<div id="card-element">
<!-- a Stripe Element will be inserted here. -->
</div>
</div>
<!-- Used to display form errors -->
<div id="card-errors" role="alert"></div>
</div>
</p>
</div>

<script type="text/javascript" src="https://js.stripe.com/v3/"></script>

<script>
Spree.stripeElementsPaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
var stripeElements = Stripe("<%= payment_method.preferred_publishable_key %>");
var elements = stripeElements.elements();

var card = elements.create('card', {
iconStyle: 'solid',
hidePostalCode: true,
style: {
base: {
iconColor: '#555555',
lineHeight: '24px',
fontWeight: 300,
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSize: '14px',

'::placeholder': {
color: '#555555',
},
},
invalid: {
iconColor: '#e85746',
color: '#e85746',
}
},
classes: {
focus: 'is-focused',
empty: 'is-empty',
},
});
card.mount('#card-element');

function addCreditCardFieldToForm(form, name, value) {
var hiddenInput = document.createElement('input');

hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', name);
hiddenInput.setAttribute('value', value);
form.appendChild(hiddenInput);
};

function stripeElementsTokenHandler(token) {
var form = document.getElementById('checkout_form_payment');
addCreditCardFieldToForm(form, '<%= param_prefix %>[gateway_payment_profile_id]', token.id)
addCreditCardFieldToForm(form, '<%= param_prefix %>[number]', token.card.last4)
addCreditCardFieldToForm(form, '<%= param_prefix %>[month]', token.card.exp_month)
addCreditCardFieldToForm(form, '<%= param_prefix %>[year]', token.card.exp_year)
form.submit();
};

function createStripeElementsToken() {
stripeElements.createToken(card, Spree.stripeAdditionalInfo).then(function (result) {
if (result.error) {
// Inform the user if there was an error
var errorElement = document.getElementById('card-errors');

$('#stripeElementsError').html(result.error.message);
$('#stripeElementsError').show()
Spree.enableSave();
} else {
stripeElementsTokenHandler(result.token);
}
});
};

Spree.ready(function() {
Spree.stripeElementsPaymentMethod.prepend("<div id='stripeElementsError' class='errorExplanation alert alert-danger' style='display:none'></div>");
var form = document.getElementById('checkout_form_payment');
form.addEventListener('submit', function(e) {
if (Spree.stripeElementsPaymentMethod.is(':visible')) {
$('#stripeElementsError').hide();
e.preventDefault();
createStripeElementsToken();
}
});
});
</script>

<%= render 'spree/checkout/payment/stripe_additional_info' %>
Loading

0 comments on commit 36ef368

Please sign in to comment.