diff --git a/.travis.yml b/.travis.yml index 0872ce675..377ed0f38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,39 @@ language: ruby script: "bundle exec rake test:units" sudo: false +cache: bundler rvm: -- "2.2" -- "2.1" -- "2.0" -- "1.9" +- 2.4.0 +- 2.3.3 +- 2.2.3 +- 2.1 gemfile: - Gemfile.rails32 - Gemfile.rails40 - Gemfile.rails41 - Gemfile.rails42 +- Gemfile.rails50 +- Gemfile.rails51 + +matrix: + exclude: + - rvm: 2.1 + gemfile: Gemfile.rails50 + - rvm: 2.1 + gemfile: Gemfile.rails51 + - rvm: 2.4.0 + gemfile: Gemfile.rails32 + - rvm: 2.4.0 + gemfile: Gemfile.rails40 + - rvm: 2.4.0 + gemfile: Gemfile.rails41 + - rvm: 2.4.0 + gemfile: Gemfile.rails42 + include: + - rvm: 2.3.3 + gemfile: Gemfile.shopify + +before_install: + - gem update bundler diff --git a/CHANGELOG.md b/CHANGELOG.md index 33cbcb4ea..1fdfbfb85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,47 @@ # Offsite Payments CHANGELOG +### Version 2.6.4 (Jan 8, 2018) +- Use multiple address fields for WorldPay instead of concatenating them [joshnuss] #270 + +### Version 2.6.3 (Nov 7, 2017) +- Fix nil coercion to BigDecimal zero in PayuIn [aman-dureja] #265 + +### Version 2.6.2 (Oct 30, 2017) +- Fix ArgumentError in PayuIn with BigDecimal v1.3.2 [aman-dureja] #264 +- Fix molpay and citrus tests [aman-dureja] #264 + +### Version 2.6.1 (Sep 7, 2017) +- Fix PayTM checksum generation [christianblais] #262 + +### Version 2.6.0 (Aug 29, 2017) +- Rails 5.1 compatibility [eitoball, patientdev] #245 +- Update paydollar URLs [SimonLeungAPHK] #228 +- Fixed compatibility between RubyMoney/money and Shopify/money gems [elfassy] #246 +- Fixed calling Mollie with extra parameters [edwinv] #247 +- Stop hiding JSON parse errors for Bitpay [bdewater] #251 +- Do not send locale parameter to Sagepay [pi3r] #258 +- Updats Skrill URL [sergey-alekseev ] #259 +- Changed PayTM integration [Mohit-Aggarwal1] #260 + +### Version 2.5.0 (April 12, 2017) +- corrected zip parameter to from zip to zipcode +- [Realex] guard against nil when extracting AVS code +- only use fields that start with `x_` to generate the signature +- bump active_utils version 3.3.0 + +### Version 2.4.0 (March 7, 2017) +- Fixed use of decimal instead of float +- Fixed use Money gem +- Fixed sanitize of the phone field for payu_in +- Added Paytm +- Updated dependency on nokogiri 1.6 + +### Version 2.3.0 (February 8, 2016) +- Release 2.3.0 + +### Version 2.2.0 (October 14, 2015) +- Bump active_utils dependency. [lucasuyezu] + ### Version 2.1.0 (January 16, 2015) - **Change:** network exceptions now use ActiveUtils instead of ActiveMerchant as namespace, diff --git a/Gemfile b/Gemfile index 854e63716..2937fecc3 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,6 @@ source 'https://rubygems.org' gemspec gem 'jruby-openssl', :platforms => :jruby -gem 'money', '~> 5.0' group :remote_test do gem 'mechanize' diff --git a/Gemfile.rails32 b/Gemfile.rails32 index 1d9063613..1b496d649 100644 --- a/Gemfile.rails32 +++ b/Gemfile.rails32 @@ -2,7 +2,6 @@ source 'https://rubygems.org' gemspec gem 'jruby-openssl', :platforms => :jruby -gem 'money', '~> 5.0' group :remote_test do gem 'mechanize' @@ -10,5 +9,4 @@ group :remote_test do gem 'mongrel', '1.2.0.pre2', :platforms => :ruby end - -gem 'rails', '~> 3.2.0' +gem 'actionpack', '~> 3.2.0' diff --git a/Gemfile.rails40 b/Gemfile.rails40 index 4dc6e9402..b68bbb259 100644 --- a/Gemfile.rails40 +++ b/Gemfile.rails40 @@ -2,7 +2,6 @@ source 'https://rubygems.org' gemspec gem 'jruby-openssl', :platforms => :jruby -gem 'money', '~> 5.0' group :remote_test do gem 'mechanize' @@ -10,5 +9,4 @@ group :remote_test do gem 'mongrel', '1.2.0.pre2', :platforms => :ruby end - -gem 'rails', '~> 4.0.0' +gem 'actionpack', '~> 4.0.0' diff --git a/Gemfile.rails41 b/Gemfile.rails41 index f33b2a4b7..a9e82bbf8 100644 --- a/Gemfile.rails41 +++ b/Gemfile.rails41 @@ -2,7 +2,6 @@ source 'https://rubygems.org' gemspec gem 'jruby-openssl', :platforms => :jruby -gem 'money', '~> 5.0' group :remote_test do gem 'mechanize' @@ -10,5 +9,4 @@ group :remote_test do gem 'mongrel', '1.2.0.pre2', :platforms => :ruby end - -gem 'rails', '~> 4.1.0' +gem 'actionpack', '~> 4.1.0' diff --git a/Gemfile.rails42 b/Gemfile.rails42 index 86c335d87..2d18a40a5 100644 --- a/Gemfile.rails42 +++ b/Gemfile.rails42 @@ -2,7 +2,6 @@ source 'https://rubygems.org' gemspec gem 'jruby-openssl', :platforms => :jruby -gem 'money', '~> 5.0' group :remote_test do gem 'mechanize' @@ -10,4 +9,4 @@ group :remote_test do gem 'mongrel', '1.2.0.pre2', :platforms => :ruby end -gem 'rails', '~> 4.2.0' +gem 'actionpack', '~> 4.2.0' diff --git a/Gemfile.rails50 b/Gemfile.rails50 new file mode 100644 index 000000000..c77643271 --- /dev/null +++ b/Gemfile.rails50 @@ -0,0 +1,12 @@ +source 'https://rubygems.org' +gemspec + +gem 'jruby-openssl', :platforms => :jruby + +group :remote_test do + gem 'mechanize' + gem 'launchy' + gem 'mongrel', '1.2.0.pre2', :platforms => :ruby +end + +gem 'actionpack', '~> 5.0.0' diff --git a/Gemfile.rails51 b/Gemfile.rails51 new file mode 100644 index 000000000..ddffe52eb --- /dev/null +++ b/Gemfile.rails51 @@ -0,0 +1,12 @@ +source 'https://rubygems.org' +gemspec + +gem 'jruby-openssl', :platforms => :jruby + +group :remote_test do + gem 'mechanize' + gem 'launchy' + gem 'mongrel', '1.2.0.pre2', :platforms => :ruby +end + +gem 'actionpack', '~> 5.1.0' diff --git a/Gemfile.shopify b/Gemfile.shopify new file mode 100644 index 000000000..363557596 --- /dev/null +++ b/Gemfile.shopify @@ -0,0 +1,13 @@ +source 'https://rubygems.org' +gemspec + +gem 'jruby-openssl', :platforms => :jruby + +group :remote_test do + gem 'mechanize' + gem 'launchy' + gem 'mongrel', '1.2.0.pre2', :platforms => :ruby +end + +gem 'actionpack', '~> 5.0.0' +gem 'money', git: 'https://github.com/Shopify/money.git' diff --git a/README.md b/README.md index bcdf2cfa5..710390258 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Offsite Payments -[![Build Status](https://travis-ci.org/activemerchant/offsite_payments.png?branch=master)](https://travis-ci.org/activemerchant/offsite_payments) -[![Code Climate](https://codeclimate.com/github/activemerchant/offsite_payments.png)](https://codeclimate.com/github/activemerchant/offsite_payments) +[![Build Status](https://travis-ci.org/activemerchant/offsite_payments.svg?branch=master)](https://travis-ci.org/activemerchant/offsite_payments) +[![Code Climate](https://codeclimate.com/github/activemerchant/offsite_payments/badges/gpa.svg)](https://codeclimate.com/github/activemerchant/offsite_payments) Offsite Payments is an extraction from the ecommerce system [Shopify](http://www.shopify.com). Shopify's requirements for a simple and unified API to handle dozens of different offsite payment pages (often called hosted payment pages) with very different exposed APIs was the chief principle in designing the library. diff --git a/bin/console b/bin/console new file mode 100755 index 000000000..3885bfa4a --- /dev/null +++ b/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "offsite_payments" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup new file mode 100755 index 000000000..dce67d860 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/generators/templates/notification_test.rb b/generators/templates/notification_test.rb index 9045d3b8c..150b67072 100644 --- a/generators/templates/notification_test.rb +++ b/generators/templates/notification_test.rb @@ -19,7 +19,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(3166, 'USD'), @<%= identifier %>.amount + assert_equal Money.from_amount(31.66, 'USD'), @<%= identifier %>.amount end # Replace with real successful acknowledgement code diff --git a/lib/offsite_payments.rb b/lib/offsite_payments.rb index 7870341f8..25b08702c 100644 --- a/lib/offsite_payments.rb +++ b/lib/offsite_payments.rb @@ -2,8 +2,8 @@ require 'cgi' require "timeout" require "socket" - -require 'active_support/core_ext/class/delegating_attributes' +require 'bigdecimal' +require 'bigdecimal/util' require 'active_utils' diff --git a/lib/offsite_payments/helper.rb b/lib/offsite_payments/helper.rb index c339019e9..fe99e189a 100644 --- a/lib/offsite_payments/helper.rb +++ b/lib/offsite_payments/helper.rb @@ -16,7 +16,7 @@ def self.inherited(subclass) end def initialize(order, account, options = {}) - options.assert_valid_keys([:amount, :currency, :test, :credential2, :credential3, :credential4, :country, :account_name, :description, :transaction_type, :authcode, :notify_url, :return_url, :redirect_param, :forward_url, :checkout_token, :deposit_flag]) + options.assert_valid_keys([:amount, :currency, :test, :credential2, :credential3, :credential4, :country, :account_name, :description, :transaction_type, :authcode, :notify_url, :return_url, :redirect_param, :forward_url, :checkout_token]) @fields = {} @raw_html_fields = [] @test = options[:test] diff --git a/lib/offsite_payments/integrations/bit_pay.rb b/lib/offsite_payments/integrations/bit_pay.rb index 11f4bcee4..f8a40de77 100644 --- a/lib/offsite_payments/integrations/bit_pay.rb +++ b/lib/offsite_payments/integrations/bit_pay.rb @@ -89,7 +89,6 @@ def transaction_id def item_id JSON.parse(params['posData'])['orderId'] - rescue JSON::ParserError end def status @@ -132,14 +131,12 @@ def acknowledge(authcode = nil) retrieved_json = JSON.parse(@raw).tap { |j| j.delete('currentTime') } posted_json == retrieved_json - rescue JSON::ParserError end private def parse(body) @raw = body @params = JSON.parse(@raw) - rescue JSON::ParserError end end diff --git a/lib/offsite_payments/integrations/checkout_finland.rb b/lib/offsite_payments/integrations/checkout_finland.rb new file mode 100644 index 000000000..e0777d511 --- /dev/null +++ b/lib/offsite_payments/integrations/checkout_finland.rb @@ -0,0 +1,166 @@ +module OffsitePayments #:nodoc: + module Integrations #:nodoc: + module CheckoutFinland + + mattr_accessor :service_url + self.service_url = 'https://payment.checkout.fi/' + + def self.notification(post) + Notification.new(post) + end + + class Helper < OffsitePayments::Helper + include ActiveUtils::PostsData + self.country_format = :alpha3 + + def initialize(order, account, options = {}) + md5secret options.delete(:credential2) + super + + # Add default fields + add_field("VERSION", "0001") # API version + add_field("ALGORITHM", "3") # Return MAC version (3 is HMAC-SHA256) + add_field("TYPE", "0") + add_field("DEVICE", "1") # Offsite payment by default + end + + def md5secret(value) + @md5secret = value + end + + # Add MAC to form fields + def form_fields + @fields.merge("MAC" => generate_md5string) + end + + # Apply parameter length limitations recommended by Checkout.fi + def add_field(name, value) + return if name.blank? || value.blank? + @fields[name.to_s] = check_param_length(name_to_s, value.to_s) + end + + # API parameter length limitations specified by Checkout.fi + # Parameters longer than this cause the HTTP POST to fail + def check_param_length(name, value) + # Soft limitations, fields that can be clipped + max_length_substring = { "FIRSTNAME" => 40, "FAMILYNAME" => 40, "ADDRESS" => 40, "POSTCODE" => 14, "POSTOFFICE" => 18, "MESSAGE" => 1000, "EMAIL" => 200, "PHONE" => 30 } + # Hard limitations, fields that cannot be clipped + max_length_exception = { "RETURN" => 300, "CANCEL" => 300, "REJECT" => 300, "DELAYED" => 300, "STAMP" => 20, "AMOUNT" => 8, "REFERENCE" => 20, "CONTENT" => 2, "LANGUAGE" => 2, "MERCHANT" => 20, "COUNTRY" => 3, "CURRENCY" => 3, "DELIVERY_DATE" => 8 } + if max_length_substring.include? name + return value.to_s[0, max_length_substring[name]] + end + if max_length_exception.include? name + if value.to_s.length > max_length_exception[name] + raise ArgumentError, "Field #{name} length #{value.length} is longer than permitted by provider API. Maximum length #{max_length_exception[name]}." + else + return value + end + end + value + end + + # Calculate MAC + def generate_md5string + fields = [@fields["VERSION"], @fields["STAMP"], @fields["AMOUNT"], @fields["REFERENCE"], + @fields["MESSAGE"], @fields["LANGUAGE"], @fields["MERCHANT"], @fields["RETURN"], + @fields["CANCEL"], @fields["REJECT"], @fields["DELAYED"], @fields["COUNTRY"], + @fields["CURRENCY"], @fields["DEVICE"], @fields["CONTENT"], @fields["TYPE"], + @fields["ALGORITHM"], @fields["DELIVERY_DATE"], @fields["FIRSTNAME"], @fields["FAMILYNAME"], + @fields["ADDRESS"], @fields["POSTCODE"], @fields["POSTOFFICE"], @md5secret] + fields = fields.join("+") + Digest::MD5.hexdigest(fields).upcase + end + + # Mappings + mapping :order, 'STAMP' # Unique order number for each payment + mapping :account, 'MERCHANT' # Checkout Merchant ID + mapping :amount, 'AMOUNT' # Amount in cents + mapping :reference, 'REFERENCE' # Reference for bank statement + mapping :language, 'LANGUAGE' # "FI" / "SE" / "EN" + mapping :currency, 'CURRENCY' # "EUR" currently only + mapping :device, 'DEVICE' # "1" = HTML / "10" = XML + mapping :content, 'CONTENT' # "1" = NORMAL "2" = ADULT CONTENT + mapping :delivery_date, 'DELIVERY_DATE' # "YYYYMMDD" + mapping :description, 'MESSAGE' # Description of the order + + # Optional customer data supported by API (not mandatory) + mapping :customer, :first_name => 'FIRSTNAME', + :last_name => 'FAMILYNAME', + :email => 'EMAIL', + :phone => 'PHONE' + + # Optional fields supported by API (not mandatory) + mapping :billing_address, :city => 'POSTOFFICE', + :address1 => 'ADDRESS', + :zip => 'POSTCODE', + :country => 'COUNTRY' + + mapping :notify_url, 'DELAYED' # Delayed payment URL (mandatory) + mapping :reject_url, 'REJECT' # Rejected payment URL (mandatory) + mapping :return_url, 'RETURN' # Payment URL (optional) + mapping :cancel_return_url, 'CANCEL' # Cancelled payment URL (optional) + end + + class Notification < OffsitePayments::Notification + # Payment can be market complete with the following status codes + def complete? + ["2", "5", "6", "8", "9", "10"].include? params["STATUS"] + end + + # Did the customer choose delayed payment method + def delayed? + params['STATUS'] == "3" + end + + # Did the customer cancel the payment + def cancelled? + params['STATUS'] == "-1" + end + + # Payment requires manual activation (fraud check etc) + def activation? + params['STATUS'] == "7" + end + + # Reference specified by the client when sending payment + def reference + params['REFERENCE'] + end + + # Unique ID assigned by Checkout + def transaction_id + params['PAYMENT'] + end + + # Unique ID assigned by customer + def stamp + params['STAMP'] + end + + # Returned Message Authentication Code + def mac + params['MAC'] + end + + def status + params['STATUS'] + end + + # Verify authenticity of returned data + def acknowledge(authcode = nil) + return_authcode = [params["VERSION"], params["STAMP"], params["REFERENCE"], params["PAYMENT"], params["STATUS"], params["ALGORITHM"]].join("&") + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), authcode, return_authcode).upcase == params["MAC"] + end + + private + + # Take the posted data and move the data into params + def parse(post) + post.each do |key, value| + params[key] = value + end + end + end + end + end +end diff --git a/lib/offsite_payments/integrations/citrus.rb b/lib/offsite_payments/integrations/citrus.rb index 610428405..302472160 100644 --- a/lib/offsite_payments/integrations/citrus.rb +++ b/lib/offsite_payments/integrations/citrus.rb @@ -96,8 +96,8 @@ def invoice_ok?( order_id ) order_id.to_s == invoice.to_s end - def amount_ok?( order_amount ) - BigDecimal.new( amount ) == order_amount + def amount_ok?(order_amount) + amount == Money.from_amount(order_amount, currency) end def item_id @@ -120,7 +120,7 @@ def gross end def amount - gross + Money.from_amount(BigDecimal.new(gross), currency) end def transaction_id @@ -182,7 +182,7 @@ def acknowledge(authcode = nil) end def checksum_ok? - fields = [invoice, transaction_status, amount.to_s, transaction_id, issuerrefno, authidcode, customer_first_name, customer_last_name, pgrespcode, customer_address[:zip]].join + fields = [invoice, transaction_status, sprintf('%.2f', amount), transaction_id, issuerrefno, authidcode, customer_first_name, customer_last_name, pgrespcode, customer_address[:zip]].join unless Citrus.checksum(@secret_key, fields ) == checksum @message = 'checksum mismatch...' diff --git a/lib/offsite_payments/integrations/coinbase.rb b/lib/offsite_payments/integrations/coinbase.rb index f1f3f3bdc..5f4ef14a2 100755 --- a/lib/offsite_payments/integrations/coinbase.rb +++ b/lib/offsite_payments/integrations/coinbase.rb @@ -105,7 +105,6 @@ def status # apc arrives. Coinbase will verify that all the information we received are correct # and will return a ok or a fail. def acknowledge(authcode = {}) - uri = URI.parse(Coinbase.notification_confirmation_url % transaction_id) response = Coinbase.do_request(uri, @options[:credential1], @options[:credential2]) @@ -114,6 +113,7 @@ def acknowledge(authcode = {}) posted_order = @params parse(response) + return false unless @params %w(id custom total_native status).all? { |param| posted_order[param] == @params[param] } end diff --git a/lib/offsite_payments/integrations/diamond_mind.rb b/lib/offsite_payments/integrations/diamond_mind.rb new file mode 100644 index 000000000..2a93d7d3d --- /dev/null +++ b/lib/offsite_payments/integrations/diamond_mind.rb @@ -0,0 +1,170 @@ +module OffsitePayments #:nodoc: + module Integrations #:nodoc: + module DiamondMind + mattr_accessor :production_url + mattr_accessor :test_url + self.production_url = 'https://secure.networkmerchants.com/api/v2/three-step' + self.test_url = 'https://secure.networkmerchants.com/api/v2/three-step' + + def self.helper(order, account, options={}) + Helper.new(order, account, options) + end + + def self.notification(query_string, options={}) + Notification.new(query_string, options) + end + + def self.return(query_string, options={}) + Return.new(query_string, options) + end + + def self.service_url + mode = OffsitePayments.mode + case mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + module Common + CURRENCY_SPECIAL_MINOR_UNITS = { + 'BIF' => 0, + 'BYR' => 0, + 'CLF' => 0, + 'CLP' => 0, + 'CVE' => 0, + 'DJF' => 0, + 'GNF' => 0, + 'HUF' => 0, + 'ISK' => 0, + 'JPY' => 0, + 'KMF' => 0, + 'KRW' => 0, + 'PYG' => 0, + 'RWF' => 0, + 'UGX' => 0, + 'UYI' => 0, + 'VND' => 0, + 'VUV' => 0, + 'XAF' => 0, + 'XOF' => 0, + 'XPF' => 0, + 'BHD' => 3, + 'IQD' => 3, + 'JOD' => 3, + 'KWD' => 3, + 'LYD' => 3, + 'OMR' => 3, + 'TND' => 3, + 'COU' => 4 + } + + def create_signature(fields, secret) + data = fields.join('.') + digest = Digest::SHA1.hexdigest(data) + signed = "#{digest}.#{secret}" + Digest::SHA1.hexdigest(signed) + end + + def format_amount(amount, currency) + if amount.is_a? Float + units = CURRENCY_SPECIAL_MINOR_UNITS[currency] || 2 + multiple = 10**units + return (amount.to_f * multiple.to_f).to_i + else + return amount + end + end + + # Realex returns currency amount as an integer + def format_amount_as_float(amount, currency) + units = CURRENCY_SPECIAL_MINOR_UNITS[currency] || 2 + divisor = 10**units + return (amount.to_f / divisor.to_f) + end + + def extract_digits(value) + value.scan(/\d+/).join('') + end + + def extract_avs_code(params={}) + [extract_digits(params[:zip]), extract_digits(params[:address1])].join('|') + end + end + + class Helper < OffsitePayments::Helper + include Common + + def initialize(order, account, options = {}) + @timestamp = Time.now.strftime('%Y%m%d%H%M%S') + @currency = options[:currency] + @merchant_id = account + @sub_account = options[:credential2] + @secret = options[:credential3] + super + add_field 'currency', @currency + end + + def form_fields + {'api-toke' => @merchant_id, 'amount' => :amount} + end + + def amount=(amount) + add_field 'amount', format_amount(amount, @currency) + end + + def billing_address(params={}) + add_field(mappings[:billing_address][:zip], extract_avs_code(params)) + add_field(mappings[:billing_address][:country], lookup_country_code(params[:country])) + end + + def shipping_address(params={}) + add_field(mappings[:shipping_address][:zip], extract_avs_code(params)) + add_field(mappings[:shipping_address][:country], lookup_country_code(params[:country])) + end + + mapping :currency, 'currency' + mapping :order, 'order-id' + mapping :amount, 'amount' + mapping :return_url, 'redirect-url,' + mapping :customer, :email => 'email' + mapping :shipping_address, :zip => 'postal', + :country => 'country' + mapping :billing_address, :zip => 'postal', + :country => 'country' + end + + class Notification < OffsitePayments::Notification + include Common + def initialize(post, options={}) + super + @secret = options[:credential3] + end + end + + class Return < OffsitePayments::Return + def initialize(data, options) + super + @notification = Notification.new(data, options) + end + + def success? + notification.complete? + end + + # TODO: realex does not provide a separate cancelled endpoint + def cancelled? + false + end + + def message + notification.message + end + end + end + end +end diff --git a/lib/offsite_payments/integrations/easy_pay.rb b/lib/offsite_payments/integrations/easy_pay.rb index cbd19874e..bf0169225 100644 --- a/lib/offsite_payments/integrations/easy_pay.rb +++ b/lib/offsite_payments/integrations/easy_pay.rb @@ -101,7 +101,7 @@ def complete? end def amount - BigDecimal.new(gross) + Money.from_amount(BigDecimal.new(gross), currency) end def item_id @@ -131,6 +131,10 @@ def acknowledge(authcode = nil) def success_response(*args) { :nothing => true } end + + def currency + 'BYR' + end end end end diff --git a/lib/offsite_payments/integrations/epay.rb b/lib/offsite_payments/integrations/epay.rb index 220407a2d..2c0f1a5bd 100644 --- a/lib/offsite_payments/integrations/epay.rb +++ b/lib/offsite_payments/integrations/epay.rb @@ -125,7 +125,7 @@ def test? return false end - %w(txnid orderid amount currency date time hash fraud payercountry issuercountry txnfee subscriptionid paymenttype cardno).each do |attr| + %w(txnid orderid currency date time hash fraud payercountry issuercountry txnfee subscriptionid paymenttype cardno).each do |attr| define_method(attr) do params[attr] end @@ -135,10 +135,6 @@ def currency CURRENCY_CODES.invert[params['currency']].to_s end - def amount - Money.new(params['amount'].to_i, currency) - end - def generate_md5string md5string = String.new for line in @raw.split('&') diff --git a/lib/offsite_payments/integrations/hi_trust.rb b/lib/offsite_payments/integrations/hi_trust.rb index 71b414d8e..bb64cb204 100644 --- a/lib/offsite_payments/integrations/hi_trust.rb +++ b/lib/offsite_payments/integrations/hi_trust.rb @@ -31,7 +31,7 @@ def initialize(order, account, options = {}) add_field('Type', 'Auth') # Capture the payment right away - add_field('depositflag', options[:deposit_flag] || '0') + add_field('depositflag', '1') # Disable auto query - who knows what it does? add_field('queryflag', '1') diff --git a/lib/offsite_payments/integrations/liqpay.rb b/lib/offsite_payments/integrations/liqpay.rb index 7a785b76c..e9331fbee 100644 --- a/lib/offsite_payments/integrations/liqpay.rb +++ b/lib/offsite_payments/integrations/liqpay.rb @@ -80,7 +80,7 @@ def account end def amount - BigDecimal.new(gross) + Money.from_amount(BigDecimal.new(gross), currency) end def item_id diff --git a/lib/offsite_payments/integrations/migs.rb b/lib/offsite_payments/integrations/migs.rb new file mode 100644 index 000000000..c9b53bd1e --- /dev/null +++ b/lib/offsite_payments/integrations/migs.rb @@ -0,0 +1,292 @@ +require 'openssl' +require 'base64' + +module OffsitePayments + module Integrations #:nodoc: + module Migs + + # Overwrite this if you want to change the ANS production url + mattr_accessor :production_url + self.production_url = 'https://migs.mastercard.com.au/vpcpay' + + HASH_ALGORITHM = 'SHA256'.freeze + + def self.service_url + mode = OffsitePayments.mode + case mode + when :production + self.production_url + # There is no test URL + when :test + self.production_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.return(query_string, options = {}) + Return.new(query_string, options) + end + + class Helper < OffsitePayments::Helper + ELECTRON = /^(424519|42496[23]|450875|48440[6-8]|4844[1-5][1-5]|4917[3-5][0-9]|491880)\d{10}(\d{3})?$/ + + # WARNING + # + # From reading most of the tests for various integrations, + # including the more popular ones (Authorize.net), it seems + # that the amount field is provided using dollars, not cents + # like the Gateway classes. The currency for transactions is + # dictated by the merchant account. Thus, the user will need + # to determine the correct amount. It seems that cents are + # used for USD and INR at the very least. + + # The MiGS merchant id should be passed as the account parameter. + # + # Options must include: + # + # - :locale + # - :access_code => Migs access code + # - :secret => Migs secure secret + # - :amount => price in cents + # + # Also, the method add_secure_hash should be called at the very end + # of the block passed to payment_service_for. + def initialize(order, account, options = {}) + requires!(options, :amount, :locale, :secret, :access_code) + + # The following elements need to be removed from params to not + # trigger an error, but can't be added to the object yet since + # the @fields Hash has not been set up yet via super() + locale = options.delete(:locale) + access_code = options.delete(:access_code) + # For generating the secure hash + secret = options.delete(:secret) + + super + + add_field('vpc_gateway', 'ssl') + add_field('vpc_Command', 'pay') + add_field('vpc_Version', '1') + add_field('vpc_VirtualPaymentClientURL', 'https://migs.mastercard.com.au/vpcpay') + + self.locale = locale + self.access_code = access_code + @secret = secret + end + + # A custom handler for credit cards to extract the card type + # since Migs wants that passed with the data + def credit_card(params = {}) + brand = params[:brand].to_sym + params.delete(:brand) + + exp_month = sprintf("%.2i", Integer(params[:expiry_month], 10)) + exp_year = sprintf("%.4i", Integer(params[:expiry_year], 10)) + params.delete(:expiry_month) + params.delete(:expiry_year) + + method_missing(:credit_card, params) + + # The expiration data needs to be combined together + exp = "#{exp_year[-2..-1]}#{exp_month}" + add_field(mappings[:credit_card][:expiry_month], exp) + + # Map the card type to what Migs is expecting + if params[:number] =~ ELECTRON + brand_name = 'VisaDebit' + else + brand_name = { + :visa => 'Visa', + :master => 'Mastercard', + :american_express => 'Amex', + :diners_club => 'Dinersclub', + :jcb => 'JCB', + :solo => 'Solo' + }[brand] + end + + add_field(mappings[:credit_card][:brand], brand_name) + end + + # Make sure the order id and attempt number are combined into + # the appropriate fields in the form. The transaction reference + # is constructed as: + # + # order-attempt_number + # + # The order info is constructed as: + # + # order-attempt_number/description + + def order=(value) + # Both of these fields include the order id + {'vpc_MerchTxnRef' => 40, 'vpc_OrderInfo' => 34}.each do |field, max_length| + existing_value = @fields[field] || "" + + # Inserts the description as (/description) into any existing variation of "order-attempt/description" + new_value = existing_value.gsub(/^(\d+)?(-\d+)?(\/.*)?$/, "#{value}\\2\\3") + + add_field(field, new_value[0...max_length]) + end + end + + def attempt_number(value) + # Both of these fields include the atempt_number + {'vpc_MerchTxnRef' => 40, 'vpc_OrderInfo' => 34}.each do |field, max_length| + existing_value = @fields[field] || "" + + # Inserts the attempt number as (-\d) into any existing variation of "order-attempt/description" + new_value = existing_value.gsub(/^(\d+)?(-\d+)?(\/.*)?$/, "\\1-#{value}\\3") + + add_field(field, new_value[0...max_length]) + end + end + + def description(value) + field = 'vpc_OrderInfo' + max_length = 34 + existing_value = @fields[field] || "" + + # Inserts the description as (/description) into any existing variation of "order-attempt/description" + value = existing_value.gsub(/^(\d+)?(-\d+)?(\/.*)?$/, "\\1\\2/#{value}") + + add_field(field, value[0...max_length]) + end + + # This must be called at the end after all other fields have been added + def add_secure_hash + # Per MIGS requirements we must stringify, sort fields alphabetically + # minus the 'vpc_' prefix, add back the 'vpc_' prefix after sorting, + # then join all fields as a query string separated by '&'. + sorted_values = @fields.stringify_keys + .map { |(k, v)| [k.gsub('vpc_', ''), v] } + .sort + .map { |i| "vpc_#{i[0]}=#{i[1]}" } + .join('&') + hash = OpenSSL::HMAC.hexdigest(HASH_ALGORITHM, [@secret].pack('H*'), sorted_values).upcase + add_field('vpc_SecureHash', hash) + add_field('vpc_SecureHashType', 'SHA256') + end + + mapping :account, 'vpc_Merchant' + mapping :access_code, 'vpc_AccessCode' + mapping :locale, 'vpc_Locale' + mapping :return_url, 'vpc_ReturnURL' + mapping :amount, 'vpc_Amount' + + mapping :billing_address, :city => 'vpc_AVS_City', + :address1 => 'vpc_AVS_Street_01', + :state => 'vpc_AVS_StateProv', + :zip => 'vpc_AVS_PostCode', + :country => 'vpc_AVS_Country' + + mapping :credit_card, :number => 'vpc_CardNum', + :expiry_month => 'vpc_CardExp', + :expiry_year => 'vpc_CardExp', + :verification_value => 'vpc_CardSecurityCode', + :brand => 'vpc_card' + end + + class Return < OffsitePayments::Return + def initialize(query_string, options = {}) + super + @valid = secure_hash_matches? + end + + def message + return 'Response from MiGS could not be validated' unless @valid + params['vpc_Message'] + end + + def command + params['vpc_Command'] + end + + def transaction_id + params['vpc_TransactionNo'] + end + + def authorization_code + params['vpc_AuthorizeId'] + end + + def description + params['vpc_OrderInfo'].gsub(/^.*\//, '') + end + + def order + params['vpc_MerchTxnRef'].gsub(/-\d+$/, '') + end + + def attempt_number + params['vpc_MerchTxnRef'].gsub(/^\d+-/, '') + end + + def response_code + params['vpc_TxnResponseCode'] + end + + def merchant + params['vpc_Merchant'] + end + + def receipt_number + params['vpc_ReceiptNo'] + end + + def amount + params['vpc_Amount'].to_i + end + + def success? + return false if not @valid + params['vpc_TxnResponseCode'] == '0' + end + + def cancelled? + params['vpc_TxnResponseCode'] != '0' + end + + def secure_hash + params['vpc_SecureHash'] + end + + def avs_code + params['vpc_AVSResultCode'] + end + + def cvv_code + params['vpc_CSCResultCode'] + end + + def secure_hash_matches? + return false unless params['vpc_SecureHash'] + response = params.clone + response.delete('vpc_SecureHash') + response.delete('vpc_SecureHashType') + sorted_values = response.stringify_keys + .map { |(k, v)| [k.gsub('vpc_', ''), v] } + .sort.map { |i| "vpc_#{i[0]}=#{i[1]}" } + .join('&') + hash = OpenSSL::HMAC.hexdigest(HASH_ALGORITHM, [@options[:secret]].pack('H*'), sorted_values).upcase + hash == secure_hash + end + + # Returns true if one of the following is true: + # + # - address and 9-digit zip matches + # - address and 5-digit zip matches + # - 5-digit zip matches, address not checked + def avs_code_matches? + return ['Y', 'X', 'P'].include? avs_code + end + + def cvv_code_matches? + return ['M'].include? cvv_code + end + end + + end + end +end diff --git a/lib/offsite_payments/integrations/mollie.rb b/lib/offsite_payments/integrations/mollie.rb index 4d1ce4054..75fc36357 100644 --- a/lib/offsite_payments/integrations/mollie.rb +++ b/lib/offsite_payments/integrations/mollie.rb @@ -14,7 +14,7 @@ def initialize(token) def get_request(resource, params = nil) uri = URI.parse(MOLLIE_API_V1_URI + resource) - uri.query = params.map { |k,v| "#{CGI.escape(k)}=#{CGI.escape(v)}}"}.join('&') if params + uri.query = params.map { |k,v| "#{CGI.escape(k)}=#{CGI.escape(v)}"}.join('&') if params headers = { "Authorization" => "Bearer #{token}", "Content-Type" => "application/json" } JSON.parse(ssl_get(uri.to_s, headers)) end @@ -29,4 +29,4 @@ def post_request(resource, params = nil) end end -end \ No newline at end of file +end diff --git a/lib/offsite_payments/integrations/mollie_ideal.rb b/lib/offsite_payments/integrations/mollie_ideal.rb index 8ae4d1525..1fe4eacd0 100644 --- a/lib/offsite_payments/integrations/mollie_ideal.rb +++ b/lib/offsite_payments/integrations/mollie_ideal.rb @@ -7,7 +7,7 @@ module MollieIdeal self.live_issuers = [ ["ABN AMRO", "ideal_ABNANL2A"], ["ASN Bank", "ideal_ASNBNL21"], - ["Friesland Bank", "ideal_FRBKNL2L"], + ["Bunq", "ideal_BUNQNL2A"], ["ING", "ideal_INGBNL2A"], ["Knab", "ideal_KNABNL2H"], ["Rabobank", "ideal_RABONL2U"], diff --git a/lib/offsite_payments/integrations/molpay.rb b/lib/offsite_payments/integrations/molpay.rb index e90e68123..68cb85f4c 100644 --- a/lib/offsite_payments/integrations/molpay.rb +++ b/lib/offsite_payments/integrations/molpay.rb @@ -4,8 +4,8 @@ module Molpay mattr_accessor :acknowledge_url self.acknowledge_url = 'https://www.onlinepayment.com.my/MOLPay/API/chkstat/returnipn.php' - def self.notification(post) - Notification.new(post) + def self.notification(post, options = {}) + Notification.new(post, options) end def self.return(query_string, options={}) @@ -24,19 +24,20 @@ class Helper < OffsitePayments::Helper SERVICE_URL = 'https://www.onlinepayment.com.my/MOLPay/pay/'.freeze - mapping :account, 'merchantid' - mapping :amount, 'amount' - mapping :order, 'orderid' - mapping :customer, :name => 'bill_name', - :email => 'bill_email', - :phone => 'bill_mobile' - - mapping :description, 'bill_desc' - mapping :language, 'langcode' - mapping :country, 'country' - mapping :currency, 'cur' - mapping :return_url, 'returnurl' - mapping :signature, 'vcode' + mapping :account, 'merchantid' + mapping :amount, 'amount' + mapping :order, 'orderid' + mapping :customer, :name => 'bill_name', + :email => 'bill_email', + :phone => 'bill_mobile' + + mapping :description, 'bill_desc' + mapping :language, 'langcode' + mapping :country, 'country' + mapping :currency, 'cur' + mapping :return_url, 'returnurl' + mapping :notify_url, 'callbackurl' + mapping :signature, 'vcode' attr_reader :amount_in_cents, :verify_key, :channel @@ -86,6 +87,17 @@ def signature class Notification < OffsitePayments::Notification include ActiveUtils::PostsData + def status + case params['status'] + when '00' + 'Completed' + when '11' + 'Failed' + when '22' + 'Pending' + end + end + def complete? status == 'Completed' end @@ -144,10 +156,6 @@ def status_orig params['status'] end - def status - params['status'] == '00' ? 'Completed' : 'Failed' - end - def acknowledge(authcode = nil) payload = raw + '&treq=1' ssl_post(Molpay.acknowledge_url, payload, @@ -175,6 +183,10 @@ def initialize(query_string, options = {}) def success? @notification.acknowledge end + + def pending? + @notification.status == 'Pending' + end end end end diff --git a/lib/offsite_payments/integrations/moneybookers.rb b/lib/offsite_payments/integrations/moneybookers.rb index c997b65ce..82c24a0e8 100644 --- a/lib/offsite_payments/integrations/moneybookers.rb +++ b/lib/offsite_payments/integrations/moneybookers.rb @@ -2,7 +2,7 @@ module OffsitePayments #:nodoc: module Integrations #:nodoc: module Moneybookers mattr_accessor :production_url - self.production_url = 'https://www.moneybookers.com/app/payment.pl' + self.production_url = 'https://pay.skrill.com' def self.service_url self.production_url diff --git a/lib/offsite_payments/integrations/network_merchants_offsite.rb b/lib/offsite_payments/integrations/network_merchants_offsite.rb new file mode 100644 index 000000000..e7bbae998 --- /dev/null +++ b/lib/offsite_payments/integrations/network_merchants_offsite.rb @@ -0,0 +1,176 @@ +module OffsitePayments #:nodoc: + module Integrations #:nodoc: + module NetworkMerchantsOffsite + mattr_accessor :production_url + mattr_accessor :test_url + self.production_url = 'https://secure.networkmerchants.com/api/v2/three-step' + self.test_url = 'https://secure.networkmerchants.com/api/v2/three-step' + + def self.helper(order, account, options={}) + Helper.new(order, account, options) + end + + def self.notification(query_string, options={}) + Notification.new(query_string, options) + end + + def self.return(query_string, options={}) + Return.new(query_string, options) + end + + def self.service_url + mode = OffsitePayments.mode + case mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + module Common + CURRENCY_SPECIAL_MINOR_UNITS = { + 'BIF' => 0, + 'BYR' => 0, + 'CLF' => 0, + 'CLP' => 0, + 'CVE' => 0, + 'DJF' => 0, + 'GNF' => 0, + 'HUF' => 0, + 'ISK' => 0, + 'JPY' => 0, + 'KMF' => 0, + 'KRW' => 0, + 'PYG' => 0, + 'RWF' => 0, + 'UGX' => 0, + 'UYI' => 0, + 'VND' => 0, + 'VUV' => 0, + 'XAF' => 0, + 'XOF' => 0, + 'XPF' => 0, + 'BHD' => 3, + 'IQD' => 3, + 'JOD' => 3, + 'KWD' => 3, + 'LYD' => 3, + 'OMR' => 3, + 'TND' => 3, + 'COU' => 4 + } + + def create_signature(fields, secret) + data = fields.join('.') + digest = Digest::SHA1.hexdigest(data) + signed = "#{digest}.#{secret}" + Digest::SHA1.hexdigest(signed) + end + + # Realex accepts currency amounts as an integer in the lowest value + # e.g. + # format_amount(110.56, 'GBP') + # => 11056 + def format_amount(amount, currency) + if amount.is_a? Float + units = CURRENCY_SPECIAL_MINOR_UNITS[currency] || 2 + multiple = 10**units + return (amount.to_f * multiple.to_f).to_i + else + return amount + end + end + + # Realex returns currency amount as an integer + def format_amount_as_float(amount, currency) + units = CURRENCY_SPECIAL_MINOR_UNITS[currency] || 2 + divisor = 10**units + return (amount.to_f / divisor.to_f) + end + + def extract_digits(value) + value.scan(/\d+/).join('') + end + + def extract_avs_code(params={}) + [extract_digits(params[:zip]), extract_digits(params[:address1])].join('|') + end + + end + + class Helper < OffsitePayments::Helper + include Common + + def initialize(order, account, options = {}) + @timestamp = Time.now.strftime('%Y%m%d%H%M%S') + @currency = options[:currency] + @merchant_id = account + @sub_account = options[:credential2] + @secret = options[:credential3] + super + add_field 'currency', @currency + end + + def form_fields + {'api-toke' => @merchant_id, 'amount' => :amount} + end + + def amount=(amount) + add_field 'amount', format_amount(amount, @currency) + end + + def billing_address(params={}) + add_field(mappings[:billing_address][:zip], extract_avs_code(params)) + add_field(mappings[:billing_address][:country], lookup_country_code(params[:country])) + end + + def shipping_address(params={}) + add_field(mappings[:shipping_address][:zip], extract_avs_code(params)) + add_field(mappings[:shipping_address][:country], lookup_country_code(params[:country])) + end + + mapping :currency, 'currency' + mapping :order, 'order-id' + mapping :amount, 'amount' + mapping :return_url, 'redirect-url,' + mapping :customer, :email => 'email' + mapping :shipping_address, :zip => 'postal', + :country => 'country' + mapping :billing_address, :zip => 'postal', + :country => 'country' + end + + class Notification < OffsitePayments::Notification + include Common + def initialize(post, options={}) + super + @secret = options[:credential3] + end + end + + class Return < OffsitePayments::Return + def initialize(data, options) + super + @notification = Notification.new(data, options) + end + + def success? + notification.complete? + end + + # TODO: realex does not provide a separate cancelled endpoint + def cancelled? + false + end + + def message + notification.message + end + end + + end + end +end diff --git a/lib/offsite_payments/integrations/pay_fast.rb b/lib/offsite_payments/integrations/pay_fast.rb index dd0abe0c9..a91e8c819 100644 --- a/lib/offsite_payments/integrations/pay_fast.rb +++ b/lib/offsite_payments/integrations/pay_fast.rb @@ -212,7 +212,7 @@ def fee # The net amount credited to the receiver's account. def amount - params['amount_net'] + Money.from_amount(BigDecimal.new(params['amount_net']), currency) end # The name of the item being charged for. @@ -226,7 +226,7 @@ def merchant_id end def currency - nil + 'ZAR' end # Generated hash depends on params order so use OrderedHash instead of Hash diff --git a/lib/offsite_payments/integrations/paydollar.rb b/lib/offsite_payments/integrations/paydollar.rb index 572dfbebd..f8bda6418 100644 --- a/lib/offsite_payments/integrations/paydollar.rb +++ b/lib/offsite_payments/integrations/paydollar.rb @@ -2,34 +2,64 @@ module OffsitePayments #:nodoc: module Integrations #:nodoc: module Paydollar CURRENCY_MAP = { - 'AED' => '784', - 'AUD' => '036', - 'BND' => '096', - 'CAD' => '124', + 'HKD' => '344', + 'USD' => '840', + 'SGD' => '702', 'CNY' => '156', + 'JPY' => '392', + 'TWD' => '901', + 'AUD' => '036', 'EUR' => '978', 'GBP' => '826', - 'HKD' => '344', - 'IDR' => '360', - 'JPY' => '392', - 'KRW' => '410', + 'CAD' => '124', 'MOP' => '446', + 'PHP' => '608', + 'THB' => '764', 'MYR' => '458', + 'IDR' => '360', + 'KRW' => '410', + 'BND' => '096', 'NZD' => '554', - 'PHP' => '608', 'SAR' => '682', - 'SGD' => '702', - 'THB' => '764', - 'TWD' => '901', - 'USD' => '840', + 'AED' => '784', + 'BRL' => '986', + 'INR' => '356', + 'TRY' => '949', + 'ZAR' => '710', + 'VND' => '704', + 'DKK' => '208', + 'ILS' => '376', + 'NOK' => '578', + 'RUB' => '643', + 'SEK' => '752', + 'CHF' => '756', + 'ARS' => '032', + 'CLP' => '152', + 'COP' => '170', + 'CZK' => '203', + 'EGP' => '818', + 'HUF' => '348', + 'KZT' => '398', + 'LBP' => '422', + 'MXN' => '484', + 'NGN' => '566', + 'PKR' => '586', + 'PEN' => '604', + 'PLN' => '985', + 'QAR' => '634', + 'RON' => '946', + 'UAH' => '980', + 'VEF' => '937', + 'LKR' => '144', + 'KWD' => '414', } - + # change url def self.service_url case OffsitePayments.mode when :production - 'https://www.paydollar.com/b2c2/eng/payment/payForm.jsp' + 'https://www.paydollar.com/b2c2/eng/payment/payShopify.jsp' when :test - 'https://test.paydollar.com/b2cDemo/eng/payment/payForm.jsp' + 'https://test.paydollar.com/b2cDemo/eng/payment/payShopify.jsp' else raise StandardError, "Integration mode set to an invalid value: #{mode}" end diff --git a/lib/offsite_payments/integrations/paytm.rb b/lib/offsite_payments/integrations/paytm.rb new file mode 100644 index 000000000..816c5140d --- /dev/null +++ b/lib/offsite_payments/integrations/paytm.rb @@ -0,0 +1,248 @@ +module OffsitePayments #:nodoc: + module Integrations #:nodoc: + module Paytm + CIPHER = 'AES-128-CBC' + SALT_ALPHABET = ['a'..'z', 'A'..'Z', '0'..'9'].flat_map { |i| i.to_a } + SALT_LENGTH = 4 + STATIC_IV = '@@@@&&&&####$$$$' + + mattr_accessor :test_url + mattr_accessor :production_url + + self.test_url = 'https://pguat.paytm.com/oltp-web/processTransaction' + self.production_url = 'https://secure.paytm.in/oltp-web/processTransaction' + + def self.service_url + OffsitePayments.mode == :production ? production_url : test_url + end + + def self.notification(post, options = {}) + Notification.new(post, options) + end + + def self.return(post, options = {}) + Return.new(post, options) + end + + def self.checksum(hash, salt = nil) + if salt.nil? + salt = SALT_LENGTH.times.map { SALT_ALPHABET[SecureRandom.random_number(SALT_ALPHABET.length)] }.join + end + + values = hash.sort.to_h.values + values << salt + Digest::SHA256.hexdigest(values.join('|')) + salt + end + + def self.encrypt(data, key) + aes = OpenSSL::Cipher.new(CIPHER) + aes.encrypt + aes.key = key + aes.iv = STATIC_IV + + encrypted_data = aes.update(data) + aes.final + Base64.strict_encode64(encrypted_data) + end + + class Helper < OffsitePayments::Helper + CHECKSUM_FIELDS = %w(MID ORDER_ID CALLBACK_URL CUST_ID TXN_AMOUNT CHANNEL_ID INDUSTRY_TYPE_ID WEBSITE MERC_UNQ_REF).freeze + + mapping :amount, 'TXN_AMOUNT' + mapping :account, 'MID' + mapping :order, 'MERC_UNQ_REF' + + mapping :customer, :email => 'CUST_ID' + + + mapping :credential3, 'INDUSTRY_TYPE_ID' + mapping :credential4, 'WEBSITE' + mapping :channel_id, 'CHANNEL_ID' + mapping :return_url, 'CALLBACK_URL' + mapping :checksum, 'CHECKSUMHASH' + + def initialize(order, account, options = {}) + super + @options = options + @timestamp = Time.now.strftime('%Y%m%d%H%M%S') + + add_field(mappings[:channel_id], "WEB") + add_field 'ORDER_ID', "#{order}-#{@timestamp.to_i}" + + self.pg = 'CC' + end + + def form_fields + sanitize_fields + @fields.merge(mappings[:checksum] => encrypt_checksum) + end + + def encrypt_checksum + payload_items = {} + + CHECKSUM_FIELDS.each do |field| + payload_items[field] = @fields[field] + end + + Paytm.encrypt(Paytm.checksum(payload_items), @options[:credential2]) + end + + def sanitize_fields + %w(email phone).each do |field| + @fields[field].gsub!(/[^a-zA-Z0-9\-_@\/\s.]/, '') if @fields[field] + end + end + end + + class Notification < OffsitePayments::Notification + PAYTM_RESPONSE_PARAMS = %w(MID BANKTXNID TXNAMOUNT CURRENCY STATUS RESPCODE RESPMSG TXNDATE GATEWAYNAME BANKNAME PAYMENTMODE PROMO_CAMP_ID PROMO_STATUS PROMO_RESPCODE ORDERID TXNID REFUNDAMOUNT REFID MERC_UNQ_REF CUSTID).freeze + + def initialize(post, options = {}) + super + @secret_key = options[:credential2] + end + + def complete? + status == 'Completed' + end + + def status + if transaction_status.casecmp("TXN_SUCCESS").zero? + 'Completed' + elsif transaction_status.casecmp("pending").zero? + 'Pending' + else + 'Failed' + end + end + + def invoice_ok?(order_id) + order_id.to_s == invoice.to_s + end + + # Order amount should be equal to gross + def amount_ok?(order_amount) + BigDecimal.new(original_gross) == order_amount + end + + # Status of transaction return from the Paytm. List of possible values: + # TXN_SUCCESS:: + # PENDING:: + # TXN_FAILURE:: + def transaction_status + @params['STATUS'] + end + + # ID of this transaction (Paytm transaction id) + def transaction_id + @params['TXNID'] + end + + # Mode of Payment + # + # 'CC' for credit-card + # 'NB' for net-banking + # 'PPI' for wallet + def type + @params['PAYMENTMODE'] + end + + # What currency have we been dealing with + def currency + @params['CURRENCY'] + end + + def item_id + @params['MERC_UNQ_REF'] + end + + # This is the invoice which you passed to Paytm + def invoice + @params['MERC_UNQ_REF'] + end + + # Merchant Id provided by the Paytm + def account + @params['MID'] + end + + # original amount send by merchant + def original_gross + @params['TXNAMOUNT'] + end + + def gross + parse_and_round_gross_amount(@params['TXNAMOUNT']) + end + + def message + @params['RESPMSG'] + end + + def checksum + @params['CHECKSUMHASH'] + end + + def acknowledge + checksum_ok? + end + + def checksum_ok? + normalized_data = checksum.delete("\n").tr(' ', '+') + encrypted_data = Base64.strict_decode64(normalized_data) + + aes = OpenSSL::Cipher::Cipher.new(CIPHER) + aes.decrypt + aes.key = @secret_key + aes.iv = STATIC_IV + received_checksum = aes.update(encrypted_data) + aes.final + + salt = received_checksum[-SALT_LENGTH..-1] + expected_params = @params.keep_if { |k| PAYTM_RESPONSE_PARAMS.include?(k) }.sort.to_h + expected_checksum = Paytm.checksum(expected_params, salt) + + if received_checksum == expected_checksum + @message = @params['RESPMSG'] + @params['RESPCODE'] == '01' + else + @message = 'Return checksum not matching the data provided' + false + end + end + + private + + def parse_and_round_gross_amount(amount) + rounded_amount = (amount.to_f * 100.0).round + sprintf('%.2f', rounded_amount / 100.00) + end + end + + class Return < OffsitePayments::Return + def initialize(query_string, options = {}) + super + @notification = Notification.new(query_string, options) + end + + def transaction_id + @notification.transaction_id + end + + def status(order_id, order_amount) + if @notification.invoice_ok?(order_id) && @notification.amount_ok?(BigDecimal.new(order_amount)) + @notification.status + else + 'Mismatch' + end + end + + def success? + status(@params['MERC_UNQ_REF'], @params['TXNAMOUNT']) == 'Completed' + end + + def message + @notification.message + end + end + end + end +end diff --git a/lib/offsite_payments/integrations/payu_in.rb b/lib/offsite_payments/integrations/payu_in.rb index 7ce98b624..32bec46ba 100755 --- a/lib/offsite_payments/integrations/payu_in.rb +++ b/lib/offsite_payments/integrations/payu_in.rb @@ -42,7 +42,7 @@ class Helper < OffsitePayments::Helper :address1 => 'address1', :address2 => 'address2', :state => 'state', - :zip => 'zip', + :zip => 'zipcode', :country => 'country' # Which tab you want to be open default on PayU @@ -84,8 +84,9 @@ def generate_checksum end def sanitize_fields - ['address1', 'address2', 'city', 'state', 'country', 'productinfo', 'email', 'phone'].each do |field| - @fields[field].gsub!(/[^a-zA-Z0-9\-_@\/\s.]/, '') if @fields[field] + @fields['phone'] = @fields['phone'].gsub(/[^0-9]/, '') if @fields['phone'] + ['address1', 'address2', 'city', 'state', 'country', 'productinfo', 'email'].each do |field| + @fields[field] = @fields[field].gsub(/[^a-zA-Z0-9\-_@\/\s.]/, '') if @fields[field] end end @@ -116,7 +117,8 @@ def invoice_ok?( order_id ) # Order amount should be equal to gross - discount def amount_ok?( order_amount, order_discount = BigDecimal.new( '0.0' ) ) - BigDecimal.new( original_gross ) == order_amount && BigDecimal.new( discount.to_s ) == order_discount + parsed_discount = discount.nil? ? 0.to_d : discount.to_d + BigDecimal.new( original_gross ) == order_amount && parsed_discount == order_discount end # Status of transaction return from the PayU. List of possible values: diff --git a/lib/offsite_payments/integrations/platron.rb b/lib/offsite_payments/integrations/platron.rb index b4d7d625c..2695de94c 100644 --- a/lib/offsite_payments/integrations/platron.rb +++ b/lib/offsite_payments/integrations/platron.rb @@ -120,7 +120,7 @@ def ps_full_amount end def amount - params['pg_amount'] + Money.from_amount(BigDecimal.new(params['pg_amount']), currency) end def secret diff --git a/lib/offsite_payments/integrations/realex_offsite.rb b/lib/offsite_payments/integrations/realex_offsite.rb index 65958ee48..dd6af55ed 100644 --- a/lib/offsite_payments/integrations/realex_offsite.rb +++ b/lib/offsite_payments/integrations/realex_offsite.rb @@ -3,8 +3,8 @@ module Integrations #:nodoc: module RealexOffsite mattr_accessor :production_url mattr_accessor :test_url - self.production_url = 'https://epage.payandshop.com/epage.cgi' - self.test_url = 'https://hpp.sandbox.realexpayments.com/pay' + self.production_url = 'https://pay.realexpayments.com/pay' + self.test_url = 'https://pay.sandbox.realexpayments.com/pay' def self.helper(order, account, options={}) Helper.new(order, account, options) @@ -71,23 +71,28 @@ def create_signature(fields, secret) end # Realex accepts currency amounts as an integer in the lowest value - # e.g. + # e.g. # format_amount(110.56, 'GBP') # => 11056 def format_amount(amount, currency) - units = CURRENCY_SPECIAL_MINOR_UNITS[currency] || 2 - multiple = 10**units - return (amount.to_f * multiple.to_f).to_i + if amount.is_a? Float + units = CURRENCY_SPECIAL_MINOR_UNITS[currency] || 2 + multiple = 10**units + return (amount.to_f * multiple.to_f).to_i + else + return amount + end end # Realex returns currency amount as an integer def format_amount_as_float(amount, currency) units = CURRENCY_SPECIAL_MINOR_UNITS[currency] || 2 divisor = 10**units - return (amount.to_f / divisor.to_f) + return ((amount || 0).to_d / divisor.to_d) end def extract_digits(value) + return unless value value.scan(/\d+/).join('') end @@ -211,10 +216,15 @@ def gross format_amount_as_float(params['AMOUNT'], currency) end + def complete? verified? && status == 'Completed' end + def success? + status == 'Completed' + end + # Fields for Realex signature verification def timestamp params['TIMESTAMP'] @@ -231,6 +241,7 @@ def checkout_id def order_id params['ORDER_ID'] end + alias_method :transaction_id, :order_id def result params['RESULT'] @@ -247,6 +258,7 @@ def pasref def authcode params['AUTHCODE'] end + alias_method :authorization_code, :authcode def signature params['SHA1HASH'] diff --git a/lib/offsite_payments/integrations/robokassa.rb b/lib/offsite_payments/integrations/robokassa.rb index a8a104472..3180a1e34 100644 --- a/lib/offsite_payments/integrations/robokassa.rb +++ b/lib/offsite_payments/integrations/robokassa.rb @@ -103,10 +103,6 @@ def complete? true end - def amount - BigDecimal.new(gross) - end - def item_id params['InvId'] end @@ -138,6 +134,10 @@ def acknowledge(authcode = nil) def success_response(*args) "OK#{item_id}" end + + def currency + 'RUB' + end end class Return < OffsitePayments::Return diff --git a/lib/offsite_payments/integrations/sage_pay_form.rb b/lib/offsite_payments/integrations/sage_pay_form.rb index 70c056622..ef1a16a0d 100644 --- a/lib/offsite_payments/integrations/sage_pay_form.rb +++ b/lib/offsite_payments/integrations/sage_pay_form.rb @@ -69,14 +69,20 @@ def cipher(action, key, payload) class Helper < OffsitePayments::Helper include Encryption + attr_reader :identifier + + def initialize(order, account, options={}) + super + @identifier = rand(0..99999).to_s.rjust(5, '0') + add_field 'VendorTxCode', "#{order}-#{@identifier}" + end + mapping :credential2, 'EncryptKey' mapping :account, 'Vendor' mapping :amount, 'Amount' mapping :currency, 'Currency' - mapping :order, 'VendorTxCode' - mapping :customer, :first_name => 'BillingFirstnames', :last_name => 'BillingSurname', @@ -121,6 +127,8 @@ def map_billing_address_to_shipping_address end def form_fields + fields.delete('locale') + map_billing_address_to_shipping_address unless @shipping_address_set fields['DeliveryFirstnames'] ||= fields['BillingFirstnames'] @@ -154,8 +162,8 @@ def create_crypt_field(fields, key) parts = fields.map { |k, v| "#{k}=#{sanitize(k, v)}" unless v.nil? }.compact.shuffle parts.unshift(sage_encrypt_salt(key.length, key.length * 2)) sage_encrypt(parts.join('&'), key) - rescue OpenSSL::Cipher::CipherError => e - if e.message == 'key length too short' + rescue OpenSSL::Cipher::CipherError, ArgumentError => e + if e.message == 'key length too short' || e.message == 'key must be 16 bytes' raise ActionViewHelperError, 'Invalid encryption key.' else raise @@ -246,7 +254,7 @@ def message # Vendor-supplied code (:order mapping). def item_id - params['VendorTxCode'] + params['VendorTxCode'].rpartition('-').first end # Internal SagePay code, typically "{LONG-UUID}". diff --git a/lib/offsite_payments/integrations/universal.rb b/lib/offsite_payments/integrations/universal.rb index 8b79d889a..6b2503326 100644 --- a/lib/offsite_payments/integrations/universal.rb +++ b/lib/offsite_payments/integrations/universal.rb @@ -73,20 +73,13 @@ def amount=(amount) add_field 'x_amount', format_amount(amount, @currency) end - def shipping(amount) - add_field 'x_amount_shipping', format_amount(amount, @currency) - end - - def tax(amount) - add_field 'x_amount_tax', format_amount(amount, @currency) - end - def sign_fields @fields.merge!('x_signature' => generate_signature) end def generate_signature - Universal.sign(@fields, @key) + fields_to_sign = @fields.select { |key, _| key.start_with?('x_') && key != 'x_signature' } + Universal.sign(fields_to_sign, @key) end mapping :account, 'x_account_id' @@ -105,6 +98,17 @@ def generate_signature :email => 'x_customer_email', :phone => 'x_customer_phone' + mapping :billing_address, :first_name => 'x_customer_billing_first_name', + :last_name => 'x_customer_billing_last_name', + :city => 'x_customer_billing_city', + :company => 'x_customer_billing_company', + :address1 => 'x_customer_billing_address1', + :address2 => 'x_customer_billing_address2', + :state => 'x_customer_billing_state', + :zip => 'x_customer_billing_zip', + :country => 'x_customer_billing_country', + :phone => 'x_customer_billing_phone' + mapping :shipping_address, :first_name => 'x_customer_shipping_first_name', :last_name => 'x_customer_shipping_last_name', :city => 'x_customer_shipping_city', @@ -160,6 +164,10 @@ def status result && result.capitalize end + def message + @params['x_message'] + end + def test? @params['x_test'] == 'true' end @@ -181,6 +189,10 @@ def initialize(query_string, options = {}) def success? @notification.acknowledge end + + def message + @notification.message + end end end end diff --git a/lib/offsite_payments/integrations/web_pay.rb b/lib/offsite_payments/integrations/web_pay.rb index 402be6c42..b1159e9bc 100644 --- a/lib/offsite_payments/integrations/web_pay.rb +++ b/lib/offsite_payments/integrations/web_pay.rb @@ -150,7 +150,7 @@ def complete? end def amount - BigDecimal.new(gross) + Money.from_amount(BigDecimal.new(gross), currency) end def item_id @@ -180,6 +180,10 @@ def acknowledge(authcode = nil) def success_response(*args) {:nothing => true} end + + def currency + params['currency_id'] + end end end end diff --git a/lib/offsite_payments/integrations/webmoney.rb b/lib/offsite_payments/integrations/webmoney.rb index 5c4393b5d..3d9861d07 100644 --- a/lib/offsite_payments/integrations/webmoney.rb +++ b/lib/offsite_payments/integrations/webmoney.rb @@ -83,7 +83,7 @@ def recognizes? end def amount - BigDecimal.new(gross) + Money.from_amount(BigDecimal.new(gross), currency) end def key_present? @@ -113,6 +113,10 @@ def acknowledge(authcode = nil) def success_response(*args) {:nothing => true} end + + def currency + 'RUB' + end end end end diff --git a/lib/offsite_payments/integrations/world_pay.rb b/lib/offsite_payments/integrations/world_pay.rb index 0495e4536..5ce4a900f 100644 --- a/lib/offsite_payments/integrations/world_pay.rb +++ b/lib/offsite_payments/integrations/world_pay.rb @@ -34,8 +34,13 @@ class Helper < OffsitePayments::Helper mapping :customer, :email => 'email', :phone => 'tel' - mapping :billing_address, :zip => 'postcode', - :country => 'country' + mapping :billing_address, + :address1 => 'address1', + :address2 => 'address2', + :city => 'town', + :state => 'region', + :zip => 'postcode', + :country => 'country' mapping :description, 'desc' mapping :notify_url, 'MC_callback' @@ -62,16 +67,6 @@ def initialize(order, account, options = {}) end end - # WorldPay only supports a single address field so we - # have to concat together - lines are separated using - def billing_address(params={}) - add_field(mappings[:billing_address][:zip], params[:zip]) - add_field(mappings[:billing_address][:country], lookup_country_code(params[:country])) - - address = [params[:address1], params[:address2], params[:city], params[:state]].compact - add_field('address', address.join(' ')) - end - # WorldPay only supports a single name field so we have to concat def customer(params={}) add_field(mappings[:customer][:email], params[:email]) diff --git a/lib/offsite_payments/notification.rb b/lib/offsite_payments/notification.rb index 386631314..fb7aa03ec 100644 --- a/lib/offsite_payments/notification.rb +++ b/lib/offsite_payments/notification.rb @@ -30,11 +30,10 @@ def gross_cents (gross.to_f * 100.0).round end - # This combines the gross and currency and returns a proper Money object. - # this requires the money library located at http://rubymoney.github.io/money/ def amount - return Money.new(gross_cents, currency) rescue ArgumentError - return Money.new(gross_cents) # maybe you have an own money object which doesn't take a currency? + amount = gross ? gross.to_d : 0 + return Money.from_amount(amount, currency) rescue ArgumentError + return Money.from_amount(amount) # maybe you have an own money object which doesn't take a currency? end # reset the notification. diff --git a/lib/offsite_payments/return.rb b/lib/offsite_payments/return.rb index 94446d463..11a2cc6c0 100644 --- a/lib/offsite_payments/return.rb +++ b/lib/offsite_payments/return.rb @@ -25,9 +25,9 @@ def parse(query_string) return {} if query_string.blank? query_string.split('&').inject({}) do |memo, chunk| - next if chunk.empty? + next memo if chunk.empty? key, value = chunk.split('=', 2) - next if key.empty? + next memo if key.empty? value = value.nil? ? nil : CGI.unescape(value) memo[CGI.unescape(key)] = value memo diff --git a/lib/offsite_payments/version.rb b/lib/offsite_payments/version.rb index cbbbeda84..e6781328e 100644 --- a/lib/offsite_payments/version.rb +++ b/lib/offsite_payments/version.rb @@ -1,3 +1,3 @@ module OffsitePayments - VERSION = "2.1.0" + VERSION = "2.6.4" end diff --git a/offsite_payments.gemspec b/offsite_payments.gemspec index 107466c19..3dc3fc2ee 100644 --- a/offsite_payments.gemspec +++ b/offsite_payments.gemspec @@ -22,13 +22,13 @@ Gem::Specification.new do |s| s.files = Dir['CHANGELOG', 'README.md', 'MIT-LICENSE', 'lib/**/*'] s.require_path = 'lib' - s.add_dependency('activesupport', '>= 3.2.14', '< 5.0.0') - s.add_dependency('i18n', '~> 0.5') + s.add_dependency('activesupport', '>= 3.2.14', '< 5.2') + s.add_dependency('i18n', '>= 0.6.6') s.add_dependency('money', '< 7.0.0') s.add_dependency('builder', '>= 2.1.2', '< 4.0.0') - s.add_dependency('active_utils', '~> 3.0.0') - s.add_dependency('nokogiri', "~> 1.4") - s.add_dependency('actionpack', ">= 3.2.20", "< 5.0.0") + s.add_dependency('active_utils', '~> 3.3.0') + s.add_dependency('nokogiri', "~> 1.6") + s.add_dependency('actionpack', '>= 3.2.20', '< 5.2') s.add_development_dependency('rake') s.add_development_dependency('test-unit', '~> 3.0') diff --git a/test/fixtures.yml b/test/fixtures.yml index 255717ced..9147fb622 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -11,10 +11,6 @@ # Paste any required PEM certificates after the pem key. # -app55: - api_key: "QDtACYMCFqtuKOQ22QtCkGR1TzD7XM8i" - api_secret: "yT1FRpuusBIQoEBIfIQ8bPStkl7yzdTz" - authorize_net: login: X password: Y @@ -105,6 +101,11 @@ certo_direct: login: 1 password: vP6OwK3 +# Fully working credentials +checkout_finland: + merchant: '375917' + secret: 'SAIPPUAKAUPPIAS' + conekta: key: key_eYvWV7gSDkNYXsmr diff --git a/test/remote/remote_checkout_finland_test.rb b/test/remote/remote_checkout_finland_test.rb new file mode 100644 index 000000000..c408a9246 --- /dev/null +++ b/test/remote/remote_checkout_finland_test.rb @@ -0,0 +1,61 @@ +require 'test_helper' +require 'remote_test_helper' + +class RemoteCheckoutFinlandTest < Test::Unit::TestCase + include RemoteTestHelper + + def setup + @stamp = Time.now.to_i.to_s # Unique identifier for the payment with all information + @stamp2 = (Time.now.to_i+1000).to_s # Unique identifier for the payment with minimal information + @merchant = fixtures(:checkout_finland)[:merchant] + @secret = fixtures(:checkout_finland)[:secret] + end + + def test_valid_payment_page_minimal_fields + payment_page = submit %( + <% payment_service_for('#{@stamp}', '#{@merchant}', :service => :checkout_finland, :amount => '200', :currency => 'EUR', :credential2 => '#{@secret}') do |service| %> + <% service.language = 'FI' %> # Payment page language 2 character ISO code. + <% service.reference = '123123123' %> # Payment reference number. 20 digits max. + <% service.content = '1' %> # '1' for normal and '2' for adult payments. + <% service.delivery_date = '20140110' %> # Delivery date in the form of YYYYMMDD + <% service.notify_url = 'http://example.org/return' %> # Notify URL + <% service.reject_url = 'http://example.org/return' %> # Reject URL + <% service.return_url = 'http://example.org/return' %> # Return URL + <% service.cancel_return_url = 'http://example.org/return' %> # Cancel URL + <% end %> + ) + + assert_match(%r(Testi Oy)i, payment_page.body) + assert_match(%r(Testikuja 1)i, payment_page.body) + assert_match(%r(12345 Testi)i, payment_page.body) + end + + def test_valid_payment_page_all_fields + payment_page = submit %( + <% payment_service_for('#{@stamp2}', '#{@merchant}', :service => :checkout_finland, :amount => '200', :currency => 'EUR',:credential2 => '#{@secret}') do |service| %> + <% service.customer :first_name => "Tero", # Optional customer information + :last_name => 'Testaaja', + :phone => '0800 552 010', + :email => 'support@checkout.fi' %> + <% service.language = 'FI' %> + <% service.billing_address :address1 => 'Testikatu 1 A 10', # Optional billing address + :city => 'Helsinki', + :zip => '00100', + :country => 'FIN' %> + <% service.reference = '123123123' %> + <% service.content = '1' %> + <% service.delivery_date = '20140110' %> + <% service.description = 'Remote test items' %> + <% service.notify_url = 'http://example.org/return' %> + <% service.reject_url = 'http://example.org/return' %> + <% service.return_url = 'http://example.org/return' %> + <% service.cancel_return_url = 'http://example.org/return' %> + <% end %> + ) + + assert_match(%r(Tero Testaaja)i, payment_page.body) + assert_match(%r(Testikatu 1 A 10)i, payment_page.body) + assert_match(%r(Remote test items)i, payment_page.body) + end + +end diff --git a/test/unit/action_view_helper_test.rb b/test/unit/action_view_helper_test.rb index a30262a57..d6a7e9d32 100644 --- a/test/unit/action_view_helper_test.rb +++ b/test/unit/action_view_helper_test.rb @@ -38,9 +38,7 @@ class ActionView::Base include ActionView::Helpers::TextHelper end - ::MissingSourceFile::REGEXPS << [/^cannot load such file -- (.+)$/i, 1] class PaymentServiceController < ActionController::Base - def payment_action render :inline => "<% payment_service_for('order-1','test', :service => :bogus){} %>" end diff --git a/test/unit/integrations/a1agregator/a1agregator_notification_test.rb b/test/unit/integrations/a1agregator/a1agregator_notification_test.rb index 8210f961c..19f478782 100644 --- a/test/unit/integrations/a1agregator/a1agregator_notification_test.rb +++ b/test/unit/integrations/a1agregator/a1agregator_notification_test.rb @@ -23,7 +23,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(10000, 'RUB'), @a1agregator.amount + assert_equal Money.from_amount(100.00, 'RUB'), @a1agregator.amount end def test_acknowledgement diff --git a/test/unit/integrations/authorize_net_sim/authorize_net_sim_notification_test.rb b/test/unit/integrations/authorize_net_sim/authorize_net_sim_notification_test.rb index e8732ca49..05141366c 100644 --- a/test/unit/integrations/authorize_net_sim/authorize_net_sim_notification_test.rb +++ b/test/unit/integrations/authorize_net_sim/authorize_net_sim_notification_test.rb @@ -26,7 +26,7 @@ def test_accessors_when_not_set end def test_compositions - assert_equal Money.new(12100, 'USD'), @authorize_net_sim.amount + assert_equal Money.from_amount(121.00, 'USD'), @authorize_net_sim.amount end def test_accessors_when_set diff --git a/test/unit/integrations/bit_pay/bit_pay_helper_test.rb b/test/unit/integrations/bit_pay/bit_pay_helper_test.rb index be1ddddb0..c97dc2d40 100644 --- a/test/unit/integrations/bit_pay/bit_pay_helper_test.rb +++ b/test/unit/integrations/bit_pay/bit_pay_helper_test.rb @@ -50,10 +50,4 @@ def test_form_fields_uses_invoice_id assert_equal '98kui1gJ7FocK41gUaBZxG', @helper.form_fields['id'] end - - def test_raises_when_invalid_json_returned - Net::HTTP.any_instance.expects(:request).returns(stub(:body => 'Invalid JSON')) - - assert_raises(ActionViewHelperError) { @helper.form_fields['id'] } - end end diff --git a/test/unit/integrations/bit_pay/bit_pay_notification_test.rb b/test/unit/integrations/bit_pay/bit_pay_notification_test.rb index 394571584..ece1a2aad 100644 --- a/test/unit/integrations/bit_pay/bit_pay_notification_test.rb +++ b/test/unit/integrations/bit_pay/bit_pay_notification_test.rb @@ -17,22 +17,8 @@ def test_accessors assert_equal 123, @bit_pay.item_id end - def test_invalid_data - hash = JSON.parse(http_raw_data) - @bit_pay = BitPay::Notification.new('{"invalid":json}') - - assert @bit_pay.params.empty? - end - - def test_item_id_invalid_json - hash = JSON.parse(http_raw_data) - @bit_pay = BitPay::Notification.new(hash.merge('posData' => 'Invalid JSON').to_json) - - assert_nil @bit_pay.item_id - end - def test_compositions - assert_equal Money.new(1000, 'USD'), @bit_pay.amount + assert_equal Money.from_amount(10.00, 'USD'), @bit_pay.amount end def test_successful_acknowledgement @@ -45,11 +31,6 @@ def test_acknowledgement_error assert !@bit_pay.acknowledge end - def test_acknowledgement_invalid_json - Net::HTTP.any_instance.expects(:request).returns(stub(:body => '{invalid json')) - assert !@bit_pay.acknowledge - end - private def http_raw_data { diff --git a/test/unit/integrations/checkout_finland/checkout_finland_helper_test.rb b/test/unit/integrations/checkout_finland/checkout_finland_helper_test.rb new file mode 100644 index 000000000..ae989c3a3 --- /dev/null +++ b/test/unit/integrations/checkout_finland/checkout_finland_helper_test.rb @@ -0,0 +1,77 @@ +# encoding: UTF-8 +require 'test_helper' + +class CheckoutFinlandHelperTest < Test::Unit::TestCase + include OffsitePayments::Integrations + + def setup + @helper = CheckoutFinland::Helper.new('1389003386','375917', :amount => 200, :currency => 'EUR', :credential2 => "SAIPPUAKAUPPIAS") + end + + def test_basic_helper_fields + assert_field 'MERCHANT', '375917' + + assert_field 'AMOUNT', '200' + assert_field 'STAMP', '1389003386' + end + + def test_customer_fields + @helper.customer :first_name => 'Tero', :last_name => 'Testaaja', :phone => '0800 552 010', :email => 'support@checkout.fi' + assert_field 'FIRSTNAME', 'Tero' + assert_field 'FAMILYNAME', 'Testaaja' + assert_field 'PHONE', '0800 552 010' + assert_field 'EMAIL', 'support@checkout.fi' + end + + def test_address_mapping + @helper.billing_address :address1 => 'Testikatu 1 A 10', + :city => 'Helsinki', + :zip => '00100', + :country => 'FIN' + + assert_field 'ADDRESS', 'Testikatu 1 A 10' + assert_field 'POSTOFFICE', 'Helsinki' + assert_field 'POSTCODE', '00100' + assert_field 'COUNTRY', 'FIN' + end + + def test_authcode_generation + @helper.customer :first_name => 'Tero', :last_name => 'Testaaja', :phone => '0800 552 010', :email => 'support@checkout.fi' + @helper.billing_address :address1 => 'Testikatu 1 A 10', + :city => 'Helsinki', + :zip => '00100', + :country => 'FIN' + + @helper.reference = "474738238" + @helper.language = "FI" + @helper.content = "1" + @helper.delivery_date = "20140110" + @helper.description = "Some items" + + @helper.notify_url = "http://www.example.com/notify" + @helper.reject_url = "http://www.example.com/reject" + @helper.return_url = "http://www.example.com/return" + @helper.cancel_return_url = "http://www.example.com/cancel" + + assert_equal @helper.generate_md5string, "0968BCF2A747F4A9118A889C8EC5CDA3" + + end + + def test_unknown_address_mapping + @helper.billing_address :farm => 'CA' + assert_equal 8, @helper.fields.size + end + + def test_unknown_mapping + assert_nothing_raised do + @helper.company_address :address => '500 Dwemthy Fox Road' + end + end + + def test_setting_invalid_address_field + fields = @helper.fields.dup + @helper.billing_address :street => 'My Street' + assert_equal fields, @helper.fields + end + +end diff --git a/test/unit/integrations/checkout_finland/checkout_finland_module_test.rb b/test/unit/integrations/checkout_finland/checkout_finland_module_test.rb new file mode 100644 index 000000000..062c5d127 --- /dev/null +++ b/test/unit/integrations/checkout_finland/checkout_finland_module_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' + +class CheckoutFinlandModuleTest < Test::Unit::TestCase + include OffsitePayments::Integrations + + def test_notification_method + assert_instance_of CheckoutFinland::Notification, CheckoutFinland.notification({"VERSION" => "0001", "STAMP" => "1388998411", "REFERENCE" => "474738238", "PAYMENT" => "12288575", "STATUS" => "3", "ALGORITHM" => "3", "MAC" =>"2657BA96CC7879C79192547EB6C9D4082EA39CA52FE1DAD09CB1C632ECFDAE67"}) + end +end diff --git a/test/unit/integrations/checkout_finland/checkout_finland_notification_test.rb b/test/unit/integrations/checkout_finland/checkout_finland_notification_test.rb new file mode 100644 index 000000000..9662cad0b --- /dev/null +++ b/test/unit/integrations/checkout_finland/checkout_finland_notification_test.rb @@ -0,0 +1,34 @@ +require 'test_helper' + +class CheckoutFinlandNotificationTest < Test::Unit::TestCase + include OffsitePayments::Integrations + + def setup + @checkout_finland = CheckoutFinland::Notification.new(http_params) + end + + def test_accessors + assert_equal false, @checkout_finland.complete? # http_params return data for a delayed payment that is not complete + assert_equal "3", @checkout_finland.status + assert_equal "12288575", @checkout_finland.transaction_id + assert_equal "474738238", @checkout_finland.reference + assert_equal "2657BA96CC7879C79192547EB6C9D4082EA39CA52FE1DAD09CB1C632ECFDAE67", @checkout_finland.mac + assert @checkout_finland.delayed? + assert_equal false, @checkout_finland.activation? + assert_equal false, @checkout_finland.cancelled? + end + + def test_acknowledgement + assert @checkout_finland.acknowledge("SAIPPUAKAUPPIAS") + end + + def test_faulty_acknowledgement + # Same data different (invalid) authcode + assert_equal false, @checkout_finland.acknowledge("LOREMIPSUM") + end + + private + def http_params + {"VERSION" => "0001", "STAMP" => "1388998411", "REFERENCE" => "474738238", "PAYMENT" => "12288575", "STATUS" => "3", "ALGORITHM" => "3", "MAC" =>"2657BA96CC7879C79192547EB6C9D4082EA39CA52FE1DAD09CB1C632ECFDAE67"} + end +end diff --git a/test/unit/integrations/chronopay/chronopay_notification_test.rb b/test/unit/integrations/chronopay/chronopay_notification_test.rb index 25b387ee5..11bf80c77 100644 --- a/test/unit/integrations/chronopay/chronopay_notification_test.rb +++ b/test/unit/integrations/chronopay/chronopay_notification_test.rb @@ -41,7 +41,7 @@ def test_parse_received_at end def test_compositions - assert_equal Money.new(50000, 'CAD'), @notification.amount + assert_equal Money.from_amount(500.00, 'CAD'), @notification.amount end def test_payment_successful_status diff --git a/test/unit/integrations/citrus/citrus_notification_test.rb b/test/unit/integrations/citrus/citrus_notification_test.rb index ff8f918ad..21006d4e4 100644 --- a/test/unit/integrations/citrus/citrus_notification_test.rb +++ b/test/unit/integrations/citrus/citrus_notification_test.rb @@ -12,7 +12,6 @@ def test_accessors assert_equal "Completed", @citrus.status assert_equal "CTX1309180549472058821", @citrus.transaction_id assert_equal "SUCCESS", @citrus.transaction_status - assert_equal "10.00", @citrus.amount assert_equal "INR", @citrus.currency assert_equal true, @citrus.invoice_ok?('ORD427') assert_equal true, @citrus.amount_ok?(BigDecimal.new('10.00')) @@ -24,7 +23,7 @@ def test_accessors end def test_compositions - assert_equal '10.00', @citrus.amount + assert_equal Money.from_amount(10.00, @citrus.currency), @citrus.amount end def test_acknowledgement diff --git a/test/unit/integrations/citrus/citrus_return_test.rb b/test/unit/integrations/citrus/citrus_return_test.rb index 2d040265e..25f0be68d 100644 --- a/test/unit/integrations/citrus/citrus_return_test.rb +++ b/test/unit/integrations/citrus/citrus_return_test.rb @@ -44,7 +44,7 @@ def test_return_has_notification assert_equal "Completed", notification.status assert_equal "CTX1309180549472058821", notification.transaction_id assert_equal "SUCCESS", notification.transaction_status - assert_equal "10.00", notification.amount + assert_equal Money.from_amount(BigDecimal.new(10), "inr"), notification.amount assert_equal "INR", notification.currency assert_equal true, notification.invoice_ok?('ORD427') assert_equal true, notification.amount_ok?(BigDecimal.new('10.00')) diff --git a/test/unit/integrations/coinbase/coinbase_return_test.rb b/test/unit/integrations/coinbase/coinbase_return_test.rb index af3307ba4..e3c5308b9 100644 --- a/test/unit/integrations/coinbase/coinbase_return_test.rb +++ b/test/unit/integrations/coinbase/coinbase_return_test.rb @@ -23,12 +23,24 @@ def test_invalid_return assert coinbase_return.success? end + def test_catch_nil_params + Net::HTTP.any_instance.expects(:request).returns(stub(:body => http_raw_data_missing_order_key)) + coinbase_return = Coinbase::Return.new(valid_query_string, @options) + + assert !coinbase_return.notification.acknowledge + assert coinbase_return.success? + end + private def valid_http_raw_data '{"order":{"id":"OQJ836AF","created_at":"2014-07-09T07:00:33-07:00","status":"completed","event":{"type":"completed"},"total_btc":{"cents":1463,"currency_iso":"BTC"},"total_native":{"cents":1,"currency_iso":"CAD"},"total_payout":{"cents":0,"currency_iso":"USD"},"custom":"10","receive_address":"19FuVxoEvVLxRibVnmSciNXEsfgFs8W29Z","button":{"type":"buy_now","name":"Shop One - #10","description":null,"id":"0c3e9c6dd38619a2ba11b4561631e6ad"},"refund_address":"1BmmrdqcLqGCtx54vvencNS8VCMsjJCEBA","transaction":{"id":"53bd4b0e86fd2456d8000003","hash":null,"confirmations":0}}}' end + def http_raw_data_missing_order_key + JSON.parse(valid_http_raw_data).reject { |key, value| key == 'order' }.to_json + end + def valid_query_string 'utm_nooverride=1&order%5Bbutton%5D%5Bdescription%5D=&order%5Bbutton%5D%5Bid%5D=0c3e9c6dd38619a2ba11b4561631e6ad&order%5Bbutton%5D%5Bname%5D=Shop+One+-+%2310&order%5Bbutton%5D%5Btype%5D=buy_now&order%5Bcreated_at%5D=2014-07-09+07%3A00%3A33+-0700&order%5Bcustom%5D=10&order%5Bevent%5D=&order%5Bid%5D=OQJ836AF&order%5Breceive_address%5D=19FuVxoEvVLxRibVnmSciNXEsfgFs8W29Z&order%5Brefund_address%5D=1BmmrdqcLqGCtx54vvencNS8VCMsjJCEBA&order%5Bstatus%5D=completed&order%5Btotal_btc%5D%5Bcents%5D=1463&order%5Btotal_btc%5D%5Bcurrency_iso%5D=BTC&order%5Btotal_native%5D%5Bcents%5D=1&order%5Btotal_native%5D%5Bcurrency_iso%5D=CAD&order%5Btotal_payout%5D%5Bcents%5D=0&order%5Btotal_payout%5D%5Bcurrency_iso%5D=USD&order%5Btransaction%5D%5Bconfirmations%5D=0&order%5Btransaction%5D%5Bhash%5D=&order%5Btransaction%5D%5Bid%5D=53bd4b0e86fd2456d8000003' end @@ -36,4 +48,5 @@ def valid_query_string def invalid_query_string 'utm_nooverride=1&order%5Bbutton%5D%5Bdescription%5D=&order%5Bbutton%5D%5Bid%5D=0c3e9c6dd38619a2ba11b4561631e6ad&order%5Bbutton%5D%5Bname%5D=Shop+One+-+%2310&order%5Bbutton%5D%5Btype%5D=buy_now&order%5Bcreated_at%5D=2014-07-09+07%3A00%3A33+-0700&order%5Bcustom%5D=10&order%5Bevent%5D=&order%5Bid%5D=OQJ836AF&order%5Breceive_address%5D=19FuVxoEvVLxRibVnmSciNXEsfgFs8W29Z&order%5Brefund_address%5D=1BmmrdqcLqGCtx54vvencNS8VCMsjJCEBA&order%5Bstatus%5D=failed&order%5Btotal_btc%5D%5Bcents%5D=1463&order%5Btotal_btc%5D%5Bcurrency_iso%5D=BTC&order%5Btotal_native%5D%5Bcents%5D=1&order%5Btotal_native%5D%5Bcurrency_iso%5D=CAD&order%5Btotal_payout%5D%5Bcents%5D=0&order%5Btotal_payout%5D%5Bcurrency_iso%5D=USD&order%5Btransaction%5D%5Bconfirmations%5D=0&order%5Btransaction%5D%5Bhash%5D=&order%5Btransaction%5D%5Bid%5D=53bd4b0e86fd2456d8000003' end + end diff --git a/test/unit/integrations/direc_pay/direc_pay_notification_test.rb b/test/unit/integrations/direc_pay/direc_pay_notification_test.rb index eb68a70a4..7a7a91176 100644 --- a/test/unit/integrations/direc_pay/direc_pay_notification_test.rb +++ b/test/unit/integrations/direc_pay/direc_pay_notification_test.rb @@ -37,7 +37,7 @@ def test_error end def test_compositions - assert_equal Money.new(100, 'INR'), @direc_pay.amount + assert_equal Money.from_amount(1.00, 'INR'), @direc_pay.amount end def test_acknowledgement diff --git a/test/unit/integrations/direc_pay/direc_pay_return_test.rb b/test/unit/integrations/direc_pay/direc_pay_return_test.rb index d23cf375b..5e6721233 100644 --- a/test/unit/integrations/direc_pay/direc_pay_return_test.rb +++ b/test/unit/integrations/direc_pay/direc_pay_return_test.rb @@ -28,7 +28,7 @@ def test_return_has_notification assert_equal '1001', notification.item_id assert_equal '1.00', notification.gross assert_equal 100, notification.gross_cents - assert_equal Money.new(100, 'INR'), notification.amount + assert_equal Money.from_amount(1.00, 'INR'), notification.amount assert_equal 'INR', notification.currency assert_equal 'IND', notification.country assert_equal 'NULL', notification.other_details diff --git a/test/unit/integrations/directebanking/directebanking_notification_test.rb b/test/unit/integrations/directebanking/directebanking_notification_test.rb index 94460782f..3c96fe808 100644 --- a/test/unit/integrations/directebanking/directebanking_notification_test.rb +++ b/test/unit/integrations/directebanking/directebanking_notification_test.rb @@ -19,7 +19,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(100, 'EUR'), @deb.amount + assert_equal Money.from_amount(1.00, 'EUR'), @deb.amount end def test_acknowledgement diff --git a/test/unit/integrations/dotpay/dotpay_notification_test.rb b/test/unit/integrations/dotpay/dotpay_notification_test.rb index efec044cd..92724a737 100644 --- a/test/unit/integrations/dotpay/dotpay_notification_test.rb +++ b/test/unit/integrations/dotpay/dotpay_notification_test.rb @@ -30,7 +30,7 @@ def test_accessors_error end def test_compositions - assert_equal Money.new(15000, 'PLN'), @dotpay.amount + assert_equal Money.from_amount(150.00, 'PLN'), @dotpay.amount end # Replace with real successful acknowledgement code diff --git a/test/unit/integrations/dwolla/dwolla_notification_test.rb b/test/unit/integrations/dwolla/dwolla_notification_test.rb index 6b1a5cb16..a05e47e2e 100644 --- a/test/unit/integrations/dwolla/dwolla_notification_test.rb +++ b/test/unit/integrations/dwolla/dwolla_notification_test.rb @@ -28,7 +28,7 @@ def test_error_accessors end def test_compositions - assert_equal Money.new(1, 'USD'), @success.amount + assert_equal Money.from_amount(0.01, 'USD'), @success.amount end # Replace with real successful acknowledgement code diff --git a/test/unit/integrations/easy_pay/easy_pay_notification_test.rb b/test/unit/integrations/easy_pay/easy_pay_notification_test.rb index f9912f44a..578536775 100644 --- a/test/unit/integrations/easy_pay/easy_pay_notification_test.rb +++ b/test/unit/integrations/easy_pay/easy_pay_notification_test.rb @@ -14,7 +14,7 @@ def test_accessors end def test_compositions - assert_equal BigDecimal.new("100"), @easypay.amount + assert_equal Money.from_amount(BigDecimal.new('100.00'), 'BYR'), @easypay.amount end def test_credential2_required diff --git a/test/unit/integrations/epay/epay_notification_test.rb b/test/unit/integrations/epay/epay_notification_test.rb index c25774c53..29922a282 100644 --- a/test/unit/integrations/epay/epay_notification_test.rb +++ b/test/unit/integrations/epay/epay_notification_test.rb @@ -18,7 +18,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(398750, 'DKK'), @epay.amount + assert_equal Money.from_amount(3987.50, 'DKK'), @epay.amount end def test_acknowledgement diff --git a/test/unit/integrations/first_data/first_data_notification_test.rb b/test/unit/integrations/first_data/first_data_notification_test.rb index 11c6142ae..4e61c6adc 100644 --- a/test/unit/integrations/first_data/first_data_notification_test.rb +++ b/test/unit/integrations/first_data/first_data_notification_test.rb @@ -28,7 +28,7 @@ def test_accessors_when_not_set end def test_compositions - assert_equal Money.new(12100, 'USD'), @first_data.amount + assert_equal Money.from_amount(121.00, 'USD'), @first_data.amount end def test_accessors_when_set diff --git a/test/unit/integrations/gestpay/gestpay_notification_test.rb b/test/unit/integrations/gestpay/gestpay_notification_test.rb index 316ce716e..2efb99f56 100644 --- a/test/unit/integrations/gestpay/gestpay_notification_test.rb +++ b/test/unit/integrations/gestpay/gestpay_notification_test.rb @@ -13,7 +13,7 @@ def test_successful_notification assert_equal "1000", notification.item_id assert_equal "1234.56", notification.gross assert_equal "EUR", notification.currency - assert_equal Money.new(123456, 'EUR'), notification.amount + assert_equal Money.from_amount(1234.56, 'EUR'), notification.amount end def test_failed_notification diff --git a/test/unit/integrations/hi_trust/hi_trust_helper_test.rb b/test/unit/integrations/hi_trust/hi_trust_helper_test.rb index 3e5ad5a0b..1c7b40932 100644 --- a/test/unit/integrations/hi_trust/hi_trust_helper_test.rb +++ b/test/unit/integrations/hi_trust/hi_trust_helper_test.rb @@ -12,11 +12,5 @@ def test_basic_helper_fields assert_field 'amount', '500' assert_field 'ordernumber', 'order-500' assert_field 'currency', 'USD' - assert_field 'depositflag', '0' - end - - def test_depositflag_option - @helper = HiTrust::Helper.new('order-500','cody@example.com', :amount => 500, :currency => 'USD', :deposit_flag => '1') - assert_field 'depositflag', '1' end end diff --git a/test/unit/integrations/hi_trust/hi_trust_notification_test.rb b/test/unit/integrations/hi_trust/hi_trust_notification_test.rb index 00d99d873..ab158a67b 100644 --- a/test/unit/integrations/hi_trust/hi_trust_notification_test.rb +++ b/test/unit/integrations/hi_trust/hi_trust_notification_test.rb @@ -24,7 +24,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(500, 'USD'), @notification.amount + assert_equal Money.from_amount(5.00, 'USD'), @notification.amount end def test_send_acknowledgement diff --git a/test/unit/integrations/ipay88/ipay88_helper_test.rb b/test/unit/integrations/ipay88/ipay88_helper_test.rb index 8e173570f..5a4ea8dd1 100644 --- a/test/unit/integrations/ipay88/ipay88_helper_test.rb +++ b/test/unit/integrations/ipay88/ipay88_helper_test.rb @@ -106,9 +106,9 @@ def test_sig_components_amount_doesnt_include_decimal_points assert_equal "abcipay88merchcodeorder-5001234MYR", @helper.send(:sig_components) @helper.amount = 1000 assert_equal "abcipay88merchcodeorder-5001000MYR", @helper.send(:sig_components) - @helper.amount = Money.new(90) + @helper.amount = Money.from_amount(0.90) assert_equal "abcipay88merchcodeorder-500090MYR", @helper.send(:sig_components) - @helper.amount = Money.new(1000) + @helper.amount = Money.from_amount(10.00) assert_equal "abcipay88merchcodeorder-5001000MYR", @helper.send(:sig_components) end diff --git a/test/unit/integrations/klarna/klarna_helper_test.rb b/test/unit/integrations/klarna/klarna_helper_test.rb index 2fb140ab6..49d1532c1 100644 --- a/test/unit/integrations/klarna/klarna_helper_test.rb +++ b/test/unit/integrations/klarna/klarna_helper_test.rb @@ -7,7 +7,7 @@ def setup @order_id = 1 @credential1 = "Example Merchant ID" @options = { - :amount => Money.new(10.00), + :amount => Money.from_amount(0.10), :currency => 'SEK', :country => 'SE', :account_name => 'Example Shop Name', diff --git a/test/unit/integrations/klarna/klarna_notification_test.rb b/test/unit/integrations/klarna/klarna_notification_test.rb index f862a9948..f6c880c4b 100644 --- a/test/unit/integrations/klarna/klarna_notification_test.rb +++ b/test/unit/integrations/klarna/klarna_notification_test.rb @@ -24,7 +24,7 @@ def test_x2ness_of_gross_amount end def test_compositions - assert_equal Money.new(5000, 'SEK'), @klarna.amount + assert_equal Money.from_amount(50.00, 'SEK'), @klarna.amount end def test_acknowledge diff --git a/test/unit/integrations/mollie/mollie_test.rb b/test/unit/integrations/mollie/mollie_test.rb index 5ae4f8020..1ac308199 100644 --- a/test/unit/integrations/mollie/mollie_test.rb +++ b/test/unit/integrations/mollie/mollie_test.rb @@ -4,20 +4,49 @@ class MollieTest < Test::Unit::TestCase include OffsitePayments::Integrations def setup - @token = 'test_8isBjQoJXJoiXRSzjhwKPO1Bo9AkVA' - JSON.stubs(:parse).returns(CREATE_PAYMENT_RESPONSE_JSON) + @api = Mollie::API.new('test_8isBjQoJXJoiXRSzjhwKPO1Bo9AkVA') + @api_response = { + "id" => "tr_djsfilasX", + "mode" => "test", + "createdDatetime" => "2014-03-03T10:17:05.0Z", + "status" => "open", + "amount" => "500.00", + "description" => "My order description", + "method" => "ideal", + "metadata" => { + "my_reference" => "unicorn" + }, + "details" => nil, + "links" => { + "paymentUrl" => "https://www.mollie.com/paymentscreen/mistercash/testmode/ca8195a3dc8d5cbf2f7b130654abe5a5", + "redirectUrl" => "https://example.com/return" + } + }.to_json end def test_get_request - @request = Mollie::API.new(@token).get_request("payments/#{@payment_id}") - assert_equal 'tr_djsfilasX', @request['id'] - assert_equal '500.00', @request['amount'] - assert_equal 'https://example.com/return', @request['links']['redirectUrl'] + @api + .expects(:ssl_request) + .with( + :get, + 'https://api.mollie.nl/v1/payments/tr_QkwjRvZBzH', + nil, + { + "Authorization" => "Bearer test_8isBjQoJXJoiXRSzjhwKPO1Bo9AkVA", + "Content-Type" => "application/json" + } + ) + .returns(@api_response) + + response = @api.get_request("payments/tr_QkwjRvZBzH") + + assert_equal 'tr_djsfilasX', response['id'] + assert_equal '500.00', response['amount'] + assert_equal 'https://example.com/return', response['links']['redirectUrl'] end def test_post_request - @payment_id ='tr_QkwjRvZBzH' - params = { + params = { :amount => BigDecimal.new('123.45'), :description => 'My order description', :redirectUrl => 'https://example.com/return', @@ -25,29 +54,24 @@ def test_post_request :issuer => 'ideal_TESTNL99', :metadata => { :my_reference => 'unicorn' } } - @request = Mollie::API.new(@token).post_request('payments', params) - assert_equal 'tr_djsfilasX', @request['id'] - assert_equal '500.00', @request['amount'] - assert_equal 'https://example.com/return', @request['links']['redirectUrl'] - end - CREATE_PAYMENT_RESPONSE_JSON = JSON.parse(<<-JSON) - { - "id":"tr_djsfilasX", - "mode":"test", - "createdDatetime":"2014-03-03T10:17:05.0Z", - "status":"open", - "amount":"500.00", - "description":"My order description", - "method":"ideal", - "metadata":{ - "my_reference":"unicorn" - }, - "details":null, - "links":{ - "paymentUrl":"https://www.mollie.com/paymentscreen/mistercash/testmode/ca8195a3dc8d5cbf2f7b130654abe5a5", - "redirectUrl":"https://example.com/return" - } - } - JSON -end \ No newline at end of file + @api + .expects(:ssl_request) + .with( + :post, + 'https://api.mollie.nl/v1/payments', + params.to_json, + { + "Authorization" => "Bearer test_8isBjQoJXJoiXRSzjhwKPO1Bo9AkVA", + "Content-Type" => "application/json" + } + ) + .returns(@api_response) + + + response = @api.post_request('payments', params) + assert_equal 'tr_djsfilasX', response['id'] + assert_equal '500.00', response['amount'] + assert_equal 'https://example.com/return', response['links']['redirectUrl'] + end +end diff --git a/test/unit/integrations/mollie_ideal/mollie_ideal_notification_test.rb b/test/unit/integrations/mollie_ideal/mollie_ideal_notification_test.rb index 3c0d5b67d..be70b199c 100644 --- a/test/unit/integrations/mollie_ideal/mollie_ideal_notification_test.rb +++ b/test/unit/integrations/mollie_ideal/mollie_ideal_notification_test.rb @@ -24,7 +24,7 @@ def test_acknowledgement_sets_params assert_equal "EUR", @notification.currency assert_equal 12345, @notification.gross_cents assert_equal "123.45", @notification.gross - assert_equal Money.new(12345, 'EUR'), @notification.amount + assert_equal Money.from_amount(123.45, 'EUR'), @notification.amount assert_equal "123", @notification.item_id end diff --git a/test/unit/integrations/mollie_mistercash/mollie_mistercash_notification_test.rb b/test/unit/integrations/mollie_mistercash/mollie_mistercash_notification_test.rb index b1b5df10b..4d36793c1 100644 --- a/test/unit/integrations/mollie_mistercash/mollie_mistercash_notification_test.rb +++ b/test/unit/integrations/mollie_mistercash/mollie_mistercash_notification_test.rb @@ -24,7 +24,7 @@ def test_acknowledgement_sets_params assert_equal "EUR", @notification.currency assert_equal 12345, @notification.gross_cents assert_equal "123.45", @notification.gross - assert_equal Money.new(12345, 'EUR'), @notification.amount + assert_equal Money.from_amount(123.45, 'EUR'), @notification.amount assert_equal "123", @notification.item_id end diff --git a/test/unit/integrations/molpay/molpay_helper_test.rb b/test/unit/integrations/molpay/molpay_helper_test.rb index b6bf261a4..729619dca 100644 --- a/test/unit/integrations/molpay/molpay_helper_test.rb +++ b/test/unit/integrations/molpay/molpay_helper_test.rb @@ -36,7 +36,7 @@ def test_product_fields end def test_supported_currency - ['MYR', 'USD', 'SGD', 'PHP', 'VND', 'IDR', 'AUD'].each do |cur| + ['MYR', 'USD', 'SGD', 'PHP', 'VND', 'IDR', 'AUD', 'CNY', 'THB', 'GBP', 'EUR', 'HKD'].each do |cur| @helper.currency cur assert_field "cur", cur end @@ -65,6 +65,11 @@ def test_return_url @helper.return_url "http://www.example.com" assert_field "returnurl", "http://www.example.com" end + + def test_notify_url + @helper.notify_url "http://www.example.com" + assert_field "callbackurl", "http://www.example.com" + end def test_signature assert_equal '16d122e1cf4d4fac19f3c839db12b6a5', @helper.form_fields["vcode"] diff --git a/test/unit/integrations/molpay/molpay_notification_test.rb b/test/unit/integrations/molpay/molpay_notification_test.rb index 60f0076cf..d1b33b68f 100644 --- a/test/unit/integrations/molpay/molpay_notification_test.rb +++ b/test/unit/integrations/molpay/molpay_notification_test.rb @@ -33,11 +33,17 @@ def test_transaction_test molpay = Molpay::Notification.new(http_raw_data(:test), :credential2 => @secret_key) assert molpay.test? end - + def test_acknowledgement assert @molpay.acknowledge end + def test_result_pending + molpay = Molpay::Notification.new("status=22") + assert !molpay.complete? + assert_equal "Pending", molpay.status + end + def test_unsuccessful_acknowledge_due_to_signature molpay = Molpay::Notification.new(http_raw_data(:invalid_skey), :credential2 => @secret_key) refute molpay.acknowledge @@ -82,7 +88,6 @@ def http_raw_data(mode=:success) r = ['amount','appcode','error_code', 'error_desc', 'skey'] basedata.reject{|k| r.include?(k)}.collect {|k,v| "#{k}=#{CGI.escape(v.to_s)}"}.join('&') end - end def generate_signature diff --git a/test/unit/integrations/molpay/molpay_return_test.rb b/test/unit/integrations/molpay/molpay_return_test.rb index 01414c8f5..684f4fb07 100644 --- a/test/unit/integrations/molpay/molpay_return_test.rb +++ b/test/unit/integrations/molpay/molpay_return_test.rb @@ -15,11 +15,16 @@ def test_success? end def test_failed? + Molpay::Notification.any_instance.stubs(:ssl_post).returns('DECLINED') molpay = Molpay::Return.new('', :credential2 => @secret) - refute molpay.success? end + def test_pending? + molpay = Molpay::Return.new('status=22', :credential2 => @secret) + assert molpay.pending? + end + private def query_data diff --git a/test/unit/integrations/moneybookers/moneybookers_module_test.rb b/test/unit/integrations/moneybookers/moneybookers_module_test.rb index ccea73b2f..31405de57 100644 --- a/test/unit/integrations/moneybookers/moneybookers_module_test.rb +++ b/test/unit/integrations/moneybookers/moneybookers_module_test.rb @@ -8,7 +8,7 @@ def test_notification_method end def test_service_url - url = 'https://www.moneybookers.com/app/payment.pl' + url = 'https://pay.skrill.com' assert_equal url, Moneybookers.service_url end end diff --git a/test/unit/integrations/moneybookers/moneybookers_notification_test.rb b/test/unit/integrations/moneybookers/moneybookers_notification_test.rb index 0bde6807e..042519b0d 100644 --- a/test/unit/integrations/moneybookers/moneybookers_notification_test.rb +++ b/test/unit/integrations/moneybookers/moneybookers_notification_test.rb @@ -20,7 +20,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(3960, 'EUR'), @moneybookers.amount + assert_equal Money.from_amount(39.60, 'EUR'), @moneybookers.amount end def test_respond_to_acknowledge diff --git a/test/unit/integrations/nochex/nochex_notification_test.rb b/test/unit/integrations/nochex/nochex_notification_test.rb index 312eaebca..8df5f2efd 100644 --- a/test/unit/integrations/nochex/nochex_notification_test.rb +++ b/test/unit/integrations/nochex/nochex_notification_test.rb @@ -19,7 +19,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(3166, 'GBP'), @nochex.amount + assert_equal Money.from_amount(31.66, 'GBP'), @nochex.amount end def test_successful_acknowledgement diff --git a/test/unit/integrations/pag_seguro/pag_seguro_notification_test.rb b/test/unit/integrations/pag_seguro/pag_seguro_notification_test.rb index 11405d617..decd4dea4 100644 --- a/test/unit/integrations/pag_seguro/pag_seguro_notification_test.rb +++ b/test/unit/integrations/pag_seguro/pag_seguro_notification_test.rb @@ -27,7 +27,7 @@ def test_accessors def test_compositions Net::HTTP.expects(:get_response).with(@uri).returns(stub(code: "200", body: http_raw_data)) pag_seguro = PagSeguro::Notification.new(notification_data) - assert_equal Money.new(4912, 'BRL'), pag_seguro.amount + assert_equal Money.from_amount(49.12, 'BRL'), pag_seguro.amount end def test_respond_to_acknowledge diff --git a/test/unit/integrations/pay_fast/pay_fast_notification_test.rb b/test/unit/integrations/pay_fast/pay_fast_notification_test.rb index 7a064c233..7aaf4414c 100644 --- a/test/unit/integrations/pay_fast/pay_fast_notification_test.rb +++ b/test/unit/integrations/pay_fast/pay_fast_notification_test.rb @@ -14,8 +14,12 @@ def test_accessors assert_equal "Name", @pay_fast.item_name assert_equal "123.00", @pay_fast.gross assert_equal "-2.80", @pay_fast.fee - assert_equal "120.20", @pay_fast.amount assert_equal "10000100", @pay_fast.merchant_id + assert_equal "ZAR", @pay_fast.currency + end + + def test_compositions + assert_equal Money.from_amount(BigDecimal.new('120.20'), 'ZAR'), @pay_fast.amount end def test_acknowledgement diff --git a/test/unit/integrations/paydollar/paydollar_module_test.rb b/test/unit/integrations/paydollar/paydollar_module_test.rb index ed7a4b55d..61aa2cb1d 100644 --- a/test/unit/integrations/paydollar/paydollar_module_test.rb +++ b/test/unit/integrations/paydollar/paydollar_module_test.rb @@ -13,14 +13,14 @@ def test_return_method def test_production_url OffsitePayments.mode = :production - assert_equal 'https://www.paydollar.com/b2c2/eng/payment/payForm.jsp', Paydollar.service_url + assert_equal 'https://www.paydollar.com/b2c2/eng/payment/payShopify.jsp', Paydollar.service_url ensure OffsitePayments.mode = :test end def test_test_url OffsitePayments.mode = :test - assert_equal 'https://test.paydollar.com/b2cDemo/eng/payment/payForm.jsp', Paydollar.service_url + assert_equal 'https://test.paydollar.com/b2cDemo/eng/payment/payShopify.jsp', Paydollar.service_url end def test_currency_map diff --git a/test/unit/integrations/paydollar/paydollar_notification_test.rb b/test/unit/integrations/paydollar/paydollar_notification_test.rb index 06c313a75..ef9c285c2 100644 --- a/test/unit/integrations/paydollar/paydollar_notification_test.rb +++ b/test/unit/integrations/paydollar/paydollar_notification_test.rb @@ -18,7 +18,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(13962, 'HKD'), @paydollar.amount + assert_equal Money.from_amount(139.62, 'HKD'), @paydollar.amount end def test_acknowledgement diff --git a/test/unit/integrations/paypal/paypal_notification_test.rb b/test/unit/integrations/paypal/paypal_notification_test.rb index ca07c44dc..e5f62b45a 100644 --- a/test/unit/integrations/paypal/paypal_notification_test.rb +++ b/test/unit/integrations/paypal/paypal_notification_test.rb @@ -57,7 +57,7 @@ def test_mass_pay_accessors end def test_compositions - assert_equal Money.new(50000, 'CAD'), @paypal.amount + assert_equal Money.from_amount(500.00, 'CAD'), @paypal.amount end def test_acknowledgement diff --git a/test/unit/integrations/paytm/paytm_helper_test.rb b/test/unit/integrations/paytm/paytm_helper_test.rb new file mode 100644 index 000000000..18b5f1db9 --- /dev/null +++ b/test/unit/integrations/paytm/paytm_helper_test.rb @@ -0,0 +1,27 @@ +require 'test_helper' + +class RemotePaytmTest < Test::Unit::TestCase + include OffsitePayments::Integrations + + def setup + @paytm = Paytm::Notification.new(http_raw_data, credential1: 'WorldP64425807474247', credential2: 'kbzk1DSbJiV_O3p5', credential3: 'Retail', credential4: 'worldpressplg') + end + + def test_raw + OffsitePayments.mode = :production + assert_equal 'https://secure.paytm.in/oltp-web/processTransaction', Paytm.service_url + + OffsitePayments.mode = :test + assert_equal 'https://pguat.paytm.com/oltp-web/processTransaction', Paytm.service_url + + assert_nothing_raised do + assert @paytm.checksum_ok? + end + end + + private + + def http_raw_data + 'MID=WorldP64425807474247&ORDERID=100PT012&TXNAMOUNT=10&CURRENCY=INR&TXNID=494157&BANKTXNID=201512236592678&STATUS=TXN_SUCCESS&RESPCODE=01&RESPMSG=Txn Successful&TXNDATE=2015-12-23 16:06:22.0&GATEWAYNAME=ICICI&BANKNAME=ICICI&PAYMENTMODE=CC&MERC_UNQ_REF=100PT012&CHECKSUMHASH=M9E4OLugj4L3TLLiof2BSO03hhQQLMucnVgtYuHi4wIVLB20dCXS632PRw0dTmnAa58R0kEuh9+V3bfQs4F/SrlmsmV+hvhb3Nui1zlnh/o=' + end +end diff --git a/test/unit/integrations/paytm/paytm_module_test.rb b/test/unit/integrations/paytm/paytm_module_test.rb new file mode 100644 index 000000000..eb015eba2 --- /dev/null +++ b/test/unit/integrations/paytm/paytm_module_test.rb @@ -0,0 +1,50 @@ +require 'test_helper' + +class PaytmModuleTest < Test::Unit::TestCase + include OffsitePayments::Integrations + + def setup + OffsitePayments.mode = :test + @merchant_id = 'WorldP64425807474247' + @secret_key = 'kbzk1DSbJiV_O3p5' + @industry_type_id = 'Retail' + @website = 'worldpressplg' + end + + def test_service_url_method + OffsitePayments.mode = :test + assert_equal 'https://pguat.paytm.com/oltp-web/processTransaction', Paytm.service_url + + OffsitePayments.mode = :production + assert_equal 'https://secure.paytm.in/oltp-web/processTransaction', Paytm.service_url + ensure + OffsitePayments.mode = :test + end + + def test_return_method + assert_instance_of Paytm::Return, Paytm.return('name=foo', {}) + end + + def test_notification_method + assert_instance_of Paytm::Notification, Paytm.notification('name=foo', {}) + end + + def test_checksum_method + paytm_load = { 'MID' => @merchant_id, 'ORDER_ID' => '100PT012', 'CUST_ID' => 'test@example.com', 'TXN_AMOUNT' => '10', 'CHANNEL_ID' => 'WEB', 'INDUSTRY_TYPE_ID' => @industry_type_id, 'WEBSITE' => @website, 'MERC_UNQ_REF' => '100PT012', 'CALLBACK_URL' => 'http://www.shopify.com/paytmRes' } + + salt = '1234' + values = paytm_load.sort.to_h.values + values << salt + check_sum = Digest::SHA256.hexdigest(values.join('|')) + salt + ### encrypting checksum ### + aes = OpenSSL::Cipher::AES.new('128-CBC') + aes.encrypt + aes.key = @secret_key + aes.iv = '@@@@&&&&####$$$$' + + encrypted_data = aes.update(check_sum.to_s) + aes.final + checksum = Base64.strict_encode64(encrypted_data) + + assert_equal checksum, Paytm.encrypt(Paytm.checksum(paytm_load, '1234'), @secret_key) + end +end diff --git a/test/unit/integrations/paytm/paytm_notification_test.rb b/test/unit/integrations/paytm/paytm_notification_test.rb new file mode 100644 index 000000000..064792960 --- /dev/null +++ b/test/unit/integrations/paytm/paytm_notification_test.rb @@ -0,0 +1,39 @@ +require 'test_helper' + +class PaytmNotificationTest < Test::Unit::TestCase + include OffsitePayments::Integrations + + def setup + @paytm = Paytm::Notification.new(http_raw_data, credential1: 'WorldP64425807474247', credential2: 'kbzk1DSbJiV_O3p5', credential3: 'Retail', credential4: 'worldpressplg') + end + + def test_accessors + assert @paytm.complete? + assert_equal 'Completed', @paytm.status + assert_equal '494157', @paytm.transaction_id + assert_equal 'TXN_SUCCESS', @paytm.transaction_status + assert_equal '10.00', @paytm.gross + assert_equal 'INR', @paytm.currency + assert_equal true, @paytm.invoice_ok?('100PT012') + assert_equal true, @paytm.amount_ok?(BigDecimal.new('10.00')) + assert_equal 'CC', @paytm.type + assert_equal '100PT012', @paytm.invoice + assert_equal 'WorldP64425807474247', @paytm.account + assert_equal 'M9E4OLugj4L3TLLiof2BSO03hhQQLMucnVgtYuHi4wIVLB20dCXS632PRw0dTmnAa58R0kEuh9+V3bfQs4F/SrlmsmV+hvhb3Nui1zlnh/o=', @paytm.checksum + assert_equal true, @paytm.checksum_ok? + end + + def test_compositions + assert_equal '10.00', @paytm.gross + end + + def test_acknowledgement + assert @paytm.acknowledge + end + + private + + def http_raw_data + 'MID=WorldP64425807474247&ORDERID=100PT012&TXNAMOUNT=10&CURRENCY=INR&TXNID=494157&BANKTXNID=201512236592678&STATUS=TXN_SUCCESS&RESPCODE=01&RESPMSG=Txn Successful&TXNDATE=2015-12-23 16:06:22.0&GATEWAYNAME=ICICI&BANKNAME=ICICI&PAYMENTMODE=CC&MERC_UNQ_REF=100PT012&CHECKSUMHASH=M9E4OLugj4L3TLLiof2BSO03hhQQLMucnVgtYuHi4wIVLB20dCXS632PRw0dTmnAa58R0kEuh9%2bV3bfQs4F/SrlmsmV%2bhvhb3Nui1zlnh/o=' + end +end diff --git a/test/unit/integrations/paytm/paytm_return_test.rb b/test/unit/integrations/paytm/paytm_return_test.rb new file mode 100644 index 000000000..222e20e18 --- /dev/null +++ b/test/unit/integrations/paytm/paytm_return_test.rb @@ -0,0 +1,60 @@ +require 'test_helper' + +class PaytmReturnTest < Test::Unit::TestCase + include OffsitePayments::Integrations + + def setup + @paytm = Paytm::Return.new(http_raw_data_success, credential1: 'WorldP64425807474247', credential2: 'kbzk1DSbJiV_O3p5', credential3: 'Retail', credential4: 'worldpressplg') + end + + def setup_failed_return + @paytm = Paytm::Return.new(http_raw_data_failure, credential1: 'WorldP64425807474247', credential2: 'kbzk1DSbJiV_O3p5', credential3: 'Retail', credential4: 'worldpressplg') + end + + def test_success + assert @paytm.success? + assert_equal 'Completed', @paytm.status('100PT012', '10') + end + + def test_failure_is_successful + setup_failed_return + assert_equal 'Failed', @paytm.status('100PT012', '10') + end + + def test_treat_initial_failures_as_pending + setup_failed_return + assert_equal 'Failed', @paytm.notification.status + end + + def test_return_has_notification + notification = @paytm.notification + + assert notification.complete? + assert_equal 'Completed', notification.status + assert notification.invoice_ok?('100PT012') + assert notification.amount_ok?(BigDecimal.new('10.00')) + assert_equal 'TXN_SUCCESS', notification.transaction_status + assert_equal '494157', @paytm.notification.transaction_id + assert_equal 'CC', @paytm.notification.type + assert_equal 'INR', notification.currency + assert_equal '100PT012', notification.invoice + assert_equal 'WorldP64425807474247', notification.account + assert_equal '10.00', notification.gross + assert_equal 'M9E4OLugj4L3TLLiof2BSO03hhQQLMucnVgtYuHi4wIVLB20dCXS632PRw0dTmnAa58R0kEuh9+V3bfQs4F/SrlmsmV+hvhb3Nui1zlnh/o=', notification.checksum + assert notification.checksum_ok? + end + + private + + def http_raw_data_success + 'MID=WorldP64425807474247&ORDERID=100PT012&TXNAMOUNT=10&CURRENCY=INR&TXNID=494157&BANKTXNID=201512236592678&STATUS=TXN_SUCCESS&RESPCODE=01&RESPMSG=Txn+Successful&TXNDATE=2015-12-23 16:06:22.0&GATEWAYNAME=ICICI&BANKNAME=ICICI&PAYMENTMODE=CC&MERC_UNQ_REF=100PT012&CHECKSUMHASH=M9E4OLugj4L3TLLiof2BSO03hhQQLMucnVgtYuHi4wIVLB20dCXS632PRw0dTmnAa58R0kEuh9%2bV3bfQs4F/SrlmsmV%2bhvhb3Nui1zlnh/o=' + end + + def http_raw_data_failure + 'MID=WorldP64425807474247&ORDERID=100PT012&TXNAMOUNT=10&CURRENCY=INR&TXNID=494157&BANKTXNID=201512236592678&TXNDATE=2015-12-23 16:06:22.0&GATEWAYNAME=ICICI&BANKNAME=ICICI&PAYMENTMODE=CC&MERC_UNQ_REF=100PT012&STATUS=TXN_FAILURE&RESPCODE=330&RESPMSG=Invalid+Checksum&CHECKSUMHASH=x6FmPuyiHDCZ4dI4ffflLEDhngj8zuf6CVgrkpZEkmxfLw22coNvru8H1xZztSKUQeF7qsnR5aC/O36m%2bam6sDLXN9eqSdl7%2bNhe8kp9qFs=' + end + + def checksum + 'M9E4OLugj4L3TLLiof2BSO03hhQQLMucnVgtYuHi4wIVLB20dCXS632PRw0dTmnAa58R0kEuh9%2bV3bfQs4F/SrlmsmV%2bhvhb3Nui1zlnh/o=' + end +end diff --git a/test/unit/integrations/payu_in/payu_in_helper_test.rb b/test/unit/integrations/payu_in/payu_in_helper_test.rb index 478dd3128..84812a86e 100644 --- a/test/unit/integrations/payu_in/payu_in_helper_test.rb +++ b/test/unit/integrations/payu_in/payu_in_helper_test.rb @@ -28,7 +28,7 @@ def test_billing_address_fields assert_equal '666, Wooo', @helper.fields['address1'] assert_equal 'EEE Street', @helper.fields['address2'] assert_equal 'New Delhi', @helper.fields['state'] - assert_equal '110001', @helper.fields['zip'] + assert_equal '110001', @helper.fields['zipcode'] assert_equal 'india', @helper.fields['country'] end @@ -71,4 +71,8 @@ def test_sanitize_fields_in_form_fields assert_nil @helper.fields['email'] end + def test_phone_replace_non_digits + @helper.fields['phone'] = '+(999)-99 99999' + assert_equal '9999999999', @helper.form_fields['phone'] + end end diff --git a/test/unit/integrations/payu_in_paisa/payu_in_paisa_helper_test.rb b/test/unit/integrations/payu_in_paisa/payu_in_paisa_helper_test.rb index 905c75f9d..e6b3c4385 100644 --- a/test/unit/integrations/payu_in_paisa/payu_in_paisa_helper_test.rb +++ b/test/unit/integrations/payu_in_paisa/payu_in_paisa_helper_test.rb @@ -28,7 +28,7 @@ def test_billing_address_fields assert_equal '666, Wooo', @helper.fields['address1'] assert_equal 'EEE Street', @helper.fields['address2'] assert_equal 'New Delhi', @helper.fields['state'] - assert_equal '110001', @helper.fields['zip'] + assert_equal '110001', @helper.fields['zipcode'] assert_equal 'india', @helper.fields['country'] end diff --git a/test/unit/integrations/platron/platron_notification_test.rb b/test/unit/integrations/platron/platron_notification_test.rb index 46a5cbf20..768d6cd8d 100644 --- a/test/unit/integrations/platron/platron_notification_test.rb +++ b/test/unit/integrations/platron/platron_notification_test.rb @@ -16,7 +16,10 @@ def test_accessors assert_equal '1', @correct_notification.complete? assert_equal 'Visa', @correct_notification.payment_system assert_equal 'VI', @correct_notification.card_brand - assert_equal '990',@correct_notification.amount + end + + def test_compositions + assert_equal Money.from_amount(990, 'USD'), @correct_notification.amount end def test_acknowledgement diff --git a/test/unit/integrations/quickpay/quickpay_notification_test.rb b/test/unit/integrations/quickpay/quickpay_notification_test.rb index f6ab855bc..2b2ce0196 100644 --- a/test/unit/integrations/quickpay/quickpay_notification_test.rb +++ b/test/unit/integrations/quickpay/quickpay_notification_test.rb @@ -18,7 +18,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(123, 'DKK'), @quickpay.amount + assert_equal Money.from_amount(1.23, 'DKK'), @quickpay.amount end def test_acknowledgement diff --git a/test/unit/integrations/realex_offsite/realex_offsite_helper_test.rb b/test/unit/integrations/realex_offsite/realex_offsite_helper_test.rb index 624413f03..ff5e3d1ee 100644 --- a/test/unit/integrations/realex_offsite/realex_offsite_helper_test.rb +++ b/test/unit/integrations/realex_offsite/realex_offsite_helper_test.rb @@ -67,11 +67,18 @@ def test_shipping_address end def test_format_amount_as_float - amount_gbp = @helper.format_amount_as_float(999, 'GBP') - assert_in_delta amount_gbp, 9.99, 0.00 + amount_gbp = @helper.format_amount_as_float(929, 'GBP') + assert_in_delta amount_gbp, 9.29, 0.00 - amount_bhd = @helper.format_amount_as_float(999, 'BHD') - assert_in_delta amount_bhd, 0.999, 0.00 + amount_bhd = @helper.format_amount_as_float(929, 'BHD') + assert_in_delta amount_bhd, 0.929, 0.00 end + def test_format_amount + amount_gbp = @helper.format_amount('9.29', 'GBP') + assert_equal amount_gbp, 929 + + amount_bhd = @helper.format_amount(0.929, 'BHD') + assert_equal amount_bhd, 929 + end end diff --git a/test/unit/integrations/realex_offsite/realex_offsite_notification_test.rb b/test/unit/integrations/realex_offsite/realex_offsite_notification_test.rb index 917b88732..314b15c74 100644 --- a/test/unit/integrations/realex_offsite/realex_offsite_notification_test.rb +++ b/test/unit/integrations/realex_offsite/realex_offsite_notification_test.rb @@ -21,7 +21,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(5000, 'USD'), @notification.amount + assert_equal Money.from_amount(50.00, 'USD'), @notification.amount end def test_test_mode diff --git a/test/unit/integrations/robokassa/robokassa_notification_test.rb b/test/unit/integrations/robokassa/robokassa_notification_test.rb index 1b62fd865..b0d49854a 100644 --- a/test/unit/integrations/robokassa/robokassa_notification_test.rb +++ b/test/unit/integrations/robokassa/robokassa_notification_test.rb @@ -14,7 +14,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(50000, 'USD'), @robokassa.amount + assert_equal Money.from_amount(500.00, 'RUB'), @robokassa.amount end # Replace with real successful acknowledgement code diff --git a/test/unit/integrations/sage_pay_form/sage_pay_form_helper_test.rb b/test/unit/integrations/sage_pay_form/sage_pay_form_helper_test.rb index 18755d454..c90c2e24f 100644 --- a/test/unit/integrations/sage_pay_form/sage_pay_form_helper_test.rb +++ b/test/unit/integrations/sage_pay_form/sage_pay_form_helper_test.rb @@ -24,7 +24,11 @@ def test_basic_helper_fields assert_equal 5, @helper.fields.size assert_field 'Vendor', 'cody@example.com' assert_field 'Amount', '5.00' - assert_field 'VendorTxCode', 'order-500' + assert_field 'VendorTxCode', "order-500-#{@helper.identifier}" + end + + def test_identifier_should_only_contain_digits + assert_match /^[0-9]*$/, @helper.identifier end def test_customer_fields @@ -264,6 +268,20 @@ def test_do_not_send_referrer_id_by_default assert_false @helper.fields['ReferrerID'] end + def test_form_fields_does_not_contain_locale + @helper.add_field(:locale, 'en') + + refute @helper.form_fields.key?('locale') + end + + def test_encrypted_fields_do_not_contain_locale + @helper.add_field(:locale, 'en') + + with_crypt_plaintext do |plain| + refute plain.include?('locale') + end + end + private def with_crypt_plaintext diff --git a/test/unit/integrations/sage_pay_form/sage_pay_form_notification_test.rb b/test/unit/integrations/sage_pay_form/sage_pay_form_notification_test.rb index 9c6a8eac3..6422f041e 100644 --- a/test/unit/integrations/sage_pay_form/sage_pay_form_notification_test.rb +++ b/test/unit/integrations/sage_pay_form/sage_pay_form_notification_test.rb @@ -2,6 +2,7 @@ class SagePayFormNotificationTest < Test::Unit::TestCase include OffsitePayments::Integrations + include OffsitePayments::Integrations::SagePayForm::Encryption def setup @options = {:credential2 => 'EncryptionKey123'} @@ -15,7 +16,7 @@ def test_successful_purchase assert_equal 'Completed', n.status assert_equal 'OK', n.status_code assert_equal 'Successfully Authorised Transaction', n.message - assert_equal '28', n.item_id + assert_equal 'order-28', n.item_id assert_equal '{2D370B0B-692D-4D07-B616-91B86CCDF85A}', n.transaction_id assert_equal '7349', n.auth_id assert_equal '1231.47', n.gross @@ -44,7 +45,7 @@ def test_failed_purchase assert_equal 'Failed', n.status assert_equal 'NOTAUTHED', n.status_code assert_equal 'NOTAUTHED message generated by Simulator', n.message - assert_equal '28', n.item_id + assert_equal 'order-28', n.item_id assert_equal '{2D370B0B-692D-4D07-B616-91B86CCDF85A}', n.transaction_id assert_equal '31.47', n.gross assert_equal 'ALL MATCH', n.avs_cv2_result @@ -73,7 +74,7 @@ def test_cancelled_purchase assert_equal 'Failed', n.status assert_equal 'ABORT', n.status_code assert_equal 'ABORT message generated by Simulator', n.message - assert_equal '5', n.item_id + assert_equal 'order-5', n.item_id assert_equal '{90A42BA2-1281-4CA9-8E84-E43C0E7FD85F}', n.transaction_id assert_equal '148.99', n.gross assert_equal 'ALL MATCH', n.avs_cv2_result @@ -96,7 +97,7 @@ def test_cancelled_purchase def test_compositions n = SagePayForm::Notification.new(successful_purchase, @options) - assert_equal Money.new(123147, nil), n.amount + assert_equal Money.from_amount(1231.47), n.amount end def test_bogus_crypt @@ -120,14 +121,30 @@ def test_missing_key private def successful_purchase - 'utm_nooverride=1&crypt=@BE1F6226E7478FACFA239E0F882B10E7BAE1149909C1F8D163038E623A188E693C6CFC594A49D5BB257E2CBBA58D3436A6FED00FCD7268FB9D0978CEEE3086A8B25F92FF65F059886A07BFE7E228AA965F44B9018AD73612BA2FFDFD3A2A468B88E4E77D81252B3F28173E9DCD5559D5A89937BBB88AE8FE358FE89974FC4761172694ADEAD7671F07BCECB3552F285F78C30F8DF830494429777F91B58344FDE411794FD64F215EC8ED395FB621958D4241EA41B08C7F5FDC3302C8B75BC92E55FABA6126CE1B27E9B1A01CD6F03BF99051D67A0829E2AF3C22E6159D2D5551D499A3677C695E50C3000EFFDA15102987BFF0329BEB8FED280D343723C47EF2A4475835AABBE1980560356E3D9F0F84562E8739EAC4E859F72E4FBEBB5C3830099E2908B433318FD1B3905244AD5CAB4CE8120D33D39391CF4E9F0E4CC9C62B' + encrypt_params(raw_successful_purchase) end def failed_purchase - 'utm_nooverride=1&crypt=@A60BFC79F8FAA1C02E548AD3D3D2EC888C6FF1DA74889EB45D6ED7D680CC45087A449B3E3E8727AA90AC451C0B3AC0D61F3761F3269EFEA8276DF568905DA1EFB19EE7526DA4EDC894C29A1D1D92831C22C1BC0614B86C6C6262ED29F5E8178BD80C1D9389A4B7A78305A625D49522E19C6D623C28CF34D9F87E4607F2D361894B344037120F745BC267E055762D7BD642847647FA98E6573245AFD125C2212319499D7C332893DF2E9EB085C6F58D7FD34665AC5365F46FEE276E3D17615C3FA97ACB335045BF79B6F2F612FED564587EC52CBAA558D910209ACD88E5859F6EA0FD4FC7160CD4D68E3E6B8894737708AB212A2E33CE22EB245E117AFCE6F7B29A51E903BE9209E6EA8E3C9C543F85F4A9F3109A464A0BE85FE7706BC24F7F257BE9C2D54FE46E3B382F726B53E9876D292ACB76931AA2ACD0EED18766D2D142' + encrypt_params(raw_failed_purchase) end def cancelled_purchase - 'utm_nooverride=1&crypt=@F2039156479BC99C3448EB6CDAAD52E1746785AAB0B7477467FD9F4F84AC52EC981DA7105EAE3FA281648ADA29F2E2874A0B49C5D35159CB1B191A0FF1CEF68F4C1D06BDE7AAEC9356585210B013872790D12172726FFBD443E0F31925275DA6BCBAA198349D79828DE210088D1621E512272B4D325B35B12FFF931CBDE601E47F8F21FE7F0E9CE20069CB156AAB61C595D97190EBEEA1160524A0E557360C3C606909C0B01F0FEA252489AB873299DD427F21AAA14304886219E4AD16E2A288A16063CC058543619D7B80F283595B9AD3DBF9D27DB4E4631900AFDECABC45D899D5A12689104B6B76C1F119939D26BD4160074A46517A97194C8C3C882D901F28417FA5CE3BBC9B11D220E0CCE4E07BB3EDE72ECEEF7B5EA1E56EA3C1EE77F37479A226A9A1ADB91B0F4AC405C94F37CACA147939FC317FA0BE799140B149B7' + encrypt_params(raw_cancelled_purchase) + end + + def encrypt_params(raw) + "utm_nooverride=1&crypt=#{sage_encrypt(raw, @options[:credential2])}" + end + + def raw_successful_purchase + "Status=OK&StatusDetail=Successfully Authorised Transaction&VendorTxCode=order-28-1234&VPSTxId={2D370B0B-692D-4D07-B616-91B86CCDF85A}&TxAuthNo=7349&Amount=1,231.47&AVSCV2=ALL MATCH&AddressResult=MATCHED&PostCodeResult=MATCHED&CV2Result=MATCHED&GiftAid=0&3DSecureStatus=OK&CAVV=MNG8ZAJDJRUKW90GGYZNTH&CardType=VISA&Last4Digits=8356" + end + + def raw_failed_purchase + "Status=NOTAUTHED&StatusDetail=NOTAUTHED message generated by Simulator&VendorTxCode=order-28-1234&VPSTxId={2D370B0B-692D-4D07-B616-91B86CCDF85A}&Amount=31.47&AVSCV2=ALL MATCH&AddressResult=MATCHED&PostCodeResult=MATCHED&CV2Result=MATCHED&GiftAid=0&3DSecureStatus=OK&CAVV=MNVJYYXXHMCNH0BOTBT97Z&CardType=VISA&Last4Digits=4353" + end + + def raw_cancelled_purchase + "Status=ABORT&StatusDetail=ABORT message generated by Simulator&VendorTxCode=order-5-1234&VPSTxId={90A42BA2-1281-4CA9-8E84-E43C0E7FD85F}&Amount=148.99&AVSCV2=ALL MATCH&AddressResult=MATCHED&PostCodeResult=MATCHED&CV2Result=MATCHED&GiftAid=0&3DSecureStatus=OK&CAVV=MNJ8W58FNX1Q5OAV4TZKW2&CardType=VISA&Last4Digits=6425" end end diff --git a/test/unit/integrations/two_checkout/two_checkout_notification_test.rb b/test/unit/integrations/two_checkout/two_checkout_notification_test.rb index 7c4420868..7c474f6ed 100644 --- a/test/unit/integrations/two_checkout/two_checkout_notification_test.rb +++ b/test/unit/integrations/two_checkout/two_checkout_notification_test.rb @@ -21,7 +21,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(20, 'USD'), @live_notification.amount + assert_equal Money.from_amount(0.20, 'USD'), @live_notification.amount end def test_acknowledgement diff --git a/test/unit/integrations/universal/universal_helper_test.rb b/test/unit/integrations/universal/universal_helper_test.rb index 8fb7b9803..45bd759d1 100644 --- a/test/unit/integrations/universal/universal_helper_test.rb +++ b/test/unit/integrations/universal/universal_helper_test.rb @@ -34,8 +34,6 @@ def test_credential_based_url end def test_core_fields - @helper.shipping 6.78 - @helper.tax 0 @helper.description 'Box of Red Wine' @helper.invoice 'Invoice #1A' @@ -44,8 +42,6 @@ def test_core_fields assert_field 'x_credential4', @credential4 assert_field 'x_currency', @currency assert_field 'x_amount', '123.45' - assert_field 'x_amount_shipping', '6.78' - assert_field 'x_amount_tax', '0.00' assert_field 'x_reference', @order assert_field 'x_shop_country', @country assert_field 'x_shop_name', @account_name @@ -67,13 +63,9 @@ def test_empty_credential_field_not_present_in_request def test_special_currency_formatting @options[:currency] = 'COU' @helper = Universal::Helper.new(@order, @account, @options) - @helper.shipping 6.78 - @helper.tax 0 assert_field 'x_currency', 'COU' assert_field 'x_amount', '123.4500' - assert_field 'x_amount_shipping', '6.7800' - assert_field 'x_amount_tax', '0.0000' end def test_customer_fields @@ -112,6 +104,30 @@ def test_shipping_address_fields assert_field 'x_customer_shipping_phone', '(416) 123-4567' end + def test_billing_address_fields + @helper.billing_address :first_name => 'John', + :last_name => 'Doe', + :city => 'Toronto', + :company => 'Shopify Toronto', + :address1 => '241 Spadina Ave', + :address2 => 'Front Entrance', + :state => 'ON', + :zip => 'M5T 3A8', + :country => 'CA', + :phone => '(416) 123-4567' + + assert_field 'x_customer_billing_first_name', 'John' + assert_field 'x_customer_billing_last_name', 'Doe' + assert_field 'x_customer_billing_city', 'Toronto' + assert_field 'x_customer_billing_company', 'Shopify Toronto' + assert_field 'x_customer_billing_address1', '241 Spadina Ave' + assert_field 'x_customer_billing_address2', 'Front Entrance' + assert_field 'x_customer_billing_state', 'ON' + assert_field 'x_customer_billing_zip', 'M5T 3A8' + assert_field 'x_customer_billing_country', 'CA' + assert_field 'x_customer_billing_phone', '(416) 123-4567' + end + def test_url_fields @helper.notify_url 'https://zork.com/notify' @helper.return_url 'https://zork.com/return' @@ -129,6 +145,18 @@ def test_signature assert_field 'x_signature', expected_signature end + def test_signature_only_uses_fields_that_start_with_x_ + expected_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @key, 'x_account_idzorkx_amount123.45x_credential3123456789x_credential4abcdefghijkx_currencyUSDx_referenceorder-500x_shop_countryUSx_shop_nameWidgets Incx_testfalsex_transaction_typesale') + @helper.sign_fields + + assert_field 'x_signature', expected_signature + + @helper.add_field('should_not_be_used_in_signature', 'value') + @helper.sign_fields + + assert_field 'x_signature', expected_signature + end + def test_signature_when_some_credentials_are_not_defined @options[:credential3] = '' @options[:credential4] = '' diff --git a/test/unit/integrations/universal/universal_notification_test.rb b/test/unit/integrations/universal/universal_notification_test.rb index f7a18c63c..c1a124594 100644 --- a/test/unit/integrations/universal/universal_notification_test.rb +++ b/test/unit/integrations/universal/universal_notification_test.rb @@ -14,11 +14,12 @@ def test_accessors assert_equal '123.45', @notification.gross assert_equal 'blorb123', @notification.transaction_id assert_equal 'Completed', @notification.status + assert_equal 'helloworld', @notification.message assert @notification.test? end def test_compositions - assert_equal Money.new(12345, 'USD'), @notification.amount + assert_equal Money.from_amount(123.45, 'USD'), @notification.amount end def test_acknowledge_valid_signature @@ -46,7 +47,7 @@ def test_acknowledge_invalid_signature private def http_raw_data - 'x_account_id=zork&x_reference=order-500&x_currency=USD&x_test=true&x_amount=123.45&x_gateway_reference=blorb123&x_timestamp=2014-03-24T12:15:41Z&x_result=completed&x_signature=d8797220f2f0ccef90c1ee80e82494cd709fb10ab1f50a016578208c3fb5a0c1' + 'x_account_id=zork&x_reference=order-500&x_currency=USD&x_test=true&x_amount=123.45&x_gateway_reference=blorb123&x_timestamp=2014-03-24T12:15:41Z&x_result=completed&x_signature=82b635305a18e3e614c473ab4dce900a7c00db79bd68525e565a29ff69218f37&x_message=helloworld' end def http_raw_data_extra_parameter diff --git a/test/unit/integrations/universal/universal_return_test.rb b/test/unit/integrations/universal/universal_return_test.rb index 189dbc743..c01813666 100644 --- a/test/unit/integrations/universal/universal_return_test.rb +++ b/test/unit/integrations/universal/universal_return_test.rb @@ -23,9 +23,13 @@ def test_success_after_acknowledge assert @return.success? end + def test_return_message + assert_equal 'helloworld', @return.message + end + private def query_data - 'x_account_id=zork&x_reference=order-500&x_currency=USD&x_test=true&x_amount=123.45&x_gateway_reference=blorb123&x_timestamp=2014-03-24T12:15:41Z&x_result=success&x_signature=4365fef32f5309845052b728c8cbe962e583ecaf62bf1cdec91f248162b7f65e' + 'x_account_id=zork&x_reference=order-500&x_currency=USD&x_test=true&x_amount=123.45&x_gateway_reference=blorb123&x_timestamp=2014-03-24T12:15:41Z&x_result=success&x_signature=55bd5acfabe65041568a94cb8981489ebced1f1eceebe0b985c8db43f3fedf91&x_message=helloworld' end end diff --git a/test/unit/integrations/web_pay/web_pay_notification_test.rb b/test/unit/integrations/web_pay/web_pay_notification_test.rb index 09b79d0e7..72edad896 100644 --- a/test/unit/integrations/web_pay/web_pay_notification_test.rb +++ b/test/unit/integrations/web_pay/web_pay_notification_test.rb @@ -14,7 +14,7 @@ def test_accessors end def test_compositions - assert_equal BigDecimal.new("500"), @web_pay.amount + assert_equal Money.from_amount(500, 'BYR'), @web_pay.amount end def test_respond_to_acknowledge diff --git a/test/unit/integrations/webmoney/webmoney_notification_test.rb b/test/unit/integrations/webmoney/webmoney_notification_test.rb index 24590eaf5..50ac21f27 100644 --- a/test/unit/integrations/webmoney/webmoney_notification_test.rb +++ b/test/unit/integrations/webmoney/webmoney_notification_test.rb @@ -10,7 +10,10 @@ def setup def test_accessors assert_equal "1.00", @webmoney.gross assert_equal "123", @webmoney.item_id - assert_equal BigDecimal.new("1"), @webmoney.amount + end + + def test_compositions + assert_equal Money.from_amount(1.00, 'RUB'), @webmoney.amount end def test_acknowledgement diff --git a/test/unit/integrations/world_pay/world_pay_helper_test.rb b/test/unit/integrations/world_pay/world_pay_helper_test.rb index 4102d4916..6a70993c5 100644 --- a/test/unit/integrations/world_pay/world_pay_helper_test.rb +++ b/test/unit/integrations/world_pay/world_pay_helper_test.rb @@ -33,7 +33,10 @@ def test_address_mapping :zip => 'CV1 1AA', :country => 'GB' - assert_field 'address', '1 Nowhere Close Electric Wharf Coventry Warwickshire' + assert_field 'address1', '1 Nowhere Close' + assert_field 'address2', 'Electric Wharf' + assert_field 'town', 'Coventry' + assert_field 'region', 'Warwickshire' assert_field 'postcode', 'CV1 1AA' assert_field 'country', 'GB' end @@ -44,7 +47,8 @@ def test_address_mapping_without_address1_and_state :zip => '10000', :country => 'DE' - assert_field 'address', 'Teststr. 1 Berlin' + assert_field 'address1', 'Teststr. 1' + assert_field 'town', 'Berlin' assert_field 'postcode', '10000' assert_field 'country', 'DE' end diff --git a/test/unit/integrations/world_pay/world_pay_notification_test.rb b/test/unit/integrations/world_pay/world_pay_notification_test.rb index 4d0df109e..14652bdb6 100644 --- a/test/unit/integrations/world_pay/world_pay_notification_test.rb +++ b/test/unit/integrations/world_pay/world_pay_notification_test.rb @@ -19,7 +19,7 @@ def test_accessors end def test_compositions - assert_equal Money.new(500, 'GBP'), @world_pay.amount + assert_equal Money.from_amount(5.00, 'GBP'), @world_pay.amount end def test_extra_accessors @@ -66,7 +66,7 @@ def test_custom_parameters end def test_valid_sender - OffsitePayments.mode = :production + OffsitePayments.mode = :production assert @world_pay.valid_sender?('195.35.90.0') assert @world_pay.valid_sender?('195.35.90.11') assert @world_pay.valid_sender?('195.35.91.255') diff --git a/test/unit/return_test.rb b/test/unit/return_test.rb index 0c4741b44..2e8588a7a 100644 --- a/test/unit/return_test.rb +++ b/test/unit/return_test.rb @@ -7,4 +7,17 @@ def test_return r = Return.new('') assert r.success? end + + def test_parse + r = Return.new('') + assert r.parse('account=test').is_a?(Hash) + end + + def test_parse_with_bad_query + r = Return.new('') + assert r.parse('&account=test').is_a?(Hash) + assert r.parse('key1=value1&key2&key3=value3').is_a?(Hash) + assert r.parse('foooo=value1&=value&key3=value3').is_a?(Hash) + assert r.parse('key1=value1&&key3=value3').is_a?(Hash) + end end