├── .gitignore ├── README.md ├── lib ├── liqpay.rb └── liqpay │ ├── client.rb │ ├── coder.rb │ ├── config.rb │ ├── liqpay.rb │ ├── parameters.rb │ └── version.rb ├── liqpay.gemspec └── spec ├── classes ├── api │ └── invoice_spec.rb ├── config_spec.rb └── liqpay_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.idea 3 | Gemfile.lock 4 | *.gem 5 | spec/dummy/config.rb -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruby gem for liqpay.ua API 2 | 3 | Ruby gem wrapper for official Liqpay SDK https://github.com/liqpay/sdk-ruby 4 | 5 | ## Installation 6 | 7 | Add the gem to your Gemfile: 8 | 9 | ```ruby 10 | gem 'liqpay', git: 'https://github.com/liqpay/sdk-ruby.git' 11 | ``` 12 | 13 | And don't forget to run Bundler: 14 | 15 | ```shell 16 | $ bundle install 17 | ``` 18 | 19 | ## Configuration 20 | 21 | Get API keys on https://www.liqpay.ua/ and save them in config (RoR): 22 | 23 | ```ruby 24 | # config/initializers/liqpay.rb 25 | 26 | ::Liqpay.configure do |config| 27 | config.public_key = 'public_key' 28 | config.private_key = 'private_key' 29 | end 30 | ``` 31 | 32 | You can also store API keys in `ENV['LIQPAY_PUBLIC_KEY']` and `ENV['LIQPAY_PRIVATE_KEY']` 33 | 34 | 35 | And for pure Ruby (liqpay_config.rb for example) 36 | 37 | ```ruby 38 | module LiqpayConfig 39 | def self.configure 40 | ::Liqpay.configure do |config| 41 | config.public_key = 'public_key' 42 | config.private_key = 'private_key' 43 | end 44 | end 45 | end 46 | 47 | LiqpayConfig.configure 48 | ``` 49 | 50 | And after that in main.rb or in another each 51 | ```ruby 52 | require 'liqpay' 53 | require_relative 'liqpay_config' 54 | ``` 55 | 56 | ## Usage 57 | 58 | Full Liqpay API documentation is available on https://www.liqpay.ua/en/doc 59 | 60 | ### Api 61 | 62 | ```ruby 63 | require 'liqpay' 64 | liqpay = Liqpay.new 65 | liqpay.api('request', { 66 | action: 'invoice_send', 67 | version: '3', 68 | email: email, 69 | amount: '1', 70 | currency: 'USD', 71 | order_id: order_id, 72 | goods: [{ 73 | amount: 100, 74 | count: 2, 75 | unit: 'шт.', 76 | name: 'телефон' 77 | }] 78 | }) 79 | ``` 80 | 81 | ### Checkout 82 | 83 | ```ruby 84 | html = liqpay.cnb_form({ 85 | action: "pay", 86 | amount: "1", 87 | currency: "USD", 88 | description: "description text", 89 | order_id: "order_id_1", 90 | version: "3" 91 | }) 92 | ``` 93 | 94 | ### Callback 95 | 96 | ```ruby 97 | data = request.parameters['data'] 98 | signature = request.parameters['signature'] 99 | 100 | if liqpay.match?(data, signature) 101 | responce_hash = liqpay.decode_data(data) 102 | # Check responce_hash['status'] and process due to Liqpay API documentation. 103 | else 104 | # Wrong signature! 105 | end 106 | ``` 107 | 108 | ## Tests 109 | 110 | To pass the API tests, specify API keys in `ENV['LIQPAY_PUBLIC_KEY']` and `ENV['LIQPAY_PRIVATE_KEY']` 111 | or in `spec/dummy/config.rb`: 112 | 113 | ```ruby 114 | # spec/dummy/config.rb 115 | ::Liqpay.configure do |config| 116 | config.public_key = 'public_key' 117 | config.private_key = 'private_key' 118 | end 119 | ``` 120 | To run local only tests(keys are not required), execute 121 | ``` 122 | rspec --tag ~@real 123 | ``` 124 | 125 | With real api test you can specify email to recive invoce from Liqpay: 126 | ``` 127 | LIQPAY_PUBLIC_KEY=real_public_key LIQPAY_PRIVATE_KEY=real_private_key TEST_EMAIL=real_email rspec --tag @real 128 | ``` 129 | -------------------------------------------------------------------------------- /lib/liqpay.rb: -------------------------------------------------------------------------------- 1 | require 'liqpay/config' 2 | require 'liqpay/client' 3 | require 'liqpay/coder' 4 | require 'liqpay/parameters' 5 | require 'liqpay/liqpay' 6 | require 'liqpay/version' 7 | 8 | module Liqpay 9 | def self.config # :nodoc: 10 | @config ||= ::Liqpay::Config.new 11 | end 12 | 13 | def self.new(*options) 14 | ::Liqpay::Liqpay.new(*options) 15 | end 16 | 17 | def self.configure # :nodoc: 18 | yield config 19 | @config 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/liqpay/client.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # -*- frozen-string-literal: true -*- 3 | 4 | require 'net/http' 5 | require 'uri' 6 | 7 | module Liqpay 8 | class Client 9 | API_URL = 'https://www.liqpay.ua/api/' 10 | 11 | attr_reader :api_uri 12 | 13 | def initialize(api_url = nil) 14 | @api_uri = URI.parse(api_url || API_URL) 15 | end 16 | 17 | def post(path, data, signature) 18 | params = { data: data, signature: signature } 19 | uri = endpoint(path) 20 | # read_timeout is 60seec by default 21 | response = Net::HTTP.post_form(uri, params) 22 | Coder.decode_json(response.body) 23 | end 24 | 25 | def endpoint(path) 26 | api_uri + path 27 | end 28 | 29 | end # Client 30 | end # Liqpay 31 | -------------------------------------------------------------------------------- /lib/liqpay/coder.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'base64' 4 | require 'digest/sha1' 5 | require 'json' 6 | 7 | module Liqpay 8 | module Coder 9 | extend self 10 | 11 | def encode_signature(param) 12 | sha1 = Digest::SHA1.digest(param) 13 | #Base64.strict_encode64(sha1) 14 | encode64(sha1) 15 | end 16 | 17 | def encode64_json(params) 18 | encode64(encode_json(params)) 19 | end 20 | 21 | def decode64_json(data) 22 | decode_json(Base64.decode64(data)) 23 | end 24 | 25 | def encode_json(params) 26 | JSON.generate(params) 27 | end 28 | 29 | def decode_json(json) 30 | JSON.parse(json) 31 | end 32 | 33 | def encode64(param) 34 | Base64.strict_encode64(param) 35 | end 36 | 37 | end # Coder 38 | end # Liqpay 39 | -------------------------------------------------------------------------------- /lib/liqpay/config.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # -*- frozen-string-literal: true -*- 3 | 4 | module Liqpay 5 | class Config 6 | attr_accessor :private_key, :public_key, :version, :server_url, :return_url 7 | 8 | def initialize 9 | @private_key = ENV['LIQPAY_PRIVATE_KEY'] 10 | @public_key = ENV['LIQPAY_PUBLIC_KEY'] 11 | @version = 3 12 | end 13 | 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/liqpay/liqpay.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # -*- frozen-string-literal: true -*- 3 | 4 | # Liqpay Payment Module 5 | 6 | # NOTICE OF LICENSE 7 | 8 | # This source file is subject to the Open Software License (OSL 3.0) 9 | # that is available through the world-wide-web at this URL: 10 | # http://opensource.org/licenses/osl-3.0.php 11 | 12 | # @category LiqPay 13 | # @package LiqPay 14 | # @version 0.0.3 15 | # @author Liqpay 16 | # @copyright Copyright (c) 2014 Liqpay 17 | # @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 18 | 19 | # EXTENSION INFORMATION 20 | 21 | # LIQPAY API https://www.liqpay.ua/documentation/en 22 | 23 | # Payment method liqpay process 24 | 25 | # @author Liqpay 26 | 27 | module Liqpay 28 | class Liqpay 29 | attr_reader :client, :public_key, :private_key 30 | 31 | AVAILABLE_LANGUAGES = ['ru', 'uk', 'en'] 32 | BUTTON_TEXTS = { ru: 'Оплатить', uk: 'Сплатити', en: 'Pay' } 33 | 34 | def initialize(options = {}) 35 | @public_key = options[:public_key] || ::Liqpay.config.public_key 36 | @private_key = options[:private_key] || ::Liqpay.config.private_key 37 | @client = Client.new 38 | end 39 | 40 | def api(path, params) 41 | params = normalize_and_check(params, api_defaults, :version) 42 | 43 | data, signature = data_and_signature(params) 44 | 45 | client.post(path, data, signature) 46 | end 47 | 48 | def checkout_url(params) 49 | params = normalize_and_check(params, api_defaults, :version, :action, :amount, :currency, :description, :order_id) 50 | language = if AVAILABLE_LANGUAGES.include?(params[:language]) 51 | params[:language] 52 | else 53 | 'uk' 54 | end 55 | 56 | data, signature = data_and_signature(params) 57 | 58 | client.endpoint("3/checkout?data=#{data}&signature=#{signature}&language=#{language}") 59 | end 60 | 61 | def cnb_form(params) 62 | params = normalize_and_check(params, {}, :version, :amount, :currency, :description) 63 | language = if AVAILABLE_LANGUAGES.include?(params[:language]) 64 | params[:language] 65 | else 66 | 'uk' 67 | end 68 | 69 | data, signature = data_and_signature(params) 70 | 71 | HTML_FORM % [client.endpoint('3/checkout'), data, signature, BUTTON_TEXTS[language.to_sym]] 72 | end 73 | 74 | def match?(data, signature) 75 | signature == encode_signature(data) 76 | end 77 | 78 | def decode_data(data) 79 | Coder.decode64_json(data) 80 | end 81 | 82 | def encode_signature(data) 83 | str = private_key + data + private_key 84 | Coder.encode_signature(str) 85 | end 86 | 87 | # Useless method for backward compatibility 88 | def cnb_signature(params = {}) 89 | warn 'DEPRECATION WARNING: the method cnb_signature is deprecated.' 90 | params = normalize_and_check(params, {}, :version, :amount, :currency, :description) 91 | data_and_signature(params).last 92 | end 93 | 94 | # Useless method for backward compatibility 95 | def str_to_sign(str) 96 | warn 'DEPRECATION WARNING: the method str_to_sign is deprecated. Use encode_signature insted.' 97 | Coder.encode_signature str 98 | end 99 | 100 | private 101 | 102 | def normalize_and_check(params, defaults, *check_keys) 103 | nomalized = Parameters.normalize(params) 104 | defaults.merge!(nomalized).tap do |result| 105 | Parameters.validate_presence!(result, *check_keys) 106 | result[:public_key] = public_key 107 | end 108 | end 109 | 110 | def api_defaults 111 | { 112 | version: ::Liqpay.config.version.to_s, 113 | server_url: ::Liqpay.config.server_url, 114 | return_url: ::Liqpay.config.return_url, 115 | } 116 | end 117 | 118 | def data_and_signature(params) 119 | base64_json = Coder.encode64_json(params) 120 | [base64_json, encode_signature(base64_json)] 121 | end 122 | 123 | HTML_FORM = <<-FORM_CODE 124 |
125 | 126 | 127 | 128 | 129 |
130 | FORM_CODE 131 | 132 | end # Liqpay 133 | end # Liqpay 134 | -------------------------------------------------------------------------------- /lib/liqpay/parameters.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # -*- frozen-string-literal: true -*- 3 | 4 | module Liqpay 5 | module Parameters 6 | extend self 7 | 8 | def normalize(params) 9 | params 10 | .map do |key, value| 11 | [ key.to_sym, (Enumerable === value ? value : value.to_s) ] 12 | end 13 | .to_h 14 | end 15 | 16 | def validate_presence!(params, *keys) 17 | keys.each do |key| 18 | param = params[ key ].to_s 19 | if param.nil? || param.empty? 20 | fail("%s can't be empty" % key.to_s.capitalize) 21 | end 22 | end 23 | end 24 | 25 | end # Parameters 26 | end # Liqpay 27 | -------------------------------------------------------------------------------- /lib/liqpay/version.rb: -------------------------------------------------------------------------------- 1 | # -*- frozen-string-literal: true -*- 2 | 3 | module Liqpay 4 | VERSION = '0.0.4' 5 | end 6 | -------------------------------------------------------------------------------- /liqpay.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib/', __FILE__) 2 | $:.unshift lib unless $:.include?(lib) 3 | 4 | require 'liqpay/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.name = 'liqpay' 8 | s.version = Liqpay::VERSION 9 | s.platform = Gem::Platform::RUBY 10 | s.authors = ['Oleg Kukareka'] 11 | s.email = 'oleg@kukareka.com' 12 | s.homepage = 'https://github.com/liqpay/sdk-ruby/' 13 | s.summary = 'liqpay.ua Ruby SDK' 14 | s.description = 'Gem wrapper for official liqpay/sdk-ruby' 15 | s.required_ruby_version = '>= 1.9' 16 | 17 | s.rubyforge_project = 'liqpay' 18 | 19 | s.files = Dir['{lib}/**/*.rb'] 20 | s.require_path = 'lib' 21 | end 22 | -------------------------------------------------------------------------------- /spec/classes/api/invoice_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | begin 4 | require File.expand_path('../../../dummy/config.rb', __FILE__) unless ENV['LIQPAY_PUBLIC_KEY'] && ENV['LIQPAY_PRIVATE_KEY'] 5 | rescue LoadError 6 | puts "Could not load API keys. Please provide API keys in ENV['LIQPAY_PUBLIC_KEY'] && ENV['LIQPAY_PRIVATE_KEY'] or spec/dummy/config.rb" 7 | end 8 | 9 | module Liqpay 10 | describe 'Invoice API' do 11 | before :all do 12 | @liqpay = Liqpay.new 13 | end 14 | it 'should be able to send and cancel invoices', real: true do 15 | email = ENV[ 'TEST_EMAIL' ] || 'test@example.com' 16 | order_id = 'test-%s-%s' % [Time.now.to_i.to_s(16), rand(1000)] 17 | r = @liqpay.api('request', { 18 | action: 'invoice_send', 19 | version: '3', 20 | email: email, 21 | amount: '1', 22 | currency: 'USD', 23 | order_id: order_id, 24 | goods: [{ 25 | amount: 100, 26 | count: 2, 27 | unit: 'шт.', 28 | name: 'телефон' 29 | }] 30 | }) 31 | 32 | expect(r['result']).to eq 'ok' 33 | # r = @liqpay.api 'invoice/cancel', {order_id: order_id} 34 | r = @liqpay.api('request', { 35 | action: 'invoice_cancel', 36 | version: '3', 37 | order_id: order_id 38 | }) 39 | expect(r['result']).to eq 'ok' 40 | 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/classes/config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Liqpay 4 | describe ::Liqpay::Config do 5 | before :each do 6 | ::Liqpay.configure do |c| 7 | c.private_key = 'private_key' 8 | c.public_key = 'public_key' 9 | end 10 | end 11 | 12 | it 'should have private key' do 13 | expect(::Liqpay.config.private_key).to eq 'private_key' 14 | end 15 | 16 | it 'should have public key' do 17 | expect(::Liqpay.config.public_key).to eq 'public_key' 18 | end 19 | 20 | it 'should reflect changes through block' do 21 | ::Liqpay.configure do |c| 22 | c.private_key = 'private_key3' 23 | end 24 | expect(::Liqpay.config.private_key).to eq 'private_key3' 25 | end 26 | end 27 | 28 | end 29 | 30 | -------------------------------------------------------------------------------- /spec/classes/liqpay_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'spec_helper' 3 | 4 | 5 | describe :cnd_form do 6 | let(:liqpay_empty) { Liqpay::Liqpay.new( 7 | :private_key => '', 8 | :public_key => '' 9 | )} 10 | let(:liqpay_full) { Liqpay::Liqpay.new( 11 | :private_key => 'private_key', 12 | :public_key => 'public_key' 13 | )} 14 | context 'when creating form' do 15 | before :all do 16 | ::Liqpay.configure do |c| 17 | c.version = nil 18 | c.private_key = nil 19 | end 20 | end 21 | it 'does not create form without version field' do 22 | expect { 23 | Liqpay::Liqpay.new.cnb_form({}) 24 | }.to raise_error(RuntimeError,"Version can't be empty") 25 | end 26 | it 'does not create form without amount field' do 27 | expect { 28 | Liqpay::Liqpay.new.cnb_form( 29 | :version => "3" 30 | ) 31 | }.to raise_error(RuntimeError,"Amount can't be empty") 32 | end 33 | it 'does not create form without currency field' do 34 | expect { 35 | Liqpay::Liqpay.new.cnb_form( 36 | :version => "3", 37 | :amount => "1" 38 | ) 39 | }.to raise_error(RuntimeError,"Currency can't be empty") 40 | end 41 | it 'does not create form without description field' do 42 | expect { 43 | Liqpay::Liqpay.new.cnb_form( 44 | :version => "3", 45 | :amount => "1", 46 | :currency => "UAH" 47 | ) 48 | }.to raise_error(RuntimeError,"Description can't be empty") 49 | end 50 | it 'does not create form without private key' do 51 | expect { 52 | Liqpay::Liqpay.new.cnb_form( 53 | :version => "3", 54 | :amount => "1", 55 | :currency => "UAH", 56 | :description => "my comment" 57 | ) 58 | }.to raise_error(NoMethodError,"undefined method `+' for nil:NilClass") 59 | end 60 | it 'creates form with empty keys' do 61 | expect( 62 | liqpay_empty.cnb_form( 63 | :version => "3", 64 | :amount => "1", 65 | :currency => "UAH", 66 | :description => "my comment" 67 | ) 68 | ).to eq("
\n\n\n\n\n
\n") 69 | end 70 | it 'creates form with not empty keys' do 71 | expect( 72 | liqpay_full.cnb_form( 73 | :version => "3", 74 | :amount => "1", 75 | :currency => "UAH", 76 | :description => "my comment" 77 | ) 78 | ).to eq("
\n\n\n\n\n
\n") 79 | end 80 | end 81 | end 82 | 83 | describe :api do 84 | let(:liqpay_full) { Liqpay::Liqpay.new( 85 | :private_key => 'private_key', 86 | :public_key => 'public_key' 87 | )} 88 | context 'when creating api request' do 89 | it 'does not create request without version field' do 90 | expect{liqpay_full.api("payment/pay",{}) 91 | }.to raise_error(RuntimeError,"Version can't be empty") 92 | end 93 | =begin 94 | it 'returns err_access for pay request' do 95 | expect(liqpay_full.api("payment/pay",{ 96 | :version => "3" 97 | }) 98 | ).to eq({"code"=>"err_access", "result"=>"err_access"}) 99 | end 100 | it 'returns err_access for pay request' do 101 | expect(liqpay_full.api("payment/status",{ 102 | :version => "3", 103 | :order_id => "test" 104 | }) 105 | ).to eq({"result"=>"error", "description"=>"payment_not_found"}) 106 | end 107 | it 'returns err_access for pay request' do 108 | expect(liqpay_full.api("payment/status",{ 109 | :version => "3", 110 | :order_id => "тест" 111 | }) 112 | ).to eq({"result"=>"error", "description"=>"payment_not_found"}) 113 | end 114 | =end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'liqpay' --------------------------------------------------------------------------------