diff --git a/Gemfile b/Gemfile
index fed00725..6c0a4b76 100644
--- a/Gemfile
+++ b/Gemfile
@@ -12,6 +12,8 @@ gem 'solidus_auth_devise', '~> 1.0'
group :development, :test do
gem "pry-rails"
+ gem "selenium-webdriver"
+ gem "chromedriver-helper"
end
gemspec
diff --git a/app/assets/javascripts/spree/frontend/paypal_button.js b/app/assets/javascripts/spree/frontend/paypal_button.js
new file mode 100644
index 00000000..df8a1586
--- /dev/null
+++ b/app/assets/javascripts/spree/frontend/paypal_button.js
@@ -0,0 +1,182 @@
+/**
+ * Constructor for PayPal button object
+ * @constructor
+ * @param {object} element - The DOM element of your PayPal button
+ */
+function PaypalButton(element) {
+ this.element = element;
+}
+
+/**
+ * Creates the PayPal session using the provided options and enables the button
+ *
+ * @param {object} options - The options passed to tokenize when constructing
+ * the PayPal instance
+ *
+ * See {@link https://braintree.github.io/braintree-web/3.5.0/PayPal.html#tokenize}
+ */
+PaypalButton.prototype.initialize = function(options) {
+ var self = this;
+
+ /* This sets the payment method id returned by fetchToken on the PaypalButton
+ * instance so that we can use it to build the transaction params later. */
+ SolidusPaypalBraintree.fetchToken(function(token, paymentMethodId) {
+ self.paymentMethodId = paymentMethodId;
+
+ SolidusPaypalBraintree.initializeWithDataCollector(token, function(client) {
+ self.createPaypalInstance(client, function(paypal) {
+
+ self.initializePaypalSession({
+ paypalInstance: paypal,
+ paypalButton: self.element,
+ paypalOptions: options
+ }, self.tokenizeCallback.bind(self));
+ });
+
+ });
+ });
+};
+
+PaypalButton.prototype.createPaypalInstance = function(braintreeClient, readyCallback) {
+ braintree.paypal.create({
+ client: braintreeClient
+ }, function (paypalErr, paypalInstance) {
+ if (paypalErr) {
+ console.error("Error creating PayPal:", paypalErr);
+ return;
+ }
+ readyCallback(paypalInstance);
+ });
+};
+
+/* Initializes and begins the Paypal session
+ *
+ * @param config Configuration settings for the session
+ * @param config.paypalInstance {object} The Paypal instance returned by Braintree
+ * @param config.paypalButton {object} The button DOM element
+ * @param config.paypalOptions {object} Configuration options for Paypal
+ * @param config.error {tokenizeErrorCallback} Callback function for tokenize errors
+ * @param {tokenizeCallback} callback Callback function for tokenization
+ */
+PaypalButton.prototype.initializePaypalSession = function(config, callback) {
+ config.paypalButton.removeAttribute('disabled');
+ config.paypalButton.addEventListener('click', function(event) {
+ config.paypalInstance.tokenize(config.paypalOptions, callback);
+ }, false);
+ },
+
+/**
+ * Default callback function for when tokenization completes
+ *
+ * @param {object|null} tokenizeErr - The error returned by Braintree on failure
+ * @param {object} payload - The payload returned by Braintree on success
+ */
+PaypalButton.prototype.tokenizeCallback = function(tokenizeErr, payload) {
+ if (tokenizeErr) {
+ console.error('Error tokenizing:', tokenizeErr);
+ } else {
+ var params = this.transactionParams(payload);
+
+ Spree.ajax({
+ url: Spree.pathFor("solidus_paypal_braintree/transactions"),
+ type: 'POST',
+ dataType: 'json',
+ data: params,
+ success: function(response) {
+ window.location.href = response.redirectUrl;
+ },
+ error: function(xhr) {
+ console.error("Error submitting transaction")
+ },
+ });
+ }
+};
+
+/**
+ * Assigns a new callback function for when tokenization completes
+ *
+ * @callback callback - The callback function to assign
+ */
+PaypalButton.prototype.setTokenizeCallback = function(callback) {
+ this.tokenizeCallback = callback;
+};
+
+/**
+ * Builds the transaction parameters to submit to Solidus for the given
+ * payload returned by Braintree
+ *
+ * @param {object} payload - The payload returned by Braintree after tokenization
+ */
+PaypalButton.prototype.transactionParams = function(payload) {
+ return {
+ "payment_method_id" : this.paymentMethodId,
+ "transaction" : {
+ "email" : payload.details.email,
+ "phone" : payload.details.phone,
+ "nonce" : payload.nonce,
+ "payment_type" : payload.type,
+ "address_attributes" : this.addressParams(payload)
+ }
+ }
+};
+
+/**
+ * Builds the address parameters to submit to Solidus using the payload
+ * returned by Braintree
+ *
+ * @param {object} payload - The payload returned by Braintree after tokenization
+ */
+PaypalButton.prototype.addressParams = function(payload) {
+ if (payload.details.shippingAddress.recipientName) {
+ var first_name = payload.details.shippingAddress.recipientName.split(" ")[0];
+ var last_name = payload.details.shippingAddress.recipientName.split(" ")[1];
+ }
+ if (first_name == null || last_name == null) {
+ var first_name = payload.details.firstName;
+ var last_name = payload.details.lastName;
+ }
+
+ return {
+ "first_name" : first_name,
+ "last_name" : last_name,
+ "address_line_1" : payload.details.shippingAddress.line1,
+ "address_line_2" : payload.details.shippingAddress.line2,
+ "city" : payload.details.shippingAddress.city,
+ "state_code" : payload.details.shippingAddress.state,
+ "zip" : payload.details.shippingAddress.postalCode,
+ "country_code" : payload.details.shippingAddress.countryCode
+ }
+};
+
+$(document).ready(function() {
+ if (document.getElementById("empty-cart")) {
+ $.when(
+ $.getScript("https://js.braintreegateway.com/web/3.5.0/js/client.min.js"),
+ $.getScript("https://js.braintreegateway.com/web/3.5.0/js/paypal.min.js"),
+ $.getScript("https://js.braintreegateway.com/web/3.5.0/js/data-collector.min.js"),
+ $.Deferred(function( deferred ){
+ $( deferred.resolve );
+ })
+ ).done(function() {
+ $('').attr({
+ 'data-merchant' : "braintree",
+ 'data-id' : "paypal-button",
+ 'data-button' : "checkout",
+ 'data-color' : "blue",
+ 'data-size' : "medium",
+ 'data-shape' : "pill",
+ 'data-button_type' : "button",
+ 'data-button_disabled' : "true"
+ }).
+ load(function() {
+ var button = new PaypalButton(document.querySelector("#paypal-button"));
+ button.initialize({
+ flow: 'vault',
+ enableShippingAddress: true,
+ });
+ }).
+ insertAfter("#content").
+ attr('src', 'https://www.paypalobjects.com/api/button.js?')
+ });
+ }
+});
diff --git a/app/assets/javascripts/spree/frontend/solidus_paypal_braintree.js b/app/assets/javascripts/spree/frontend/solidus_paypal_braintree.js
index 40e58b6d..28de0ecf 100644
--- a/app/assets/javascripts/spree/frontend/solidus_paypal_braintree.js
+++ b/app/assets/javascripts/spree/frontend/solidus_paypal_braintree.js
@@ -30,6 +30,27 @@ window.SolidusPaypalBraintree = {
});
},
+ initializeWithDataCollector: function(authToken, clientReadyCallback) {
+ braintree.client.create({
+ authorization: authToken
+ }, function (clientErr, clientInstance) {
+ braintree.dataCollector.create({
+ client: clientInstance,
+ paypal: true
+ }, function (err, dataCollectorInstance) {
+ if (err) {
+ console.error('Error creating data collector:', err);
+ return;
+ }
+ });
+ if (clientErr) {
+ console.error('Error creating client:', clientErr);
+ return;
+ }
+ clientReadyCallback(clientInstance);
+ });
+ },
+
setupApplePay: function(braintreeClient, merchantId, readyCallback) {
if(window.ApplePaySession) {
var promise = ApplePaySession.canMakePaymentsWithActiveCard(merchantId);
diff --git a/app/models/solidus_paypal_braintree/source.rb b/app/models/solidus_paypal_braintree/source.rb
index be51bc54..f4629482 100644
--- a/app/models/solidus_paypal_braintree/source.rb
+++ b/app/models/solidus_paypal_braintree/source.rb
@@ -1,4 +1,7 @@
class SolidusPaypalBraintree::Source < ApplicationRecord
+ PAYPAL = "PayPalAccount"
+ APPLE_PAY = "ApplePayCard"
+
belongs_to :user, class_name: "Spree::User"
belongs_to :payment_method, class_name: 'Spree::PaymentMethod'
has_many :payments, as: :source, class_name: "Spree::Payment"
@@ -25,4 +28,16 @@ def can_void?(payment)
def can_credit?(payment)
payment.completed? && payment.credit_allowed > 0
end
+
+ def friendly_payment_type
+ I18n.t(payment_type.underscore, scope: "solidus_paypal_braintree.payment_type")
+ end
+
+ def apple_pay?
+ payment_type == APPLE_PAY
+ end
+
+ def paypal?
+ payment_type == PAYPAL
+ end
end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 547bfb1b..cdec84f8 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -2,3 +2,6 @@ en:
solidus_paypal_braintree:
nonce: Nonce
token: Token
+ payment_type:
+ apple_pay_card: Apple Pay
+ pay_pal_account: PayPal
diff --git a/lib/generators/solidus_paypal_braintree/install/install_generator.rb b/lib/generators/solidus_paypal_braintree/install/install_generator.rb
index 1c02f97d..85597ac0 100644
--- a/lib/generators/solidus_paypal_braintree/install/install_generator.rb
+++ b/lib/generators/solidus_paypal_braintree/install/install_generator.rb
@@ -5,6 +5,7 @@ class InstallGenerator < Rails::Generators::Base
def add_javascripts
append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/solidus_paypal_braintree\n"
+ append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/paypal_button\n"
append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_paypal_braintree\n"
end
diff --git a/lib/solidus_paypal_braintree/engine.rb b/lib/solidus_paypal_braintree/engine.rb
index a268f19f..bcd356ee 100644
--- a/lib/solidus_paypal_braintree/engine.rb
+++ b/lib/solidus_paypal_braintree/engine.rb
@@ -26,7 +26,10 @@ def self.frontend_available?
end
if frontend_available?
- config.assets.precompile += ['spree/frontend/solidus_paypal_braintree']
+ config.assets.precompile += [
+ 'spree/frontend/solidus_paypal_braintree',
+ 'spree/frontend/paypal_button'
+ ]
paths["app/controllers"] << "lib/controllers/frontend"
paths["app/views"] << "lib/views/frontend"
end
diff --git a/lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb b/lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb
new file mode 100644
index 00000000..eb97dc0c
--- /dev/null
+++ b/lib/views/frontend/spree/checkout/payment/_paypal_braintree.html.erb
@@ -0,0 +1,38 @@
+<% address = current_order.ship_address %>
+
+
+
+
+
+
+
+
+
diff --git a/lib/views/frontend/spree/checkout/payment/_paypal_credit_braintree.html.erb b/lib/views/frontend/spree/checkout/payment/_paypal_credit_braintree.html.erb
new file mode 100644
index 00000000..ae6b3d96
--- /dev/null
+++ b/lib/views/frontend/spree/checkout/payment/_paypal_credit_braintree.html.erb
@@ -0,0 +1,40 @@
+<% address = current_order.ship_address %>
+
+
+
+
+
+
+
+
+
diff --git a/spec/features/paypal_checkout_spec.rb b/spec/features/paypal_checkout_spec.rb
new file mode 100644
index 00000000..1c1d822f
--- /dev/null
+++ b/spec/features/paypal_checkout_spec.rb
@@ -0,0 +1,93 @@
+require 'spec_helper'
+
+describe "Checkout", type: :feature, js: true do
+ Capybara.default_max_wait_time = 30
+ let!(:store) { create(:store, payment_methods: [payment_method]) }
+ let!(:country) { create(:country, states_required: true) }
+ let!(:state) { create(:state, country: country, abbr: "CA", name: "California") }
+ let!(:shipping_method) { create(:shipping_method) }
+ let!(:stock_location) { create(:stock_location) }
+ let!(:mug) { create(:product, name: "RoR Mug") }
+ let!(:payment_method) { create_gateway }
+ let!(:zone) { create(:zone) }
+
+ context "goes through checkout using paypal one touch", vcr: { cassette_name: 'paypal/one_touch_checkout', match_requests_on: [:method, :uri] } do
+ before do
+ payment_method
+ add_mug_to_cart
+ end
+
+ it "should check out successfully using one touch" do
+ move_through_paypal_popup
+ expect(page).to have_content("Shipments")
+ click_on "Place Order"
+ expect(page).to have_content("Your order has been processed successfully")
+ end
+ end
+
+ context "goes through checkout using paypal", vcr: { cassette_name: 'paypal/checkout', match_requests_on: [:method, :uri] } do
+ before do
+ payment_method
+ add_mug_to_cart
+ end
+
+ it "should check out successfully through regular checkout" do
+ expect(page).to have_button("paypal-button")
+ click_button("Checkout")
+ fill_in("order_email", with: "stembolt_buyer@stembolttest.com")
+ click_button("Continue")
+ expect(page).to have_content("Customer E-Mail")
+ fill_in_address
+ click_button("Save and Continue")
+ expect(page).to have_content("SHIPPING METHOD")
+ click_button("Save and Continue")
+ move_through_paypal_popup
+ expect(page).to have_content("Shipments")
+ click_on "Place Order"
+ expect(page).to have_content("Your order has been processed successfully")
+ end
+ end
+
+ # Selenium does not clear cookies properly between test runs, even when
+ # using Capybara.reset_sessions!, see:
+ # https://github.com/jnicklas/capybara/issues/535
+ #
+ # This causes Paypal to remain logged in and not prompt for an email on the
+ # second test run and causes the test to fail. Adding conditional logic for
+ # this greatly increases the test time, so it is left out since CI runs
+ # these with poltergeist.
+ def move_through_paypal_popup
+ expect(page).to have_button("paypal-button")
+ popup = page.window_opened_by do
+ click_button("paypal-button")
+ end
+ page.switch_to_window(popup)
+ expect(page).to_not have_selector('body.loading')
+ page.within_frame("injectedUl") do
+ fill_in("email", with: "stembolt_buyer@stembolttest.com")
+ fill_in("password", with: "test1234")
+ click_button("btnLogin")
+ end
+ expect(page).to have_button('Agree & Continue')
+ click_button("Agree & Continue")
+ page.switch_to_window(page.windows.first)
+ end
+
+ def fill_in_address
+ address = "order_bill_address_attributes"
+ fill_in "#{address}_firstname", with: "Ryan"
+ fill_in "#{address}_lastname", with: "Bigg"
+ fill_in "#{address}_address1", with: "143 Swan Street"
+ fill_in "#{address}_city", with: "San Jose"
+ select "United States of America", from: "#{address}_country_id"
+ select "California", from: "#{address}_state_id"
+ fill_in "#{address}_zipcode", with: "95131"
+ fill_in "#{address}_phone", with: "(555) 555-0111"
+ end
+
+ def add_mug_to_cart
+ visit spree.root_path
+ click_link mug.name
+ click_button "add-to-cart-button"
+ end
+end
diff --git a/spec/fixtures/cassettes/paypal/checkout.yml b/spec/fixtures/cassettes/paypal/checkout.yml
new file mode 100644
index 00000000..2da24ffb
--- /dev/null
+++ b/spec/fixtures/cassettes/paypal/checkout.yml
@@ -0,0 +1,369 @@
+---
+http_interactions:
+- request:
+ method: post
+ uri: https://api.sandbox.braintreegateway.com/merchants/7rdg92j7bm7fk5h3/client_token
+ body:
+ encoding: UTF-8
+ string: |
+
+
+ 2
+
+ headers:
+ Accept-Encoding:
+ - gzip
+ Accept:
+ - application/xml
+ User-Agent:
+ - Braintree Ruby Gem 2.68.1
+ X-Apiversion:
+ - '4'
+ Authorization:
+ - Basic bXdqa2t4d2NwMzJja2huZjphOTI5OGY0M2IzMGM2OTlkYjMwNzJjYzRhMDBmN2Y0OQ==
+ Content-Type:
+ - application/xml
+ response:
+ status:
+ code: 201
+ message: Created
+ headers:
+ Date:
+ - Fri, 28 Oct 2016 18:12:32 GMT
+ Content-Type:
+ - application/xml; charset=utf-8
+ Transfer-Encoding:
+ - chunked
+ X-Frame-Options:
+ - SAMEORIGIN
+ - SAMEORIGIN
+ X-Xss-Protection:
+ - 1; mode=block
+ X-Content-Type-Options:
+ - nosniff
+ X-Authentication:
+ - basic_auth
+ X-User:
+ - 3v249hqtptsg744y
+ Vary:
+ - Accept-Encoding
+ Content-Encoding:
+ - gzip
+ Etag:
+ - W/"66427b04cf5c730187abd03b1b88c689"
+ Cache-Control:
+ - max-age=0, private, must-revalidate
+ X-Request-Id:
+ - ecfaf951-ed60-4da1-85a6-36d4e8433bdb
+ X-Runtime:
+ - '0.110858'
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ body:
+ encoding: ASCII-8BIT
+ string: !binary |-
+ H4sIABCVE1gAA6RWX3ObOBB/76fI5L1XECVXZtJ0GhswGoNrYgToDSRSAxL4
+ gg2GT38r3EuTm1ynM/fgycSsdle/f/j2y1mKq7546sq2+Xyt/6FdXxUNa3nZ
+ fP98He2c95+uv9y9u2WiLJrj+2NbF83du6ur2z4Tp+KuGDGiCZ6y2Dp5VTuu
+ F3jPk7DNDXwopKOp70MpThSRka3wIW+25ab0zv4khD99N9PJR3Tp1MFUG8EU
+ mTSOdH9iUyA9Y7MUIp1ETWMq/Co1g6rW/ThCVEZaiqIhnUgVVNDHtTXV49EN
+ Rho7Go3DxzTZWn719RyU2tlfaOPG2Z43y/bsV+3klx+Rv7Th83XcLL0n+Dv4
+ zllX/wdSF0wGbRqbWoJE/W3HR+oy05etkUrNoLLTs2XA+A7OVHTgMe6yOHjM
+ EDG/xbqRyeNTseIVW/pjio5tLtMSMKlyZMos5oTJAe6PW74KBza1/Ro5Q/Zg
+ TjCvTqX1cS3xmMbixFdY0JjvuUuMNKlPKbKOm2qr+aN1BLyrzHWgxu8DA9d0
+ qses4mW+4zKbSAs1Ve4KkTcKh/vD2kjPaxT0uaQHapAxTcJDjj7Oe0GfLneJ
+ 4mfyqkOePAwljU2UJRjqdXHhCz/v55VDmaLzAWq0LfQmCe6A9zJbhRpb+Tfr
+ 0doztz4x5Jyoi/tiYZZMOsB7CNyQBmYLjhxzLWGfXasFS7/PY9gJ7fdQM62n
+ t/D2+39mJjNeXudJZ2KIaEwnY77wbjy51/jqftqUn/o0CSaaQK9fYDnfIyFa
+ 5rx1Hr5fmAjmHfPx97iZ+8XmPl8J0HygsPzTa97o/eMe69g5pfHZ5K6o2G/O
+ +A9sykfgjLt7ha8dIVLxBIsQdknlWVCYD710CjXMdUz4dL96Nu8twwMz7rs0
+ EZs01oXiNzJCkcegidX2oueLvrDq4dXOniHaBzUuaRXcELAtEdiN7IOz2Qkz
+ tjHaNq1Ol/snYn8ymA361MGrjjPwHV7tbItEFR6ZI7RdhPfhzvlWuEG1sb0x
+ RGHP7fAvOuFNthSBwlnlCJdOVZCX+H5V2hOF6xyZexazvh6sgS1AKw0BPeB7
+ aihMlabDVz5UvNAE9Jfcd/TBBK9qPXetaeYUdEbJ/9HYpe4Nn4D3SJUhS+cL
+ cwCshzQens9RVwCfgbZ9MT9vSJcvlM7OXW5wT+0OfGmsIQI4gXvRA5PWCfx0
+ 4rbZ7yTROLLGbPRuqHQ6hiLwDTyTYoQ8Oiodqp1ySFTg80QfZqzgOZxzSb17
+ 9iVkgKSg7UDNeenn5SsdSH3PQCeQn5CJ0Ae0qeoz2DeLzWYbc3Vmnss0AvrH
+ HY2hN/TisdrtZ/Zu46DKDXUPUasd2b/0BxqHXmZVRGLKtaCnbqT4JJG2nf0A
+ OB5yiUGXxMmlU0LGQZ+DBOwmpfcXmXPZvXrbWzDrRw6o+1mSlnP2DEpjEXjm
+ 4vNAg1zT2Vyjg8e7i0fU/itsbpFVA7Y3niC7WROQwQx4SA2xzFE4+4uQwFbP
+ Xu0FuQz5AO8NMs48/eTjZb4CnpBXgO8rLcI7BTg8QqZ0oK8D7A/cBjpb3fes
+ CQW1TcFXvGfyOOe+p7SDbLX3ETSrqXdMmuD6kmu6KBaYPyba59sPl/f+u9sP
+ r38R/A0AAP//AwDvfcy0SAgAAA==
+ http_version:
+ recorded_at: Fri, 28 Oct 2016 18:12:38 GMT
+- request:
+ method: post
+ uri: https://api.sandbox.braintreegateway.com/merchants/7rdg92j7bm7fk5h3/client_token
+ body:
+ encoding: UTF-8
+ string: |
+
+
+ 2
+
+ headers:
+ Accept-Encoding:
+ - gzip
+ Accept:
+ - application/xml
+ User-Agent:
+ - Braintree Ruby Gem 2.68.1
+ X-Apiversion:
+ - '4'
+ Authorization:
+ - Basic bXdqa2t4d2NwMzJja2huZjphOTI5OGY0M2IzMGM2OTlkYjMwNzJjYzRhMDBmN2Y0OQ==
+ Content-Type:
+ - application/xml
+ response:
+ status:
+ code: 201
+ message: Created
+ headers:
+ Date:
+ - Fri, 28 Oct 2016 18:12:34 GMT
+ Content-Type:
+ - application/xml; charset=utf-8
+ Transfer-Encoding:
+ - chunked
+ X-Frame-Options:
+ - SAMEORIGIN
+ - SAMEORIGIN
+ X-Xss-Protection:
+ - 1; mode=block
+ X-Content-Type-Options:
+ - nosniff
+ X-Authentication:
+ - basic_auth
+ X-User:
+ - 3v249hqtptsg744y
+ Vary:
+ - Accept-Encoding
+ Content-Encoding:
+ - gzip
+ Etag:
+ - W/"794372140cdef51e5810697f3455f5f0"
+ Cache-Control:
+ - max-age=0, private, must-revalidate
+ X-Request-Id:
+ - 420c3010-0c34-426f-92ef-b65bbc8ecd32
+ X-Runtime:
+ - '0.097862'
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ body:
+ encoding: ASCII-8BIT
+ string: !binary |-
+ H4sIABKVE1gAA6RW0XKbOBR971dk8t4tiJCtZ9J2EhswGiPXBAvQG0ikBiTw
+ Bhsbvn6vcLdNdrKdzuxDJjNwde/ROecefPflrORVXzx3Zdt8ujb/MK6vioa3
+ omy+fbreRu77j9dfPr+747IsmsP7Q1sXzed3V1d3fSaPxediwIgleMzi2dGv
+ 2mE1xzuRhG1u4X2hXEM/D5U8MkQHvsT7vNmU69I/BSNTQcwUU1SxRXBOI26Q
+ 0R9StbFJJCqy+AY1jp1WKYK6OhgfoG5rpsiFJ0SxqkYs2pxJRSSpwpJE9+aT
+ RwYWuwaLw6c02cyC6v5MSuMczI1h7W7O60V7Dqp2JPMbFESOESz4zXrx7TlY
+ 3J8C92zC/4EoU3JF2jS2jQTJ+mskBuZxO1CtlSrDYqozswXhIoIzFTuJGHdZ
+ TJ4yRO2vsWll6vBcLEXFF8GQokObq7QETqoc2SqLBeXqBPfHrViGJz62/Qq5
+ p+zRHmFenarZzUrhIY3lUSyxZLHYCY9aaVIfUzQ7rKuNEQyzA/BdZZ4LNUFP
+ LFyzsR6ySpR5JFQ20hZqqtyTMm80Dw/7lZWeV4j0uWJ7ZtEhTcJ9jm4mXNCn
+ yz2q9Rn9ap8nj6eSxTbKEgz1przohX/g88tTmaLzHmqMDfSmCe5A9zJbhgZf
+ BrerYbbjXn3kyD0yD/fF3C65ckH3ELShDcyWArn2SgGeqDXIIujzGDCh3Q5q
+ xtX4Ft9B/8/MZOLL73zljhxRg5t0yOf+ra92hlg+jOvyY58mZGQJ9PoFl9M9
+ Empk7lvn4fncRjDvkA+/p83UL7Z3+VKC54nm8k+/eaP393usYveYxmdbeLLi
+ vznjP7gpn0Az4e00v84W0UokWIaAJVVnyWA+9DIZ1HDPteGv+9W7CbcK99x6
+ 6NJErtPYlFrfrRXKPAZPLDcXP1/8hXUPv3Z3HLGe1LhkFbmlUqZUYm/r7N11
+ JO3YwWjTtCZb7J6p89HiDvjThF113ZOI8DJyZnRb4YG70oi2eBdG7tfCI9Xa
+ 8YcQhb1wwr/YiNfZQhLNs84RodyqoC/5vdfek4XnHrh3lpO/HmcnPgevNBT8
+ gB+YpTnVng5f7aHWhSXgv+ShY4827KrRC282TpqCzxj9Px671L2xJ7B7tMrQ
+ zBRz+wRcn9L49OMc8yToSYzNi/l5Q7t8rn127nJL+Bo76GXwhkrQBO7F9lzN
+ jrBPR+HYfaSoIdBsyAb/lim342gLewPvlBwgjw7ahxpTjpgCPY/sceIK3sM5
+ j9bRj72EDFAMvE30nJf7vHjlA2XuOPgE8hMyEfqAN3V9Bniz2G42sdBnprnc
+ oOB/3LEYekMvEWtsP7N3E5Mqt/Q9ZK0x8n/5DzwOveyq2MoxN0jPvK3Wk26N
+ zbQPwOM+Vxh8Sd1cuSVkHPTZK+Bu1H5/kTkX7NXbuwWzvueAvt9MsXLKnpP2
+ 2BZ25rLnxIBcM/lUY8KOd5cd0fiX2N6gWQ3c3vqSRpMnIIM56JBacpGjcNov
+ Somj373CBbkM+QDfDTpMOv3U42W+Ap+QV8DvKy/CNwU0PECmdOCvPeAHbYnJ
+ lw89b0LJHFuKpei5Oky572vvIEfjPoBnDf2NSRNcX3LNlMUci6fE+HT34fLd
+ f3f34fUvgr8BAAD//wMAJ+j4AEgIAAA=
+ http_version:
+ recorded_at: Fri, 28 Oct 2016 18:12:40 GMT
+- request:
+ method: post
+ uri: https://api.sandbox.braintreegateway.com/merchants/7rdg92j7bm7fk5h3/customers
+ body:
+ encoding: UTF-8
+ string: |
+
+
+ 09fe9af0-43c0-4dbc-977c-e1e7a3f1afcb
+
+ headers:
+ Accept-Encoding:
+ - gzip
+ Accept:
+ - application/xml
+ User-Agent:
+ - Braintree Ruby Gem 2.68.1
+ X-Apiversion:
+ - '4'
+ Authorization:
+ - Basic bXdqa2t4d2NwMzJja2huZjphOTI5OGY0M2IzMGM2OTlkYjMwNzJjYzRhMDBmN2Y0OQ==
+ Content-Type:
+ - application/xml
+ response:
+ status:
+ code: 201
+ message: Created
+ headers:
+ Date:
+ - Fri, 28 Oct 2016 18:12:48 GMT
+ Content-Type:
+ - application/xml; charset=utf-8
+ Transfer-Encoding:
+ - chunked
+ X-Frame-Options:
+ - SAMEORIGIN
+ - SAMEORIGIN
+ X-Xss-Protection:
+ - 1; mode=block
+ X-Content-Type-Options:
+ - nosniff
+ X-Authentication:
+ - basic_auth
+ X-User:
+ - 3v249hqtptsg744y
+ Vary:
+ - Accept-Encoding
+ Content-Encoding:
+ - gzip
+ Etag:
+ - W/"dadf50459ab398759e3bd003f35a3315"
+ Cache-Control:
+ - max-age=0, private, must-revalidate
+ X-Request-Id:
+ - 1ce26846-2d99-4130-9af5-846985f51651
+ X-Runtime:
+ - '0.312284'
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ body:
+ encoding: ASCII-8BIT
+ string: !binary |-
+ H4sIACCVE1gAA6RTPZObMBDt/SsYelkGf54H40yKNMmkyaVJ41nQgnWnD0YS
+ Z/PvI8DYd8YpMlfue293pbe7yf4sRfCGxnKtdmE0nYUBqlwzrspd+Pv5G9mE
+ +3SS5LV1WqJJJ0GQcJbG8XKx2qzWCfVBi3kuP4JyxMdrw8qn+GWdyXXxujzO
+ E/qebdUFN9YRBRIDxcUudKbGkHaUgH8xuZYVqGaEowQuRmh11Gpco4DzCDth
+ Zrl70M8gOGQEXOCaCnch86HjEsM0nkUrEs1IvHmONtso3i7WfxJ6S+jy64r9
+ X/4toe/feU4KjoLZ65MYdyQHw+ylKBgDzfBnaCoQBPJc18p9VLSCkaQHPZxx
+ IfzICZQGUWI/qa9k8etHHMXL6Pt6uVnM59HPhD5UDmU+aVlf47Jq5MOavUcH
+ JcMCajG0yrQWCCpM2xEm9EJexd2WpNahzLRwh6xu0HwZQofWTf1+JbSXDUlc
+ QomkNiI9OlfZLaVgLTo7zQxw5bwDpX/+CZo2mXpvW0MOEt1Rs4PQpaa939NK
+ lXtUb9xo1Up2FhTL9Nmfz7XD0NPWmc0Nr5y/yAdD7jROv6JKlyf29MIS2kcD
+ 98m16/9tSXuvCgXhijve8vc2FyCs9/mR9FrH/74dmSr0/XV1tOCSt51ri0Qb
+ 1k13LEzoeGnvMduBwJhBP6CRbbf9SSd/AQAA//8DAL1TiRDzBAAA
+ http_version:
+ recorded_at: Fri, 28 Oct 2016 18:12:53 GMT
+- request:
+ method: post
+ uri: https://api.sandbox.braintreegateway.com/merchants/7rdg92j7bm7fk5h3/transactions
+ body:
+ encoding: UTF-8
+ string: |
+
+
+ 29.99
+
+ true
+
+ R765286042-AKGE5U5C
+ 5wd9jd
+ 22546867
+ sale
+
+ headers:
+ Accept-Encoding:
+ - gzip
+ Accept:
+ - application/xml
+ User-Agent:
+ - Braintree Ruby Gem 2.68.1
+ X-Apiversion:
+ - '4'
+ Authorization:
+ - Basic bXdqa2t4d2NwMzJja2huZjphOTI5OGY0M2IzMGM2OTlkYjMwNzJjYzRhMDBmN2Y0OQ==
+ Content-Type:
+ - application/xml
+ response:
+ status:
+ code: 201
+ message: Created
+ headers:
+ Date:
+ - Fri, 28 Oct 2016 18:12:53 GMT
+ Content-Type:
+ - application/xml; charset=utf-8
+ Transfer-Encoding:
+ - chunked
+ X-Frame-Options:
+ - SAMEORIGIN
+ - SAMEORIGIN
+ X-Xss-Protection:
+ - 1; mode=block
+ X-Content-Type-Options:
+ - nosniff
+ X-Authentication:
+ - basic_auth
+ X-User:
+ - 3v249hqtptsg744y
+ Vary:
+ - Accept-Encoding
+ Content-Encoding:
+ - gzip
+ Etag:
+ - W/"a8562bdb10033faa02696ca2a2bc00b2"
+ Cache-Control:
+ - max-age=0, private, must-revalidate
+ X-Request-Id:
+ - a582458e-29af-41c6-a02e-a66c83d1c052
+ X-Runtime:
+ - '4.642481'
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ body:
+ encoding: ASCII-8BIT
+ string: !binary |-
+ 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
+ AAA=
+ http_version:
+ recorded_at: Fri, 28 Oct 2016 18:12:58 GMT
+recorded_with: VCR 3.0.3
diff --git a/spec/fixtures/cassettes/paypal/one_touch_checkout.yml b/spec/fixtures/cassettes/paypal/one_touch_checkout.yml
new file mode 100644
index 00000000..3f1e9375
--- /dev/null
+++ b/spec/fixtures/cassettes/paypal/one_touch_checkout.yml
@@ -0,0 +1,277 @@
+---
+http_interactions:
+- request:
+ method: post
+ uri: https://api.sandbox.braintreegateway.com/merchants/7rdg92j7bm7fk5h3/client_token
+ body:
+ encoding: UTF-8
+ string: |
+
+
+ 2
+
+ headers:
+ Accept-Encoding:
+ - gzip
+ Accept:
+ - application/xml
+ User-Agent:
+ - Braintree Ruby Gem 2.68.1
+ X-Apiversion:
+ - '4'
+ Authorization:
+ - Basic bXdqa2t4d2NwMzJja2huZjphOTI5OGY0M2IzMGM2OTlkYjMwNzJjYzRhMDBmN2Y0OQ==
+ Content-Type:
+ - application/xml
+ response:
+ status:
+ code: 201
+ message: Created
+ headers:
+ Date:
+ - Fri, 28 Oct 2016 18:12:09 GMT
+ Content-Type:
+ - application/xml; charset=utf-8
+ Transfer-Encoding:
+ - chunked
+ X-Frame-Options:
+ - SAMEORIGIN
+ - SAMEORIGIN
+ X-Xss-Protection:
+ - 1; mode=block
+ X-Content-Type-Options:
+ - nosniff
+ X-Authentication:
+ - basic_auth
+ X-User:
+ - 3v249hqtptsg744y
+ Vary:
+ - Accept-Encoding
+ Content-Encoding:
+ - gzip
+ Etag:
+ - W/"0abbbd99801e6c6eac33f403727c38c8"
+ Cache-Control:
+ - max-age=0, private, must-revalidate
+ X-Request-Id:
+ - 25ef8aae-46ed-42ad-8ea6-702739b6b2da
+ X-Runtime:
+ - '0.137755'
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ body:
+ encoding: ASCII-8BIT
+ string: !binary |-
+ H4sIAPmUE1gAA6RWX3ObOBB/76fI5L13IEKunknbiW3AaIxcEyxAbyA5Z0AC
+ X7Ex8OlvhXttcpPrdOYePBmb1e7q9488fO6VvOn2X9uiqT/emr8Ztzf7mjei
+ qP/8eLuL3Pcfbj9/evfAZbGvT+9PTbWvP727uXnoMnnef9oPGLEEj1k8O/tl
+ M6wX+CCSsMktfNwr19C/h0qeGaIDX+FjXm+LTeHbZOn3pDxUBIXlJno00jg1
+ mcIlWR7KYJQqWD5am6VjpaNjBGOFgtHvWUwqtnyE73NJIrdkane3iXZjMIqC
+ jPPy2SMDi12DxeFzmmxnQfnYk8Log4UxbNxtv1k2fVA2l83T3UCWTh+MgUEi
+ 5yvMugRub8LfgShTckWaNLaNBMnqSyQG5nE7UI2VKsNiqjWzJeEigjMlu4gY
+ t1lMnjNE7S+xaWXq9HW/EiVfBkOKTk2u0gIwKXNkqywWlKsL3B83YhVe+Nh0
+ a+Resid7hHlVqmZ3a4WHNJZnscKSxeIgPGqlSXVO0ey0KbdGMMxOgHeZeS7U
+ BB2xcMXGashKUeSRUNlIG6gpc0/KvNY4zI9rK+3XiHS5Ykdm0SFNwmOO7qa9
+ oE+be1TzM/rlMU+eLgWLbZQlGOpNeeULf9/PLy5Fivoj1Bhb6E0T3ALvRbYK
+ Db4K7tfD7MC96syRe2Ye7vYLu+DKBd5D4IbWMFsK5NprBftEjUGWQZfHsBM6
+ HKBmXI9v4R10/8xMJrz81lfuyBE1uEmHfOHf++pgiNV83BQfujQhI0ug10+w
+ nO6RUCNz3zoPvy9sBPNO+fBr3Ez9YvuQryRonmgs//DrN3p/u8c6ds9p3NvC
+ kyX/xRn/gU3xDJwJ76DxdXaIliLBMoRdUtVLBvOhl8mghnuuDZ/2Z8+mvVV4
+ 5Na8TRO5SWNTan53VijzGDSx2l71fNUX1j38yj1wxDpS4YKV5J5KmVKJvZ1z
+ dDeRtGMHo23dmGx5+EqdDxZ3QJ8meNV1LyLCq8iZ0V2JB+5KI9rhQxi5X/Ye
+ KTeOP4Qo7IQT/sVGvMmWkmicdY4I5ZZ7+hLfR609uffcE/d6OenraXbhC9BK
+ TUEPeM4sjanWdPjKh5oXloD+knnLnmzwqtEJbzZOnILOGP0/GrvWveET8B4t
+ MzQzxcK+ANaXNL58P8c8CXwSY/tifl7TNl9onfVtbglf7w58GbymEjiBe7Ej
+ V7Mz+OksHLuLFDUEmg3Z4N8z5bYc7cA38EzJAfLopHWod8oRU8DnmT1NWMFz
+ OOfRKvruS8gAxUDbRM956eflKx0o88BBJ5CfkInQB7Sp6zPYN4vtehsLfWaa
+ yw0K+scti6E39BKx3u1H9m5jUuaWvoes9I78X/oDjUMvu9zv5JgbpGPeTvNJ
+ d8Z28gPgeMwVBl1SN1duARkHfY4KsBu13l9kznX38m1vwaxvOaDvN1OsmLLn
+ ojW2A89cfU4MyDWTTzUmeLy9ekTvv8L2Fs0qwPbelzSaNAEZzIGH1JLLHIWT
+ vygljn72ai/IZcgHeG/QYeLpBx8v8xXwhLwCfF9pEd4pwOEJMqUFfR1hf+CW
+ mHw173gdSubYUqxEx9Vpyn1fawc5eu8TaNbQ75g0wdU110y5X2DxnBgfH36/
+ vvffPfz++j+CvwEAAP//AwBjzI7nSAgAAA==
+ http_version:
+ recorded_at: Fri, 28 Oct 2016 18:12:14 GMT
+- request:
+ method: post
+ uri: https://api.sandbox.braintreegateway.com/merchants/7rdg92j7bm7fk5h3/customers
+ body:
+ encoding: UTF-8
+ string: |
+
+
+ dbec3c8b-01eb-4c84-9974-b66ae97766ca
+
+ headers:
+ Accept-Encoding:
+ - gzip
+ Accept:
+ - application/xml
+ User-Agent:
+ - Braintree Ruby Gem 2.68.1
+ X-Apiversion:
+ - '4'
+ Authorization:
+ - Basic bXdqa2t4d2NwMzJja2huZjphOTI5OGY0M2IzMGM2OTlkYjMwNzJjYzRhMDBmN2Y0OQ==
+ Content-Type:
+ - application/xml
+ response:
+ status:
+ code: 201
+ message: Created
+ headers:
+ Date:
+ - Fri, 28 Oct 2016 18:12:24 GMT
+ Content-Type:
+ - application/xml; charset=utf-8
+ Transfer-Encoding:
+ - chunked
+ X-Frame-Options:
+ - SAMEORIGIN
+ - SAMEORIGIN
+ X-Xss-Protection:
+ - 1; mode=block
+ X-Content-Type-Options:
+ - nosniff
+ X-Authentication:
+ - basic_auth
+ X-User:
+ - 3v249hqtptsg744y
+ Vary:
+ - Accept-Encoding
+ Content-Encoding:
+ - gzip
+ Etag:
+ - W/"fa41c5145af34ac6905aad524421ee65"
+ Cache-Control:
+ - max-age=0, private, must-revalidate
+ X-Request-Id:
+ - df5e5087-ac5e-4b1e-9ad3-57db375e392f
+ X-Runtime:
+ - '0.351738'
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ body:
+ encoding: ASCII-8BIT
+ string: !binary |-
+ H4sIAAiVE1gAA6RTTXPaMBC98ys8vgthEzAwxrQ99BekM51emLW1Nmr04ZHk
+ BP/7yjaGBJNDJ8d97+2u9HY3PZylCF7RWK7VPozmizBAVWjGVbUPfz3/JJvw
+ kM3SorFOSzTZLAhSzrJovUo2mzhOqQ86zHPFCZQjPk4Mq7bx3ySXSfmyOi1T
+ +p7t1CU31hEFEgPFxT50psGQ9pSAz5hCyxpUO8FRAhcTtD5pNa1RwnmCvWFu
+ uXvQzyA4ZARc4Noa9yHzoeMSwyxeRGsSLUi8eY42uyjexU9/UnpL6PObmv1f
+ /i1h6N97TkqOgtnrkxh3pADD7KUoGAPt+GdoaxAEikI3yn1UdIKJZAA9nHMh
+ /MgJVAZR4jCpH2S5/r1IVqskSrbbp+Ui/p7Sh8qxzBctG2pcVo18WLP36Khk
+ WEIjxla51gJBhVk3wpReyKu435LMOpS5Fu6YNy2ab2Po0Lq536+UDrIxiUuo
+ kDRGZCfnarujFKxFZ+e5Aa6cd6Dyz3+Dtkum3tvOkKNEd9LsKHSl6eD3vFbV
+ AdUrN1p1kr0FxXJ99udz7TD2tE1uC8Nr5y/ywZB7jdMvqLJErkzNUjpEI/fF
+ tRv+bUl3rwoF4Yo73vH3NpcgrPf5kfRax/++G5kq9f119bTgknedG4tEG9ZP
+ dypM6XRp7zHbg8CYQT+giW23/clm/wAAAP//AwA/PVDq8wQAAA==
+ http_version:
+ recorded_at: Fri, 28 Oct 2016 18:12:30 GMT
+- request:
+ method: post
+ uri: https://api.sandbox.braintreegateway.com/merchants/7rdg92j7bm7fk5h3/transactions
+ body:
+ encoding: UTF-8
+ string: |
+
+
+ 29.99
+
+ true
+
+ R310468715-YPKS3ANJ
+ 7m5rpd
+ 16578822
+ sale
+
+ headers:
+ Accept-Encoding:
+ - gzip
+ Accept:
+ - application/xml
+ User-Agent:
+ - Braintree Ruby Gem 2.68.1
+ X-Apiversion:
+ - '4'
+ Authorization:
+ - Basic bXdqa2t4d2NwMzJja2huZjphOTI5OGY0M2IzMGM2OTlkYjMwNzJjYzRhMDBmN2Y0OQ==
+ Content-Type:
+ - application/xml
+ response:
+ status:
+ code: 201
+ message: Created
+ headers:
+ Date:
+ - Fri, 28 Oct 2016 18:12:30 GMT
+ Content-Type:
+ - application/xml; charset=utf-8
+ Transfer-Encoding:
+ - chunked
+ X-Frame-Options:
+ - SAMEORIGIN
+ - SAMEORIGIN
+ X-Xss-Protection:
+ - 1; mode=block
+ X-Content-Type-Options:
+ - nosniff
+ X-Authentication:
+ - basic_auth
+ X-User:
+ - 3v249hqtptsg744y
+ Vary:
+ - Accept-Encoding
+ Content-Encoding:
+ - gzip
+ Etag:
+ - W/"1f06d16a8abb48fd1b5629107c91de8a"
+ Cache-Control:
+ - max-age=0, private, must-revalidate
+ X-Request-Id:
+ - 4e9593b7-c55b-42be-8cd0-ca37839d6ef8
+ X-Runtime:
+ - '5.382297'
+ Strict-Transport-Security:
+ - max-age=31536000; includeSubDomains
+ body:
+ encoding: ASCII-8BIT
+ string: !binary |-
+ H4sIAA6VE1gAA+xYS3PbNhC+51d4dIcp0pYlZ2imTpN2HHs8mcbqxLl4QHIl
+ IiYBBgBlqb++C75EimCsQw499CbtflhgH9j9QP/dNktPNiAVE/xq4p5OJyfA
+ IxEzvr6aLB/+IIvJu+CNryXlikYaUcGbkxOfxcH3+Y9ku+Vb38E/RqY01YUK
+ aKETIdk/EPtOLTJavcshUDQF3yl/GllUSIm77QhTguCmECy/fPCdodiAaSYK
+ rgPv8vTy0nfqf0aRgYwSyjWhUWSEBM+jNGShSLXv2LTlaYuQWHQnnKVXEy0L
+ mDiVdYq25FFQIWNEov2/ztzp+cVi7s7I4+fbL2fX9598p9WWnkugGmJC9YmJ
+ xtUkxr+aZTAJvKl7Qdwp8RYP7uKt6731Zt8wJu2Ccn2Rx0evP5vi+v2COvJK
+ C/TJ/KnS6V7M5ouF5zXpROmKSaUJpxkcuorKlI7rIpHllO8sGsgoSy3yFwgV
+ 0zZbeSK4Tb6i20ECnK5bfsjSFMu4ddFm5Nd7qLQEwDKJYwlK2UKw1cBjk4pR
+ SCoimjJtMy9hjXfQFieBly0tr4v1vFizcjfuTqU2qwlN84R6R6HOXkPxArPB
+ omGmOslBn1YFj203qtWousyplHTXU2IgO83JZiSnUjMMjQKtU8gAL29/hc34
+ vou9Zr5jNqQ6SqyYhOX5/7X4H63FbnbqzkhWDNJY1bWwUQSkFJJgjHLBFVhd
+ K3Ed1/vo4AaH1k8BjYl+1uxWfoop3dhshiuHQgNd42B4oTvUfIeqynHWqGFi
+ /VyKCHfDODS3g5ZwazD24P6O7nQ69Z0x7chKjYUaXOeo2RhaMYYoIxjHzJwK
+ YzyEDU65ESwyeVhhfnEFlkgIcuhLYYY/7lLN8BGUpltS0RKrCraQ5c24DoVI
+ gfJJsKKpMpSoBTT0AL0gEZXNLNbiGXgwz2YyxwBU/ypNyGxXsGwY57YrgTaJ
+ OYS1HeRMVjnNBNdJ4DsD0QC5Ayr7wFJS71YPZGLaiLZ3C3OiRKRlZO0tgWV0
+ DaSQaZBonau3jkMVtl11GkrKuLkKdQ2fYi90croz3fgpAyzS+CkVa+EU/JmL
+ F36a8/U74BsmBTeYK0V5HArDYdst6hYmIafIhJbVQlNzlaBSJ0BTneDJYY/o
+ yCpQDCHTe331t1YVEvOGhbcuUsPLOqhDTdvkDQXFObaHdmT1oelOirSDaAR1
+ GJUqsM3hmOLPe0xP2m+bYkWMlvIIursOlU3MRFxEJcPuhK2VVaCCsx8F1HcI
+ xZgFhs12cJ3M5QSeCaLi55FL0+pr8te/NPV7hCQMS1DuejO+nY8lAtBQnRhz
+ 05BFoyLLj2TWLb618NOHUIkYe8tUEVJIYNvK/q15ypjaRh6vGtJujtphJkpg
+ k4KA5gyPNJRXDjuHHreSOkpVu0upnekUoYoky0eZUEffNqeS5pEcJ6+ICZIN
+ YuJpSfYBEo8ltRWLRz7Yx/R8gu3dQuNipsp6teqgsiKaZjXSfcZeINgshmfr
+ G0WKZB6z6NdICbf6qu3jC5PD0CrmfGPm1ApgbMKYbcULqbI50GIYwkKqiqXG
+ oPERpppO1FPZc9OhuPbt+5jB+/1IOGxNALANS/sxDN3HSkViZjNYRJGFwWJG
+ Rnw3nueFBltp1COEMI5Eq6ieDOajBcpzmj7V3wB8ZwzXpzEdX/tsp8tkRkGv
+ 2yq5z2u2WoKkE+wqBG+YKT3Ao6+E5clk/HyVeiAMe3j5pm+/uDyFBQrbroXx
+ rTtXF9wur6IXB5+vH8n511vv0ptf3j+48wv3fLa8u/707fb3m06YmyHS558o
+ vpg+LLzZbP5heXnuzi7O59hVDzG/iktUwTmeSuDcL9bmAKF3Np/H81UYuat5
+ yQcq+T6YQMY+kHSfJbYeVQYXbd3Pz27/XnqPy6/e58cm6L09JNm/NIMv7Xey
+ gaq7on1+Bu9Ndhv0Xtxc6jRFMRairl8S9dy7uf94d/Pnzfu7j9i9x0ANG8x1
+ WZc2N0e/FBwMw/FGaQEe066atm5hsL7T3Jbe4A3e/AsAAP//AwCSUrfdXBUA
+ AA==
+ http_version:
+ recorded_at: Fri, 28 Oct 2016 18:12:35 GMT
+recorded_with: VCR 3.0.3
diff --git a/spec/models/solidus_paypal_braintree/source_spec.rb b/spec/models/solidus_paypal_braintree/source_spec.rb
index d010e83d..c5647080 100644
--- a/spec/models/solidus_paypal_braintree/source_spec.rb
+++ b/spec/models/solidus_paypal_braintree/source_spec.rb
@@ -86,4 +86,56 @@
it { is_expected.not_to be }
end
end
+
+ describe "#friendly_payment_type" do
+ subject { described_class.new(payment_type: type).friendly_payment_type }
+
+ context "when then payment type is PayPal" do
+ let(:type) { "PayPalAccount" }
+
+ it "returns the translated payment type" do
+ expect(subject).to eq "PayPal"
+ end
+ end
+
+ context "when the payment type is Apple Pay" do
+ let(:type) { "ApplePayCard" }
+
+ it "returns the translated payment type" do
+ expect(subject).to eq "Apple Pay"
+ end
+ end
+ end
+
+ describe "#apple_pay?" do
+ subject { described_class.new(payment_type: type).apple_pay? }
+
+ context "when the payment type is Apple Pay" do
+ let(:type) { "ApplePayCard" }
+
+ it { is_expected.to be true }
+ end
+
+ context "when the payment type is not PayPal" do
+ let(:type) { "DogeCoin" }
+
+ it { is_expected.to be false }
+ end
+ end
+
+ describe "#paypal?" do
+ subject { described_class.new(payment_type: type).paypal? }
+
+ context "when the payment type is PayPal" do
+ let(:type) { "PayPalAccount" }
+
+ it { is_expected.to be true }
+ end
+
+ context "when the payment type is not PayPal" do
+ let(:type) { "MonopolyMoney" }
+
+ it { is_expected.to be false }
+ end
+ end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 463bee38..190294ad 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -35,6 +35,16 @@
# Requires factories defined in lib/solidus_paypal_braintree/factories.rb
require 'solidus_paypal_braintree/factories'
+# Requires poltergeist for feature specs
+require 'capybara/poltergeist'
+Capybara.register_driver :poltergeist do |app|
+ # Paypal requires TLS v1.2 for ssl connections
+ Capybara::Poltergeist::Driver.new(app, { phantomjs_options: ['--ssl-protocol=tlsv1.2'] })
+end
+Capybara.register_driver :chrome do |app|
+ Capybara::Selenium::Driver.new(app, browser: :chrome)
+end
+
VCR.configure do |c|
c.cassette_library_dir = "spec/fixtures/cassettes"
c.hook_into :webmock
@@ -83,6 +93,10 @@ def create_gateway(opts = {})
config.include Spree::TestingSupport::UrlHelpers
config.include BraintreeHelpers
+ config.before(:each, type: :feature, js: true) do |ex|
+ Capybara.current_driver = ex.metadata[:driver] || :poltergeist
+ end
+
config.before :suite do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with :truncation