checkout_ru 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4f7c669476b48bf6c2585075af8cf275904df085
4
+ data.tar.gz: 901fa3aaa1776114ae22bbf858643ee11e952002
5
+ SHA512:
6
+ metadata.gz: fa118d735dc00134517804754068b6f0eafbc26ba73cb07327d6219e69c3243fd315f21f5da068538ab617336848c739adc19d946fd22412cbc1390954217828
7
+ data.tar.gz: 7efcb3ac91d2159a020ef60ca72657c2541f46f959addd1f3c1a70072cb7580509e754d45f3d2d9827b6fed24e70695140ed04ca91bafe598e2011079d81bc88
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in checkout_ru.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Maxim Chernyak
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # CheckoutRu
2
+
3
+ Thin ruby client for checkout.ru integration.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'checkout_ru'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install checkout_ru
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ CheckoutRu.api_key = 'my-api-key'
23
+
24
+ session = CheckoutRu::Session.initiate
25
+ places = session.get_places_by_query('Москва')
26
+ places[0].name # => "г. Москва"
27
+
28
+ order = CheckoutRu::Order.new(
29
+ goods: [
30
+ CheckoutRu::Item.new(
31
+ name: 'blue tshirt',
32
+ code: 'blt',
33
+ variant_code: 'blue',
34
+ quantity: 2,
35
+ assessed_cost: 1000,
36
+ pay_cost: 750,
37
+ weight: 0.5
38
+ )
39
+ ],
40
+
41
+ delivery: CheckoutRu::Delivery.new(
42
+ delivery_id: 2,
43
+ place_fias_id: '0c5b2444-70a0-4932-980c-b4dc0d3f02b5',
44
+ address_pvz: 'Энтузиастов ш., д. 54',
45
+ type: 'postamat',
46
+ cost: 224.41,
47
+ min_term: 2,
48
+ max_term: 11
49
+ ),
50
+
51
+ user: CheckoutRu::User.new(
52
+ fullname: 'Вася Пупкин',
53
+ email: 'vasyapupkin@example.com',
54
+ phone: '555'
55
+ ),
56
+
57
+ comment: 'test order',
58
+ shop_order_id: '777',
59
+ payment_method: 'cash'
60
+ )
61
+
62
+ CheckoutRu.create_order(order) # => <Hashie::Mash>, structure:
63
+ # {"order":{"id":75},"delivery":{"id":2,"service_name":"PickPoint"}}
64
+ ```
65
+
66
+ ## Contributing
67
+
68
+ 1. Fork it ( http://github.com/maxim/checkout_ru/fork )
69
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
70
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
71
+ 4. Push to the branch (`git push origin my-new-feature`)
72
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.pattern = 'test/**/*_test.rb'
7
+ end
8
+
9
+ task :default => :test
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'checkout_ru/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "checkout_ru"
8
+ spec.version = CheckoutRu::VERSION
9
+ spec.authors = ["Maxim Chernyak"]
10
+ spec.email = ["max@bitsonnet.com"]
11
+ spec.summary = %q{Ruby client for checkout.ru}
12
+ spec.description = %q{Ruby client for checkout.ru.}
13
+ spec.homepage = 'http://github.com/maxim/checkout_ru'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency 'faraday', '~> 0.8'
22
+ spec.add_runtime_dependency 'faraday_middleware', '~> 0.9'
23
+ spec.add_runtime_dependency 'faraday_middleware-multi_json', '~> 0.0'
24
+ spec.add_runtime_dependency 'multi_json', '~> 1.8'
25
+ spec.add_runtime_dependency 'hashie', '~> 2.0'
26
+ spec.add_runtime_dependency 'nokogiri', '~> 1.6'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.5'
29
+ spec.add_development_dependency 'rake', '~> 10'
30
+ spec.add_development_dependency 'minitest', '~> 5.2'
31
+ spec.add_development_dependency 'vcr', '~> 2.8'
32
+ end
@@ -0,0 +1,12 @@
1
+ require 'checkout_ru/entity'
2
+
3
+ module CheckoutRu
4
+ class Address < Entity
5
+ property :postindex
6
+ property :street_fias_id, :from => :streetFiasId
7
+ property :house
8
+ property :housing
9
+ property :building
10
+ property :appartment # [sic]
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ require 'checkout_ru/entity'
2
+ require 'checkout_ru/address'
3
+
4
+ module CheckoutRu
5
+ class Delivery < Entity
6
+ property :delivery_id, :required => true, :from => :deliveryId
7
+ property :place_fias_id, :required => true, :from => :placeFiasId
8
+
9
+ property :address_express, :from => :addressExpress
10
+ property :address_pvz, :from => :addressPvz
11
+
12
+ property :type, :required => true
13
+ property :cost, :required => true
14
+ property :min_term, :required => true, :from => :minTerm
15
+ property :max_term, :required => true, :from => :maxTerm
16
+
17
+ coerce_key :address_express, Address
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ require 'hashie'
2
+ require 'multi_json'
3
+
4
+ module CheckoutRu
5
+ class Entity < Hashie::Trash
6
+ include Hashie::Extensions::Coercion
7
+
8
+ def to_json(options = {})
9
+ hash = self.to_hash
10
+ CheckoutRu.camelize_keys!(hash)
11
+ MultiJson.dump(hash, options.merge(:pretty => true))
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ require 'checkout_ru/entity'
2
+
3
+ module CheckoutRu
4
+ class Item < Entity
5
+ class << self
6
+ def coerce(value)
7
+ value.is_a?(Array) ?
8
+ value.map{ |hash| new(hash) } :
9
+ new(value)
10
+ end
11
+ end
12
+
13
+ property :name, :required => true
14
+ property :code, :required => true
15
+ property :variant_code, :required => true, :from => :variantCode
16
+ property :quantity, :required => true
17
+ property :assessed_cost, :required => true, :from => :assessedCost
18
+ property :pay_cost, :required => true, :from => :payCost
19
+ property :weight, :required => true
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ require 'checkout_ru/entity'
2
+ require 'checkout_ru/item'
3
+ require 'checkout_ru/delivery'
4
+ require 'checkout_ru/user'
5
+
6
+ module CheckoutRu
7
+ class Order < Entity
8
+ module Status
9
+ CREATED = 'CREATED'.freeze
10
+ CANCELLED_BEFORE_SHIPMENT = 'CANCELLED_BEFORE_SHIPMENT'.freeze
11
+
12
+ MAP = {
13
+ :created => CREATED,
14
+ :cancelled_before_shipment => CANCELLED_BEFORE_SHIPMENT
15
+ }.freeze
16
+ end
17
+
18
+ property :goods
19
+ property :delivery, :required => true
20
+ property :user, :required => true
21
+ property :comment
22
+ property :shop_order_id, :from => :shopOrderId
23
+ property :payment_method, :required => true, :from => :paymentMethod
24
+
25
+ coerce_key :goods, Item
26
+ coerce_key :delivery, Delivery
27
+ coerce_key :user, User
28
+
29
+ def initialize(*)
30
+ super
31
+ self[:goods] ||= []
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,46 @@
1
+ module CheckoutRu
2
+ class Session
3
+ class << self
4
+ def initiate
5
+ ticket = CheckoutRu.get_ticket
6
+ new(ticket)
7
+ end
8
+ end
9
+
10
+ def initialize(ticket, options = {})
11
+ @ticket = ticket
12
+ @conn = CheckoutRu.build_connection
13
+ end
14
+
15
+ def get_places_by_query(options = {})
16
+ get('checkout/getPlacesByQuery', options).suggestions
17
+ end
18
+
19
+ def calculation(options = {})
20
+ params = options.dup
21
+ get('checkout/calculation', params)
22
+ end
23
+
24
+ def get_streets_by_query(options = {})
25
+ params = options.dup
26
+ get('checkout/getStreetsByQuery', params).suggestions
27
+ end
28
+
29
+ def get_postal_code_by_address(options = {})
30
+ params = options.dup
31
+ get('checkout/getPostalCodeByAddress', params).postindex
32
+ end
33
+
34
+ def get_place_by_postal_code(options = {})
35
+ get('checkout/getPlaceByPostalCode', options)
36
+ end
37
+
38
+ private
39
+ def get(service, params = {})
40
+ CheckoutRu.make_request \
41
+ "/service/#{service}",
42
+ :connection => @conn,
43
+ :params => params.merge(:ticket => @ticket)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,9 @@
1
+ require 'checkout_ru/entity'
2
+
3
+ module CheckoutRu
4
+ class User < Entity
5
+ property :fullname
6
+ property :email
7
+ property :phone
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module CheckoutRu
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,147 @@
1
+ require 'date'
2
+ require 'time'
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+ require 'faraday_middleware-multi_json'
6
+ require 'nokogiri'
7
+
8
+ require 'checkout_ru/version'
9
+ require 'checkout_ru/session'
10
+ require 'checkout_ru/order'
11
+
12
+ module CheckoutRu
13
+ SERVICE_URL = 'http://platform.checkout.ru'.freeze
14
+
15
+ Error = Class.new(Faraday::Error::ClientError)
16
+
17
+ class << self
18
+ attr_accessor :api_key, :adapter
19
+
20
+ def get_ticket(options = {})
21
+ api_key = options[:api_key] || api_key
22
+ make_request("/service/login/ticket/#{api_key}")['ticket']
23
+ end
24
+
25
+ def create_order(order, options = {})
26
+ api_key = options[:api_key] || api_key
27
+ make_request '/service/order/create',
28
+ :via => :post,
29
+ :mash => true,
30
+ :params => { :api_key => api_key, :order => order }
31
+ end
32
+
33
+ def update_order(remote_id, order, options = {})
34
+ api_key = options[:api_key] || api_key
35
+ make_request "/service/order/#{remote_id}",
36
+ :via => :post,
37
+ :mash => true,
38
+ :params => { :api_key => api_key, :order => order }
39
+ end
40
+
41
+ def status(remote_id, status, options = {})
42
+ api_key = options[:api_key] || api_key
43
+ status_map = Order::Status::MAP
44
+
45
+ status_string = if status.is_a?(Symbol)
46
+ unless status_map.keys.include?(status)
47
+ raise Error, "Invalid order status: #{status}"
48
+ end
49
+
50
+ status_map[status]
51
+ else
52
+ unless status_map.values.include?(status)
53
+ raise Error, "Invalid order status: #{status}"
54
+ end
55
+
56
+ status
57
+ end
58
+
59
+ make_request "/service/order/status/#{remote_id}",
60
+ :via => :post,
61
+ :params => { :api_key => api_key, :status => status_string }
62
+ end
63
+
64
+ def status_history(order_id, options = {})
65
+ api_key = options[:api_key] || api_key
66
+ response = make_request "/service/order/statushistory/#{order_id}",
67
+ :params => { :api_key => api_key }
68
+
69
+ response.order.date = Date.parse(response.order.date)
70
+ response
71
+ end
72
+
73
+ def build_connection(options = {})
74
+ Faraday.new(:url => options[:url] || SERVICE_URL) do |faraday|
75
+ faraday.request :multi_json
76
+ faraday.response :raise_error
77
+ faraday.response :multi_json
78
+ faraday.adapter options[:adapter] || adapter || Faraday.default_adapter
79
+ end
80
+ end
81
+
82
+ def make_request(service, options = {})
83
+ conn = options[:connection] || build_connection
84
+ method = options[:via] || :get
85
+ params = options[:params].dup if options[:params]
86
+ camelize_keys!(params)
87
+
88
+ body = conn.public_send(method, service, params,
89
+ { 'Accept' => 'application/json' }
90
+ ).body
91
+
92
+ underscore_keys!(body)
93
+
94
+ case body
95
+ when Hash
96
+ ::Hashie::Mash.new(body)
97
+ when Array
98
+ body.map{|el| ::Hashie::Mash.new(el)}
99
+ else
100
+ body
101
+ end
102
+
103
+ rescue Faraday::Error::ClientError => e
104
+ begin
105
+ doc = Nokogiri::HTML(e.response[:body])
106
+ doc.css('script, link').each(&:remove)
107
+ msg = doc.css('body h1').text
108
+ rescue
109
+ raise e
110
+ else
111
+ raise Error, msg
112
+ end
113
+ end
114
+
115
+ def camelize_keys!(obj)
116
+ case obj
117
+ when Hash
118
+ obj.replace(Hash[
119
+ obj.map do |key, value|
120
+ [ key.to_s.downcase.gsub(/_([a-z\d]*)/) { "#{$1.capitalize}" },
121
+ camelize_keys!(value) ]
122
+ end
123
+ ])
124
+ when Array
125
+ obj.map! {|el| camelize_keys!(el)}
126
+ else
127
+ obj
128
+ end
129
+ end
130
+
131
+ def underscore_keys!(obj)
132
+ case obj
133
+ when Hash
134
+ obj.replace(Hash[
135
+ obj.map do |key, value|
136
+ [ key.to_s.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase,
137
+ underscore_keys!(value) ]
138
+ end
139
+ ])
140
+ when Array
141
+ obj.map! {|el| underscore_keys!(el)}
142
+ else
143
+ obj
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,82 @@
1
+ require 'test_helper'
2
+
3
+ class CheckoutRu::SessionTest < MiniTest::Test
4
+ def setup
5
+ @session = CheckoutRu::Session.new('valid-ticket')
6
+ end
7
+
8
+ def test_get_places_by_query
9
+ VCR.use_cassette('get_places_by_query') do
10
+ places = @session.get_places_by_query(:place => 'москва')
11
+
12
+ assert_equal 4, places.size
13
+
14
+ assert_equal '0c5b2444-70a0-4932-980c-b4dc0d3f02b5', places[0].id
15
+ assert_equal 'Москва', places[0].name
16
+ assert_equal 'г. Москва', places[0].full_name
17
+
18
+ assert_equal '3605e660-e90e-47d3-b58e-068f24e68145', places[1].id
19
+ assert_equal 'Москва', places[1].name
20
+ assert_equal 'д. Москва (Пеновский район, Тверская область)',
21
+ places[1].full_name
22
+ end
23
+ end
24
+
25
+ def test_calculation
26
+ VCR.use_cassette('calculation') do
27
+ calculation = @session.calculation(
28
+ :place_id => '0c5b2444-70a0-4932-980c-b4dc0d3f02b5',
29
+ :total_sum => 1500,
30
+ :assessed_sum => 2000,
31
+ :items_count => 2,
32
+ :total_weight => 1
33
+ )
34
+
35
+ assert calculation.keys.include?('postamat')
36
+ assert calculation.keys.include?('pvz')
37
+ assert calculation.keys.include?('express')
38
+
39
+ assert_equal 73, calculation.postamat.costs.size
40
+ assert_equal 68, calculation.pvz.costs.size
41
+ assert_equal 340, calculation.express.cost
42
+ end
43
+ end
44
+
45
+ def test_get_streets_by_query
46
+ VCR.use_cassette('get_streets_by_query') do
47
+ streets = @session.get_streets_by_query(
48
+ :street => 'мас',
49
+ :place_id => '0c5b2444-70a0-4932-980c-b4dc0d3f02b5'
50
+ )
51
+
52
+ assert_equal '960972e8-48bb-4837-b0ee-ee0347931b73', streets[0].id
53
+ assert_equal 'Масловка Верхн.', streets[0].name
54
+ assert_equal 'ул', streets[0].type
55
+
56
+ assert_equal 'ad4b99f0-33da-4661-aa29-057557cf4147', streets[1].id
57
+ assert_equal 'Масловка Нижн.', streets[1].name
58
+ assert_equal 'ул', streets[1].type
59
+ end
60
+ end
61
+
62
+ def test_get_postal_code_by_address
63
+ VCR.use_cassette('get_postal_code_by_address') do
64
+ postal_code = @session.get_postal_code_by_address(
65
+ :street_id => '2b453e3c-d908-4608-b81c-a314a687bee3',
66
+ :house => 13, :housing => '', :building => ''
67
+ )
68
+
69
+ assert_equal '111524', postal_code
70
+ end
71
+ end
72
+
73
+ def test_get_place_by_postal_code
74
+ VCR.use_cassette('get_place_by_postal_code') do
75
+ place = @session.get_place_by_postal_code(:post_index => '111524')
76
+
77
+ assert_equal '0c5b2444-70a0-4932-980c-b4dc0d3f02b5', place.id
78
+ assert_equal 'Москва', place.name
79
+ assert_equal 'г. Москва', place.full_name
80
+ end
81
+ end
82
+ end