Skip to content

Commit

Permalink
Add frontend view and API controllers for PayPal checkout
Browse files Browse the repository at this point in the history
Adds routes for orders_controller - which allows the creation of
orders on PayPals side. This is called when the paypal button is
first clicked, to send the order info to PayPal so they know how
to build the order and how much is going to be charged.
Also adds a route for payments_controller, which handles the creation
of the source/payment for the order.
en.yml was updated to display nothing for validation errors for
paypal_order_id - the reason is that when you try to proceed
without having created a payment on PayPal, the error message
"Payments Source #{en.yml entry} Can't Be Blank" appears, with
whatever you've set in the paypal_order_id entry appearing in the
middle of the string. We may want to adjust the entire error message,
but I think it's clearer with nothing in the middle - so just
"Payments Source Can't Be Blank", instead of "Payments Source
Paypal Order Id Can't Be Blank".
Also adds tests for the frontend! 🎉
  • Loading branch information
seand7565 committed Jun 3, 2020
1 parent 5791f6a commit ff692c0
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// Placeholder manifest file.
// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js'
//= require spree/frontend/solidus_paypal_commerce_platform/namespace
//= require spree/frontend/solidus_paypal_commerce_platform/buttons
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
SolidusPaypalCommercePlatform.renderButton = function(payment_method_id) {
paypal.Buttons({
createOrder: function (data, actions) {
return Spree.ajax({
url: '/solidus_paypal_commerce_platform/orders',
method: 'POST',
data: {
payment_method_id: payment_method_id,
order_id: Spree.current_order_id,
order_token: Spree.current_order_token
}
}).then(function(res) {
return res.table.id;
})
},
onApprove: function (data, actions) {
$("#payments_source_paypal_order_id").val(data.orderID)
$("#checkout_form_payment").submit()
}
}).render('#paypal-button-container')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
window.SolidusPaypalCommercePlatform = {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module SolidusPaypalCommercePlatform
class OrdersController < ::Spree::Api::BaseController
before_action :load_order
before_action :load_payment_method
skip_before_action :authenticate_user

def create
authorize! :update, @order, order_token
request = SolidusPaypalCommercePlatform::Requests.new(
@payment_method.client_id, @payment_method.client_secret
).create_order(@order, @payment_method.auto_capture)
render json: request, status: :ok
end

private

def load_order
@order = Spree::Order.find_by!(number: params[:order_id])
end

def load_payment_method
@payment_method = Spree::PaymentMethod.find(params[:payment_method_id])
end

end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module SolidusPaypalCommercePlatform
class PaymentsController < ::Spree::Api::BaseController
before_action :load_order
skip_before_action :authenticate_user

def create
authorize! :update, @order, order_token
paypal_order_id = paypal_params[:paypal_order_id]

if !paypal_order_id
return redirect_to checkout_state_path(@order.state), notice: "Invalid order confirmation data passed in"
end

if @order.complete?
return redirect_to spree.order_path(@order), notice: "Order is already in complete state"
end

source = SolidusPaypalCommercePlatform::Source.new(paypal_order_id: paypal_order_id)

source.transaction do
if source.save!
payment = @order.payments.create!({
payment_method_id: paypal_params[:payment_method_id],
source: source
})

render json: {}, status: :ok
end
end
end

private

def paypal_params
params.permit(:paypal_order_id, :order_id, :order_token, :payment_method_id)
end

def load_order
@order = Spree::Order.find_by!(number: params[:order_id])
end

end
end
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
en:
activerecord:
attributes:
solidus_paypal_commerce_platform/source:
paypal_order_id: ""
models:
solidus_paypal_commerce_platform/gateway: Paypal Commerce Platform
hello: Hello world
Expand Down
3 changes: 2 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
SolidusPaypalCommercePlatform::Engine.routes.draw do
# Add your extension routes here
resources :wizard, only: [:create]
# post :paypal_wizard, to: "wizard#create"
resources :orders, only: [:create]
resources :payments, only: [:create]
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script
src="<%= payment_method.sdk_url %>">
</script>

<div id="paypal-button-container"></div>
<input type="hidden" name="payment_source[<%= payment_method.id %>][paypal_order_id]" id="payments_source_paypal_order_id">

<script>
$( document ).ready(function() {
SolidusPaypalCommercePlatform.renderButton("<%= payment_method.id %>")
})
</script>
14 changes: 14 additions & 0 deletions spec/factories/paypal_payment_method_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

FactoryBot.define do
factory :paypal_payment_method, class: 'SolidusPaypalCommercePlatform::Gateway' do
type { "SolidusPaypalCommercePlatform::Gateway" }
name { "PayPal Payment Method" }
preferences {
{
client_id: SecureRandom.hex(8),
client_secret: SecureRandom.hex(10)
}
}
end
end
55 changes: 55 additions & 0 deletions spec/features/frontend/checkout_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
require 'spec_helper'

RSpec.describe "Checkout" do

context "paypal payment method" do
let(:order) { Spree::TestingSupport::OrderWalkthrough.up_to(:payment) }
let(:paypal_payment_method) { create(:paypal_payment_method) }

before do
user = create(:user)
order.user = user
order.recalculate

paypal_payment_method
allow_any_instance_of(Spree::CheckoutController).to receive_messages(current_order: order, try_spree_current_user: user)
end

it "should generate a js file with the correct credentials and intent attached" do
visit '/checkout/payment'
expect(page).to have_css(
'script[src*="sdk/js?client-id=' + paypal_payment_method.preferences[:client_id] + '&intent=authorize"]', visible: false
)
end

context "when auto-capture is set to true" do
it "should generate a js file with intent capture" do
paypal_payment_method.update(auto_capture: true)
visit '/checkout/payment'
expect(page).to have_css(
'script[src*="sdk/js?client-id=' + paypal_payment_method.preferences[:client_id] + '&intent=capture"]', visible: false
)
end
end

context "if no payment has been made" do
it "should fail to process" do
visit '/checkout/payment'
choose(option: paypal_payment_method.id)
click_button("Save and Continue")
expect(page).to have_content("Payments source can't be blank")
end
end

context "if payment has been made" do
it "should proceed to the next step" do
visit '/checkout/payment'
choose(option: paypal_payment_method.id)
find(:xpath, "//input[@id='payments_source_paypal_order_id']", visible: false).set SecureRandom.hex(8)
click_button("Save and Continue")
expect(page).to have_css(".current", text: "Confirm")
end
end

end
end
82 changes: 82 additions & 0 deletions spec/models/gateway_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
require 'spec_helper'
require 'paypal-checkout-sdk'

RSpec.describe "SolidusPaypalCommercePlatform::Gateway", type: :model do
let(:paypal_payment_method) { create(:paypal_payment_method) }
let(:payment) { create(:payment) }

before do
response = OpenStruct.new(
status_code: 201,
result: OpenStruct.new(
purchase_units: [
OpenStruct.new(
payments: OpenStruct.new(
authorizations: [
OpenStruct.new(id: SecureRandom.hex(4))
]
)
)
]
)
)

allow_any_instance_of(PayPal::PayPalHttpClient).to receive(:execute) { response }
allow_any_instance_of(PayPal::SandboxEnvironment).to receive(:authorizationString) { "test auth" }
end

context "#purchase" do
it "should send a purchase request to paypal" do
paypal_order_id = SecureRandom.hex(8)
source = paypal_payment_method.payment_source_class.create(paypal_order_id: paypal_order_id)
request = {
path: "/v2/checkout/orders/#{paypal_order_id}/capture",
headers: {
"Content-Type" => "application/json",
"Authoriation" => "test auth",
"PayPal-Partner-Attribution-Id" => "Solidus_PCP_SP",
},
verb: "POST"
}
expect(SolidusPaypalCommercePlatform::Requests::Request).to receive(:new).with(request)
paypal_payment_method.purchase(1000,source,{})
end
end

context "#authorize" do
it "should send an authorize request to paypal" do
paypal_order_id = SecureRandom.hex(8)
source = paypal_payment_method.payment_source_class.create(paypal_order_id: paypal_order_id)
request = {
path: "/v2/checkout/orders/#{paypal_order_id}/authorize",
headers: {
"Content-Type" => "application/json",
"Authoriation" => "test auth",
"PayPal-Partner-Attribution-Id" => "Solidus_PCP_SP",
},
verb: "POST"
}
expect(SolidusPaypalCommercePlatform::Requests::Request).to receive(:new).with(request)
paypal_payment_method.authorize(1000,source,{})
end
end

context "#capture" do
it "should send a capture request to paypal" do
authorization_id = SecureRandom.hex(8)
source = paypal_payment_method.payment_source_class.create(authorization_id: authorization_id)
payment.source = source
request = {
path: "/v2/payments/authorizations/#{authorization_id}/capture",
headers: {
"Content-Type" => "application/json",
"Authoriation" => "test auth",
"PayPal-Partner-Attribution-Id" => "Solidus_PCP_SP",
},
verb: "POST"
}
expect(SolidusPaypalCommercePlatform::Requests::Request).to receive(:new).with(request)
paypal_payment_method.capture(1000,{},{originator: payment})
end
end
end
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

# Requires factories and other useful helpers defined in spree_core.
require 'solidus_dev_support/rspec/feature_helper'
require 'spree/testing_support/order_walkthrough'

# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Expand Down

0 comments on commit ff692c0

Please sign in to comment.