diff --git a/lib/active_merchant/billing/integrations/payflow_link.rb b/lib/active_merchant/billing/integrations/payflow_link.rb new file mode 100644 index 00000000000..875eac688fb --- /dev/null +++ b/lib/active_merchant/billing/integrations/payflow_link.rb @@ -0,0 +1,21 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayflowLink + autoload :Helper, 'active_merchant/billing/integrations/payflow_link/helper.rb' + autoload :Notification, 'active_merchant/billing/integrations/payflow_link/notification.rb' + + mattr_accessor :service_url + self.service_url = 'https://payflowlink.paypal.com' + + def self.notification(post, options = {}) + Notification.new(post) + end + + def self.return(query_string, options = {}) + ActiveMerchant::Billing::Integrations::Return.new(query_string) + end + end + end + end +end diff --git a/lib/active_merchant/billing/integrations/payflow_link/helper.rb b/lib/active_merchant/billing/integrations/payflow_link/helper.rb new file mode 100644 index 00000000000..f02173cda15 --- /dev/null +++ b/lib/active_merchant/billing/integrations/payflow_link/helper.rb @@ -0,0 +1,58 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayflowLink + class Helper < ActiveMerchant::Billing::Integrations::Helper + + def initialize(order, account, options = {}) + super + add_field('login', account) + add_field('type', 'S') + add_field('echodata', 'True') + add_field('user2', ActiveMerchant::Billing::Base.integration_mode == :test || options[:test]) + add_field('invoice', order) + end + + mapping :amount, 'amount' + mapping :account, 'login' + mapping :credential2, 'partner' + mapping :order, 'user1' + mapping :description, 'description' + + + mapping :billing_address, :city => 'city', + :address => 'address', + :state => 'state', + :zip => 'zip', + :country => 'country', + :phone => 'phone', + :name => 'name' + + mapping :customer, :name => 'name' + + def customer(params = {}) + add_field(mappings[:customer][:name], [params.delete(:first_name), params.delete(:last_name)].compact.join(' ')) + end + + def billing_address(params = {}) + # Get the country code in the correct format + # Use what we were given if we can't find anything + country_code = lookup_country_code(params.delete(:country)) + add_field(mappings[:billing_address][:country], country_code) + + add_field(mappings[:billing_address][:address], [params.delete(:address1), params.delete(:address2)].compact.join(' ')) + + province_code = params.delete(:state) + add_field(mappings[:billing_address][:state], province_code.blank? ? 'N/A' : province_code.upcase) + + # Everything else + params.each do |k, v| + field = mappings[:billing_address][k] + add_field(field, v) unless field.nil? + end + end + end + end + end + end +end diff --git a/lib/active_merchant/billing/integrations/payflow_link/notification.rb b/lib/active_merchant/billing/integrations/payflow_link/notification.rb new file mode 100644 index 00000000000..f2879869413 --- /dev/null +++ b/lib/active_merchant/billing/integrations/payflow_link/notification.rb @@ -0,0 +1,78 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayflowLink + class Notification < ActiveMerchant::Billing::Integrations::Notification + + # Was the transaction complete? + def complete? + status == "Completed" + end + + # When was this payment received by the client. + # sometimes it can happen that we get the notification much later. + # One possible scenario is that our web application was down. In this case paypal tries several + # times an hour to inform us about the notification + def received_at + DateTime.parse(params['TRANSTIME']) if params['TRANSTIME'] + rescue ArgumentError + nil + end + + def status + params['RESPMSG'] + end + + # Id of this transaction (paypal number) + def transaction_id + params['PNREF'] + end + + # What type of transaction are we dealing with? + def type + params['TYPE'] + end + + # the money amount we received in X.2 decimal. + def gross + params['AMT'] + end + + # What currency have we been dealing with + def currency + nil + end + + def status + params['RESULT'] == '0' ? 'Completed' : 'Failed' + end + + # This is the item number which we submitted to paypal + def item_id + params['USER1'] + end + + # This is the invoice which you passed to paypal + def invoice + params['INVNUM'] + end + + # Was this a test transaction? + def test? + params['USER2'] == 'true' + end + + def account + params["ACCT"] + end + + def acknowledge + true + end + end + end + end + end +end diff --git a/test/unit/integrations/helpers/payflow_link_helper_test.rb b/test/unit/integrations/helpers/payflow_link_helper_test.rb new file mode 100644 index 00000000000..c7e0d718b4d --- /dev/null +++ b/test/unit/integrations/helpers/payflow_link_helper_test.rb @@ -0,0 +1,83 @@ +require 'test_helper' + +class PayflowLinkHelperTest < Test::Unit::TestCase + include ActiveMerchant::Billing::Integrations + + def setup + @helper = PayflowLink::Helper.new(1121,'loginname', :amount => 500, :currency => 'CAD', :credential2 => 'PayPal') + @url = 'http://example.com' + end + + def test_basic_helper_fields + assert_field 'login', 'loginname' + assert_field 'partner', 'PayPal' + assert_field 'amount', '500' + assert_field 'type', 'S' + assert_field 'user1', '1121' + assert_field 'invoice', '1121' + end + + def test_description + @helper.description = "my order" + assert_field 'description', 'my order' + end + + def test_name + @helper.customer :first_name => "John", :last_name => "Doe" + + assert_field 'name', 'John Doe' + end + + def test_billing_information + @helper.billing_address :country => 'CA', + :address1 => '1 My Street', + :address2 => 'APT. 2', + :city => 'Ottawa', + :state => 'ON', + :zip => '90210', + :phone => '(555)123-4567' + + assert_field 'address', '1 My Street APT. 2' + assert_field 'city', 'Ottawa' + assert_field 'state', 'ON' + assert_field 'zip', '90210' + assert_field 'country', 'CA' + assert_field 'phone', '(555)123-4567' + end + + def test_province + @helper.billing_address :country => 'CA', + :state => 'On' + + assert_field 'country', 'CA' + assert_field 'state', 'ON' + end + + def test_state + @helper.billing_address :country => 'US', + :state => 'TX' + + assert_field 'country', 'US' + assert_field 'state', 'TX' + end + + def test_country_code + @helper.billing_address :country => 'CAN' + assert_field 'country', 'CA' + end + + def test_setting_invalid_address_field + fields = @helper.fields.dup + fields["state"] = 'N/A' + + @helper.billing_address :street => 'My Street' + assert_equal fields, @helper.fields + end + + def test_uk_shipping_address_with_no_state + @helper.billing_address :country => 'GB', + :state => '' + + assert_field 'state', 'N/A' + end +end diff --git a/test/unit/integrations/notifications/payflow_link_notification_test.rb b/test/unit/integrations/notifications/payflow_link_notification_test.rb new file mode 100644 index 00000000000..463bd577025 --- /dev/null +++ b/test/unit/integrations/notifications/payflow_link_notification_test.rb @@ -0,0 +1,61 @@ +require 'test_helper' + +class PayflowLinkNotificationTest < Test::Unit::TestCase + include ActiveMerchant::Billing::Integrations + + def setup + @payflow = ActiveMerchant::Billing::Integrations::PayflowLink.notification(http_raw_data) + end + + def test_accessors + assert @payflow.complete? + assert_equal "Completed", @payflow.status + assert_equal "V24A0C03E977", @payflow.transaction_id + assert_equal "S", @payflow.type + assert_equal "21.30", @payflow.gross + assert_equal "20" , @payflow.item_id + assert_equal "1111" , @payflow.account + assert_equal "2011-08-03T10:05:41+00:00", @payflow.received_at.to_s + assert @payflow.test? + end + + def test_payment_successful_status + notification = PayflowLink::Notification.new('RESULT=0') + assert_equal 'Completed', notification.status + end + + def test_missing_transittime + notification = PayflowLink::Notification.new('') + assert_nil notification.received_at + end + + def test_invalid_transittime + notification = PayflowLink::Notification.new('TRANSTIME=magic') + assert_nil notification.received_at + end + + def test_payment_failure_status + notification = PayflowLink::Notification.new('RESULT=7') + assert_equal 'Failed', notification.status + end + + def test_respond_to_acknowledge + assert @payflow.respond_to?(:acknowledge) + end + + def test_item_id_mapping + notification = PayflowLink::Notification.new('USER1=1') + assert_equal '1', notification.item_id + end + + def test_invoice_mapping + notification = PayflowLink::Notification.new('INVNUM=1') + assert_equal '1', notification.invoice + end + + private + + def http_raw_data + "AVSZIP=Y&STATE=ON&TYPE=S&USER4=&ZIPTOSHIP=&ACCT=1111&EMAIL=&EMAILTOSHIP=&ADDRESSTOSHIP=&METHOD=CC&TRANSTIME=2011-08-03+10%3A05%3A41&USER8=&USER5=&IAVS=N&STATETOSHIP=&USER3=&PHONETOSHIP=&USER7=&TAX=&CARDTYPE=1&AVSDATA=YYY&CITYTOSHIP=&USER6=&PROCAVS=Y&INVNUM=&CITY=Ottawa&USER1=20&DESCRIPTION=Shop+One+store+purchase.+Order+1008&HOSTCODE=A&RESULT=0&USER10=&USER2=true&FAX=&PONUM=&LASTNAME=Doe&PNREF=V24A0C03E977&PHONE=6132623672&AMT=21.30&NAMETOSHIP=&ZIP=K1p4l2&AUTHCODE=026PNI&EXPDATE=0522&RESPMSG=Approved&COUNTRY=&CUSTID=&ORIGMETHOD=&FIRSTNAME=John&USER9=&FAXTOSHIP=&AVSADDR=Y&NAME=John+Doe+&COUNTRYTOSHIP=&ADDRESS=43+Somewhere+Street+" + end +end