Initial fork changes
This commit is contained in:
parent
48bdd6c2d1
commit
7da2fed7ac
|
@ -0,0 +1,8 @@
|
||||||
|
class PaypalExpressCallbacksController < Admin::BaseController
|
||||||
|
def index
|
||||||
|
render :text => "index"
|
||||||
|
end
|
||||||
|
def show
|
||||||
|
render :text => "text to render..."
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,10 @@
|
||||||
|
class BillingIntegration::PaypalExpress < BillingIntegration
|
||||||
|
preference :login, :string
|
||||||
|
preference :password, :password
|
||||||
|
preference :signature, :string
|
||||||
|
|
||||||
|
def provider_class
|
||||||
|
ActiveMerchant::Billing::PaypalExpressGateway
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -1,18 +1,17 @@
|
||||||
class PaypalPayment < Payment
|
class PaypalPayment < Payment
|
||||||
has_many :creditcard_txns, :foreign_key => 'creditcard_payment_id' # reused and faked
|
has_many :paypal_txns
|
||||||
belongs_to :creditcard # allow for saving of fake details
|
|
||||||
# accepts_nested_attributes_for :creditcard
|
|
||||||
|
|
||||||
alias :txns :creditcard_txns # should PUSH to parent/interface
|
alias :txns :paypal_txns
|
||||||
|
|
||||||
|
# def find_authorization
|
||||||
|
# #find the transaction associated with the original authorization/capture
|
||||||
|
# txns.find(:first,
|
||||||
|
# :conditions => ["txn_type = ? AND response_code IS NOT NULL", CreditcardTxn::TxnType::AUTHORIZE],
|
||||||
|
# :order => 'created_at DESC')
|
||||||
|
# end
|
||||||
|
|
||||||
def find_authorization
|
|
||||||
#find the transaction associated with the original authorization/capture
|
|
||||||
txns.find(:first,
|
|
||||||
:conditions => ["txn_type = ? AND response_code IS NOT NULL", CreditcardTxn::TxnType::AUTHORIZE],
|
|
||||||
:order => 'created_at DESC')
|
|
||||||
end
|
|
||||||
|
|
||||||
def can_capture? # push to parent? perhaps not
|
def can_capture? # push to parent? perhaps not
|
||||||
txns.last == find_authorization
|
true
|
||||||
|
#txns.last == find_authorization
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
class PaypalTxn < ActiveRecord::Base
|
||||||
|
belongs_to :paypal_payment
|
||||||
|
end
|
|
@ -1,6 +0,0 @@
|
||||||
<div style="width: 100%; margin-top: 10px; margin-bottom: 8px;">
|
|
||||||
<strong style="font-size: 90%; text-align: center;">- OR CHOOSE -</strong>
|
|
||||||
</div>
|
|
||||||
<a href="<%= paypal_checkout_order_url order %>" style="text-align: center;">
|
|
||||||
<img src="https://www.paypal.com/en_GB/GB/i/btn/btn_xpressCheckout.gif" align="left" style="margin-right:7px;"/>
|
|
||||||
</a>
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<a href="<%= paypal_checkout_order_url @checkout.order, :integration_id => integration %>" style="text-align: center;">
|
||||||
|
<img src="https://www.paypal.com/en_GB/GB/i/btn/btn_xpressCheckout.gif" align="left" style="margin-right:7px;"/>
|
||||||
|
</a>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<h1><%= t("confirm") %></h1>
|
||||||
|
<p>
|
||||||
|
<%= t("order_not_yet_placed") %>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<%= render :partial => 'shared/order_details', :locals => {:order => @order} -%>
|
||||||
|
<div class="form-buttons">
|
||||||
|
<%= button_to t('place_order'), "/orders/#{@order.number}/paypal_finish?token=#{params[:token]}&PayerID=#{params[:PayerID]}", :class => "button primary" %>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<%= @ppx_details.to_yaml %>
|
||||||
|
</pre>
|
|
@ -0,0 +1 @@
|
||||||
|
Thanks.
|
|
@ -0,0 +1,3 @@
|
||||||
|
<a href="<%= paypal_payment_order_url @checkout.order, :integration_id => integration %>" style="text-align: center;">
|
||||||
|
<img src="https://www.paypal.com/en_GB/GB/i/btn/btn_xpressCheckout.gif" align="left" style="margin-right:7px;"/>
|
||||||
|
</a>
|
|
@ -5,4 +5,5 @@ en-GB:
|
||||||
paypal_txn_id: Transaction Code
|
paypal_txn_id: Transaction Code
|
||||||
paypal_capture_complete: Paypal Transaction has been captured.
|
paypal_capture_complete: Paypal Transaction has been captured.
|
||||||
unable_to_capture_paypal: Unable to capture Paypal Transaction.
|
unable_to_capture_paypal: Unable to capture Paypal Transaction.
|
||||||
|
signature: Signature
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
en-US:
|
||||||
|
edit_paypal_info: Edit Paypal Express Payment
|
||||||
|
paypal_payment: Paypal Express Payment
|
||||||
|
paypal_txn_id: Transaction Code
|
||||||
|
paypal_capture_complete: Paypal Transaction has been captured.
|
||||||
|
unable_to_capture_paypal: Unable to capture Paypal Transaction.
|
||||||
|
signature: Signature
|
||||||
|
order_not_yet_placed: "Your order has <strong>not</strong> been be placed, please review the details and click Confirm below to finalise your order."
|
|
@ -1,6 +1,8 @@
|
||||||
# Put your extension routes here.
|
# Put your extension routes here.
|
||||||
|
|
||||||
map.resources :orders, :member => {:paypal_checkout => :any, :paypal_finish => :any}
|
map.resources :orders, :member => {:paypal_checkout => :any, :paypal_payment => :any, :paypal_confirm => :any, :paypal_finish => :any}
|
||||||
|
|
||||||
|
map.resources :paypal_express_callbacks, :only => [:index, :show]
|
||||||
|
|
||||||
map.namespace :admin do |admin|
|
map.namespace :admin do |admin|
|
||||||
admin.resources :orders do |order|
|
admin.resources :orders do |order|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
class CreatePaypalExpressGateway < ActiveRecord::Migration
|
|
||||||
def self.up
|
|
||||||
login = GatewayOption.create(:name => "login", :description => "Your login email.")
|
|
||||||
password = GatewayOption.create(:name => "password", :description => "Your Paypal API Credentials Password.")
|
|
||||||
signature = GatewayOption.create(:name => "signature", :textarea => true, :description => "Your Paypal API Credentials signature string.")
|
|
||||||
|
|
||||||
gateway = Gateway.create(:name => "Paypal Express UK",
|
|
||||||
:clazz => "ActiveMerchant::Billing::PaypalExpressUkGateway",
|
|
||||||
:description => "Active Merchant's Paypal Express (UK) Gateway.",
|
|
||||||
:gateway_options => [login, password, signature])
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.down
|
|
||||||
Gateway.find_by_name("Paypal Express UK").destroy
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
class CreatePaypalTxns < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table :paypal_txns do |t|
|
||||||
|
t.references :paypal_payment
|
||||||
|
t.decimal :gross_amount, :precision => 8, :scale => 2
|
||||||
|
t.string :payment_status
|
||||||
|
t.text :message
|
||||||
|
t.string :pending_reason
|
||||||
|
t.string :transaction_type
|
||||||
|
t.string :payment_type
|
||||||
|
t.string :ack
|
||||||
|
t.string :token
|
||||||
|
t.string :avs_code
|
||||||
|
t.string :cvv_code
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
drop_table :paypal_txns
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,19 +7,19 @@ module ActiveMerchant #:nodoc:
|
||||||
base.cattr_accessor :pem_file
|
base.cattr_accessor :pem_file
|
||||||
base.cattr_accessor :signature
|
base.cattr_accessor :signature
|
||||||
end
|
end
|
||||||
|
|
||||||
API_VERSION = '57.0' # TODO - check absolute adherence in this file, override in sub?
|
API_VERSION = '60.0' # TODO - check absolute adherence in this file, override in sub?
|
||||||
|
|
||||||
URLS = {
|
URLS = {
|
||||||
:test => { :certificate => 'https://api.sandbox.paypal.com/2.0/',
|
:test => { :certificate => 'https://api.sandbox.paypal.com/2.0/',
|
||||||
:signature => 'https://api-3t.sandbox.paypal.com/2.0/' },
|
:signature => 'https://api-3t.sandbox.paypal.com/2.0/' },
|
||||||
:live => { :certificate => 'https://api-aa.paypal.com/2.0/',
|
:live => { :certificate => 'https://api-aa.paypal.com/2.0/',
|
||||||
:signature => 'https://api-3t.paypal.com/2.0/' }
|
:signature => 'https://api-3t.paypal.com/2.0/' }
|
||||||
}
|
}
|
||||||
|
|
||||||
PAYPAL_NAMESPACE = 'urn:ebay:api:PayPalAPI'
|
PAYPAL_NAMESPACE = 'urn:ebay:api:PayPalAPI'
|
||||||
EBAY_NAMESPACE = 'urn:ebay:apis:eBLBaseComponents'
|
EBAY_NAMESPACE = 'urn:ebay:apis:eBLBaseComponents'
|
||||||
|
|
||||||
ENVELOPE_NAMESPACES = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
|
ENVELOPE_NAMESPACES = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
|
||||||
'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
|
'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
|
||||||
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
|
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
|
||||||
|
@ -28,7 +28,7 @@ module ActiveMerchant #:nodoc:
|
||||||
'xmlns:n1' => EBAY_NAMESPACE,
|
'xmlns:n1' => EBAY_NAMESPACE,
|
||||||
'env:mustUnderstand' => '0'
|
'env:mustUnderstand' => '0'
|
||||||
}
|
}
|
||||||
|
|
||||||
AUSTRALIAN_STATES = {
|
AUSTRALIAN_STATES = {
|
||||||
'ACT' => 'Australian Capital Territory',
|
'ACT' => 'Australian Capital Territory',
|
||||||
'NSW' => 'New South Wales',
|
'NSW' => 'New South Wales',
|
||||||
|
@ -39,11 +39,11 @@ module ActiveMerchant #:nodoc:
|
||||||
'VIC' => 'Victoria',
|
'VIC' => 'Victoria',
|
||||||
'WA' => 'Western Australia'
|
'WA' => 'Western Australia'
|
||||||
}
|
}
|
||||||
|
|
||||||
SUCCESS_CODES = [ 'Success', 'SuccessWithWarning' ]
|
SUCCESS_CODES = [ 'Success', 'SuccessWithWarning' ]
|
||||||
|
|
||||||
FRAUD_REVIEW_CODE = "11610"
|
FRAUD_REVIEW_CODE = "11610"
|
||||||
|
|
||||||
# The gateway must be configured with either your PayPal PEM file
|
# The gateway must be configured with either your PayPal PEM file
|
||||||
# or your PayPal API Signature. Only one is required.
|
# or your PayPal API Signature. Only one is required.
|
||||||
#
|
#
|
||||||
|
@ -54,27 +54,27 @@ module ActiveMerchant #:nodoc:
|
||||||
# globally and then you won't need to
|
# globally and then you won't need to
|
||||||
# include this option
|
# include this option
|
||||||
#
|
#
|
||||||
# <tt>:signature</tt> The text of your PayPal signature.
|
# <tt>:signature</tt> The text of your PayPal signature.
|
||||||
# If you are only using one API Signature
|
# If you are only using one API Signature
|
||||||
# on your site you can declare it
|
# on your site you can declare it
|
||||||
# globally and then you won't need to
|
# globally and then you won't need to
|
||||||
# include this option
|
# include this option
|
||||||
|
|
||||||
def initialize(options = {})
|
def initialize(options = {})
|
||||||
requires!(options, :login, :password)
|
requires!(options, :login, :password)
|
||||||
|
|
||||||
@options = {
|
@options = {
|
||||||
:pem => pem_file,
|
:pem => pem_file,
|
||||||
:signature => signature
|
:signature => signature
|
||||||
}.update(options)
|
}.update(options)
|
||||||
|
|
||||||
if @options[:pem].blank? && @options[:signature].blank?
|
if @options[:pem].blank? && @options[:signature].blank?
|
||||||
raise ArgumentError, "An API Certificate or API Signature is required to make requests to PayPal"
|
raise ArgumentError, "An API Certificate or API Signature is required to make requests to PayPal"
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def test?
|
def test?
|
||||||
@options[:test] || Base.gateway_mode == :test
|
@options[:test] || Base.gateway_mode == :test
|
||||||
end
|
end
|
||||||
|
@ -82,11 +82,11 @@ module ActiveMerchant #:nodoc:
|
||||||
def reauthorize(money, authorization, options = {})
|
def reauthorize(money, authorization, options = {})
|
||||||
commit 'DoReauthorization', build_reauthorize_request(money, authorization, options)
|
commit 'DoReauthorization', build_reauthorize_request(money, authorization, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def capture(money, authorization, options = {})
|
def capture(money, authorization, options = {})
|
||||||
commit 'DoCapture', build_capture_request(money, authorization, options)
|
commit 'DoCapture', build_capture_request(money, authorization, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Transfer money to one or more recipients.
|
# Transfer money to one or more recipients.
|
||||||
#
|
#
|
||||||
# gateway.transfer 1000, 'bob@example.com',
|
# gateway.transfer 1000, 'bob@example.com',
|
||||||
|
@ -104,7 +104,7 @@ module ActiveMerchant #:nodoc:
|
||||||
def void(authorization, options = {})
|
def void(authorization, options = {})
|
||||||
commit 'DoVoid', build_void_request(authorization, options)
|
commit 'DoVoid', build_void_request(authorization, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def credit(money, identification, options = {})
|
def credit(money, identification, options = {})
|
||||||
commit 'RefundTransaction', build_credit_request(money, identification, options)
|
commit 'RefundTransaction', build_credit_request(money, identification, options)
|
||||||
end
|
end
|
||||||
|
@ -112,7 +112,7 @@ module ActiveMerchant #:nodoc:
|
||||||
private
|
private
|
||||||
def build_reauthorize_request(money, authorization, options)
|
def build_reauthorize_request(money, authorization, options)
|
||||||
xml = Builder::XmlMarkup.new
|
xml = Builder::XmlMarkup.new
|
||||||
|
|
||||||
xml.tag! 'DoReauthorizationReq', 'xmlns' => PAYPAL_NAMESPACE do
|
xml.tag! 'DoReauthorizationReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||||
xml.tag! 'DoReauthorizationRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
xml.tag! 'DoReauthorizationRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||||
xml.tag! 'n2:Version', API_VERSION
|
xml.tag! 'n2:Version', API_VERSION
|
||||||
|
@ -121,12 +121,12 @@ module ActiveMerchant #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
xml.target!
|
xml.target!
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_capture_request(money, authorization, options)
|
def build_capture_request(money, authorization, options)
|
||||||
xml = Builder::XmlMarkup.new
|
xml = Builder::XmlMarkup.new
|
||||||
|
|
||||||
xml.tag! 'DoCaptureReq', 'xmlns' => PAYPAL_NAMESPACE do
|
xml.tag! 'DoCaptureReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||||
xml.tag! 'DoCaptureRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
xml.tag! 'DoCaptureRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||||
xml.tag! 'n2:Version', API_VERSION
|
xml.tag! 'n2:Version', API_VERSION
|
||||||
|
@ -137,12 +137,12 @@ module ActiveMerchant #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
xml.target!
|
xml.target!
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_credit_request(money, identification, options)
|
def build_credit_request(money, identification, options)
|
||||||
xml = Builder::XmlMarkup.new
|
xml = Builder::XmlMarkup.new
|
||||||
|
|
||||||
xml.tag! 'RefundTransactionReq', 'xmlns' => PAYPAL_NAMESPACE do
|
xml.tag! 'RefundTransactionReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||||
xml.tag! 'RefundTransactionRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
xml.tag! 'RefundTransactionRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||||
xml.tag! 'n2:Version', API_VERSION
|
xml.tag! 'n2:Version', API_VERSION
|
||||||
|
@ -152,13 +152,13 @@ module ActiveMerchant #:nodoc:
|
||||||
xml.tag! 'Memo', options[:note] unless options[:note].blank?
|
xml.tag! 'Memo', options[:note] unless options[:note].blank?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
xml.target!
|
xml.target!
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_void_request(authorization, options)
|
def build_void_request(authorization, options)
|
||||||
xml = Builder::XmlMarkup.new
|
xml = Builder::XmlMarkup.new
|
||||||
|
|
||||||
xml.tag! 'DoVoidReq', 'xmlns' => PAYPAL_NAMESPACE do
|
xml.tag! 'DoVoidReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||||
xml.tag! 'DoVoidRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
xml.tag! 'DoVoidRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||||
xml.tag! 'n2:Version', API_VERSION
|
xml.tag! 'n2:Version', API_VERSION
|
||||||
|
@ -167,15 +167,15 @@ module ActiveMerchant #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
xml.target!
|
xml.target!
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_mass_pay_request(*args)
|
def build_mass_pay_request(*args)
|
||||||
default_options = args.last.is_a?(Hash) ? args.pop : {}
|
default_options = args.last.is_a?(Hash) ? args.pop : {}
|
||||||
recipients = args.first.is_a?(Array) ? args : [args]
|
recipients = args.first.is_a?(Array) ? args : [args]
|
||||||
|
|
||||||
xml = Builder::XmlMarkup.new
|
xml = Builder::XmlMarkup.new
|
||||||
|
|
||||||
xml.tag! 'MassPayReq', 'xmlns' => PAYPAL_NAMESPACE do
|
xml.tag! 'MassPayReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||||
xml.tag! 'MassPayRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
xml.tag! 'MassPayRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||||
xml.tag! 'n2:Version', API_VERSION
|
xml.tag! 'n2:Version', API_VERSION
|
||||||
|
@ -191,24 +191,24 @@ module ActiveMerchant #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
xml.target!
|
xml.target!
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse(action, xml)
|
def parse(action, xml)
|
||||||
response = {}
|
response = {}
|
||||||
|
|
||||||
error_messages = []
|
error_messages = []
|
||||||
error_codes = []
|
error_codes = []
|
||||||
|
|
||||||
xml = REXML::Document.new(xml)
|
xml = REXML::Document.new(xml)
|
||||||
if root = REXML::XPath.first(xml, "//#{action}Response")
|
if root = REXML::XPath.first(xml, "//#{action}Response")
|
||||||
root.elements.each do |node|
|
root.elements.each do |node|
|
||||||
case node.name
|
case node.name
|
||||||
when 'Errors'
|
when 'Errors'
|
||||||
short_message = nil
|
short_message = nil
|
||||||
long_message = nil
|
long_message = nil
|
||||||
|
|
||||||
node.elements.each do |child|
|
node.elements.each do |child|
|
||||||
case child.name
|
case child.name
|
||||||
when "LongMessage"
|
when "LongMessage"
|
||||||
|
@ -250,20 +250,20 @@ module ActiveMerchant #:nodoc:
|
||||||
|
|
||||||
def build_request(body)
|
def build_request(body)
|
||||||
xml = Builder::XmlMarkup.new
|
xml = Builder::XmlMarkup.new
|
||||||
|
|
||||||
xml.instruct!
|
xml.instruct!
|
||||||
xml.tag! 'env:Envelope', ENVELOPE_NAMESPACES do
|
xml.tag! 'env:Envelope', ENVELOPE_NAMESPACES do
|
||||||
xml.tag! 'env:Header' do
|
xml.tag! 'env:Header' do
|
||||||
add_credentials(xml)
|
add_credentials(xml)
|
||||||
end
|
end
|
||||||
|
|
||||||
xml.tag! 'env:Body' do
|
xml.tag! 'env:Body' do
|
||||||
xml << body
|
xml << body
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
xml.target!
|
xml.target!
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_credentials(xml)
|
def add_credentials(xml)
|
||||||
xml.tag! 'RequesterCredentials', CREDENTIALS_NAMESPACES do
|
xml.tag! 'RequesterCredentials', CREDENTIALS_NAMESPACES do
|
||||||
xml.tag! 'n1:Credentials' do
|
xml.tag! 'n1:Credentials' do
|
||||||
|
@ -274,7 +274,7 @@ module ActiveMerchant #:nodoc:
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_address(xml, element, address)
|
def add_address(xml, element, address)
|
||||||
return if address.nil?
|
return if address.nil?
|
||||||
xml.tag! element do
|
xml.tag! element do
|
||||||
|
@ -306,22 +306,23 @@ module ActiveMerchant #:nodoc:
|
||||||
# xml.tag! 'n2:EbayItemPaymentDetailsItem', item[:name]
|
# xml.tag! 'n2:EbayItemPaymentDetailsItem', item[:name]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_payment_details(xml, money, options)
|
def add_payment_details(xml, money, options)
|
||||||
currency_code = options[:currency] || currency(money)
|
currency_code = options[:currency] || currency(money)
|
||||||
# COULD USE options[:currency] || currency(options[:actual_opt])
|
|
||||||
|
|
||||||
xml.tag! 'n2:PaymentDetails' do
|
xml.tag! 'n2:PaymentDetails' do
|
||||||
xml.tag! 'n2:OrderTotal', amount(money), 'currencyID' => currency_code
|
xml.tag! 'n2:OrderTotal', amount(money), 'currencyID' => currency_code
|
||||||
|
|
||||||
# All of the values must be included together and add up to the order total
|
# All of the values must be included together and add up to the order total
|
||||||
|
puts "-----------#{options[:shipping]}---------------------#{amount(options[:shipping])}----------------"
|
||||||
|
|
||||||
if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) }
|
if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) }
|
||||||
xml.tag! 'n2:ItemTotal', amount(options[:subtotal]), 'currencyID' => currency_code
|
xml.tag! 'n2:ItemTotal', amount(options[:subtotal]), 'currencyID' => currency_code
|
||||||
xml.tag! 'n2:ShippingTotal', amount(options[:shipping]),'currencyID' => currency_code
|
xml.tag! 'n2:ShippingTotal', amount(options[:shipping]),'currencyID' => currency_code
|
||||||
xml.tag! 'n2:HandlingTotal', amount(options[:handling]),'currencyID' => currency_code
|
xml.tag! 'n2:HandlingTotal', amount(options[:handling]),'currencyID' => currency_code
|
||||||
xml.tag! 'n2:TaxTotal', amount(options[:tax]), 'currencyID' => currency_code
|
xml.tag! 'n2:TaxTotal', amount(options[:tax]), 'currencyID' => currency_code
|
||||||
end
|
end
|
||||||
|
|
||||||
# don't enforce inclusion yet - see how it works
|
# don't enforce inclusion yet - see how it works
|
||||||
xml.tag! 'n2:InsuranceOptionOffered', options[:insurance_offered] ? '1' : '0' unless options[:insurance_offered].blank?
|
xml.tag! 'n2:InsuranceOptionOffered', options[:insurance_offered] ? '1' : '0' unless options[:insurance_offered].blank?
|
||||||
xml.tag! 'n2:InsuranceTotal', amount(options[:insurance]), 'currencyID' => currency_code unless options[:insurance].blank?
|
xml.tag! 'n2:InsuranceTotal', amount(options[:insurance]), 'currencyID' => currency_code unless options[:insurance].blank?
|
||||||
|
@ -331,13 +332,29 @@ module ActiveMerchant #:nodoc:
|
||||||
xml.tag! 'n2:OrderDescription', options[:description] unless options[:description].blank?
|
xml.tag! 'n2:OrderDescription', options[:description] unless options[:description].blank?
|
||||||
xml.tag! 'n2:Custom', options[:custom] unless options[:custom].blank?
|
xml.tag! 'n2:Custom', options[:custom] unless options[:custom].blank?
|
||||||
xml.tag! 'n2:InvoiceID', options[:order_id] unless options[:order_id].blank?
|
xml.tag! 'n2:InvoiceID', options[:order_id] unless options[:order_id].blank?
|
||||||
xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
|
xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
|
||||||
xml.tag! 'n2:NotifyURL', options[:notify_url] unless options[:notify_url].blank?
|
xml.tag! 'n2:NotifyURL', options[:notify_url] unless options[:notify_url].blank?
|
||||||
add_address(xml, 'n2:ShipToAddress', options[:shipping_address] || options[:address])
|
add_address(xml, 'n2:ShipToAddress', options[:shipping_address] || options[:address])
|
||||||
options[:items].each {|i| add_payment_detail_item xml, i } if options[:items]
|
options[:items].each {|i| add_payment_detail_item xml, i } if options[:items]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_shipping_options(xml, shipping_options, options)
|
||||||
|
currency_code = options[:currency]
|
||||||
|
|
||||||
|
xml.tag! 'n2:FlatRateShippingOptions' do
|
||||||
|
|
||||||
|
shipping_options.each_with_index do |shipping_option, i|
|
||||||
|
xml.tag! 'n2:ShippingOptions' do
|
||||||
|
xml.tag! 'n2:ShippingOptionIsDefault', (i == 0)
|
||||||
|
xml.tag! 'n2:ShippingOptionName', shipping_option[:name]
|
||||||
|
xml.tag! 'n2:ShippingOptionLabel', shipping_option[:label]
|
||||||
|
xml.tag! 'n2:ShippingOptionAmount', amount(shipping_option[:amount] ), 'currencyID' => 'USD'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def endpoint_url
|
def endpoint_url
|
||||||
URLS[test? ? :test : :live][@options[:signature].blank? ? :certificate : :signature]
|
URLS[test? ? :test : :live][@options[:signature].blank? ? :certificate : :signature]
|
||||||
end
|
end
|
||||||
|
@ -346,26 +363,26 @@ module ActiveMerchant #:nodoc:
|
||||||
response = parse(action, ssl_post(endpoint_url, build_request(request)))
|
response = parse(action, ssl_post(endpoint_url, build_request(request)))
|
||||||
|
|
||||||
build_response(successful?(response), message_from(response), response,
|
build_response(successful?(response), message_from(response), response,
|
||||||
:test => test?,
|
:test => test?,
|
||||||
:authorization => authorization_from(response),
|
:authorization => authorization_from(response),
|
||||||
:fraud_review => fraud_review?(response),
|
:fraud_review => fraud_review?(response),
|
||||||
:avs_result => { :code => response[:avs_code] },
|
:avs_result => { :code => response[:avs_code] },
|
||||||
:cvv_result => response[:cvv2_code]
|
:cvv_result => response[:cvv2_code]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fraud_review?(response)
|
def fraud_review?(response)
|
||||||
response[:error_codes] == FRAUD_REVIEW_CODE
|
response[:error_codes] == FRAUD_REVIEW_CODE
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorization_from(response)
|
def authorization_from(response)
|
||||||
response[:transaction_id] || response[:authorization_id] || response[:refund_transaction_id] # middle one is from reauthorization
|
response[:transaction_id] || response[:authorization_id] || response[:refund_transaction_id] # middle one is from reauthorization
|
||||||
end
|
end
|
||||||
|
|
||||||
def successful?(response)
|
def successful?(response)
|
||||||
SUCCESS_CODES.include?(response[:ack])
|
SUCCESS_CODES.include?(response[:ack])
|
||||||
end
|
end
|
||||||
|
|
||||||
def message_from(response)
|
def message_from(response)
|
||||||
response[:message] || response[:ack]
|
response[:message] || response[:ack]
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,21 +7,25 @@ module ActiveMerchant #:nodoc:
|
||||||
class PaypalExpressGateway < Gateway
|
class PaypalExpressGateway < Gateway
|
||||||
include PaypalCommonAPI
|
include PaypalCommonAPI
|
||||||
include PaypalExpressCommon
|
include PaypalExpressCommon
|
||||||
|
|
||||||
self.test_redirect_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token='
|
self.test_redirect_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token='
|
||||||
self.supported_countries = ['US']
|
self.supported_countries = ['US']
|
||||||
self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside'
|
self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside'
|
||||||
self.display_name = 'PayPal Express Checkout'
|
self.display_name = 'PayPal Express Checkout'
|
||||||
|
|
||||||
def setup_authorization(money, options = {})
|
def setup_authorization(money, options = {})
|
||||||
requires!(options, :return_url, :cancel_return_url)
|
requires!(options, :return_url, :cancel_return_url)
|
||||||
|
|
||||||
commit 'SetExpressCheckout', build_setup_request('Authorization', money, options)
|
req = build_setup_request('Authorization', money, options)
|
||||||
|
|
||||||
|
puts req
|
||||||
|
|
||||||
|
commit 'SetExpressCheckout', req
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_purchase(money, options = {})
|
def setup_purchase(money, options = {})
|
||||||
requires!(options, :return_url, :cancel_return_url)
|
requires!(options, :return_url, :cancel_return_url)
|
||||||
|
|
||||||
commit 'SetExpressCheckout', build_setup_request('Sale', money, options)
|
commit 'SetExpressCheckout', build_setup_request('Sale', money, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -31,13 +35,17 @@ module ActiveMerchant #:nodoc:
|
||||||
|
|
||||||
def authorize(money, options = {})
|
def authorize(money, options = {})
|
||||||
requires!(options, :token, :payer_id)
|
requires!(options, :token, :payer_id)
|
||||||
|
|
||||||
commit 'DoExpressCheckoutPayment', build_sale_or_authorization_request('Authorization', money, options)
|
req = build_sale_or_authorization_request('Authorization', money, options)
|
||||||
|
|
||||||
|
puts req
|
||||||
|
|
||||||
|
commit 'DoExpressCheckoutPayment', req
|
||||||
end
|
end
|
||||||
|
|
||||||
def purchase(money, options = {})
|
def purchase(money, options = {})
|
||||||
requires!(options, :token, :payer_id)
|
requires!(options, :token, :payer_id)
|
||||||
|
|
||||||
commit 'DoExpressCheckoutPayment', build_sale_or_authorization_request('Sale', money, options)
|
commit 'DoExpressCheckoutPayment', build_sale_or_authorization_request('Sale', money, options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -53,10 +61,10 @@ module ActiveMerchant #:nodoc:
|
||||||
|
|
||||||
xml.target!
|
xml.target!
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_sale_or_authorization_request(action, money, options)
|
def build_sale_or_authorization_request(action, money, options)
|
||||||
currency_code = options[:currency] || currency(money)
|
currency_code = options[:currency] || currency(money)
|
||||||
|
|
||||||
xml = Builder::XmlMarkup.new :indent => 2
|
xml = Builder::XmlMarkup.new :indent => 2
|
||||||
xml.tag! 'DoExpressCheckoutPaymentReq', 'xmlns' => PAYPAL_NAMESPACE do
|
xml.tag! 'DoExpressCheckoutPaymentReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||||
xml.tag! 'DoExpressCheckoutPaymentRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
xml.tag! 'DoExpressCheckoutPaymentRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||||
|
@ -84,16 +92,16 @@ module ActiveMerchant #:nodoc:
|
||||||
end
|
end
|
||||||
xml.tag! 'n2:ReturnURL', options[:return_url]
|
xml.tag! 'n2:ReturnURL', options[:return_url]
|
||||||
xml.tag! 'n2:CancelURL', options[:cancel_return_url]
|
xml.tag! 'n2:CancelURL', options[:cancel_return_url]
|
||||||
xml.tag! 'n2:CallbackURL', options[:callback_url] unless options[:callback_url].blank?
|
# xml.tag! 'n2:CallbackURL', options[:callback_url] unless options[:callback_url].blank?
|
||||||
xml.tag! 'n2:CallbackTimeout', options[:callback_timeout] unless options[:callback_timeout].blank?
|
# xml.tag! 'n2:CallbackTimeout', options[:callback_timeout] unless options[:callback_timeout].blank?
|
||||||
# flat rate shipping options -- required if using callback, TODO
|
|
||||||
xml.tag! 'n2:ReqConfirmShipping', options[:req_confirm_shipping] ? '1' : '0'
|
xml.tag! 'n2:ReqConfirmShipping', options[:req_confirm_shipping] ? '1' : '0'
|
||||||
xml.tag! 'n2:NoShipping', options[:no_shipping] ? '1' : '0'
|
xml.tag! 'n2:NoShipping', options[:no_shipping] ? '1' : '0'
|
||||||
# NOT INCLUDED IN SETUP -- GRAB ELSEWHERE? -- xml.tag! 'n2:IPAddress', options[:ip]
|
## add flat rates for shipping
|
||||||
|
# add_shipping_options(xml, options[:shipping_options], options) if options[:shipping_options]
|
||||||
xml.tag! 'n2:AllowNote', options[:allow_note] ? '1' : '0'
|
xml.tag! 'n2:AllowNote', options[:allow_note] ? '1' : '0'
|
||||||
xml.tag! 'n2:AddressOverride', options[:address_override] ? '1' : '0' # force yours
|
xml.tag! 'n2:AddressOverride', options[:address_override] ? '1' : '0' # force yours
|
||||||
xml.tag! 'n2:LocaleCode', options[:locale] unless options[:locale].blank?
|
xml.tag! 'n2:LocaleCode', options[:locale] unless options[:locale].blank?
|
||||||
|
|
||||||
# Customization of the payment page
|
# Customization of the payment page
|
||||||
xml.tag! 'n2:PageStyle', options[:page_style] unless options[:page_style].blank?
|
xml.tag! 'n2:PageStyle', options[:page_style] unless options[:page_style].blank?
|
||||||
xml.tag! 'n2:cpp-header-image', options[:header_image] unless options[:header_image].blank?
|
xml.tag! 'n2:cpp-header-image', options[:header_image] unless options[:header_image].blank?
|
||||||
|
@ -120,7 +128,7 @@ module ActiveMerchant #:nodoc:
|
||||||
|
|
||||||
xml.target!
|
xml.target!
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_response(success, message, response, options = {})
|
def build_response(success, message, response, options = {})
|
||||||
PaypalExpressResponse.new(success, message, response, options)
|
PaypalExpressResponse.new(success, message, response, options)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,193 +1,113 @@
|
||||||
# aim to unpick this later
|
# aim to unpick this later
|
||||||
module Spree::PaypalExpress
|
module Spree::PaypalExpress
|
||||||
include ERB::Util
|
include ERB::Util
|
||||||
include Spree::PaymentGateway
|
include ActiveMerchant::RequiresParameters
|
||||||
include ActiveMerchant::RequiresParameters
|
|
||||||
|
|
||||||
|
|
||||||
def fixed_opts
|
|
||||||
{ :description => "Goods from #{Spree::Config[:site_name]}", # site details...
|
|
||||||
|
|
||||||
#:page_style => "foobar", # merchant account can set named config
|
|
||||||
:header_image => "https://" + Spree::Config[:site_url] + "/images/logo.png",
|
|
||||||
:background_color => "ffffff", # must be hex only, six chars
|
|
||||||
:header_background_color => "ffffff",
|
|
||||||
:header_border_color => "ffffff",
|
|
||||||
|
|
||||||
:allow_note => true,
|
|
||||||
:locale => Spree::Config[:default_locale],
|
|
||||||
:notify_url => 'to be done', # this is a callback, not tried it yet
|
|
||||||
|
|
||||||
:req_confirm_shipping => false, # for security, might make an option later
|
|
||||||
|
|
||||||
# :no_shipping => false,
|
|
||||||
# :address_override => false,
|
|
||||||
|
|
||||||
# WARNING -- don't use :ship_discount, :insurance_offered, :insurance since
|
|
||||||
# they've not been tested and may trigger some paypal bugs, eg not showing order
|
|
||||||
# see http://www.pdncommunity.com/t5/PayPal-Developer-Blog/Displaying-Order-Details-in-Express-Checkout/bc-p/92902#C851
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO: might be able to get paypal to do some of the shipping choice and costing
|
|
||||||
def order_opts(order)
|
|
||||||
items = order.line_items.map do |item|
|
|
||||||
tax = paypal_variant_tax(item.price, item.variant)
|
|
||||||
price = (item.price * 100).to_i # convert for gateway
|
|
||||||
tax = (tax * 100).to_i # truncate the tax slice
|
|
||||||
{ :name => item.variant.product.name,
|
|
||||||
:description => item.variant.product.description[0..120],
|
|
||||||
:sku => item.variant.sku,
|
|
||||||
:qty => item.quantity,
|
|
||||||
:amount => price - tax,
|
|
||||||
:tax => tax,
|
|
||||||
:weight => item.variant.weight,
|
|
||||||
:height => item.variant.height,
|
|
||||||
:width => item.variant.width,
|
|
||||||
:depth => item.variant.weight }
|
|
||||||
end
|
|
||||||
|
|
||||||
opts = { :return_url => request.protocol + request.host_with_port + "/orders/#{order.number}/paypal_finish",
|
|
||||||
:cancel_return_url => "http://" + request.host_with_port + "/orders/#{order.number}/edit",
|
|
||||||
:order_id => order.number,
|
|
||||||
:custom => order.number,
|
|
||||||
|
|
||||||
:items => items,
|
|
||||||
}
|
|
||||||
opts
|
|
||||||
end
|
|
||||||
|
|
||||||
# hook for supplying tax amount for a single unit of a variant
|
|
||||||
# expects the sale price from the line_item and the variant itself, since
|
|
||||||
# line_item price and variant price can diverge in time
|
|
||||||
def paypal_variant_tax(sale_price, variant)
|
|
||||||
0.0
|
|
||||||
end
|
|
||||||
|
|
||||||
# hook for easy site configuration, needs to return a hash
|
|
||||||
# you probably wanto over-ride the description option here, maybe the colours and logo
|
|
||||||
def paypal_site_options(order)
|
|
||||||
{}
|
|
||||||
end
|
|
||||||
|
|
||||||
# hook to allow applications to load in their own shipping and handling costs
|
|
||||||
# eg might want to estimate from cheapest shipping option and rely on ability to
|
|
||||||
# claim an extra 15% in the final auth
|
|
||||||
def paypal_shipping_and_handling_costs(order)
|
|
||||||
{}
|
|
||||||
end
|
|
||||||
|
|
||||||
def all_opts(order)
|
|
||||||
|
|
||||||
opts = fixed_opts.merge(order_opts order).
|
|
||||||
merge({ :shipping => 0, :handling => 0 } ).
|
|
||||||
merge(paypal_shipping_and_handling_costs order).
|
|
||||||
merge(paypal_site_options order)
|
|
||||||
|
|
||||||
# get the main totals from the items (already *100)
|
|
||||||
opts[:subtotal] = opts[:items].map {|i| i[:amount] * i[:qty] }.sum
|
|
||||||
opts[:tax] = opts[:items].map {|i| i[:tax] * i[:qty] }.sum
|
|
||||||
|
|
||||||
# prepare the shipping and handling costs
|
|
||||||
[:shipping, :handling].each {|amt| opts[amt] *= 100 }
|
|
||||||
|
|
||||||
# overall total
|
|
||||||
opts[:money] = opts.slice(:subtotal, :tax, :shipping, :handling).values.sum
|
|
||||||
|
|
||||||
# # add the shipping and handling estimates to spree's order total
|
|
||||||
# # (spree won't add them yet, since we've not officially chosen the shipping method)
|
|
||||||
# spree_total = order.total + opts[:shipping] + opts[:handling]
|
|
||||||
# # paypal expects this sum to work out (TODO: shift to AM code? and throw wobbly?)
|
|
||||||
# # there might be rounding issues when it comes to tax, though you can capture slightly extra
|
|
||||||
# if opts[:money] != spree_total
|
|
||||||
# raise "Ouch - precision problems: #{opts[:money]} vs #{spree_total}"
|
|
||||||
# if (opts[:money].to_f - spree_total.to_f).abs > 0.01
|
|
||||||
# raise "Ouch - precision problems: #{opts[:money].to_f} vs #{spree_total.to_f}, diff #{opts[:money].to_f - spree_total.to_f}"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# suggest current user's email or any email stored in the order
|
|
||||||
opts[:email] = current_user ? current_user.email : order.checkout.email
|
|
||||||
|
|
||||||
opts
|
|
||||||
end
|
|
||||||
|
|
||||||
def paypal_checkout
|
def paypal_checkout
|
||||||
# fix a shipping method if not already done - DISABLE - avoid spree totals interference
|
opts = all_opts(@order, 'checkout')
|
||||||
# @order.checkout.shipment.shipping_method ||= ShippingMethod.first
|
opts.merge!(address_and_selected_shipping_options(@order))
|
||||||
# @order.checkout.shipment.save
|
|
||||||
|
|
||||||
opts = all_opts(@order)
|
|
||||||
gateway = paypal_gateway
|
gateway = paypal_gateway
|
||||||
|
|
||||||
response = gateway.setup_authorization(opts[:money], opts)
|
response = gateway.setup_authorization(opts[:money], opts)
|
||||||
|
unless response.success?
|
||||||
|
gateway_error(response)
|
||||||
|
redirect_to edit_order_url(@order)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
gateway_error(response) unless response.success?
|
redirect_to (gateway.redirect_url_for response.token)
|
||||||
|
|
||||||
redirect_to (gateway.redirect_url_for response.token)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def paypal_finish
|
def paypal_payment
|
||||||
order = Order.find_by_number(params[:id])
|
opts = all_opts(@order, 'payment')
|
||||||
|
opts.merge!(address_and_selected_shipping_options(@order))
|
||||||
opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(order)
|
|
||||||
gateway = paypal_gateway
|
gateway = paypal_gateway
|
||||||
|
|
||||||
info = gateway.details_for params[:token]
|
response = gateway.setup_authorization(opts[:money], opts)
|
||||||
gateway_error(info) unless info.success?
|
unless response.success?
|
||||||
|
gateway_error(response)
|
||||||
|
redirect_to edit_order_checkout_url(@order, :step => "payment")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to (gateway.redirect_url_for response.token)
|
||||||
|
end
|
||||||
|
|
||||||
|
def paypal_confirm
|
||||||
|
@order = Order.find_by_number(params[:id])
|
||||||
|
|
||||||
|
opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order)
|
||||||
|
gateway = paypal_gateway
|
||||||
|
|
||||||
|
@ppx_details = gateway.details_for params[:token]
|
||||||
|
gateway_error(@ppx_details) unless @ppx_details.success?
|
||||||
|
|
||||||
|
|
||||||
# now save the order info
|
|
||||||
order.checkout.email = info.email
|
|
||||||
order.checkout.special_instructions = info.params["note"]
|
|
||||||
order.checkout.save
|
|
||||||
order.update_attribute(:user, current_user)
|
|
||||||
|
|
||||||
# save the address info
|
# now save the updated order info
|
||||||
ship_address = info.address
|
@order.checkout.email = @ppx_details.email
|
||||||
order_ship_address = Address.new :firstname => info.params["first_name"],
|
@order.checkout.special_instructions = @ppx_details.params["note"]
|
||||||
:lastname => info.params["last_name"],
|
|
||||||
|
@order.update_attribute(:user, current_user)
|
||||||
|
|
||||||
|
ship_address = @ppx_details.address
|
||||||
|
order_ship_address = Address.new :firstname => @ppx_details.params["first_name"],
|
||||||
|
:lastname => @ppx_details.params["last_name"],
|
||||||
:address1 => ship_address["address1"],
|
:address1 => ship_address["address1"],
|
||||||
:address2 => ship_address["address2"],
|
:address2 => ship_address["address2"],
|
||||||
:city => ship_address["city"],
|
:city => ship_address["city"],
|
||||||
:country => Country.find_by_iso(ship_address["country"]),
|
:country => Country.find_by_iso(ship_address["country"]),
|
||||||
:zipcode => ship_address["zip"],
|
:zipcode => ship_address["zip"],
|
||||||
# phone is currently blanked in AM's PPX response lib
|
# phone is currently blanked in AM's PPX response lib
|
||||||
:phone => info.params["phone"] || "(not given)"
|
:phone => @ppx_details.params["phone"] || "(not given)"
|
||||||
|
|
||||||
if (state = State.find_by_name(ship_address["state"]))
|
if (state = State.find_by_abbr(ship_address["state"]))
|
||||||
order_ship_address.state = state
|
order_ship_address.state = state
|
||||||
else
|
else
|
||||||
order_ship_address.state_name = ship_address["state"]
|
order_ship_address.state_name = ship_address["state"]
|
||||||
end
|
end
|
||||||
|
|
||||||
order_ship_address.save!
|
order_ship_address.save!
|
||||||
|
|
||||||
# TODO: refine/choose the shipping method via paypal, or in advance
|
@order.checkout.ship_address = order_ship_address
|
||||||
order.checkout.shipment.update_attributes :address => order_ship_address
|
@order.checkout.save
|
||||||
|
render :partial => "shared/paypal_express_confirm", :layout => true
|
||||||
|
end
|
||||||
|
|
||||||
|
def paypal_finish
|
||||||
|
order = Order.find_by_number(params[:id])
|
||||||
|
|
||||||
|
opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order)
|
||||||
|
gateway = paypal_gateway
|
||||||
|
|
||||||
|
|
||||||
# now do the authorization and build the record of payment
|
ppx_auth_response = gateway.authorize((order.total*100).to_i, opts)
|
||||||
# use the info total from paypal, in case the user has changed their order
|
gateway_error(ppx_auth_response) unless ppx_auth_response.success?
|
||||||
response = gateway.authorize(opts[:money], opts)
|
puts "------------------------------------------------"
|
||||||
gateway_error(response) unless response.success?
|
puts ppx_auth_response.to_yaml
|
||||||
|
|
||||||
fake_card = Creditcard.new :checkout => order.checkout,
|
puts "-----#{ppx_auth_response.avs_result.class}--------------------------------#{ppx_auth_response.avs_result["code"]}-----------"
|
||||||
:cc_type => "visa", # fixed set of labels here
|
|
||||||
:month => Time.now.month,
|
payment = order.paypal_payments.create(:amount => ppx_auth_response.params["gross_amount"].to_f)
|
||||||
:year => Time.now.year,
|
|
||||||
:first_name => info.params["first_name"],
|
|
||||||
:last_name => info.params["last_name"],
|
|
||||||
:display_number => "paypal:" + info.payer_id
|
|
||||||
payment = order.paypal_payments.create(:amount => response.params["gross_amount"].to_f,
|
|
||||||
:creditcard => fake_card)
|
|
||||||
|
|
||||||
# query - need 0 in amount for an auth? see main code
|
# query - need 0 in amount for an auth? see main code
|
||||||
transaction = CreditcardTxn.new( :amount => response.params["gross_amount"].to_f,
|
transaction = PaypalTxn.new (:paypal_payment => payment,
|
||||||
:response_code => response.authorization,
|
:gross_amount => ppx_auth_response.params["gross_amount"].to_f,
|
||||||
:txn_type => CreditcardTxn::TxnType::AUTHORIZE)
|
:payment_status => ppx_auth_response.params["payment_status"],
|
||||||
payment.creditcard_txns << transaction
|
:pending_reason => ppx_auth_response.params["pending_reason"],
|
||||||
|
:transaction_type => ppx_auth_response.params["transaction_type"],
|
||||||
|
:payment_type => ppx_auth_response.params["payment_type"],
|
||||||
|
:ack => ppx_auth_response.params["ack"],
|
||||||
|
:token => ppx_auth_response.params["token"])# ,
|
||||||
|
# :avs_code => ppx_auth_response.params["avs_result"]["code"],
|
||||||
|
# :cvv_code => ppx_auth_response.params["cvv_result"]["code"])
|
||||||
|
|
||||||
|
payment.paypal_txns << transaction
|
||||||
|
|
||||||
|
|
||||||
# save this for future reference
|
# save this for future reference
|
||||||
order.checkout.shipment.shipping_method ||= ShippingMethod.first
|
# order.checkout.shipment.shipping_method ||= ShippingMethod.first
|
||||||
order.checkout.shipment.save
|
# order.checkout.shipment.save
|
||||||
|
|
||||||
order.save!
|
order.save!
|
||||||
order.complete # get return of status? throw of problems??? else weak go-ahead
|
order.complete # get return of status? throw of problems??? else weak go-ahead
|
||||||
|
@ -197,8 +117,8 @@ module Spree::PaypalExpress
|
||||||
order_params = {:checkout_complete => true}
|
order_params = {:checkout_complete => true}
|
||||||
order_params[:order_token] = order.token unless order.user
|
order_params[:order_token] = order.token unless order.user
|
||||||
session[:order_id] = nil if order.checkout.completed_at
|
session[:order_id] = nil if order.checkout.completed_at
|
||||||
redirect_to order_url(order, order_params)
|
redirect_to order_url(order, order_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_capture(authorization)
|
def do_capture(authorization)
|
||||||
response = paypal_gateway.capture((100 * authorization.amount).to_i, authorization.response_code)
|
response = paypal_gateway.capture((100 * authorization.amount).to_i, authorization.response_code)
|
||||||
|
@ -213,26 +133,164 @@ module Spree::PaypalExpress
|
||||||
:response_code => response.authorization,
|
:response_code => response.authorization,
|
||||||
:txn_type => CreditcardTxn::TxnType::CAPTURE )
|
:txn_type => CreditcardTxn::TxnType::CAPTURE )
|
||||||
payment.save
|
payment.save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
def fixed_opts
|
||||||
|
{ :description => "Goods from #{Spree::Config[:site_name]}", # site details...
|
||||||
|
|
||||||
|
#:page_style => "foobar", # merchant account can set named config
|
||||||
|
:header_image => "https://" + Spree::Config[:site_url] + "/images/logo.png",
|
||||||
|
:background_color => "ffffff", # must be hex only, six chars
|
||||||
|
:header_background_color => "ffffff",
|
||||||
|
:header_border_color => "ffffff",
|
||||||
|
|
||||||
|
:allow_note => true,
|
||||||
|
:locale => Spree::Config[:default_locale],
|
||||||
|
:notify_url => 'to be done', # this is a callback, not tried it yet
|
||||||
|
|
||||||
|
:req_confirm_shipping => false, # for security, might make an option later
|
||||||
|
|
||||||
|
# WARNING -- don't use :ship_discount, :insurance_offered, :insurance since
|
||||||
|
# they've not been tested and may trigger some paypal bugs, eg not showing order
|
||||||
|
# see http://www.pdncommunity.com/t5/PayPal-Developer-Blog/Displaying-Order-Details-in-Express-Checkout/bc-p/92902#C851
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def order_opts(order, stage)
|
||||||
|
items = order.line_items.map do |item|
|
||||||
|
tax = paypal_variant_tax(item.price, item.variant)
|
||||||
|
price = (item.price * 100).to_i # convert for gateway
|
||||||
|
tax = (tax * 100).to_i # truncate the tax slice
|
||||||
|
{ :name => item.variant.product.name,
|
||||||
|
:description => item.variant.product.description[0..120],
|
||||||
|
:sku => item.variant.sku,
|
||||||
|
:qty => item.quantity,
|
||||||
|
:amount => price - tax,
|
||||||
|
:tax => tax,
|
||||||
|
:weight => item.variant.weight,
|
||||||
|
:height => item.variant.height,
|
||||||
|
:width => item.variant.width,
|
||||||
|
:depth => item.variant.weight }
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
opts = { :return_url => request.protocol + request.host_with_port + "/orders/#{order.number}/paypal_confirm",
|
||||||
|
:cancel_return_url => "http://" + request.host_with_port + "/orders/#{order.number}/edit",
|
||||||
|
:order_id => order.number,
|
||||||
|
:custom => order.number,
|
||||||
|
:items => items
|
||||||
|
}
|
||||||
|
|
||||||
|
if stage == "checkout"
|
||||||
|
# recalculate all totals here as we need to ignore shipping & tax because we are checking-out via paypal (spree checkout not started)
|
||||||
|
|
||||||
|
# get the main totals from the items (already *100)
|
||||||
|
opts[:subtotal] = opts[:items].map {|i| i[:amount] * i[:qty] }.sum
|
||||||
|
opts[:tax] = opts[:items].map {|i| i[:tax] * i[:qty] }.sum
|
||||||
|
|
||||||
|
# overall total
|
||||||
|
opts[:money] = opts.slice(:subtotal, :tax, :shipping, :handling).values.sum
|
||||||
|
|
||||||
|
opts[:money] = (order.total*100).to_i
|
||||||
|
|
||||||
|
opts[:callback_url] = "http://" + request.host_with_port + "/paypal_express_callbacks/#{order.number}"
|
||||||
|
opts[:callback_timeout] = 3
|
||||||
|
|
||||||
|
elsif stage == "payment"
|
||||||
|
#use real totals are we are paying via paypal (spree checkout almost complete)
|
||||||
|
opts[:subtotal] = (order.item_total*100).to_i
|
||||||
|
opts[:tax] = 0 # BQ : not sure what to do here
|
||||||
|
opts[:shipping] = (order.ship_total*100).to_i
|
||||||
|
opts[:handling] = 0 # BQ : not sure what to do here
|
||||||
|
|
||||||
|
# overall total
|
||||||
|
opts[:money] = opts.slice(:subtotal, :tax, :shipping, :handling).values.sum
|
||||||
|
|
||||||
|
opts[:money] = (order.total*100).to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
|
||||||
|
# hook for supplying tax amount for a single unit of a variant
|
||||||
|
# expects the sale price from the line_item and the variant itself, since
|
||||||
|
# line_item price and variant price can diverge in time
|
||||||
|
def paypal_variant_tax(sale_price, variant)
|
||||||
|
0.0
|
||||||
|
end
|
||||||
|
|
||||||
|
def address_and_selected_shipping_options(order)
|
||||||
|
{
|
||||||
|
:no_shipping => false,
|
||||||
|
:address_override => true,
|
||||||
|
:address => {
|
||||||
|
:name => "#{order.ship_address.firstname} #{order.ship_address.lastname}",
|
||||||
|
:address1 => order.ship_address.address1,
|
||||||
|
:address2 => order.ship_address.address2,
|
||||||
|
:city => order.ship_address.city,
|
||||||
|
:state => order.ship_address.state.nil? ? order.ship_address.state_name.to_s : order.ship_address.state.abbr,
|
||||||
|
:country => order.ship_address.country.iso,
|
||||||
|
:zip => order.ship_address.zipcode,
|
||||||
|
:phone => order.ship_address.phone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def all_opts(order, stage=nil)
|
||||||
|
opts = fixed_opts.merge(order_opts(order, stage))#.
|
||||||
|
# merge(paypal_site_options order) BQ
|
||||||
|
|
||||||
|
if stage == "payment"
|
||||||
|
opts.merge! flat_rate_shipping_and_handling_options(order, stage)
|
||||||
|
end
|
||||||
|
|
||||||
|
# suggest current user's email or any email stored in the order
|
||||||
|
opts[:email] = current_user ? current_user.email : order.checkout.email
|
||||||
|
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
|
||||||
|
# hook to allow applications to load in their own shipping and handling costs
|
||||||
|
def flat_rate_shipping_and_handling_options(order, stage)
|
||||||
|
max_fallback = 0.0
|
||||||
|
shipping_options = ShippingMethod.all.map do |shipping_method|
|
||||||
|
max_fallback = shipping_method.fallback_amount if shipping_method.fallback_amount > max_fallback
|
||||||
|
{ :name => "#{shipping_method.id}",
|
||||||
|
:label => "#{shipping_method.name} - #{shipping_method.zone.name}",
|
||||||
|
:amount => (shipping_method.fallback_amount*100) + 1,
|
||||||
|
:default => shipping_method.is_default }
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
default_shipping_method = ShippingMethod.find(:first, :conditions => {:is_default => true})
|
||||||
|
|
||||||
|
opts = { :shipping_options => shipping_options,
|
||||||
|
:max_amount => (order.total + max_fallback)*100
|
||||||
|
}
|
||||||
|
|
||||||
|
opts[:shipping] = (default_shipping_method.nil? ? 0 : default_shipping_method.fallback_amount) if stage == "checkout"
|
||||||
|
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
|
||||||
|
def gateway_error(response)
|
||||||
|
text = response.params['message'] ||
|
||||||
|
response.params['response_reason_text'] ||
|
||||||
|
response.message
|
||||||
|
msg = "#{I18n.t('gateway_error')} ... #{text}"
|
||||||
|
logger.error(msg)
|
||||||
|
flash[:error] = msg
|
||||||
|
end
|
||||||
|
|
||||||
# create the gateway from the supplied options
|
# create the gateway from the supplied options
|
||||||
def paypal_gateway
|
def paypal_gateway
|
||||||
gw_defaults = { :ppx_class => "ActiveMerchant::Billing::PaypalExpressUkGateway" }
|
integration = BillingIntegration.find(params[:integration_id]) if params.key? :integration_id
|
||||||
gw_opts = gw_defaults.merge(paypal_site_options @order)
|
integration ||= BillingIntegration.current
|
||||||
|
gw_opts = integration.options
|
||||||
begin
|
logger.debug { "-----------#{integration.provider_class}-------------------------------------" }
|
||||||
requires!(gw_opts, :ppx_class, :login, :password, :signature)
|
requires!(gw_opts, :login, :password, :signature)
|
||||||
rescue ArgumentError => err
|
|
||||||
raise ArgumentError.new(<<"EOM" + err.message)
|
|
||||||
Problem with configuring Paypal Express Gateway:
|
|
||||||
You need to ensure that hook "paypal_site_options" sets values for login, password, and signature.
|
|
||||||
It currently produces: #{paypal_site_options.inspect}
|
|
||||||
EOM
|
|
||||||
end
|
|
||||||
|
|
||||||
gateway = gw_opts[:ppx_class].constantize.new(gw_opts)
|
gateway = integration.provider_class.new(gw_opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,25 +6,18 @@ class PaypalExpressExtension < Spree::Extension
|
||||||
description "Describe your extension here"
|
description "Describe your extension here"
|
||||||
url "http://yourwebsite.com/paypal_express"
|
url "http://yourwebsite.com/paypal_express"
|
||||||
|
|
||||||
# Please use paypal_express/config/routes.rb instead for extension routes.
|
|
||||||
|
|
||||||
# def self.require_gems(config)
|
|
||||||
# config.gem "gemname-goes-here", :version => '1.2.3'
|
|
||||||
# end
|
|
||||||
|
|
||||||
def activate
|
def activate
|
||||||
# admin.tabs.add "Paypal Express", "/admin/paypal_express", :after => "Layouts", :visibility => [:all]
|
BillingIntegration::PaypalExpress.register
|
||||||
|
|
||||||
# Load up over-rides for ActiveMerchant files
|
# Load up over-rides for ActiveMerchant files
|
||||||
# these will be submitted to ActiveMerchant some time...
|
# these will be submitted to ActiveMerchant some time...
|
||||||
require File.join(PaypalExpressExtension.root, "lib", "active_merchant", "billing", "gateways", "paypal", "paypal_common_api.rb")
|
require File.join(PaypalExpressExtension.root, "lib", "active_merchant", "billing", "gateways", "paypal", "paypal_common_api.rb")
|
||||||
require File.join(PaypalExpressExtension.root, "lib", "active_merchant", "billing", "gateways", "paypal_express_uk.rb")
|
require File.join(PaypalExpressExtension.root, "lib", "active_merchant", "billing", "gateways", "paypal_express_uk.rb")
|
||||||
require File.join(PaypalExpressExtension.root, "lib", "active_merchant", "billing", "gateways", "paypal_express_uk.rb")
|
require File.join(PaypalExpressExtension.root, "lib", "active_merchant", "billing", "gateways", "paypal_express_uk.rb")
|
||||||
|
|
||||||
|
|
||||||
# inject paypal code into orders controller
|
# inject paypal code into orders controller
|
||||||
OrdersController.class_eval do
|
OrdersController.class_eval do
|
||||||
ssl_required :paypal_checkout, :paypal_finish
|
|
||||||
include Spree::PaypalExpress
|
include Spree::PaypalExpress
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
--colour
|
|
||||||
--format
|
|
||||||
progress
|
|
||||||
--loadby
|
|
||||||
mtime
|
|
||||||
--reverse
|
|
|
@ -1,37 +0,0 @@
|
||||||
unless defined? SPREE_ROOT
|
|
||||||
ENV["RAILS_ENV"] = "test"
|
|
||||||
case
|
|
||||||
when ENV["SPREE_ENV_FILE"]
|
|
||||||
require ENV["SPREE_ENV_FILE"]
|
|
||||||
when File.dirname(__FILE__) =~ %r{vendor/SPREE/vendor/extensions}
|
|
||||||
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../../")}/config/environment"
|
|
||||||
else
|
|
||||||
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../")}/config/environment"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
require "#{SPREE_ROOT}/spec/spec_helper"
|
|
||||||
|
|
||||||
if File.directory?(File.dirname(__FILE__) + "/scenarios")
|
|
||||||
Scenario.load_paths.unshift File.dirname(__FILE__) + "/scenarios"
|
|
||||||
end
|
|
||||||
if File.directory?(File.dirname(__FILE__) + "/matchers")
|
|
||||||
Dir[File.dirname(__FILE__) + "/matchers/*.rb"].each {|file| require file }
|
|
||||||
end
|
|
||||||
|
|
||||||
Spec::Runner.configure do |config|
|
|
||||||
# config.use_transactional_fixtures = true
|
|
||||||
# config.use_instantiated_fixtures = false
|
|
||||||
# config.fixture_path = RAILS_ROOT + '/spec/fixtures'
|
|
||||||
|
|
||||||
# You can declare fixtures for each behaviour like this:
|
|
||||||
# describe "...." do
|
|
||||||
# fixtures :table_a, :table_b
|
|
||||||
#
|
|
||||||
# Alternatively, if you prefer to declare them only once, you can
|
|
||||||
# do so here, like so ...
|
|
||||||
#
|
|
||||||
# config.global_fixtures = :table_a, :table_b
|
|
||||||
#
|
|
||||||
# If you declare global fixtures, be aware that they will be declared
|
|
||||||
# for all of your examples, even those that don't use them.
|
|
||||||
end
|
|
Loading…
Reference in New Issue