Skip to content

Commit

Permalink
Add support for DirecPay Gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
Soleone committed Oct 14, 2010
1 parent bf9c475 commit 19e8ed1
Show file tree
Hide file tree
Showing 11 changed files with 766 additions and 1 deletion.
37 changes: 37 additions & 0 deletions lib/active_merchant/billing/integrations/direc_pay.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
module Integrations #:nodoc:
module DirecPay
autoload :Helper, File.dirname(__FILE__) + '/direc_pay/helper.rb'
autoload :Return, File.dirname(__FILE__) + '/direc_pay/return.rb'
autoload :Notification, File.dirname(__FILE__) + '/direc_pay/notification.rb'
autoload :Status, File.dirname(__FILE__) + '/direc_pay/status.rb'

mattr_accessor :production_url, :test_url

self.production_url = "https://www.timesofmoney.com/direcpay/secure/dpMerchantTransaction.jsp"
self.test_url = "https://test.timesofmoney.com/direcpay/secure/dpMerchantTransaction.jsp"

def self.service_url
mode = ActiveMerchant::Billing::Base.integration_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

def self.notification(post)
Notification.new(post)
end

def self.return(query_string)
Return.new(query_string)
end
end
end
end
end
188 changes: 188 additions & 0 deletions lib/active_merchant/billing/integrations/direc_pay/helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
module Integrations #:nodoc:
module DirecPay
class Helper < ActiveMerchant::Billing::Integrations::Helper
mapping :account, 'MID'
mapping :order, 'Merchant Order No'
mapping :amount, 'Amount'
mapping :currency, 'Currency'
mapping :country, 'Country'

mapping :billing_address, :city => 'custCity',
:address1 => 'custAddress',
:state => 'custState',
:zip => 'custPinCode',
:country => 'custCountry',
:phone2 => 'custMobileNo'

mapping :shipping_address, :name => 'deliveryName',
:city => 'deliveryCity',
:address1 => 'deliveryAddress',
:state => 'deliveryState',
:zip => 'deliveryPinCode',
:country => 'deliveryCountry',
:phone2 => 'deliveryMobileNo'

mapping :customer, :name => 'custName',
:email => 'custEmailId'

mapping :description, 'otherNotes'
mapping :edit_allowed, 'editAllowed'

mapping :return_url, 'Success URL'
mapping :failure_url, 'Failure URL'

mapping :operating_mode, 'Operating Mode'
mapping :other_details, 'Other Details'
mapping :collaborator, 'Collaborator'

OPERATING_MODE = 'DOM'
COUNTRY = 'IND'
CURRENCY = 'INR'
OTHER_DETAILS = 'NULL'
EDIT_ALLOWED = 'N'

PHONE_CODES = {
'IN' => '91',
'US' => '01',
'CA' => '01'
}

ENCODED_PARAMS = [ :account, :operating_mode, :country, :currency, :amount, :order, :other_details, :return_url, :failure_url, :collaborator ]


def initialize(order, account, options = {})
super
collaborator = ActiveMerchant::Billing::Base.integration_mode == :test || options[:test] ? 'TOML' : 'DirecPay'
add_field(mappings[:collaborator], collaborator)
add_field(mappings[:country], 'IND')
add_field(mappings[:operating_mode], OPERATING_MODE)
add_field(mappings[:other_details], OTHER_DETAILS)
add_field(mappings[:edit_allowed], EDIT_ALLOWED)
end


def customer(params = {})
full_name = "#{params[:first_name]} #{params[:last_name]}"
add_field(mappings[:customer][:name], full_name)
add_field(mappings[:customer][:email], params[:email])
end

# Need to format the amount to have 2 decimal places
def amount=(money)
cents = money.respond_to?(:cents) ? money.cents : money
if money.is_a?(String) or cents.to_i <= 0
raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.'
end
add_field(mappings[:amount], sprintf("%.2f", cents.to_f/100))
end

def shipping_address(params = {})
add_street_address!(params)
super(params.dup)
add_field(mappings[:shipping_address][:name], fields[mappings[:customer][:name]]) if fields[mappings[:shipping_address][:name]].blank?
add_phone_for!(:shipping_address, params)
end

def billing_address(params = {})
add_street_address!(params)
super(params.dup)
add_phone_for!(:billing_address, params)
end

def form_fields
add_failure_url
add_request_parameters

unencoded_parameters
end


private

def add_request_parameters
params = ENCODED_PARAMS.map{ |param| fields[mappings[param]] }
encoded = encode_value(params.join('|'))

add_field('requestparameter', encoded)
end

def unencoded_parameters
params = fields.dup
# remove all encoded params from exported fields
ENCODED_PARAMS.each{ |param| params.delete(mappings[param]) }
# remove all special characters from each field value
params = params.collect{|name, value| [name, remove_special_characters(value)] }
Hash[params]
end

def add_failure_url
if fields[mappings[:failure_url]].nil?
add_field(mappings[:failure_url], fields[mappings[:return_url]])
end
end

def add_street_address!(params)
address = params[:address1]
address << " #{params[:address2]}" if params[:address2]
params.merge!(:address1 => address)
end

