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

Commit

Permalink
Merge pull request #41 from solidusio/merge-forked-changes
Browse files Browse the repository at this point in the history
Submit transactions via AJAX
  • Loading branch information
cbrunsdon authored Nov 1, 2016
2 parents 3328d70 + a493c44 commit ce18648
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 85 deletions.
102 changes: 65 additions & 37 deletions app/assets/javascripts/spree/frontend/solidus_paypal_braintree.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,31 @@ window.SolidusPaypalBraintree = {
};
},

initializeApplePaySession: function(applePayInstance, storeName, paymentRequest, sessionCallback) {
/* Initializes and begins the ApplePay session
*
* @param config Configuration settings for the session
* @param config.applePayInstance {object} The instance returned from applePay.create
* @param config.storeName {String} The name of the store
* @param config.paymentRequest {object} The payment request to submit
* @param config.currentUserEmail {String|undefined} The active user's email
* @param config.paymentMethodId {Integer} The SolidusPaypalBraintree::Gateway id
*/
initializeApplePaySession: function(config, sessionCallback) {

var requiredFields = ['postalAddress', 'phone'];
var currentUserEmail = document.querySelector("#transaction_email").value;

if (!currentUserEmail) {
if (!config.currentUserEmail) {
requiredFields.push('email');
}

paymentRequest['requiredShippingContactFields'] = requiredFields
var paymentRequest = applePayInstance.createPaymentRequest(paymentRequest);
config.paymentRequest['requiredShippingContactFields'] = requiredFields
var paymentRequest = config.applePayInstance.createPaymentRequest(config.paymentRequest);

var session = new ApplePaySession(SolidusPaypalBraintree.APPLE_PAY_API_VERSION, paymentRequest);
session.onvalidatemerchant = function (event) {
applePayInstance.performValidation({
config.applePayInstance.performValidation({
validationURL: event.validationURL,
displayName: storeName,
displayName: config.storeName,
}, function (validationErr, merchantSession) {
if (validationErr) {
console.error('Error validating Apple Pay:', validationErr);
Expand All @@ -77,17 +85,38 @@ window.SolidusPaypalBraintree = {
};

session.onpaymentauthorized = function (event) {
applePayInstance.tokenize({
config.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);
var contact = event.payment.shippingContact;

Spree.ajax({
data: SolidusPaypalBraintree.buildTransaction(payload, config, contact),
dataType: 'json',
type: 'POST',
url: Spree.pathFor('solidus_paypal_braintree/transactions'),
success: function(response) {
session.completePayment(ApplePaySession.STATUS_SUCCESS);
window.location.replace(response.redirectUrl);
},
error: function(xhr) {
if (xhr.status === 422) {
var errors = xhr.responseJSON.errors

if (errors && (errors["Address"] || errors["TransactionAddress"])) {
session.completePayment(ApplePaySession.STATUS_INVALID_SHIPPING_POSTAL_ADDRESS);
} else {
session.completePayment(ApplePaySession.STATUS_FAILURE);
}
}
}
});

});
};

Expand All @@ -96,35 +125,34 @@ window.SolidusPaypalBraintree = {
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];
buildTransaction: function(payload, config, shippingContact) {
return {
transaction: {
nonce: payload.nonce,
phone: shippingContact.phoneNumber,
email: config.currentUserEmail || shippingContact.emailAddress,
payment_type: payload.type,
address_attributes: SolidusPaypalBraintree.buildAddress(shippingContact)
},
payment_method_id: config.paymentMethodId
};
},

if(appleContact.addressLines.length > 1) {
document.querySelector("#transaction_address_attributes_address_line_2").value = appleContact.addressLines[1];
}
buildAddress: function(shippingContact) {
var addressHash = {
country_code: shippingContact.countryCode,
first_name: shippingContact.givenName,
last_name: shippingContact.familyName,
state_code: shippingContact.administrativeArea,
city: shippingContact.locality,
zip: shippingContact.postalCode,
address_line_1: shippingContact.addressLines[0]
};

document.querySelector("#transaction_phone").value = appleContact.phoneNumber;
if (appleContact.emailAddress) {
document.querySelector("#transaction_email").value = appleContact.emailAddress;
if(shippingContact.addressLines.length > 1) {
addressHash['address_line_2'] = shippingContact.addressLines[1];
}
},

submitBraintreePayload: function(payload) {
document.querySelector("#transaction_nonce").value = payload.nonce;
document.querySelector("#transaction_payment_type").value = payload.type;
document.querySelector('#new_transaction').submit();
return addressHash;
}
}
3 changes: 3 additions & 0 deletions app/models/solidus_paypal_braintree/transaction_import.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ class InvalidImportError < StandardError; end
include ActiveModel::Model

validate do
errors.add("Transaction", "is invalid") if !transaction.valid?
errors.add("Address", "is invalid") if address && !address.valid?

if transaction.address && !transaction.address.valid?
transaction.address.errors.each do |field, error|
errors.add("TransactionAddress", "#{field} #{error}")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class SolidusPaypalBraintree::TransactionsController < Spree::StoreController
class InvalidTransactionError < StandardError; end
class InvalidImportError < StandardError; end

PERMITTED_BRAINTREE_TRANSACTION_PARAMS = [
:nonce,
Expand All @@ -14,34 +14,34 @@ class InvalidTransactionError < StandardError; end

def create
transaction = SolidusPaypalBraintree::Transaction.new transaction_params
import = SolidusPaypalBraintree::TransactionImport.new(current_order, transaction)

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

format.html { redirect_after_import(import) }
format.json { head :ok }
format.html { redirect_to redirect_url(import) }
format.json { render json: { redirectUrl: redirect_url(import) } }
else
status = 422
format.html { transaction_error(transaction) }
format.json { render json: { errors: transaction.errors, status: status }, status: status }
format.html { import_error(import) }
format.json { render json: { errors: import.errors, status: status }, status: status }
end
end
end

private

def transaction_error(transaction)
raise InvalidTransactionError,
"Transaction invalid: #{transaction.errors.full_messages.join(', ')}"
def import_error(import)
raise InvalidImportError,
"Import invalid: #{import.errors.full_messages.join(', ')}"
end

def redirect_after_import(import)
def redirect_url(import)
if import.order.complete?
redirect_to spree.order_path(import.order)
spree.order_url(import.order)
else
redirect_to spree.checkout_state_path(import.order.state)
spree.checkout_state_url(import.order.state)
end
end

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
allow(controller).to receive(:current_order) { order }
end

describe "POST create" do
cassette_options = { cassette_name: "transactions_controller/create" }
describe "POST create", vcr: cassette_options do
subject(:post_create) { post :create, params }
let!(:country) { create :country, iso: 'US' }
let!(:state) { create :state, abbr: 'WA', country: country }
Expand Down Expand Up @@ -41,8 +42,9 @@

it "raises a validation error" do
expect { post_create }.to raise_error(
SolidusPaypalBraintree::TransactionImport::InvalidImportError,
"Validation failed: " \
SolidusPaypalBraintree::TransactionsController::InvalidImportError,
"Import invalid: " \
"Address is invalid, " \
"Transactionaddress city can't be blank"
)
end
Expand All @@ -54,7 +56,7 @@
expect(order.payments.first.amount).to eq 55
end

context "and a valid address is provided" do
context "and an address is provided" do
it "creates a new address" do
# Creating the order also creates 3 addresses, we want to make sure
# the transaction import only creates 1 new one
Expand All @@ -73,17 +75,19 @@
end
end

context "when import! leaves the order in confirm" do
it "redirects the user to the confirm page" do
expect(post_create).to redirect_to spree.checkout_state_path("confirm")
context "format is HTML" do
context "when import! leaves the order in confirm" do
it "redirects the user to the confirm page" do
expect(post_create).to redirect_to spree.checkout_state_path("confirm")
end
end
end

context "when import! completes the order" do
before { allow(order).to receive(:complete?).and_return(true) }
context "when import! completes the order" do
before { allow(order).to receive(:complete?).and_return(true) }

it "displays the order to the user" do
expect(post_create).to redirect_to spree.order_path(order)
it "displays the order to the user" do
expect(post_create).to redirect_to spree.order_path(order)
end
end
end

Expand All @@ -98,13 +102,13 @@
end

context "when the transaction is invalid" do
before { params[:transaction].delete(:phone) }
before { params[:transaction][:email] = nil }

context "format is HTML" do
it "raises an error" do
it "raises an error including the validation messages" do
expect { post_create }.to raise_error(
SolidusPaypalBraintree::TransactionsController::InvalidTransactionError,
"Transaction invalid: Phone can't be blank"
SolidusPaypalBraintree::TransactionsController::InvalidImportError,
"Import invalid: Transaction is invalid"
)
end
end
Expand All @@ -120,7 +124,7 @@

it "returns the errors as JSON" do
post_create
expect(json["errors"]["phone"]).to eq ["can't be blank"]
expect(json["errors"]["Transaction"]).to eq ["is invalid"]
end
end
end
Expand Down
Loading

0 comments on commit ce18648

Please sign in to comment.