Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

Commit

Permalink
Merge pull request #42 from solidusio/paypal_one_touch
Browse files Browse the repository at this point in the history
Enable PayPal as a payment method
  • Loading branch information
cbrunsdon authored Nov 11, 2016
2 parents ce18648 + 00bf8c4 commit f37a5c7
Show file tree
Hide file tree
Showing 14 changed files with 1,111 additions and 1 deletion.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
182 changes: 182 additions & 0 deletions app/assets/javascripts/spree/frontend/paypal_button.js
Original file line number Diff line number Diff line change
@@ -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() {
$('<script/>').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?')
});
}
});
21 changes: 21 additions & 0 deletions app/assets/javascripts/spree/frontend/solidus_paypal_braintree.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
15 changes: 15 additions & 0 deletions app/models/solidus_paypal_braintree/source.rb
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ en:
solidus_paypal_braintree:
nonce: Nonce
token: Token
payment_type:
apple_pay_card: Apple Pay
pay_pal_account: PayPal
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 4 additions & 1 deletion lib/solidus_paypal_braintree/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<% address = current_order.ship_address %>

<!-- Load the client component. -->
<script src="https://js.braintreegateway.com/web/3.4.0/js/client.min.js"></script>
<!-- Load the PayPal component. -->
<script src="https://js.braintreegateway.com/web/3.4.0/js/paypal.min.js"></script>
<!-- Load the data-collector component. -->
<script src="https://js.braintreegateway.com/web/3.4.0/js/data-collector.min.js"></script>
<script src="https://www.paypalobjects.com/api/button.js?"
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"
></script>
<script>
var address = {
line1: '<%= address.address1 %>',
line2: '<%= address.address2 %>',
city: '<%= address.city %>',
state: '<%= address.state.name %>',
postalCode: '<%= address.zipcode %>',
countryCode: '<%= address.country.iso %>',
phone: '<%= address.phone %>',
recipientName: '<%= "#{address.firstname} #{address.lastname}" %>'
}
var button = new PaypalButton(document.querySelector("#paypal-button"));

button.initialize({
flow: 'vault',
enableShippingAddress: true,
shippingAddressOverride: address,
shippingAddressEditable: false
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<% address = current_order.ship_address %>

<!-- Load the client component. -->
<script src="https://js.braintreegateway.com/web/3.4.0/js/client.min.js"></script>
<!-- Load the PayPal component. -->
<script src="https://js.braintreegateway.com/web/3.4.0/js/paypal.min.js"></script>
<!-- Load the data-collector component. -->
<script src="https://js.braintreegateway.com/web/3.4.0/js/data-collector.min.js"></script>
<script src="https://www.paypalobjects.com/api/button.js?"
data-merchant="braintree"
data-id="paypal-credit-button"
data-button="credit"
data-color="blue"
data-size="medium"
data-shape="pill"
data-button_type="button"
data-button_disabled="true"
></script>
<script>
var address = {
line1: '<%= address.address1 %>',
line2: '<%= address.address2 %>',
city: '<%= address.city %>',
state: '<%= address.state.name %>',
postalCode: '<%= address.zipcode %>',
countryCode: '<%= address.country.iso %>',
phone: '<%= address.phone %>',
recipientName: '<%= "#{address.firstname} #{address.lastname}" %>'
}
var button = new PaypalButton(document.querySelector("#paypal-credit-button"));

button.initialize({
flow: 'checkout',
amount: <%= current_order.total %>,
currency: '<%= current_order.currency %>',
enableShippingAddress: true,
shippingAddressOverride: address,
shippingAddressEditable: false
});
</script>
Loading

0 comments on commit f37a5c7

Please sign in to comment.