lakala 0.1.4

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
+ 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: []