def add_phone_for!(address_type, params)
address_field = address_type == :billing_address ? 'custPhoneNo' : 'deliveryPhNo'

if params.has_key?(:phone)
country = fields[mappings[address_type][:country]]
phone = params[:phone].to_s
# Remove all non digits
phone.gsub!(/[^\d ]+/, '')

phone_country_code, phone_area_code, phone_number = nil

if country == 'IN' && phone =~ /(91)? *(\d{3}) *(\d{4,})$/
phone_country_code, phone_area_code, phone_number = $1, $2, $3
else
numbers = phone.split(' ')
case numbers.size
when 3
phone_country_code, phone_area_code, phone_number = numbers
when 2
phone_area_code, phone_number = numbers
else
phone =~ /(\d{3})(\d+)$/
phone_area_code, phone_number = $1, $2
end
end

add_field("#{address_field}1", phone_country_code || phone_code_for_country(country) || '91')
add_field("#{address_field}2", phone_area_code)
add_field("#{address_field}3", phone_number)
end
end

# Special characters are NOT allowed while posting transaction parameters on DirecPay system
def remove_special_characters(string)
string.gsub(/[~"'&#%]/, '-')
end

def encode_value(value)
encoded = ActiveSupport::Base64.encode64s(value)
string_to_encode = encoded[0, 1] + "T" + encoded[1, encoded.length]
ActiveSupport::Base64.encode64s(string_to_encode)
end

def decode_value(value)
decoded = ActiveSupport::Base64.decode64(value)
string_to_decode = decoded[0, 1] + decoded[2, decoded.length]
ActiveSupport::Base64.decode64(string_to_decode)
end

def phone_code_for_country(country)
PHONE_CODES[country]
end
end
end
end
end
end
76 changes: 76 additions & 0 deletions lib/active_merchant/billing/integrations/direc_pay/notification.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require 'net/http'

module ActiveMerchant #:nodoc:
module Billing #:nodoc:
module Integrations #:nodoc:
module DirecPay
class Notification < ActiveMerchant::Billing::Integrations::Notification
RESPONSE_PARAMS = ['DirecPay Reference ID', 'Flag', 'Country', 'Currency', 'Other Details', 'Merchant Order No', 'Amount']

def acknowledge
true
end

def complete?
status == 'Completed' || status == 'Pending'
end

def status
case params['Flag']
when 'SUCCESS'
'Completed'
when 'PENDING'
'Pending'
when 'FAIL'
'Failed'
else
'Error'
end
end

def item_id
params['Merchant Order No']
end

def transaction_id
params['DirecPay Reference ID']
end

# the money amount we received in X.2 decimal
def gross
params['Amount']
end

def currency
params['Currency']
end

def country
params['Country']
end

def other_details
params['Other Details']
end

def test?
false
end

# Take the posted data and move the relevant data into a hash
def parse(post)
super

values = params['responseparams'].to_s.split('|')
response_params = values.size == 3 ? ['DirecPay Reference ID', 'Flag', 'Error message'] : RESPONSE_PARAMS
response_params.each_with_index do |name, index|
params[name] = values[index]
end
params
end

end
end
end
end
end
32 changes: 32 additions & 0 deletions lib/active_merchant/billing/integrations/direc_pay/return.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
module Integrations #:nodoc:

module DirecPay
class Return < ActiveMerchant::Billing::Integrations::Return

def initialize(post_data, options = {})
@notification = Notification.new(treat_failure_as_pending(post_data), options)
end

def success?
notification.complete?
end

def message
notification.status
end


private

# Work around the issue that the initial return from DirecPay is always either SUCCESS or FAIL, there is no PENDING
def treat_failure_as_pending(post_data)
post_data.sub(/FAIL/, 'PENDING')
end
end
end

end
end
end
37 changes: 37 additions & 0 deletions lib/active_merchant/billing/integrations/direc_pay/status.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
module Integrations #:nodoc:
module DirecPay

class Status
include PostsData

STATUS_TEST_URL = 'https://test.timesofmoney.com/direcpay/secure/dpPullMerchAtrnDtls.jsp'
STATUS_LIVE_URL = 'https://www.timesofmoney.com/direcpay/secure/dpPullMerchAtrnDtls.jsp'

attr_reader :account, :options

def initialize(account, options = {})
@account, @options = account, options
end


# Use this method to manually request a status update to the provided notification_url
def update(authorization, notification_url)
url = test? ? STATUS_TEST_URL : STATUS_LIVE_URL
parameters = [ authorization, account, notification_url ]
data = PostData.new
data[:requestparams] = parameters.join('|')

response = ssl_get("#{url}?#{data.to_post_data}")
end

def test?
ActiveMerchant::Billing::Base.integration_mode == :test || options[:test]
end

end
end
end
end
end
3 changes: 2 additions & 1 deletion test/fixtures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,5 @@ garanti:
login: 'paymentuser'
password: 'userpayment'


direc_pay:
mid: 200904281000001
Loading

0 comments on commit 19e8ed1

Please sign in to comment.