lakala 0.1.4

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
+ SHA256:
3
+ metadata.gz: 906a1089e329e9588c4fac765413187d6c9099d09f4f7f987f522b2bc4db574a
4
+ data.tar.gz: e3ac03ca317df3ffde80cbc0b7ffea8a6430eb83bb1bea10cf4e1749e5cf9b13
5
+ SHA512:
6
+ metadata.gz: f4dafeec450217d133fe064c935b388488c21a21e2aba58c7ccaa8572c424447eafbad60b994adefcd0d8fb7ad9e83d172b8034af41725b80205dea619c3b9ee
7
+ data.tar.gz: c0260421fb0f643f9fb7583fca100f78d67627ebadcf869073babb3cf9b28e1088a43ce9d4229414131b98415b379da2e48663f79b1c6e264d8936753b82e238
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in lakala.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ group :test do
10
+ gem 'webmock', '~> 3.19', '>= 3.19.1'
11
+ gem "rspec", "~> 3.0"
12
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,49 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lakala (0.1.3)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ addressable (2.8.5)
10
+ public_suffix (>= 2.0.2, < 6.0)
11
+ crack (0.4.5)
12
+ rexml
13
+ diff-lcs (1.5.0)
14
+ hashdiff (1.0.1)
15
+ minitest (5.19.0)
16
+ public_suffix (5.0.4)
17
+ rake (13.0.6)
18
+ rexml (3.2.6)
19
+ rspec (3.12.0)
20
+ rspec-core (~> 3.12.0)
21
+ rspec-expectations (~> 3.12.0)
22
+ rspec-mocks (~> 3.12.0)
23
+ rspec-core (3.12.0)
24
+ rspec-support (~> 3.12.0)
25
+ rspec-expectations (3.12.0)
26
+ diff-lcs (>= 1.2.0, < 2.0)
27
+ rspec-support (~> 3.12.0)
28
+ rspec-mocks (3.12.0)
29
+ diff-lcs (>= 1.2.0, < 2.0)
30
+ rspec-support (~> 3.12.0)
31
+ rspec-support (3.12.0)
32
+ webmock (3.19.1)
33
+ addressable (>= 2.8.0)
34
+ crack (>= 0.3.2)
35
+ hashdiff (>= 0.4.0, < 2.0.0)
36
+
37
+ PLATFORMS
38
+ arm64-darwin-22
39
+
40
+ DEPENDENCIES
41
+ bundler (~> 2.0)
42
+ lakala!
43
+ minitest (~> 5.0)
44
+ rake (~> 13.0)
45
+ rspec (~> 3.0)
46
+ webmock (~> 3.19, >= 3.19.1)
47
+
48
+ BUNDLED WITH
49
+ 2.3.25
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Drin-E
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # Lakala
2
+
3
+ Lakala Ruby client with some basic features for Lakala APIs.
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add lakala
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install lakala
14
+
15
+ ## Usage
16
+
17
+ ``` ruby
18
+ # Create order -> http://open.lakala.com/#/home/document/detail?id=283
19
+ Lakala::Client.new.create_order(
20
+ merchant_no: '8221210594300JY',
21
+ out_order_no: 'aa3c56712',
22
+ total_amount: 200,
23
+ order_info: 'Test Product',
24
+ order_efficient_time: '20231113163310'
25
+ )
26
+
27
+ # Query order -> http://open.lakala.com/#/home/document/detail?id=284
28
+ Lakala::Client.new.query_order(merchant_no: '8221210594300JY', out_order_no: 'aa3c56712')
29
+
30
+ # Invoke refund -> http://open.lakala.com/#/home/document/detail?id=113
31
+ Lakala::Client.new.refund(
32
+ merchant_no: '8221210594300JY',
33
+ geo_location: '30.700323750778548, 104.0029400018854',
34
+ request_ip: '222.209.111.239',
35
+ refund_amount: 1,
36
+ term_no: 'A0073841',
37
+ out_trade_no: 'aa3c56723123',
38
+ origin_trade_no: '2023120866210311384863'
39
+ )
40
+ ```
41
+ ## Development
42
+
43
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
44
+
45
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
46
+
47
+ ## License
48
+
49
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: %i[spec]
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lakala
4
+ class Client
5
+ attr_reader :response
6
+
7
+ def initialize(serial_no = nil)
8
+ @serial_no = serial_no || config.serial_no
9
+ end
10
+
11
+ def create_order(options = {})
12
+ options = Lakala::Utils.stringify_keys(options)
13
+
14
+ requires!(options, %w[out_order_no merchant_no total_amount order_info order_efficient_time])
15
+
16
+ raise ArgumentError, 'total_amount must be a positive integer' unless options['total_amount'].is_a?(Integer) && options['total_amount'] > 0
17
+
18
+ response = req('/api/v3/ccss/counter/order/create', options)
19
+
20
+ Response.new(response)
21
+ end
22
+
23
+ def create_order_enc(options = {})
24
+ options = Lakala::Utils.stringify_keys(options)
25
+
26
+ requires!(options, %w[out_order_no merchant_no total_amount order_info order_efficient_time])
27
+
28
+ raise ArgumentError, 'total_amount must be a positive integer' unless options['total_amount'].is_a?(Integer) && options['total_amount'] > 0
29
+
30
+ response = req('/api/v3/ccss/counter/order/create_encry', options, encrypt: true)
31
+
32
+ Response.new(response)
33
+ end
34
+
35
+ def query_order(options)
36
+ options = Lakala::Utils.stringify_keys(options)
37
+ if options['out_order_no'].nil?
38
+ requires!(options, %w[pay_order_no channel_id])
39
+ else
40
+ requires!(options, %w[merchant_no out_order_no])
41
+ end
42
+
43
+ response = req('/api/v3/ccss/counter/order/query', options)
44
+
45
+ Response.new(response)
46
+ end
47
+
48
+ def refund(options)
49
+ options = Lakala::Utils.stringify_keys(options)
50
+ requires!(options, %w[merchant_no term_no out_trade_no refund_amount request_ip])
51
+ requires_at_least_one!(options, %w[origin_out_trade_no origin_trade_no origin_log_no])
52
+
53
+ opts = {
54
+ 'location_info' => {
55
+ 'request_ip' => options['request_ip'],
56
+ 'location' => options['geo_location'],
57
+ 'base_station' => options['base_station']
58
+ }
59
+ }.compact
60
+ options.merge!(opts)
61
+
62
+ response = req('/api/v3/labs/relation/refund', options)
63
+
64
+ Response.new(response)
65
+ end
66
+
67
+ def query_refund_order(options)
68
+ options = Lakala::Utils.stringify_keys(options)
69
+
70
+ requires!(options, %w[merchant_no term_no out_refund_order_no])
71
+
72
+ response = req('/api/v3/labs/query/idmrefundquery', options)
73
+
74
+ Response.new(response)
75
+ end
76
+
77
+ private
78
+
79
+ def prepare_params(request_body)
80
+ {
81
+ req_data: request_body,
82
+ version: '3.0',
83
+ req_time: Time.now.strftime('%Y%m%d%H%M%S')
84
+ }
85
+ end
86
+
87
+ def config
88
+ Lakala.configuration
89
+ end
90
+
91
+ def req(path, body, encrypt: false)
92
+ uri = build_uri(path)
93
+ http = build_http(uri)
94
+
95
+ prepared_body = prepare_params(body).to_json
96
+
97
+ request_body = encrypt ? encrypt(prepared_body) : prepared_body
98
+
99
+ headers = generate_authorization_header(request_body)
100
+
101
+
102
+ request = build_post_request(uri, headers, request_body)
103
+
104
+ http.request(request)
105
+ end
106
+
107
+ def requires!(options, required_opts)
108
+ required_opts.each do |f|
109
+ raise "Missing required argument: #{f}" if options[f].nil? && !options[f]&.to_s&.empty? && options[f] != false
110
+ end
111
+ end
112
+
113
+ def requires_at_least_one!(options, required_opts)
114
+ return unless required_opts.none? { |f| options.key?(f) && !options[f]&.to_s&.empty? && options[f] != false }
115
+
116
+ raise "At least one of the options is required: #{required_opts.join(', ')}"
117
+ end
118
+
119
+ def generate_authorization_header(params)
120
+ Lakala::HeaderGenerator.new(params, @serial_no).generate
121
+ end
122
+
123
+ def build_uri(path)
124
+ uri = URI.parse("#{Lakala.gateway_url}#{path}")
125
+ uri.scheme = 'https'
126
+ uri
127
+ end
128
+
129
+ def build_http(uri)
130
+ http = Net::HTTP.new(uri.host, uri.port)
131
+ http.use_ssl = true
132
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
133
+ http
134
+ end
135
+
136
+ def build_post_request(uri, headers, prepared_params)
137
+ request = Net::HTTP::Post.new(uri.path, headers)
138
+ request.body = prepared_params
139
+ request.content_type = 'application/json'
140
+ request
141
+ end
142
+
143
+ def encrypt(data)
144
+ ::Lakala::Cypher::Client.new(data).encrypt
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lakala
4
+ class Configuration
5
+ attr_accessor :app_id,
6
+ :serial_no,
7
+ :sandbox_mode
8
+
9
+ attr_reader :private_key,
10
+ :public_key,
11
+ :cypher_key
12
+
13
+ def private_key=(private_key)
14
+ @private_key = OpenSSL::PKey::RSA.new(private_key)
15
+ end
16
+
17
+ def public_key=(public_key)
18
+ @private_key = OpenSSL::X509::Certificate.new(public_key).public_key
19
+ end
20
+
21
+ def cypher_key=(cypher_key)
22
+ @cypher_key = Base64.strict_decode64(cypher_key)
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lakala
4
+ module Cypher
5
+ class Client
6
+ ALGORITHM_NAME_ECB_PADDING = 'sm4-ecb'
7
+
8
+ def encrypt(data)
9
+ encrypted_data = encrypt_ECB_Padding(data)
10
+ Base64.strict_encode64(encrypted_data)
11
+ end
12
+
13
+ def decrypt(cipher_text)
14
+ decrypted_data = decrypt_ECB_Padding(Base64.strict_decode64(cipher_text))
15
+ decrypted_data.force_encoding('utf-8')
16
+ end
17
+
18
+ private
19
+
20
+ def encrypt_ECB_Padding(data)
21
+ cipher = OpenSSL::Cipher.new(ALGORITHM_NAME_ECB_PADDING)
22
+ cipher.encrypt
23
+ cipher.key = ::Lakala.configuration.cypher_key
24
+ cipher.update(data) + cipher.final
25
+ end
26
+
27
+ def decrypt_ECB_Padding(cipher_text)
28
+ cipher = OpenSSL::Cipher.new(ALGORITHM_NAME_ECB_PADDING)
29
+ cipher.decrypt
30
+ cipher.key = ::Lakala.configuration.cypher_key
31
+ cipher.update(cipher_text) + cipher.final
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lakala
4
+ class HeaderGenerator
5
+ def initialize(params, serial_no = nil)
6
+ @serial_no = serial_no || config.serial_no
7
+ @params = params
8
+ end
9
+
10
+ def generate
11
+ nonce_str = Lakala::Utils.nonce_str
12
+
13
+ auth_string = "appid='#{config.app_id}'," \
14
+ "serial_no='#{@serial_no}'," \
15
+ "nonce_str='#{nonce_str}'," \
16
+ "timestamp='#{Time.now.to_i}'," \
17
+ "signature='#{Lakala::Sign.generate(@params, nonce_str)}'"
18
+
19
+ {
20
+ 'Content-Type' => 'application/json',
21
+ 'Authorization' => "LKLAPI-SHA256withRSA #{auth_string}"
22
+ }
23
+ end
24
+
25
+ private
26
+
27
+ def config
28
+ Lakala.configuration
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lakala
4
+ class Response
5
+ attr_reader :net_http_response,
6
+ :body,
7
+ :code
8
+
9
+ def initialize(res)
10
+ @net_http_response = res
11
+ @body = JSON.parse(res.body&.dup&.force_encoding('UTF-8'))
12
+ @code = res.code
13
+ end
14
+
15
+ def success?
16
+ !failed?
17
+ end
18
+
19
+ def failed?
20
+ @net_http_response.code != '200'
21
+ end
22
+
23
+ def error_code
24
+ @body['code'] if failed?
25
+ end
26
+
27
+ def error_msg
28
+ @body['msg'] if failed?
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lakala
4
+ module Sign
5
+ class << self
6
+ def string_to_verify(response_headers, response_body)
7
+ lkl_app_id = response_headers['Lklapi-Appid']
8
+ lkl_serial_no = response_headers['Lklapi-Serial']
9
+ lkl_timestamp = response_headers['Lklapi-Timestamp']
10
+ lkl_nonce_str = response_headers['Lklapi-Nonce']
11
+
12
+ "#{lkl_app_id}\n#{lkl_serial_no}\n#{lkl_timestamp}\n#{lkl_nonce_str}\n#{response_body}\n"
13
+ end
14
+
15
+ def generate(params, nonce_str)
16
+ timestamp = Time.now.to_i
17
+
18
+ string_to_sign = "#{Lakala.configuration.app_id}\n" \
19
+ "#{Lakala.configuration.serial_no}\n" \
20
+ "#{timestamp}\n" \
21
+ "#{nonce_str}\n" \
22
+ "#{params}\n"
23
+
24
+ signature = Lakala.configuration.private_key.sign(OpenSSL::Digest.new('SHA256'), string_to_sign)
25
+
26
+ Base64.strict_encode64(signature)
27
+ end
28
+
29
+ def verify?(headers, body)
30
+ data_to_verify = Lakala::Sign.string_to_verify(headers, body)
31
+ signature = Base64.strict_decode64(headers['Lklapi-Signature'])
32
+
33
+ Lakala.configuration.private_key.verify(OpenSSL::Digest.new('SHA256'), signature, data_to_verify)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Lakala
6
+ module Utils
7
+ def self.nonce_str
8
+ SecureRandom.hex(7)[0, 12]
9
+ end
10
+
11
+ def self.stringify_keys(hash)
12
+ new_hash = {}
13
+ hash.each do |key, value|
14
+ new_hash[(key.to_s rescue key) || key] = value
15
+ end
16
+ new_hash
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lakala
4
+ VERSION = '0.1.4'
5
+ end
data/lib/lakala.rb ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(__dir__) unless $LOAD_PATH.include?(__dir__)
4
+
5
+ require 'base64'
6
+ require 'openssl'
7
+ require 'uri'
8
+ require 'net/http'
9
+ require 'json'
10
+
11
+ require 'lakala/configuration'
12
+ require 'lakala/client'
13
+ require 'lakala/sign'
14
+ require 'lakala/utils'
15
+ require 'lakala/response'
16
+ require 'lakala/header_generator'
17
+ require 'lakala/cypher/client'
18
+
19
+ module Lakala
20
+ GATEWAY_URL = 'https://s2.lakala.com'
21
+ SANDBOX_GATEWAY_URL = 'https://test.wsmsd.cn/sit/'
22
+
23
+ attr_accessor :configuration
24
+
25
+ class << self
26
+ def configuration
27
+ @configuration ||= Configuration.new
28
+ end
29
+
30
+ def configure
31
+ yield(configuration)
32
+ end
33
+
34
+ def gateway_url
35
+ return SANDBOX_GATEWAY_URL if Lakala.configuration.sandbox_mode
36
+
37
+ GATEWAY_URL
38
+ end
39
+ end
40
+ end
data/sig/lakala.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Lakala
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lakala
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - drineliu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-03-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.19'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 3.19.1
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '3.19'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 3.19.1
75
+ description: Lakala gem with some basic features
76
+ email:
77
+ - drineliu@gmail.com
78
+ executables: []
79
+ extensions: []
80
+ extra_rdoc_files: []
81
+ files:
82
+ - Gemfile
83
+ - Gemfile.lock
84
+ - LICENSE
85
+ - README.md
86
+ - Rakefile
87
+ - lib/lakala.rb
88
+ - lib/lakala/client.rb
89
+ - lib/lakala/configuration.rb
90
+ - lib/lakala/cypher/client.rb
91
+ - lib/lakala/header_generator.rb
92
+ - lib/lakala/response.rb
93
+ - lib/lakala/sign.rb
94
+ - lib/lakala/utils.rb
95
+ - lib/lakala/version.rb
96
+ - sig/lakala.rbs
97
+ homepage: https://github.com/CoolDrinELiu/lakala
98
+ licenses:
99
+ - MIT
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 2.6.0
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubygems_version: 3.3.7
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: Lakala gem
120
+ test_files: []