Significant changes: add missing AM libs, adapt to spree 0.8.4+,
fix admin interface 1. My modifications of AM files are now included and over-ride the installed lib 2. The admin interface now works, including being able to review paypal payments and do captures 3. The code works cleanly with the new checkout representation.
This commit is contained in:
parent
766c79c77a
commit
ed9771041c
|
@ -1,3 +1,75 @@
|
|||
= Paypal Express
|
||||
# Paypal Express
|
||||
|
||||
Bridge between ActiveMerchant's paypal express (PPX) gateway code and Spree
|
||||
|
||||
|
||||
## Setup and Customization
|
||||
|
||||
It's currently set up to run the UK version of the gateway, but this isn't an essential detail - should be easy to change.
|
||||
|
||||
1. Start by creating/identifying the relevant class representing your locale's paypal express gateway
|
||||
and change the +clazz+ in the migration and/or the database.
|
||||
|
||||
2. Modify +lib/spree/paypal_express.rb+ to load up details for your gateway
|
||||
|
||||
You'll notice that I'm using Spree's gateway config mechanism. This choice is debatable: Spree is basically
|
||||
set up for using one gateway at a time, whereas we probably want a main gateway plus Paypal as a backup
|
||||
choice.
|
||||
|
||||
|
||||
|
||||
## Interaction with Spree
|
||||
|
||||
The bridge code receives authorization and transaction info from PPX and converts it into the Spree
|
||||
equivalent.
|
||||
|
||||
The payment representation isn't perfect: basically, Spree is oriented towards creditcards and some
|
||||
work is needed to generalise it to other options. For now, it is a bit hacked. (See the TODO list.)
|
||||
|
||||
|
||||
## Relationship with active merchant
|
||||
|
||||
This ext contains three files which are updates or extensions to current active merchant code. They are
|
||||
loaded up when the extension is initialized, and will over-ride the existing gem files. The modifications
|
||||
update the base protocol, eg allowing detailed order info to be passed, and supporting some of the new
|
||||
options in version 57.0.
|
||||
|
||||
## Testing
|
||||
|
||||
Get an account for Paypal's Sandbox system first. Very good testing system!
|
||||
Pity it logs you off automatically after a relatively short time period
|
||||
|
||||
|
||||
## Status and Known issues
|
||||
|
||||
IMPORTANT: requires edge rails (it might work with 0.8.4)
|
||||
|
||||
[06Jul09] I don't know of any serious bugs or issues at present in this code, so you should be able to
|
||||
start using this without serious problems - but do note the TODO list below.
|
||||
|
||||
** Temporarily, I've had to over-ride two admin views: order/show and payments/index: this will be unpicked
|
||||
once Spree is generalised to support payment types other than creditcards
|
||||
|
||||
WARNING: there seems to be an issue with the :shipping_discount issue which causes submitted order
|
||||
info to be ignored (and not displayed) - see +lib/spree/paypal_express.rb+ for more info, so I suggest
|
||||
avoiding this option unless you've tested it.
|
||||
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
0. Allow easy change of locale for gateway version
|
||||
|
||||
1. Move gateway config to the preferences system, to avoid interference with main gateways?
|
||||
|
||||
2. Add support for accepting PPX payment at the credit card stage (important)
|
||||
|
||||
3. Look at using PPX to assist in shipping method choices (or present user with a choice before
|
||||
they jump to PPX interaction)
|
||||
|
||||
4. Improve payment tracking support in Spree (eg generalise beyond creditcard bias)
|
||||
|
||||
5. Add some tests
|
||||
|
||||
6. Get some of my code into active merchant
|
||||
|
||||
Description goes here
|
|
@ -0,0 +1,52 @@
|
|||
class Admin::PaypalPaymentsController < Admin::BaseController
|
||||
before_filter :load_data
|
||||
before_filter :load_amount, :except => :country_changed
|
||||
resource_controller
|
||||
belongs_to :order
|
||||
ssl_required
|
||||
|
||||
update do
|
||||
wants.html { redirect_to edit_object_url }
|
||||
end
|
||||
|
||||
def country_changed
|
||||
end
|
||||
|
||||
include Spree::PaypalExpress::Gateway
|
||||
|
||||
def capture
|
||||
if !@order.paypal_payments.empty? && (payment = @order.paypal_payments.last).can_capture?
|
||||
|
||||
do_capture(payment.find_authorization)
|
||||
|
||||
flash[:notice] = t("paypal_capture_complete")
|
||||
else
|
||||
flash[:error] = t("unable_to_capture_paypal")
|
||||
end
|
||||
redirect_to edit_object_url
|
||||
end
|
||||
|
||||
private
|
||||
def load_data
|
||||
load_object
|
||||
@selected_country_id = params[:payment_presenter][:address_country_id].to_i if params.has_key?('payment_presenter')
|
||||
@selected_country_id ||= @order.bill_address.country_id if @order and @order.bill_address
|
||||
@selected_country_id ||= Spree::Config[:default_country_id]
|
||||
|
||||
@states = State.find_all_by_country_id(@selected_country_id, :order => 'name')
|
||||
@countries = Country.find(:all)
|
||||
end
|
||||
|
||||
# what for?
|
||||
def load_amount
|
||||
@amount = params[:amount] || @order.total
|
||||
end
|
||||
|
||||
def build_object
|
||||
@object ||= end_of_association_chain.send parent? ? :build : :new, object_params
|
||||
# not relevant?
|
||||
# @object.creditcard = Creditcard.new(:address => @object.order.bill_address.clone) unless @object.creditcard
|
||||
@object
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,71 @@
|
|||
<div class='toolbar order-links'>
|
||||
<%= button_link_to t("resend"), resend_admin_order_url(@order), :method => :post, :icon => 'send-email' %>
|
||||
<%= event_links %>
|
||||
</div>
|
||||
|
||||
<%= render :partial => 'admin/shared/order_tabs', :locals => {:current => "Order Details"} %>
|
||||
|
||||
<%= render :partial => 'admin/shared/order_details', :locals => {:order => @order} -%>
|
||||
|
||||
<% unless @order.payments.empty? %>
|
||||
<div class='adr'>
|
||||
<%# look at the most recent (= up to date) payment %>
|
||||
<% payment = @order.payments.last %>
|
||||
<% if payment.class == "CreditcardPayment" %>
|
||||
<h4><%= link_to t("bill_address"), edit_admin_order_creditcard_payment_url(@order, payment) %></h4>
|
||||
<%= render :partial => 'admin/shared/address', :locals => {:address => @order.bill_address} %>
|
||||
<% else %>
|
||||
<% url = edit_admin_order_paypal_payment_url(@order) %> <%#, payment) %>
|
||||
<h4><%= link_to t("edit_paypal_info"), edit_admin_order_paypal_payment_url(@order, payment) %></h4>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @order.ship_address %>
|
||||
<div class='adr'>
|
||||
<h4><%= link_to t("ship_address"), edit_admin_order_shipment_url(@order, @order.shipments.last) %></h4>
|
||||
<%= render :partial => 'admin/shared/address', :locals => {:address => @order.ship_address} %>
|
||||
</div>
|
||||
<% end %>
|
||||
<hr />
|
||||
|
||||
<table class="index">
|
||||
<tr>
|
||||
<th><%= t("email") %></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%= @order.email %></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<% unless @order.special_instructions.blank? %>
|
||||
<table class="index">
|
||||
<tr>
|
||||
<th><%= t("shipping_instructions") %></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><pre><%= @order.special_instructions %></pre></td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end %>
|
||||
|
||||
<h4><%= t('history') %></h4>
|
||||
|
||||
<table class="index">
|
||||
<tr>
|
||||
<th><%= t("event") %></th>
|
||||
<th><%= t("user") %></th>
|
||||
<th><%= "#{t('spree.date')}/#{t('spree.time')}" %></th>
|
||||
</tr>
|
||||
<% @order.state_events.sort.each do |event| %>
|
||||
<tr>
|
||||
<td><%=t("#{event.name}") %></td>
|
||||
<td><%=event.user.email if event.user %></td>
|
||||
<td><%=event.created_at.to_s(:date_time24) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if @order.state_events.empty? %>
|
||||
<tr>
|
||||
<td colspan="3"><%= t("none_available") %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
|
@ -0,0 +1,31 @@
|
|||
<div class='toolbar'>
|
||||
<ul class='actions'>
|
||||
<li>
|
||||
<%= button_link_to t("new_credit_card_payment"), new_admin_order_creditcard_payment_url(@order), :icon => 'add' %>
|
||||
</li>
|
||||
</ul>
|
||||
<br class='clear' />
|
||||
</div>
|
||||
|
||||
<%= render :partial => 'admin/shared/order_tabs', :locals => {:current => "Payments"} %>
|
||||
|
||||
<table class="index">
|
||||
<tr>
|
||||
<th><%= "#{t('spree.date')}/#{t('spree.time')}" %></th>
|
||||
<th><%= t("amount") %></th>
|
||||
<th><%= t("type") %></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<% @payments.each do |payment| %>
|
||||
<tr>
|
||||
<td><%= payment.created_at.to_s(:date_time24) %></td>
|
||||
<td><%= number_to_currency(payment.amount) %></td>
|
||||
<td><%= payment.class.to_s %></td>
|
||||
<!-- TODO: don't assume credit card, make it possible to edit other kinds of payments -->
|
||||
<td>
|
||||
<% url = payment.type == "CreditcardPayment" ? edit_admin_order_creditcard_payment_url(@order, payment) : edit_admin_order_paypal_payment_url(@order, payment) %>
|
||||
<%= link_to_with_icon 'edit', t('edit'), url %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
|
@ -0,0 +1,33 @@
|
|||
<%= render :partial => 'admin/shared/order_tabs', :locals => {:current => "Payments"} %>
|
||||
<br/>
|
||||
<h2><%= t("paypal_payment")%></h2>
|
||||
<br/>
|
||||
<b><%= t("paypal_txn_id")%>: </b> #<%= @paypal_payment.creditcard.display_number %><br/>
|
||||
<br/>
|
||||
<%=error_messages_for :paypal_payment %>
|
||||
|
||||
<% form_for(@paypal_payment, :url => object_url, :html => { :method => :put}) do |payment_form| %>
|
||||
<table class="index">
|
||||
<tr>
|
||||
<th><%= t("transaction") %></th>
|
||||
<th><%= t("amount") %></th>
|
||||
<th><%= t("response_code") %></th>
|
||||
<th><%= "#{t('spree.date')}/#{t('spree.time')}" %></th>
|
||||
</tr>
|
||||
<% @paypal_payment.txns.each do |t| %>
|
||||
<tr>
|
||||
<td><%=CreditcardTxn::TxnType.from_value t.txn_type.to_i%></td>
|
||||
<td><%=number_to_currency t.amount%></td>
|
||||
<td><%=t.response_code%></td>
|
||||
<td><%=t.created_at.to_s(:date_time24)%></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
|
||||
<p class="form-buttons">
|
||||
<%= button t('update') %>
|
||||
</p>
|
||||
|
||||
<% end %>
|
||||
<%= link_to t("capture").titleize, capture_admin_order_paypal_payment_url(@order, @paypal_payment), :confirm => t('are_you_sure_you_want_to_capture') if @paypal_payment.can_capture? %>
|
||||
<%= link_to t("list"), collection_url %>
|
|
@ -0,0 +1,15 @@
|
|||
<%= render :partial => 'admin/shared/order_tabs', :locals => {:current => "Payments"} %>
|
||||
|
||||
<h2><%= t("new_credit_card_payment")%></h2>
|
||||
|
||||
<%=error_messages_for :creditcard_payment %>
|
||||
<% form_for @creditcard_payment, :url => collection_url do |payment_form| %>
|
||||
<h4><%= t("billing_address")%></h4>
|
||||
<% payment_form.fields_for :creditcard do |creditcard_form| %>
|
||||
<%= render :partial => 'admin/shared/form_address', :locals => {:f => creditcard_form} %>
|
||||
<% end %>
|
||||
<p class="form-buttons">
|
||||
<%= button t('continue') %>
|
||||
<%= t("or") %> <%= link_to t("actions.cancel"), admin_order_payments_url(@order) %>
|
||||
</p>
|
||||
<% end %>
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
Results from a capture - how much do I need to keep? check...
|
||||
|
||||
---
|
||||
:tax_amount: "37.50"
|
||||
:gross_amount: "249.00"
|
||||
:payment_status: Pending
|
||||
:gross_amount_currency_id: GBP
|
||||
:authorization_id: 6WF137128R766191U
|
||||
:pending_reason: payment-review
|
||||
:receipt_id:
|
||||
:transaction_id: 45D337153N9936001
|
||||
:build: "962735"
|
||||
:fee_amount: "8.67"
|
||||
:reason_code: none
|
||||
:correlation_id: cee300cb3a234
|
||||
:fee_amount_currency_id: GBP
|
||||
:transaction_type: express-checkout
|
||||
:ack: Success
|
||||
:timestamp: "2009-07-06T10:36:35Z"
|
||||
:protection_eligibility: Ineligible
|
||||
:parent_transaction_id: 6WF137128R766191U
|
||||
:tax_amount_currency_id: GBP
|
||||
:version: "57.0"
|
||||
:payment_type: instant
|
||||
:exchange_rate:
|
||||
:payment_date: "2009-07-06T10:36:34Z"
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
en-GB:
|
||||
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.
|
||||
|
|
@ -2,3 +2,9 @@
|
|||
|
||||
map.resources :orders, :member => {:paypal_checkout => :any, :paypal_finish => :any}
|
||||
|
||||
map.namespace :admin do |admin|
|
||||
admin.resources :orders do |order|
|
||||
order.resources :paypal_payments, :member => {:capture => :get}, :has_many => [:paypal_payments]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,385 @@
|
|||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
# This module is included in both PaypalGateway and PaypalExpressGateway
|
||||
module PaypalCommonAPI
|
||||
def self.included(base)
|
||||
base.default_currency = 'USD'
|
||||
base.cattr_accessor :pem_file
|
||||
base.cattr_accessor :signature
|
||||
end
|
||||
|
||||
API_VERSION = '57.0' # TODO - check absolute adherence in this file, override in sub?
|
||||
|
||||
URLS = {
|
||||
:test => { :certificate => 'https://api.sandbox.paypal.com/2.0/',
|
||||
:signature => 'https://api-3t.sandbox.paypal.com/2.0/' },
|
||||
:live => { :certificate => 'https://api-aa.paypal.com/2.0/',
|
||||
:signature => 'https://api-3t.paypal.com/2.0/' }
|
||||
}
|
||||
|
||||
PAYPAL_NAMESPACE = 'urn:ebay:api:PayPalAPI'
|
||||
EBAY_NAMESPACE = 'urn:ebay:apis:eBLBaseComponents'
|
||||
|
||||
ENVELOPE_NAMESPACES = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
|
||||
'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
|
||||
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
|
||||
}
|
||||
CREDENTIALS_NAMESPACES = { 'xmlns' => PAYPAL_NAMESPACE,
|
||||
'xmlns:n1' => EBAY_NAMESPACE,
|
||||
'env:mustUnderstand' => '0'
|
||||
}
|
||||
|
||||
AUSTRALIAN_STATES = {
|
||||
'ACT' => 'Australian Capital Territory',
|
||||
'NSW' => 'New South Wales',
|
||||
'NT' => 'Northern Territory',
|
||||
'QLD' => 'Queensland',
|
||||
'SA' => 'South Australia',
|
||||
'TAS' => 'Tasmania',
|
||||
'VIC' => 'Victoria',
|
||||
'WA' => 'Western Australia'
|
||||
}
|
||||
|
||||
SUCCESS_CODES = [ 'Success', 'SuccessWithWarning' ]
|
||||
|
||||
FRAUD_REVIEW_CODE = "11610"
|
||||
|
||||
# The gateway must be configured with either your PayPal PEM file
|
||||
# or your PayPal API Signature. Only one is required.
|
||||
#
|
||||
# <tt>:pem</tt> The text of your PayPal PEM file. Note
|
||||
# this is not the path to file, but its
|
||||
# contents. If you are only using one PEM
|
||||
# file on your site you can declare it
|
||||
# globally and then you won't need to
|
||||
# include this option
|
||||
#
|
||||
# <tt>:signature</tt> The text of your PayPal signature.
|
||||
# If you are only using one API Signature
|
||||
# on your site you can declare it
|
||||
# globally and then you won't need to
|
||||
# include this option
|
||||
|
||||
def initialize(options = {})
|
||||
requires!(options, :login, :password)
|
||||
|
||||
@options = {
|
||||
:pem => pem_file,
|
||||
:signature => signature
|
||||
}.update(options)
|
||||
|
||||
if @options[:pem].blank? && @options[:signature].blank?
|
||||
raise ArgumentError, "An API Certificate or API Signature is required to make requests to PayPal"
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def test?
|
||||
@options[:test] || Base.gateway_mode == :test
|
||||
end
|
||||
|
||||
def reauthorize(money, authorization, options = {})
|
||||
commit 'DoReauthorization', build_reauthorize_request(money, authorization, options)
|
||||
end
|
||||
|
||||
def capture(money, authorization, options = {})
|
||||
commit 'DoCapture', build_capture_request(money, authorization, options)
|
||||
end
|
||||
|
||||
# Transfer money to one or more recipients.
|
||||
#
|
||||
# gateway.transfer 1000, 'bob@example.com',
|
||||
# :subject => "The money I owe you", :note => "Sorry it's so late"
|
||||
#
|
||||
# gateway.transfer [1000, 'fred@example.com'],
|
||||
# [2450, 'wilma@example.com', :note => 'You will receive another payment on 3/24'],
|
||||
# [2000, 'barney@example.com'],
|
||||
# :subject => "Your Earnings", :note => "Thanks for your business."
|
||||
#
|
||||
def transfer(*args)
|
||||
commit 'MassPay', build_mass_pay_request(*args)
|
||||
end
|
||||
|
||||
def void(authorization, options = {})
|
||||
commit 'DoVoid', build_void_request(authorization, options)
|
||||
end
|
||||
|
||||
def credit(money, identification, options = {})
|
||||
commit 'RefundTransaction', build_credit_request(money, identification, options)
|
||||
end
|
||||
|
||||
private
|
||||
def build_reauthorize_request(money, authorization, options)
|
||||
xml = Builder::XmlMarkup.new
|
||||
|
||||
xml.tag! 'DoReauthorizationReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||
xml.tag! 'DoReauthorizationRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||
xml.tag! 'n2:Version', API_VERSION
|
||||
xml.tag! 'AuthorizationID', authorization
|
||||
xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
|
||||
end
|
||||
end
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_capture_request(money, authorization, options)
|
||||
xml = Builder::XmlMarkup.new
|
||||
|
||||
xml.tag! 'DoCaptureReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||
xml.tag! 'DoCaptureRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||
xml.tag! 'n2:Version', API_VERSION
|
||||
xml.tag! 'AuthorizationID', authorization
|
||||
xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
|
||||
xml.tag! 'CompleteType', 'Complete'
|
||||
xml.tag! 'Note', options[:description]
|
||||
end
|
||||
end
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_credit_request(money, identification, options)
|
||||
xml = Builder::XmlMarkup.new
|
||||
|
||||
xml.tag! 'RefundTransactionReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||
xml.tag! 'RefundTransactionRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||
xml.tag! 'n2:Version', API_VERSION
|
||||
xml.tag! 'TransactionID', identification
|
||||
xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
|
||||
xml.tag! 'RefundType', 'Partial'
|
||||
xml.tag! 'Memo', options[:note] unless options[:note].blank?
|
||||
end
|
||||
end
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_void_request(authorization, options)
|
||||
xml = Builder::XmlMarkup.new
|
||||
|
||||
xml.tag! 'DoVoidReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||
xml.tag! 'DoVoidRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||
xml.tag! 'n2:Version', API_VERSION
|
||||
xml.tag! 'AuthorizationID', authorization
|
||||
xml.tag! 'Note', options[:description]
|
||||
end
|
||||
end
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_mass_pay_request(*args)
|
||||
default_options = args.last.is_a?(Hash) ? args.pop : {}
|
||||
recipients = args.first.is_a?(Array) ? args : [args]
|
||||
|
||||
xml = Builder::XmlMarkup.new
|
||||
|
||||
xml.tag! 'MassPayReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||
xml.tag! 'MassPayRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||
xml.tag! 'n2:Version', API_VERSION
|
||||
xml.tag! 'EmailSubject', default_options[:subject] if default_options[:subject]
|
||||
recipients.each do |money, recipient, options|
|
||||
options ||= default_options
|
||||
xml.tag! 'MassPayItem' do
|
||||
xml.tag! 'ReceiverEmail', recipient
|
||||
xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
|
||||
xml.tag! 'Note', options[:note] if options[:note]
|
||||
xml.tag! 'UniqueId', options[:unique_id] if options[:unique_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def parse(action, xml)
|
||||
response = {}
|
||||
|
||||
error_messages = []
|
||||
error_codes = []
|
||||
|
||||
xml = REXML::Document.new(xml)
|
||||
if root = REXML::XPath.first(xml, "//#{action}Response")
|
||||
root.elements.each do |node|
|
||||
case node.name
|
||||
when 'Errors'
|
||||
short_message = nil
|
||||
long_message = nil
|
||||
|
||||
node.elements.each do |child|
|
||||
case child.name
|
||||
when "LongMessage"
|
||||
long_message = child.text unless child.text.blank?
|
||||
when "ShortMessage"
|
||||
short_message = child.text unless child.text.blank?
|
||||
when "ErrorCode"
|
||||
error_codes << child.text unless child.text.blank?
|
||||
end
|
||||
end
|
||||
|
||||
if message = long_message || short_message
|
||||
error_messages << message
|
||||
end
|
||||
else
|
||||
parse_element(response, node)
|
||||
end
|
||||
end
|
||||
response[:message] = error_messages.uniq.join(". ") unless error_messages.empty?
|
||||
response[:error_codes] = error_codes.uniq.join(",") unless error_codes.empty?
|
||||
elsif root = REXML::XPath.first(xml, "//SOAP-ENV:Fault")
|
||||
parse_element(response, root)
|
||||
response[:message] = "#{response[:faultcode]}: #{response[:faultstring]} - #{response[:detail]}"
|
||||
end
|
||||
|
||||
response
|
||||
end
|
||||
|
||||
def parse_element(response, node)
|
||||
if node.has_elements?
|
||||
node.elements.each{|e| parse_element(response, e) }
|
||||
else
|
||||
response[node.name.underscore.to_sym] = node.text
|
||||
node.attributes.each do |k, v|
|
||||
response["#{node.name.underscore}_#{k.underscore}".to_sym] = v if k == 'currencyID'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_request(body)
|
||||
xml = Builder::XmlMarkup.new
|
||||
|
||||
xml.instruct!
|
||||
xml.tag! 'env:Envelope', ENVELOPE_NAMESPACES do
|
||||
xml.tag! 'env:Header' do
|
||||
add_credentials(xml)
|
||||
end
|
||||
|
||||
xml.tag! 'env:Body' do
|
||||
xml << body
|
||||
end
|
||||
end
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def add_credentials(xml)
|
||||
xml.tag! 'RequesterCredentials', CREDENTIALS_NAMESPACES do
|
||||
xml.tag! 'n1:Credentials' do
|
||||
xml.tag! 'Username', @options[:login]
|
||||
xml.tag! 'Password', @options[:password]
|
||||
xml.tag! 'Subject', @options[:subject]
|
||||
xml.tag! 'Signature', @options[:signature] unless @options[:signature].blank?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_address(xml, element, address)
|
||||
return if address.nil?
|
||||
xml.tag! element do
|
||||
xml.tag! 'n2:Name', address[:name]
|
||||
xml.tag! 'n2:Street1', address[:address1]
|
||||
xml.tag! 'n2:Street2', address[:address2]
|
||||
xml.tag! 'n2:CityName', address[:city]
|
||||
xml.tag! 'n2:StateOrProvince', address[:state].blank? ? 'N/A' : address[:state]
|
||||
xml.tag! 'n2:Country', address[:country]
|
||||
xml.tag! 'n2:PostalCode', address[:zip]
|
||||
xml.tag! 'n2:Phone', address[:phone]
|
||||
end
|
||||
end
|
||||
|
||||
def add_payment_detail_item(xml, item)
|
||||
currency_code = options[:currency] || currency(item[:amount])
|
||||
xml.tag! 'n2:PaymentDetailItem' do
|
||||
xml.tag! 'n2:Name', item[:name] unless item[:name].blank?
|
||||
xml.tag! 'n2:Description', item[:description] unless item[:description].blank?
|
||||
xml.tag! 'n2:Number', item[:sku] unless item[:sku].blank?
|
||||
xml.tag! 'n2:Quantity', item[:qty] unless item[:qty].blank?
|
||||
xml.tag! 'n2:Amount', amount(item[:amount]), 'currencyID' => currency_code unless item[:amount].blank?
|
||||
xml.tag! 'n2:Tax', amount(item[:tax]), 'currencyID' => currency_code unless item[:tax].blank?
|
||||
xml.tag! 'n2:ItemWeight', item[:weight] unless item[:weight].blank?
|
||||
xml.tag! 'n2:ItemHeight', item[:height] unless item[:height].blank?
|
||||
xml.tag! 'n2:ItemWidth', item[:width] unless item[:width].blank?
|
||||
xml.tag! 'n2:ItemLength', item[:length] unless item[:length].blank?
|
||||
# not doing this yet TODO
|
||||
# xml.tag! 'n2:EbayItemPaymentDetailsItem', item[:name]
|
||||
end
|
||||
end
|
||||
|
||||
def add_payment_details(xml, money, options)
|
||||
currency_code = options[:currency] || currency(money)
|
||||
# COULD USE options[:currency] || currency(options[:actual_opt])
|
||||
|
||||
xml.tag! 'n2:PaymentDetails' do
|
||||
xml.tag! 'n2:OrderTotal', amount(money), 'currencyID' => currency_code
|
||||
|
||||
# All of the values must be included together and add up to the order total
|
||||
if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) }
|
||||
xml.tag! 'n2:ItemTotal', amount(options[:subtotal]), '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:TaxTotal', amount(options[:tax]), 'currencyID' => currency_code
|
||||
end
|
||||
|
||||
# don't enforce inclusion yet - see how it works
|
||||
xml.tag! 'n2:InsuranceOptionOffered', options[:insurance_offered] ? '1' : '0'
|
||||
xml.tag! 'n2:InsuranceTotal', amount(options[:insurance]), 'currencyID' => currency_code unless options[:insurance].blank?
|
||||
xml.tag! 'n2:ShippingDiscount', amount(options[:ship_discount]), 'currencyID' => currency_code unless options[:ship_discount].blank?
|
||||
|
||||
|
||||
# query - use slices too? or just risk reject? (QQ: injection risk???)
|
||||
xml.tag! 'n2:OrderDescription', options[:description] unless options[:description].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:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
|
||||
xml.tag! 'n2:NotifyURL', options[:notify_url] unless options[:notify_url].blank?
|
||||
add_address(xml, 'n2:ShipToAddress', options[:shipping_address] || options[:address])
|
||||
options[:items].each {|i| add_payment_detail_item xml, i } if options[:items]
|
||||
end
|
||||
end
|
||||
|
||||
def endpoint_url
|
||||
URLS[test? ? :test : :live][@options[:signature].blank? ? :certificate : :signature]
|
||||
end
|
||||
|
||||
def commit(action, request)
|
||||
response = parse(action, ssl_post(endpoint_url, build_request(request)))
|
||||
|
||||
File.open("/tmp/paypal", "a") do |f|
|
||||
f.puts "\n\n\n ************** #{Time.now}\n"
|
||||
f.puts endpoint_url.inspect
|
||||
f.puts "\n\n"
|
||||
f.puts request.to_yaml
|
||||
f.puts "\n\n"
|
||||
f.puts response.to_yaml
|
||||
f.puts "\n\n"
|
||||
end
|
||||
|
||||
build_response(successful?(response), message_from(response), response,
|
||||
:test => test?,
|
||||
:authorization => authorization_from(response),
|
||||
:fraud_review => fraud_review?(response),
|
||||
:avs_result => { :code => response[:avs_code] },
|
||||
:cvv_result => response[:cvv2_code]
|
||||
)
|
||||
end
|
||||
|
||||
def fraud_review?(response)
|
||||
response[:error_codes] == FRAUD_REVIEW_CODE
|
||||
end
|
||||
|
||||
def authorization_from(response)
|
||||
response[:transaction_id] || response[:authorization_id] || response[:refund_transaction_id] # middle one is from reauthorization
|
||||
end
|
||||
|
||||
def successful?(response)
|
||||
SUCCESS_CODES.include?(response[:ack])
|
||||
end
|
||||
|
||||
def message_from(response)
|
||||
response[:message] || response[:ack]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,129 @@
|
|||
#require File.dirname(__FILE__) + '/paypal/paypal_common_api'
|
||||
#require File.dirname(__FILE__) + '/paypal/paypal_express_response'
|
||||
#require File.dirname(__FILE__) + '/paypal_express_common'
|
||||
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
class PaypalExpressGateway < Gateway
|
||||
include PaypalCommonAPI
|
||||
include PaypalExpressCommon
|
||||
|
||||
self.test_redirect_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token='
|
||||
self.supported_countries = ['US']
|
||||
self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside'
|
||||
self.display_name = 'PayPal Express Checkout'
|
||||
|
||||
def setup_authorization(money, options = {})
|
||||
requires!(options, :return_url, :cancel_return_url)
|
||||
|
||||
commit 'SetExpressCheckout', build_setup_request('Authorization', money, options)
|
||||
end
|
||||
|
||||
def setup_purchase(money, options = {})
|
||||
requires!(options, :return_url, :cancel_return_url)
|
||||
|
||||
commit 'SetExpressCheckout', build_setup_request('Sale', money, options)
|
||||
end
|
||||
|
||||
def details_for(token)
|
||||
commit 'GetExpressCheckoutDetails', build_get_details_request(token)
|
||||
end
|
||||
|
||||
def authorize(money, options = {})
|
||||
requires!(options, :token, :payer_id)
|
||||
|
||||
commit 'DoExpressCheckoutPayment', build_sale_or_authorization_request('Authorization', money, options)
|
||||
end
|
||||
|
||||
def purchase(money, options = {})
|
||||
requires!(options, :token, :payer_id)
|
||||
|
||||
commit 'DoExpressCheckoutPayment', build_sale_or_authorization_request('Sale', money, options)
|
||||
end
|
||||
|
||||
private
|
||||
def build_get_details_request(token)
|
||||
xml = Builder::XmlMarkup.new :indent => 2
|
||||
xml.tag! 'GetExpressCheckoutDetailsReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||
xml.tag! 'GetExpressCheckoutDetailsRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||
xml.tag! 'n2:Version', API_VERSION
|
||||
xml.tag! 'Token', token
|
||||
end
|
||||
end
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_sale_or_authorization_request(action, money, options)
|
||||
currency_code = options[:currency] || currency(money)
|
||||
|
||||
xml = Builder::XmlMarkup.new :indent => 2
|
||||
xml.tag! 'DoExpressCheckoutPaymentReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||
xml.tag! 'DoExpressCheckoutPaymentRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||
xml.tag! 'n2:Version', API_VERSION
|
||||
xml.tag! 'n2:DoExpressCheckoutPaymentRequestDetails' do
|
||||
xml.tag! 'n2:PaymentAction', action
|
||||
xml.tag! 'n2:Token', options[:token]
|
||||
xml.tag! 'n2:PayerID', options[:payer_id]
|
||||
add_payment_details(xml, money, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_setup_request(action, money, options)
|
||||
xml = Builder::XmlMarkup.new :indent => 2
|
||||
xml.tag! 'SetExpressCheckoutReq', 'xmlns' => PAYPAL_NAMESPACE do
|
||||
xml.tag! 'SetExpressCheckoutRequest', 'xmlns:n2' => EBAY_NAMESPACE do
|
||||
xml.tag! 'n2:Version', API_VERSION
|
||||
xml.tag! 'n2:SetExpressCheckoutRequestDetails' do
|
||||
if options[:max_amount]
|
||||
xml.tag! 'n2:MaxAmount', amount(options[:max_amount]), 'currencyID' => options[:currency]
|
||||
end
|
||||
xml.tag! 'n2:ReturnURL', options[: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: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:NoShipping', options[:no_shipping] ? '1' : '0'
|
||||
# NOT INCLUDED IN SETUP -- GRAB ELSEWHERE? -- xml.tag! 'n2:IPAddress', options[:ip]
|
||||
xml.tag! 'n2:AllowNote', options[:allow_note] ? '1' : '0'
|
||||
xml.tag! 'n2:AddressOverride', options[:address_override] ? '1' : '0' # force yours
|
||||
xml.tag! 'n2:LocaleCode', options[:locale] unless options[:locale].blank?
|
||||
|
||||
# Customization of the payment page
|
||||
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-border-color', options[:header_border_color] unless options[:header_border_color].blank?
|
||||
xml.tag! 'n2:cpp-header-back-color', options[:header_background_color] unless options[:header_background_color].blank?
|
||||
xml.tag! 'n2:cpp-payflow-color', options[:background_color] unless options[:background_color].blank?
|
||||
|
||||
xml.tag! 'n2:PaymentAction', action
|
||||
xml.tag! 'n2:BuyerEmail', options[:email] unless options[:email].blank?
|
||||
xml.tag! 'n2:SolutionType', options[:solution_type] unless options[:solution_type].blank?
|
||||
xml.tag! 'n2:LandingPage', options[:landing_page] unless options[:landing_page].blank?
|
||||
xml.tag! 'n2:ChannelType', options[:channel_type] unless options[:channel_type].blank?
|
||||
|
||||
# only needed for certain methods in Germany
|
||||
xml.tag! 'n2:giropaySuccessURL', options[:giropay_url] unless options[:giropay_url].blank?
|
||||
xml.tag! 'n2:giropayCancelURL', options[:giropay_cancel_url] unless options[:giropay_cancel_url].blank?
|
||||
xml.tag! 'n2:BanktxnPendingURL', options[:banktxn_url] unless options[:banktxn_url].blank?
|
||||
|
||||
# for order values etc, and item info
|
||||
add_payment_details(xml, money, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_response(success, message, response, options = {})
|
||||
PaypalExpressResponse.new(success, message, response, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
require File.dirname(__FILE__) + '/paypal_express'
|
||||
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
class PaypalExpressUkGateway < PaypalExpressGateway
|
||||
self.default_currency = 'GBP'
|
||||
|
||||
self.supported_countries = ['GB']
|
||||
self.homepage_url = 'https://www.paypal.com/uk/cgi-bin/webscr?cmd=_additional-payment-overview-outside'
|
||||
self.display_name = 'PayPal Express Checkout (UK)'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
# Adapted for protx3ds
|
||||
# WARNING: the details of UK tax and my site's shipping are a bit hard-coded here for now
|
||||
# aim to unpick this later
|
||||
module Spree::PaypalExpress
|
||||
include ERB::Util
|
||||
include Spree::PaymentGateway
|
||||
include Spree::PaypalExpress::Gateway
|
||||
|
||||
def fixed_opts
|
||||
{ :description => "Parasols or related outdoor items", # site details...
|
||||
{ :description => "Goods from a Spree-based site", # site details...
|
||||
|
||||
#:page_style => "foobar", # merchant account can set default
|
||||
#:page_style => "foobar", # merchant account can set named config
|
||||
:header_image => "https://" + Spree::Config[:site_url] + "/images/logo.png",
|
||||
:background_color => "e1e1e1", # must be hex only, six chars
|
||||
:header_background_color => "ffffff",
|
||||
|
@ -14,12 +16,14 @@ module Spree::PaypalExpress
|
|||
|
||||
:allow_note => true,
|
||||
:locale => Spree::Config[:default_locale],
|
||||
:notify_url => 'to be done',
|
||||
:notify_url => 'to be done', # this is a callback
|
||||
|
||||
:req_confirm_shipping => false, # for security, might make an option later
|
||||
}
|
||||
end
|
||||
|
||||
# TODO: generalise the tax and shipping calcs
|
||||
# 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|
|
||||
{ :name => item.variant.product.name,
|
||||
|
@ -34,20 +38,16 @@ module Spree::PaypalExpress
|
|||
:depth => item.variant.weight }
|
||||
end
|
||||
|
||||
site = "localhost:3000"
|
||||
site = Spree::Config[:site_url]
|
||||
|
||||
opts = { :return_url => "https://" + site + "/orders/#{order.number}/paypal_finish",
|
||||
:cancel_return_url => "http://" + site + "/orders/#{order.number}/edit",
|
||||
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 + '--' + order.number,
|
||||
:custom => order.number,
|
||||
|
||||
# :no_shipping => false,
|
||||
# :address_override => false,
|
||||
|
||||
:items => items,
|
||||
:subtotal => items.map {|i| i[:amount] * i[:qty] }.sum,
|
||||
:shipping => NetstoresShipping::Calculator.calculate_order_shipping(order), # NEED HIDE
|
||||
:handling => 0,
|
||||
:tax => items.map {|i| i[:tax] * i[:qty]}.sum
|
||||
|
||||
|
@ -55,6 +55,16 @@ module Spree::PaypalExpress
|
|||
# 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
|
||||
}
|
||||
|
||||
opts[:email] = current_user.email if current_user
|
||||
|
||||
opts
|
||||
end
|
||||
|
||||
def all_opts(order)
|
||||
shipping_cost = NetstoresShipping::Calculator.calculate_order_shipping(order)
|
||||
opts = fixed_opts.merge(:shipping => shipping_cost).merge(order_opts order)
|
||||
|
||||
# WARNING: paypal expects this sum to work (TODO: shift to AM code? and throw wobbly?)
|
||||
# however: might be rounding issues when it comes to tax, though you can capture slightly extra
|
||||
opts[:money] = opts.slice(:subtotal, :shipping, :handling, :tax).values.sum
|
||||
|
@ -64,23 +74,20 @@ module Spree::PaypalExpress
|
|||
|
||||
[:money, :subtotal, :shipping, :handling, :tax].each {|amt| opts[amt] *= 100}
|
||||
opts[:items].each {|item| [:amount,:tax].each {|amt| item[amt] *= 100} }
|
||||
opts[:email] = current_user.email if current_user
|
||||
|
||||
opts
|
||||
end
|
||||
|
||||
def all_opts(order)
|
||||
fixed_opts.merge(order_opts order)
|
||||
end
|
||||
|
||||
def paypal_checkout
|
||||
# need build etc? at least to finalise the total?
|
||||
gateway = paypal_gateway
|
||||
|
||||
opts = all_opts(@order)
|
||||
out2 = gateway.setup_authorization(opts[:money], opts)
|
||||
response = gateway.setup_authorization(opts[:money], opts)
|
||||
|
||||
redirect_to (gateway.redirect_url_for out2.token)
|
||||
gateway_error(response) unless response.success?
|
||||
|
||||
redirect_to (gateway.redirect_url_for response.token)
|
||||
end
|
||||
|
||||
def paypal_finish
|
||||
|
@ -90,19 +97,15 @@ module Spree::PaypalExpress
|
|||
info = gateway.details_for params[:token]
|
||||
response = gateway.authorize(opts[:money], opts)
|
||||
|
||||
# unless gateway.successful? response
|
||||
unless [ 'Success', 'SuccessWithWarning' ].include?(response.params["ack"]) ## HACKY
|
||||
# TMP render :text => "<pre>" + response.params.inspect + "\n\n\n" + params.to_yaml + "\n\n\n" + response.to_yaml + "\n\n\n" + info.to_yaml + "</pre>" and return
|
||||
# OFF FOR TESTING : gateway_error(response)
|
||||
end
|
||||
gateway_error(response) unless response.success?
|
||||
|
||||
# now save info
|
||||
order = Order.find_by_number(params[:id])
|
||||
order.email = info.email
|
||||
order.special_instructions = info.params["note"]
|
||||
order.checkout.email = info.email
|
||||
order.checkout.special_instructions = info.params["note"]
|
||||
|
||||
ship_address = info.address
|
||||
order.ship_address = Address.create :firstname => info.params["first_name"],
|
||||
order_ship_address = Address.create :firstname => info.params["first_name"],
|
||||
:lastname => info.params["last_name"],
|
||||
:address1 => ship_address["address1"],
|
||||
:address2 => ship_address["address2"],
|
||||
|
@ -111,11 +114,11 @@ module Spree::PaypalExpress
|
|||
:country => Country.find_by_iso(ship_address["country"]),
|
||||
:zipcode => ship_address["zip"],
|
||||
:phone => ship_address["phone"] || "(not given)"
|
||||
shipment = Shipment.create :address => order.ship_address,
|
||||
:shipping_method => ShippingMethod.first # TODO: refine/choose
|
||||
order.shipments << shipment
|
||||
|
||||
fake_card = Creditcard.new :order => order,
|
||||
order.checkout.update_attributes :ship_address => order_ship_address,
|
||||
:shipping_method => ShippingMethod.first # TODO: refine/choose
|
||||
|
||||
fake_card = Creditcard.new :checkout => order.checkout,
|
||||
:cc_type => "visa", # hands are tied
|
||||
:month => Time.now.month,
|
||||
:year => Time.now.year,
|
||||
|
@ -138,6 +141,22 @@ module Spree::PaypalExpress
|
|||
redirect_to order_url(order, :checkout_complete => true, :order_token => session[:order_token])
|
||||
end
|
||||
|
||||
|
||||
def do_capture(authorization)
|
||||
response = paypal_gateway.capture((100 * authorization.amount).to_i, authorization.response_code)
|
||||
|
||||
gateway_error(response) unless response.success?
|
||||
|
||||
# TODO needs to be cleaned up or recast...
|
||||
payment = PaypalPayment.find(authorization.creditcard_payment_id)
|
||||
|
||||
# create a transaction to reflect the capture
|
||||
payment.txns << CreditcardTxn.new( :amount => authorization.amount,
|
||||
:response_code => response.authorization,
|
||||
:txn_type => CreditcardTxn::TxnType::CAPTURE )
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# copied from main spree code, and slightly tweaked
|
||||
|
|
|
@ -14,9 +14,21 @@ class PaypalExpressExtension < Spree::Extension
|
|||
|
||||
def activate
|
||||
# admin.tabs.add "Paypal Express", "/admin/paypal_express", :after => "Layouts", :visibility => [:all]
|
||||
|
||||
# Load up over-rides for ActiveMerchant files
|
||||
# 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_express_uk.rb")
|
||||
require File.join(PaypalExpressExtension.root, "lib", "active_merchant", "billing", "gateways", "paypal_express_uk.rb")
|
||||
|
||||
|
||||
# inject paypal code into orders controller
|
||||
OrdersController.class_eval do
|
||||
ssl_required :paypal_checkout, :paypal_finish
|
||||
include Spree::PaypalExpress
|
||||
end
|
||||
|
||||
# probably not needed once the payments mech is generalised
|
||||
Order.class_eval do
|
||||
has_many :paypal_payments
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue