Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
202 changes: 202 additions & 0 deletions app/controllers/spree/api/paypal_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
module Spree
module Api
class PaypalController < Spree::Api::BaseController
ssl_allowed

def express

order = current_order || raise(ActiveRecord::RecordNotFound)
items = order.line_items.map(&method(:line_item))

tax_adjustments = order.all_adjustments.tax.additional
shipping_adjustments = order.all_adjustments.shipping

order.all_adjustments.eligible.each do |adjustment|
next if (tax_adjustments + shipping_adjustments).include?(adjustment)
items << {
:Name => adjustment.label,
:Quantity => 1,
:Amount => {
:currencyID => order.currency,
:value => adjustment.amount
}
}
end

# Because PayPal doesn't accept $0 items at all.
# See #10
# https://cms.paypal.com/uk/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_ECCustomizing
# "It can be a positive or negative value but not zero."
items.reject! do |item|
item[:Amount][:value].zero?
end
pp_request = provider.build_set_express_checkout(express_checkout_request_details(order, items))

# Trying to conform to https://guides.spreecommerce.com/api/summary.html
# as much as possible
begin
pp_response = provider.set_express_checkout(pp_request)
if pp_response.success?
url = provider.express_checkout_url(pp_response, :useraction => 'commit')

response = Spree::Paypal.new
response.redirect_url = url
respond_with response

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm getting an error here on line 45:

Via a console app

        response = client.post("/api/paypal", {
          "order_id": order['number'],
          "payment_method_id": payment_method["id"],
          "confirm_url": "http://localhost:3001",
          "cancel_url": "http://localhost:3001/checkout/payment"
        })

Error log:

ActionView::MissingTemplate: Missing template spree/api/paypal/express, spree/api/base/express with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder, :coffee, :rabl], :versions=>[:v10, :v9, :v8, :v7, :v6, :v5, :v4, :v3, :v2, :v1]}. Searched in:
  * "/vagrant/dragondoor/app/views"
  * "/vagrant/dragondoor/app/views"
  * "/vagrant/Misinformed/better_spree_paypal_express/app/views"
  * "/home/vagrant/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/bundler/gems/spree_gateway-003416462d5a/lib/views/backend"
  * "/home/vagrant/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/bundler/gems/spree_gateway-003416462d5a/lib/views/frontend"
  * "/home/vagrant/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/bundler/gems/spree_promo_variant_rule-1ebdeb453880/app/views"

Error log from my custom web app (called similarly):

  CACHE (0.1ms)  SELECT  "spree_payment_methods".* FROM "spree_payment_methods"  WHERE "spree_payment_methods"."deleted_at" IS NULL AND "spree_payment_methods"."id" = $1 LIMIT 1  [["id", 3]]
