Skip to content

Instantly share code, notes, and snippets.

@tisho
Created October 1, 2009 19:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tisho/199183 to your computer and use it in GitHub Desktop.
Save tisho/199183 to your computer and use it in GitHub Desktop.
# Интеграция с ePay
#
#
# 1. Сложи следното в epay.rb някъде из lib/ или там, откъдето предпочитаният ти
# framework си зарежда допълнителен код. По default заявките се генерират
# в test mode към demo сървъра на ePay. Когато си готов, слагаш това в
# някой initializer.
#
# Epay.mode = :production
#
# --------------------------------- epay.rb -----------------------------------------
require 'digest/sha1'
require 'openssl'
require 'base64'
require 'date'
module Epay
@@mode = :test # or :production
def self.mode=(mode); @@mode = mode; end
def self.test?; @@mode == :test; end
DEFAULT_OPTIONS = {
:min => nil, # merchant identification number
:amount => nil, # total amount
:invoice => nil, # invoice id
:exp_date => Date.new(Date.today.year+20, 1, 1), # random date in the future
:desc => nil, # description
:secret => nil,
:url_ok => nil,
:url_cancel => nil,
:form_id => 'epay_form',
:additional_form_html => '',
:page => 'paylogin'
}
TEST_URL = 'https://devep2.datamax.bg/ep2/epay2_demo/'
PRODUCTION_URL = 'https://epay.bg'
class MissingArgumentError < StandardError; end;
class Payment
attr_reader :encoded_data, :plain_data, :options, :checksum
def initialize(options = {})
@options = DEFAULT_OPTIONS.clone.update(options)
raise MissingArgumentError if @options.values.any? { |o| o.nil? }
@options[:amount] = sprintf('%.2f', @options[:amount])
end
def form_html(additional_form_html = nil)
additional_form_html ||= options[:additional_form_html]
html = <<HTML
<form id="#{options[:form_id]}" action="#{remote_url}" method="post">
<input type="hidden" name="PAGE" value="#{options[:page]}" />
<input type="hidden" name="ENCODED" value="#{encoded_data}" />
<input type="hidden" name="CHECKSUM" value="#{checksum}" />
<input type="hidden" name="URL_OK" value="#{options[:url_ok]}" />
<input type="hidden" name="URL_CANCEL" value="#{options[:url_cancel]}" />
#{additional_form_html}
</form>
HTML
end
protected
def remote_url
Epay.test? ? TEST_URL : PRODUCTION_URL
end
def encoded_data
@encoded_data ||= Base64.encode64(plain_data).split(' ').join('')
end
def plain_data
@plain_data ||= "MIN=#{@options[:min]}
INVOICE=#{@options[:invoice]}
AMOUNT=#{@options[:amount]}
EXP_TIME=#{@options[:exp_date].strftime '%d.%m.%Y'}
DESCR=#{@options[:desc]}"
end
def checksum
@checksum ||= Epay::Util.generate_checksum_for(encoded_data, @options[:secret])
end
end
class InvalidChecksumError < StandardError; end;
class MalformedResponseError < StandardError; end;
class Response
attr_reader :invoice, :status, :pay_date, :stan, :bcode
def initialize(options = {})
@checksum, @encoded, @secret = options[:checksum], options[:encoded], options[:secret]
raise InvalidChecksumError unless valid_checksum?
process!
end
protected
def process!
decoded_response_data = Base64.decode64(@encoded)
if matchdata = decoded_response_data.match(/^INVOICE=(\d+):STATUS=(PAID|DENIED|EXPIRED)(:PAY_TIME=(\d+):STAN=(\d+):BCODE=([0-9a-zA-Z]+))?$/)
@invoice = matchdata[1]
@status = matchdata[2]
@pay_date = matchdata[4]
@stan = matchdata[5]
@bcode = matchdata[6]
else
raise MalformedResponseError
end
end
def valid_checksum?
@checksum == Epay::Util.generate_checksum_for(@encoded, @secret)
end
end
module Util
def self.generate_checksum_for(data, key)
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, key.to_s, data.to_s)
end
end
end
# --------------------------------- epay.rb -----------------------------------------
# 2. В контролера, който отговаря за иницииране на плащането:
#
# --------------------- controllers/payment_controller.rb ---------------------------
EPAY_SECRET_KEY = 'YOUR_SUPER_SECRET_KEY'
EPAY_MIN = 'YOUR_MIN'
@payment = Epay::Payment.new(
:invoice => 1234, # номер на поръчка
:amount => 5, # сума в лева
:desc => 'Product description', # описание
:secret => EPAY_SECRET_KEY, # таен ключ
:min => EPAY_MIN, # Merchant Identification Number
:url_ok => 'http://www.google.com', # къде да препрати ако е ОК
:url_cancel => 'http://www.yahoo.com', # къде да препрати ако не е ОК
:form_id => 'epay_form') # id на генерирания form елемент
# 3. В съответното view
#
# ---------------------------- views/create.html.erb --------------------------------
# <%= @payment.form_html %>
#
# По подразбиране има id="epay_form". Можеш да използваш javascript, за да си я
# submit-неш, когато потребителят натисне определено бутонче.
#
# Можеж да използваш и <%= @payment.form_html(additional_form_html) %>,
# за да си мажеш в нея каквото си искаш
# 4. ePay пращат и notification, след като мине плащането. Задължително е да
# отговориш в plain text. Тук библиотеката е малко глуха, защото трябва сам
# да си генерираш response-a.
#
# --------------------- controllers/epay_ping_controller.rb -------------------------
render :text => "ERR=Not a POST request\n" unless request.post?
begin
response = Epay::Response.new(
:checksum => params[:checksum],
:encoded => params[:encoded],
:secret => EPAY_SECRET_KEY)
# Горният ред може да хвърли няколко възможни exception-a.
rescue Epay::InvalidChecksumError
render :text => "ERR=Not valid CHECKSUM\n"
rescue Epay::MalformedResponseError
render :text => "ERR=Invalid response\n"
end
# После трябва да прегледаш статуса и да прецениш как да отговориш.
# Ако имаш модел Order, да кажем:
if order = Order.exists?(:invoice_id => response.invoice)
case response.status
when 'PAID'
# Ъпдейтваш подобаващо и пращаш мейл на клиента за
# успешно преборване с разплащателната система
when 'DENIED'
# Ъпдейтваш подобаващо и пращаш мейл на клиента да
# го питаш къде са му парите
when 'EXPIRED'
# Ъпдейтваш подобаващо и пращаш мейл на клиента да
# го питаш какво е правил цели 20 години
end
render :text => "INVOICE=#{response.invoice}:STATUS=OK\n"
else
# Нямаш идея каква е тая поръчка
render :text => "INVOICE=#{response.invoice}:STATUS=NO\n"
end
# 5. Тествай!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment