checkout_ru 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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