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

End to end transaction #16

Merged
merged 19 commits into from
Sep 13, 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
107 changes: 106 additions & 1 deletion app/assets/javascripts/spree/frontend/solidus_paypal_braintree.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,107 @@
// Placeholder manifest file.
// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js'
// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js'

window.SolidusPaypalBraintree = {
APPLE_PAY_API_VERSION: 1,

initialize: function(authToken, clientReadyCallback) {
braintree.client.create({
authorization: authToken
}, function (clientErr, clientInstance) {
if (clientErr) {
console.error('Error creating client:', clientErr);
return;
}
clientReadyCallback(clientInstance);
});
},

setupApplePay: function(braintreeClient, merchantId, readyCallback) {
if(window.ApplePaySession) {
var promise = ApplePaySession.canMakePaymentsWithActiveCard(merchantId);
promise.then(function (canMakePayments) {
if (canMakePayments) {
braintree.applePay.create({
client: braintreeClient
}, function (applePayErr, applePayInstance) {
if (applePayErr) {
console.error("Error creating ApplePay:", applePayErr);
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to throw a console.error in here so we know what happened. Similar in JS below.

}
readyCallback(applePayInstance);
});
}
});
};
},

initializeApplePaySession: function(applePayInstance, storeName, paymentRequest, sessionCallback) {

paymentRequest['requiredShippingContactFields'] = ['postalAddress', 'phone', 'email']
var paymentRequest = applePayInstance.createPaymentRequest(paymentRequest);

var session = new ApplePaySession(SolidusPaypalBraintree.APPLE_PAY_API_VERSION, paymentRequest);
session.onvalidatemerchant = function (event) {
applePayInstance.performValidation({
validationURL: event.validationURL,
displayName: storeName,
}, function (validationErr, merchantSession) {
if (validationErr) {
console.error('Error validating Apple Pay:', validationErr);
session.abort();
return;
};
session.completeMerchantValidation(merchantSession);
});
};

session.onpaymentauthorized = function (event) {
applePayInstance.tokenize({
token: event.payment.token
}, function (tokenizeErr, payload) {
if (tokenizeErr) {
console.error('Error tokenizing Apple Pay:', tokenizeErr);
session.completePayment(ApplePaySession.STATUS_FAILURE);
}
session.completePayment(ApplePaySession.STATUS_SUCCESS);

SolidusPaypalBraintree.setBraintreeApplePayContact(event.payment.shippingContact);
SolidusPaypalBraintree.submitBraintreePayload(payload);
});
};

sessionCallback(session);

session.begin();
},

setBraintreeApplePayContact: function(appleContact) {
var apple_map = {
locality: 'city',
countryCode: 'country_code',
familyName: 'last_name',
givenName: 'first_name',
postalCode: 'zip',
administrativeArea: 'state_code',
}
for (var key in apple_map) {
document.querySelector("#transaction_address_attributes_" + apple_map[key]).value = appleContact[key];
}

window.addressCon = appleContact;
document.querySelector("#transaction_address_attributes_address_line_1").value = appleContact.addressLines[0];

if(appleContact.addressLines.length > 1) {
document.querySelector("#transaction_address_attributes_address_line_2").value = appleContact.addressLines[1];
}

document.querySelector("#transaction_phone").value = appleContact.phoneNumber;
document.querySelector("#transaction_email").value = appleContact.emailAddress;
},

submitBraintreePayload: function(payload) {
document.querySelector("#transaction_nonce").value = payload.nonce;
document.querySelector("#transaction_payment_type").value = payload.type;
document.querySelector('#new_transaction').submit();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
class SolidusPaypalBraintree::TransactionsController < Spree::StoreController
PERMITTED_BRAINTREE_TRANSACTION_PARAMS = [
:nonce,
:payment_type,
:phone,
:email,
address_attributes: [
:country_code, :last_name, :first_name,
:city, :zip, :state_code, :address_line_1, :address_line_2
]
]

def create
transaction = SolidusPaypalBraintree::Transaction.new transaction_params

if transaction.valid?
import = SolidusPaypalBraintree::TransactionImport.new(current_order, transaction)
import.import!

if import.order.complete?
return redirect_to spree.order_path(import.order)
else
return redirect_to spree.checkout_state_path(import.order.state)
end
else
render text: transaction.errors
end
end

private

def transaction_params
params.require(:transaction)
.permit(PERMITTED_BRAINTREE_TRANSACTION_PARAMS)
.merge({ payment_method: payment_method })
end

def payment_method
SolidusPaypalBraintree::Gateway.find(params[:payment_method_id])
end
end
26 changes: 26 additions & 0 deletions app/models/solidus_paypal_braintree/transaction.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require 'active_model'
require 'solidus_paypal_braintree/transaction_address'

module SolidusPaypalBraintree
class Transaction
include ActiveModel::Model

attr_accessor :nonce, :payment_method, :payment_type, :address, :email, :phone

validates :nonce, presence: true
validates :payment_method, presence: true
validates :payment_type, presence: true
validates :phone, presence: true
validates :email, presence: true

validate do
unless payment_method.is_a? SolidusPaypalBraintree::Gateway
errors.add(:payment_method, 'Must be braintree')
end
end

def address_attributes=(attributes)
self.address = TransactionAddress.new attributes
end
end
end
10 changes: 10 additions & 0 deletions app/models/solidus_paypal_braintree/transaction_address.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require 'active_model'

module SolidusPaypalBraintree
class TransactionAddress
include ActiveModel::Model

attr_accessor :country_code, :last_name, :first_name,
:city, :zip, :state_code, :address_line_1, :address_line_2
end
end
63 changes: 63 additions & 0 deletions app/models/solidus_paypal_braintree/transaction_import.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
require 'active_model'

module SolidusPaypalBraintree
class TransactionImport
attr_reader :transaction, :order

def initialize(order, transaction)
@order = order
@transaction = transaction
end

def source
SolidusPaypalBraintree::Source.new nonce: transaction.nonce,
payment_type: transaction.payment_type,
user: user
end

def user
order.user
end

def import!
order.email = user.try!(:email) || transaction.email

if address
order.shipping_address = order.billing_address = address
# work around a bug in most solidus versions
# about tax zone cachine between address changes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a situation we can check for that we will not need to do this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't look like there is any reasonable options, its not fixed in master and its not reasonable to attempt to detect if we should be doing it.

order.instance_variable_set("@tax_zone", nil)
order.next
end

order.payments.new source: source,
payment_method: transaction.payment_method,
amount: order.total

order.save!

advance_order
end

def address
transaction.address.try do |ta|
country = Spree::Country.find_by(iso: ta.country_code.upcase)
Spree::Address.new first_name: ta.first_name,
last_name: ta.last_name,
city: ta.city,
country: country,
state_name: ta.state_code,
address1: ta.address_line_1,
address2: ta.address_line_2,
zipcode: ta.zip,
phone: transaction.phone
end
end

protected

def advance_order
order.next! until order.state == "confirm"
end
end
end
16 changes: 16 additions & 0 deletions app/views/solidus_paypal_braintree/_transaction_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<%= form_for [main_app, SolidusPaypalBraintree::Transaction.new], id: 'braintree_complete_form', as: :transaction do |f| %>
<%= f.hidden_field :nonce %>
<%= f.hidden_field :payment_type %>
<%= hidden_field_tag :payment_method_id, payment_method.id %>
<%= f.hidden_field :phone %>
<%= f.hidden_field :email %>

<%= f.fields_for :address, SolidusPaypalBraintree::TransactionAddress.new do |fa| %>
<% [:country_code, :last_name, :first_name,
:city, :zip, :state_code,
:address_line_1, :address_line_2].each do |address_field| %>
<%= fa.hidden_field address_field %>
<% end %>
<% end %>

<% end %>
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Rails.application.routes.draw do
namespace :solidus_paypal_braintree do
resource :checkout, only: [:update, :edit]

resource :transactions, only: [:create]
end
end
2 changes: 2 additions & 0 deletions lib/solidus_paypal_braintree/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ def self.activate
end
end

config.assets.precompile += ['spree/frontend/solidus_paypal_braintree']

config.to_prepare(&method(:activate).to_proc)
end
end
Loading