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

Enable PayPal as a payment method #42

Merged
merged 16 commits into from
Nov 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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