Created
October 1, 2009 19:34
-
-
Save tisho/199183 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Интеграция с 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