Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tweaks & Fixes #10

Open
wants to merge 26 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8dd4cb0
2012.01.15i
buddhi-desilva Jan 14, 2012
c4be320
Payment Completion
buddhi-desilva Jan 14, 2012
ec23ef8
Tweaks
buddhi-desilva Jan 14, 2012
001f340
Update Gemfile
buddhi-desilva Jan 15, 2012
5e469a1
Update .travis.yml
buddhi-desilva Jan 15, 2012
bdb3383
Update README.md
buddhi-desilva Jan 15, 2012
d5f3280
Update README.md
buddhi-desilva Jan 15, 2012
d7b8a08
Specified the class name explicitely
buddhi-desilva Jan 18, 2012
548a3a0
Updated to match the Templates
buddhi-desilva Jan 29, 2012
9c9bbfb
Issue: Empty Cart sets payment_status to paid
buddhi-desilva Jan 29, 2012
d664a17
New PayPal Button: Better and Informative
buddhi-desilva Jan 30, 2012
eb6105d
Fix: Cart reset with coupon codes
buddhi-desilva Feb 9, 2012
1c7a588
Add: previously missed check on order payment status
buddhi-desilva Feb 12, 2012
99b03d8
Added: Discount calculation
buddhi-desilva Feb 16, 2012
6ef4f5d
Theme hooks commented (marked for removal/edit)
buddhi-desilva Sep 19, 2012
4aa4ce4
Added sublime text projects files to .gitignore
buddhi-desilva Sep 20, 2012
21aa961
Preferences Screen for to Spree Admin
buddhi-desilva Sep 23, 2012
e265c88
Update README.md
buddhi-desilva Sep 23, 2012
a8c3426
Routes for paypal_preferences_controller
buddhi-desilva Sep 23, 2012
50447c0
Added a div.paypal-checkout for easy styling
buddhi-desilva Sep 26, 2012
cfd01a3
Fixes, Update & Clean up
buddhi-desilva Sep 27, 2012
970c10a
Temporary fix & to-do comments
buddhi-desilva Oct 1, 2012
772cfa8
Updated: Payment receiving process
buddhi-desilva Oct 1, 2012
6bd2bed
TO DO: New order complete check mechanism
buddhi-desilva Oct 1, 2012
299fbf4
Confirm URL with Order Number
buddhi-desilva Oct 1, 2012
8cf52bd
Add a better PayPal image
buddhi-desilva Oct 1, 2012
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ tmp
nbproject
*.swp
spec/dummy
*.sublime-workspace
*.sublime-project

14 changes: 10 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
script: "bundle exec rake test_app && bundle exec rspec spec"
before_install: gem install bundler --pre
before_script:
- "bundle exec rake test_app"
script: "DISPLAY=:99.0 bundle exec rspec spec"
notifications:
email:
- [email protected]


rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- rbx-2.0
- 1.9.3
19 changes: 11 additions & 8 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
source 'http://rubygems.org'
source :rubygems

group :test do
gem 'ffaker'
end
gem 'sqlite3'

if RUBY_VERSION < "1.9"
gem "ruby-debug"
else
gem "ruby-debug19"
gem 'spree', :git => 'git://github.com/spree/spree.git'

group :test do
gem 'rspec-rails', '= 2.6.1'
gem 'database_cleaner', '= 0.6.7'
gem 'nokogiri'
gem 'capybara', '1.0.1'
gem 'faker'
gem 'factory_girl'
end

gemspec
38 changes: 18 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A [Spree](http://spreecommerce.com) extension to allow payments using PayPal Website Standard.

[![Build Status](https://secure.travis-ci.org/tomash/spree-pp-website-standard.png)](http://travis-ci.org/tomash/spree-pp-website-standard)
[![Build Status](https://secure.travis-ci.org/buddhi-desilva/spree-pp-website-standard.png)](http://travis-ci.org/buddhi-desilva/spree-pp-website-standard)

## Before you read further

Expand Down Expand Up @@ -47,25 +47,17 @@ Configure, run, test.

## Configuration

Be sure to configure the following configuration parameters. Preferably put it in initializer like config/initializers/pp_website_standard.rb.
Navigate to Spree > Admin > Configurations and Click on 'PayPal Preferences' and you can set the following preferences

Example:

Spree::Paypal::Config.set(:account => "[email protected]")
Spree::Paypal::Config.set(:success_url => "http://localhost:3000/paypal/confirm")


The following configuration options (keys) can be set:

:account # email account of store
:success_url # url the customer is redirected to after successfully completing payment
:currency_code # default EUR
:sandbox_url # paypal url in sandbox mode, default https://www.sandbox.paypal.com/cgi-bin/webscr
:paypal_url # paypal url in production, default https://www.paypal.com/cgi-bin/webscr
:populate_address # (true/false) pre-populate fields of billing address based on spree order data
:encryption # (true/false) use encrypted shopping cart
:cert_id # id of certificate used to encrypted stuff
:ipn_secret # secret string for authorizing IPN
account # email account of store
success_url # url the customer is redirected to after successfully completing payment
currency_code # default EUR
sandbox_url # paypal url in sandbox mode, default https://www.sandbox.paypal.com/cgi-bin/webscr
paypal_url # paypal url in production, default https://www.paypal.com/cgi-bin/webscr
populate_address # (true/false) pre-populate fields of billing address based on spree order data
encryption # (true/false) use encrypted shopping cart
cert_id # id of certificate used to encrypted stuff
ipn_secret # secret string for authorizing IPN

Only the first three ones need to be set up in order to get running.

Expand All @@ -75,14 +67,20 @@ The last three are required for secure, encrypted operation (see below).

The payment link can be encrypted using an SSL key pair and a PayPal public key. In order to attempt this encryption, the following elements must be available. If these are not available a normal link will be generated.

And we shouldn't confuse this encryption with SSL provided by the website to eliminate somebody hijacking users' sessions. This encrypts the data sent to PayPal and eliminate an outsider or especially a buyer crafting a PayPal response to mark the order as paid.

Spree::Paypal::Config[:encrypted] must be set to true.
Spree::Paypal::Config[:cert_id] must be set to a valid certificate id.
Spree::Paypal::Config[:ipn_secret] must be set to a string considered secret.
Application must have a Rails.root/certs directory with following files:

app_cert.pem # application certificate
app_key.pem # application key
paypal_cert_#{Rails.env}.pem # paypal certificate
paypal_cert_#{Rails.env}.pem # paypal public certificate (downloded from PayPal)

Download the two PayPal public certificates for development and production environments and rename paypal_cert_development.pem and paypal_cert_production.pem respectively. Gem will pick the correct certificate based on the environment.

Also get the certificate ID from PayPal (copy the listed ID next to the uploaded public/application key) and set the cert_id configuration variable.

The best instructions on what is what, how these files should be generated etc. are [in AsciiCast 143](http://asciicasts.com/episodes/143-paypal-security) (basically the code of this extension is also based on this AsciiCast).

Expand Down
Binary file removed app/assets/images/pp_checkout.gif
Binary file not shown.
Binary file added app/assets/images/pp_checkout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
*= require admin/spree_core
*/
*/
28 changes: 28 additions & 0 deletions app/controllers/spree/admin/paypal_preferences_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Spree
module Admin
class PaypalPreferencesController < Spree::Admin::BaseController

def index
@pp_config = Spree::PaypalWebsiteStandard::Config
end

def create
params.each do |name, value|
next unless Spree::PaypalWebsiteStandard::Config.has_preference? name
Spree::PaypalWebsiteStandard::Config[name] = value
end

# Setting the success URL within the controller to prevent error.
# If the confirm url get change it has to be reflected here.
Spree::PaypalWebsiteStandard::Config[:success_url] = Spree::Config.site_url + "/paypal/confirm"

unless params.has_key?('encrypted')
Spree::PaypalWebsiteStandard::Config.encrypted = false
end

redirect_to admin_paypal_preferences_path
end

end
end
end
30 changes: 30 additions & 0 deletions app/controllers/spree/base_controller_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Spree::BaseController.class_eval do
before_filter :check_current_order

# Checks if an order exists and if its paid
# will display the payment success message
def check_current_order
# TODO: Introduce a proper way to check the order payment status
# Currently the order get removed from the session the moment the
# spree receives payment and no way of tracking. Might have to introduce
# other means of checking for payment receival for orders. A possible
# method would be to have session id sent along with IPN secret and
# mark a flag on payment notifications after displaying payment received
# message
# if current_order \
# && current_order.completed?
# # && ((current_order.payment_state == "paid") or (current_order.payment_state == "credit_owed"))
# flash[:notice] = t(:pp_ws_payment_received)
# @order = current_order
# session[:order_id] = nil

# if current_user
# redirect_to spree.order_path(@order)
# else
# redirect_to root_path
# end

# end
end

end
13 changes: 0 additions & 13 deletions app/controllers/spree/base_controller_decorator.rb.rb

This file was deleted.

51 changes: 10 additions & 41 deletions app/controllers/spree/payment_notifications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ def create
:order_id => @order.id,
:status => params[:payment_status],
:transaction_id => params[:txn_id])

logger.info "PayPal_Website_Standard: processing payment notification for invoice #{params["invoice"]}, amount is #{params["mc_gross"]} #{params["mc_currency"]}"
# this logging stuff won't live here for long...


Order.transaction do
# main part of hacks
order = @order
Expand All @@ -36,54 +33,26 @@ def create
payment.payment_method = Spree::Order.paypal_payment_method
order.payments << payment
payment.started_processing

order.payment.complete

payment.complete!
@order.update_attributes({:state => "complete", :completed_at => Time.now}, :without_protection => true)
logger.info("PayPal_Website_Standard: order #{order.number} (#{order.id}) -- completed payment")

until @order.state == "complete"
if @order.next!
@order.update!
state_callback(:after)
end
if @order.respond_to?(:consume_users_credit, true)
@order.send(:consume_users_credit)
end

@order.finalize!

logger.info("PayPal_Website_Standard: Order #{order.number} (#{order.id}) updated successfully, IPN complete")
end

render :nothing => true
end

private
#########################

# those methods are copy-pasted from CheckoutController
# we cannot inherit from that class unless we want to skip_before_filter
# half of calls in SpreeBase module
def state_callback(before_or_after = :before)
method_name = :"#{before_or_after}_#{@order.state}"
send(method_name) if respond_to?(method_name, true)
end

def before_address
@order.bill_address ||= Address.new(:country => default_country)
@order.ship_address ||= Address.new(:country => default_country)
end

def before_delivery
@order.shipping_method ||= (@order.rate_hash.first && @order.rate_hash.first[:shipping_method])
end

def before_payment
current_order.payments.destroy_all if request.put?
end

#This isn't working here in payment_nofitications_controller since IPN will run on a different session
def after_complete
session[:order_id] = nil
end

def default_country
Country.find Spree::PaypalWebsiteStandard::Config.default_country_id
end


end
end
39 changes: 22 additions & 17 deletions app/controllers/spree/paypal_controller.rb
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
module Spree
class PaypalController < Spree::CheckoutController
class PaypalController < BaseController
protect_from_forgery :except => [:confirm]
skip_before_filter :persist_gender

def confirm
unless current_order
redirect_to root_path
else
order = current_order
if (order.payment_state == "paid") or (order.payment_state == "credit_owed")
flash[:notice] = t(:pp_ws_payment_received)
state_callback(:after)
@pp_order = params[:order].blank? ? nil : Spree::Order.find_by_number(params[:order])

if @pp_order

if @pp_order.complete?
flash[:notice] = t(:pp_ws_payment_received)

if current_user && (current_user == @pp_order.user)
redirect_to order_path(@pp_order)
else
redirect_to root_path
end

else
while order.state != "complete"
order.next
state_callback(:after)
end
flash[:notice] = t(:pp_ws_order_processed_successfully)
flash[:commerce_tracking] = "nothing special"
flash[:notice] = t(:pp_ws_order_processing)
redirect_to root_path
end
redirect_to order_path(current_order)

else
redirect_to root_path
end

end

end
end
38 changes: 31 additions & 7 deletions app/models/spree/order_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,42 @@
has_many :payment_notifications

# SSL certificates for encrypting paypal link
PAYPAL_CERT_PEM = "#{Rails.root}/certs/paypal_cert_#{Rails.env}.pem"
APP_CERT_PEM = "#{Rails.root}/certs/app_cert.pem"
APP_KEY_PEM = "#{Rails.root}/certs/app_key.pem"
def shipment_cost
adjustment_total - credit_total
PAYPAL_CERT_PEM = "#{Rails.root}/certs/paypal_cert_#{Rails.env}.pem" # PayPal’s public certificate (downloaded from PayPal)
APP_CERT_PEM = "#{Rails.root}/certs/app_cert.pem" # Public Key
APP_KEY_PEM = "#{Rails.root}/certs/app_key.pem" # Private Key

# This is a workaround for PayPal's disocunt limitations. Discount cannot be bigger than item total.
# E.g: Before calculation
# Promo credits : 20
# Total item cost: 19
# Shipping: 5
# --------------------------
# Becomes:
# Discount :18.99 (PayPal sees it as a discount)
# Total item cost: 19 (cannot be changed, PayPal does the calculation)
# Shipping : 4.01
def paypal_cart_adjustments
pp_adjustments = {}
# The discount has to be a positive number and shipping is calcuated separately in paypal.
pp_adjustments[:discount] = (self.adjustment_total - self.ship_total).abs

# for PayPal discount has to be < total item cost
# deducting the remaining credit from the shipping cost
if pp_adjustments[:discount] > self.item_total
pp_adjustments[:ship_cost] = (pp_adjustments[:discount] - (self.item_total + self.ship_total - BigDecimal("0.01"))).abs
pp_adjustments[:discount] = self.item_total - BigDecimal("0.01")
else
pp_adjustments[:ship_cost] = self.ship_total
end
pp_adjustments
end

def payable_via_paypal?
!!self.class.paypal_payment_method
end

def self.paypal_payment_method
PaymentMethod.select{ |pm| pm.name.downcase =~ /paypal/}.first
Spree::PaymentMethod.select{ |pm| pm.name.downcase =~ /paypal/}.first
end

def self.use_encrypted_paypal_link?
Expand All @@ -33,7 +56,8 @@ def paypal_encrypted(payment_notifications_url, options = {})
:cmd => '_cart',
:upload => 1,
:currency_code => options[:currency_code] || Spree::PaypalWebsiteStandard::Config.currency_code,
:handling_cart => self.ship_total,
:handling_cart => self.paypal_cart_adjustments[:ship_cost],
:discount_amount_cart => self.paypal_cart_adjustments[:discount],
:return => Spree::PaypalWebsiteStandard::Config.success_url,
:notify_url => payment_notifications_url,
:charset => "utf-8",
Expand Down
Loading