I, [2015-06-19T22:08:39.767190 #30749]  INFO -- : Action: SetExpressCheckout
I, [2015-06-19T22:08:39.767480 #30749]  INFO -- : Request[post]: https://api-3t.sandbox.paypal.com/2.0/
I, [2015-06-19T22:08:45.550440 #30749]  INFO -- : Response[200]: OK, Duration: 5.783s
  CACHE (0.2ms)  SELECT  "spree_payment_methods".* FROM "spree_payment_methods"  WHERE "spree_payment_methods"."deleted_at" IS NULL AND "spree_payment_methods"."id" = $1 LIMIT 1  [["id", 3]]
ActionController::UnknownFormat
/home/vagrant/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/actionpack-4.1.9/lib/action_controller/metal/mime_responds.rb:440:in `retrieve_collector_from_mimes'
/home/vagrant/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/bundler/gems/spree-c0089b1b5e65/core/lib/spree/core/controller_helpers/respond_with.rb:9:in `respond_with'
/vagrant/Misinformed/better_spree_paypal_express/app/controllers/spree/api/paypal_controller.rb:45:in `express'
/home/vagrant/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/actionpack-4.1.9/lib/action_controller/metal/implicit_render.rb:4:in `send_action'
/home/vagrant/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/actionpack-4.1.9/lib/abstract_controller/base.rb:189:in `process_action'
/home/vagrant/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/actionpack-4.1.9/lib/action_controller/metal/rendering.rb:10:in `process_action'

Am I missing something?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Very strange. In my app it just picks a layout for the json. Doesn't have anything about the view it uses in the log.

It looks like your app is not understanding to render the json even though spree's api at least purports to do that here

https://github.com/spree/spree/blob/2-4-stable/api/lib/spree/api/controller_setup.rb

so I guess I need to wrap those render :json calls in respond_to blocks ala

http://stackoverflow.com/questions/22943892/actioncontrollerunknownformat/22944769#22944769

and/or

add :defaults => { :format => 'json' }

http://stackoverflow.com/questions/22943892/actioncontrollerunknownformat/28750863#28750863

to those api route definitions

Edit:
on second thought yeah thats almost certainly whats going on @MisinformedDNA .

I bet your client.post call is not properly setting a json mime type so the rails server can't figure out how to respond to it

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

My test is to simply alter the spree_api_examples to include the PayPal API. I've never had a problem with this setup in accessing any other API.

Can you run walkthrough-paypal.rb against your instance and see how it goes?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I can't even get the regular walkthrough running my friend

https://github.com/spree-contrib/spree_api_examples/blob/master/examples/checkout/walkthrough.rb

opened and issue with one of the many issues I'm running into

spree-contrib/spree_api_examples#5

the routes never seem to exist no matter how I try to change them to

which branch of that repo are your running @MisinformedDNA ?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm running 2-4-stable, 2.4.8.beta.

To run the api examples, just set up a clean sandbox. Alternately, you'd have to alter the baseUrl and all the calls since its not mounted at root.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Co-worker figured out the problem. We need a RABL file. See Infigic@60ab41b.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Ah yeah the app im working with sets up json responses differently.

else
# this one is easy we can just respond with pp_response errors
render json: {errors:pp_response.errors.collect(&:long_message).join(" ")}, status: 500
end
rescue SocketError
render json: {errors:[Spree.t('flash.connection_failed', :scope => 'paypal')]}, status: 500
end
end

def confirm
order = current_order || raise(ActiveRecord::RecordNotFound)
order.payments.create!({
:source => Spree::PaypalExpressCheckout.create({
:token => params[:token],
:payer_id => params[:PayerID]
}),
:amount => order.total,
:payment_method => payment_method
})

# using code from regular controller as psuedocode comment for what you are need to do on frontend now
#
# order.next # move order to next state
#
# if order.complete?
# flash.notice = Spree.t(:order_processed_successfully)
# redirect_to completion_route(order)
# else
# redirect_to checkout_state_path(order.state)
# end
respond_with order
end

def cancel
# unneeded in api version where paypal calls back to client first
# you can just do this client side
#
# flash[:notice] = Spree.t('flash.cancel', :scope => 'paypal')
# order = current_order || raise(ActiveRecord::RecordNotFound)
# redirect_to checkout_state_path(order.state, paypal_cancel_token: params[:token])

render status: 200
end

private

def current_order
@order = Spree::Order.find_by(number: order_id)
end

def line_item(item)
{
:Name => item.product.name,
:Number => item.variant.sku,
:Quantity => item.quantity,
:Amount => {
:currencyID => item.order.currency,
:value => item.price
},
:ItemCategory => "Physical"
}
end

def express_checkout_request_details order, items
{ :SetExpressCheckoutRequestDetails => {
:InvoiceID => order.number,
:BuyerEmail => order.email,
# Here we tell paypal redirect to client and have the client post back status to rails server
:ReturnURL => params[:confirm_url],
:CancelURL => params[:cancel_url],
:SolutionType => payment_method.preferred_solution.present? ? payment_method.preferred_solution : "Mark",
:LandingPage => payment_method.preferred_landing_page.present? ? payment_method.preferred_landing_page : "Billing",
:cppheaderimage => payment_method.preferred_logourl.present? ? payment_method.preferred_logourl : "",
:NoShipping => 1,
:PaymentDetails => [payment_details(items)]
}}
end


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

def provider
payment_method.provider
end

def payment_details items
# This retrieves the cost of shipping after promotions are applied
# For example, if shippng costs $10, and is free with a promotion, shipment_sum is now $10
shipment_sum = current_order.shipments.map(&:discounted_cost).sum

# This calculates the item sum based upon what is in the order total, but not for shipping
# or tax. This is the easiest way to determine what the items should cost, as that
# functionality doesn't currently exist in Spree core
item_sum = current_order.total - shipment_sum - current_order.additional_tax_total

if item_sum.zero?
# Paypal does not support no items or a zero dollar ItemTotal
# This results in the order summary being simply "Current purchase"
{
:OrderTotal => {
:currencyID => current_order.currency,
:value => current_order.total
}
}
else
{
:OrderTotal => {
:currencyID => current_order.currency,
:value => current_order.total
},
:ItemTotal => {
:currencyID => current_order.currency,
:value => item_sum
},
:ShippingTotal => {
:currencyID => current_order.currency,
:value => shipment_sum,
},
:TaxTotal => {
:currencyID => current_order.currency,
:value => current_order.additional_tax_total
},
:ShipToAddress => address_options,
:PaymentDetailsItem => items,
:ShippingMethod => "Shipping Method Name Goes Here",
:PaymentAction => "Sale"
}
end
end

def address_options
return {} unless address_required?

address_to_bill = current_order.bill_address || current_order.ship_address
{
:Name => address_to_bill.try(:full_name),
:Street1 => address_to_bill.address1,
:Street2 => address_to_bill.address2,
:CityName => address_to_bill.city,
:Phone => address_to_bill.phone,
:StateOrProvince => address_to_bill.state_text,
:Country => address_to_bill.country.iso,
:PostalCode => address_to_bill.zipcode
}
end

def completion_route(order)
order_path(order, :token => order.guest_token)
end

def address_required?
payment_method.preferred_solution.eql?('Sole')
end
end
end
end
13 changes: 13 additions & 0 deletions app/models/spree/paypal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Spree
class Paypal
include ActiveModel::SerializerSupport

## simple poro to make serializing easy
# http://blog.honeybadger.io/poro-plain-old-ruby-object-tests-and-specs/
#
# maybe this functionality should be rolled into
# the paypal express checkout object or be called paypal express idk
#
attr_accessor :redirect_url
end
end
10 changes: 9 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,12 @@
end
end
end
end

namespace :api do
post '/paypal', :to => "paypal#express", :as => :paypal_express_api
post '/paypal/confirm', :to => "paypal#confirm", :as => :confirm_paypal_api
post '/paypal/cancel', :to => "paypal#cancel", :as => :cancel_paypal_api
get '/paypal/notify', :to => "paypal#notify", :as => :notify_paypal_api

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I don't see a method that handles this route.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

there isnt one I just copied that from the the other paypal stuff I think it was some old thing that never got removed. I would search the history of the repository for it

end

end
2 changes: 2 additions & 0 deletions spree_paypal_express.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ Gem::Specification.new do |s|
s.requirements << 'none'

s.add_dependency 'spree_core', '~> 2.4.0'
s.add_dependency 'spree_api', '~> 2.4.0'
s.add_dependency 'paypal-sdk-merchant', '1.106.1'
s.add_dependency 'active_model_serializers', '~> 0.8.2'

s.add_development_dependency 'capybara', '~> 2.1'
s.add_development_dependency 'coffee-rails'
Expand Down