alipay-easysdk-ruby 1.0.2 → 1.0.3

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.
@@ -2,6 +2,7 @@ require_relative 'alipay_constants'
2
2
  require_relative 'config'
3
3
  require_relative 'cert_environment'
4
4
  require_relative 'easy_sdk_kernel'
5
+ require_relative '../payment/app/client'
5
6
  require_relative '../payment/wap/client'
6
7
  require_relative '../payment/page/client'
7
8
  require_relative '../payment/common/client'
@@ -13,10 +14,13 @@ module Alipay
13
14
  class ConfigurationNotSetError < StandardError; end
14
15
 
15
16
  class << self
16
- def set_options(options = nil)
17
+ def set_options(options)
18
+ raise ArgumentError, '配置参数不能为空' if options.nil?
19
+ return @instance if defined?(@instance) && @instance
20
+
17
21
  config = normalize_config(options)
18
22
  initialize_context(config)
19
- @instance
23
+ @instance = self
20
24
  end
21
25
 
22
26
  alias setOptions set_options
@@ -27,14 +31,18 @@ module Alipay
27
31
  @config
28
32
  end
29
33
 
30
- def payment
31
- ensure_context_set!
32
- @payment
33
- end
34
+ def payment
35
+ ensure_context_set!
36
+ @payment
37
+ end
34
38
 
35
- def wap
36
- payment.wap
37
- end
39
+ def app
40
+ payment.app
41
+ end
42
+
43
+ def wap
44
+ payment.wap
45
+ end
38
46
 
39
47
  def page
40
48
  payment.page
@@ -56,17 +64,15 @@ module Alipay
56
64
  private
57
65
 
58
66
  def normalize_config(options)
59
- raise ArgumentError, '配置参数不能为空' if options.nil?
60
- options.is_a?(Config) ? options : Config.new(options)
67
+ return options if options.is_a?(Config)
68
+ Config.new(options)
61
69
  end
62
70
 
63
71
  def initialize_context(config)
64
72
  apply_cert_environment(config)
65
- config.validate
66
73
  @config = config
67
74
  @kernel = EasySDKKernel.new(config)
68
75
  @payment = Payment.new(@kernel)
69
- @instance = self
70
76
  end
71
77
 
72
78
  def ensure_context_set!
@@ -74,13 +80,13 @@ module Alipay
74
80
  end
75
81
 
76
82
  def apply_cert_environment(config)
77
- return if config.alipay_cert_path.nil? || config.alipay_cert_path.to_s.strip.empty?
83
+ return if config.alipayCertPath.nil? || config.alipayCertPath.to_s.strip.empty?
78
84
 
79
85
  cert_env = CertEnvironment.new
80
- cert_env.setup(config.merchant_cert_path, config.alipay_cert_path, config.alipay_root_cert_path)
81
- config.merchant_cert_sn = cert_env.merchant_cert_sn
82
- config.alipay_root_cert_sn = cert_env.root_cert_sn
83
- config.alipay_public_key ||= cert_env.cached_alipay_public_key
86
+ cert_env.setup(config.merchantCertPath, config.alipayCertPath, config.alipayRootCertPath)
87
+ config.merchantCertSN = cert_env.merchant_cert_sn
88
+ config.alipayRootCertSN = cert_env.root_cert_sn
89
+ config.alipayPublicKey ||= cert_env.cached_alipay_public_key
84
90
  end
85
91
  end
86
92
 
@@ -89,6 +95,10 @@ module Alipay
89
95
  @kernel = kernel
90
96
  end
91
97
 
98
+ def app
99
+ @app ||= Alipay::EasySDK::Payment::App::Client.new(@kernel)
100
+ end
101
+
92
102
  def wap
93
103
  @wap ||= Alipay::EasySDK::Payment::Wap::Client.new(@kernel)
94
104
  end
@@ -0,0 +1,75 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module Alipay
5
+ module EasySDK
6
+ module Kernel
7
+ module Util
8
+ class AES
9
+ def aes_encrypt(plain_text, key)
10
+ raise encryption_error(plain_text, key) if key.to_s.empty?
11
+
12
+ secret_key = Base64.decode64(key)
13
+ padded = add_pkcs7_padding(plain_text.to_s)
14
+
15
+ cipher = OpenSSL::Cipher.new('AES-128-CBC')
16
+ cipher.encrypt
17
+ cipher.key = secret_key
18
+ cipher.iv = "\0" * 16
19
+ cipher.padding = 0
20
+
21
+ encrypted = cipher.update(padded) + cipher.final
22
+ Base64.strict_encode64(encrypted)
23
+ rescue StandardError => e
24
+ raise StandardError, "AES加密失败,plainText=#{plain_text},keySize=#{key.to_s.length}。#{e.message}"
25
+ end
26
+
27
+ def aes_decrypt(cipher_text, key)
28
+ raise decryption_error(cipher_text, key) if key.to_s.empty?
29
+
30
+ secret_key = Base64.decode64(key)
31
+ encrypted = Base64.decode64(cipher_text)
32
+
33
+ decipher = OpenSSL::Cipher.new('AES-128-CBC')
34
+ decipher.decrypt
35
+ decipher.key = secret_key
36
+ decipher.iv = "\0" * 16
37
+ decipher.padding = 0
38
+
39
+ decrypted = decipher.update(encrypted) + decipher.final
40
+ strip_pkcs7_padding(decrypted)
41
+ rescue StandardError => e
42
+ raise StandardError, "AES解密失败,cipherText=#{cipher_text},keySize=#{key.to_s.length}。#{e.message}"
43
+ end
44
+
45
+ private
46
+
47
+ def add_pkcs7_padding(source)
48
+ str = source.to_s.strip
49
+ block = 16
50
+ pad = block - (str.bytesize % block)
51
+ pad = block if pad.zero?
52
+ str + (pad.chr * pad)
53
+ end
54
+
55
+ def strip_pkcs7_padding(source)
56
+ return source if source.nil? || source.empty?
57
+
58
+ char = source[-1]
59
+ num = char.ord
60
+ return source if num == 62
61
+ source[0...-num]
62
+ end
63
+
64
+ def encryption_error(plain_text, key)
65
+ StandardError.new("AES加密失败,plainText=#{plain_text},AES密钥为空。")
66
+ end
67
+
68
+ def decryption_error(cipher_text, key)
69
+ StandardError.new("AES解密失败,cipherText=#{cipher_text},AES密钥为空。")
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,63 @@
1
+ require 'openssl'
2
+ require 'digest/md5'
3
+
4
+ module Alipay
5
+ module EasySDK
6
+ module Kernel
7
+ module Util
8
+ class AntCertificationUtil
9
+ SUPPORTED_SIGNATURES = %w[sha1WithRSAEncryption sha256WithRSAEncryption].freeze
10
+
11
+ def cert_sn(cert_path)
12
+ cert = load_certificate(cert_path)
13
+ signature_digest(cert.issuer.to_a, cert.serial)
14
+ end
15
+
16
+ def public_key(cert_path)
17
+ cert = load_certificate(cert_path)
18
+ key_pem = cert.public_key.to_pem
19
+ key_pem.gsub(/-----BEGIN PUBLIC KEY-----/, '')
20
+ .gsub(/-----END PUBLIC KEY-----/, '')
21
+ .gsub(/\s+/, '')
22
+ .strip
23
+ end
24
+
25
+ def root_cert_sn(cert_path)
26
+ certs = load_cert_chain(cert_path)
27
+ sns = certs.each_with_object([]) do |cert, acc|
28
+ next unless SUPPORTED_SIGNATURES.include?(cert.signature_algorithm)
29
+
30
+ acc << signature_digest(cert.issuer.to_a, cert.serial)
31
+ end
32
+ sns.join('_')
33
+ end
34
+
35
+ private
36
+
37
+ def load_certificate(path)
38
+ raise ArgumentError, '证书路径不能为空' if path.nil? || path.to_s.strip.empty?
39
+
40
+ OpenSSL::X509::Certificate.new(File.read(path))
41
+ rescue Errno::ENOENT => e
42
+ raise RuntimeError, "无法读取证书文件: #{path} - #{e.message}"
43
+ end
44
+
45
+ def load_cert_chain(path)
46
+ pem = File.read(path)
47
+ pem.split(/(?=-----BEGIN CERTIFICATE-----)/)
48
+ .reject(&:empty?)
49
+ .map { |chunk| OpenSSL::X509::Certificate.new(chunk) }
50
+ rescue Errno::ENOENT => e
51
+ raise RuntimeError, "无法读取证书链文件: #{path} - #{e.message}"
52
+ end
53
+
54
+ def signature_digest(issuer_array, serial)
55
+ issuer_str = issuer_array.reverse.map { |name, data, _| "#{name}=#{data}" }.join(',')
56
+ serial_str = serial.to_i.to_s
57
+ Digest::MD5.hexdigest("#{issuer_str}#{serial_str}")
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -7,14 +7,9 @@ module Alipay
7
7
  class JsonUtil
8
8
  # 完全复制PHP版本的toJsonString方法
9
9
  def to_json_string(obj)
10
- case obj
11
- when Hash, Array
12
- JSON.generate(obj)
13
- when String
14
- obj
15
- else
16
- obj.to_s
17
- end
10
+ return {} if obj.nil?
11
+ raise ArgumentError, 'to_json_string expects a Hash' unless obj.is_a?(Hash)
12
+ convert_map(obj)
18
13
  end
19
14
 
20
15
  def self.from_json_string(json_string)
@@ -23,6 +18,53 @@ module Alipay
23
18
  rescue JSON::ParserError
24
19
  json_string
25
20
  end
21
+
22
+ private
23
+
24
+ def convert_map(hash)
25
+ return hash unless hash.is_a?(Hash)
26
+
27
+ hash.each_with_object({}) do |(key, value), result|
28
+ result[underscore_key(key)] = convert_value(value)
29
+ end
30
+ end
31
+
32
+ def convert_value(value)
33
+ case value
34
+ when Hash
35
+ convert_map(value)
36
+ when Array
37
+ value.map { |item| convert_value(item) }
38
+ else
39
+ convert_object(value)
40
+ end
41
+ end
42
+
43
+ def convert_object(value)
44
+ return value if primitive?(value)
45
+
46
+ if value.respond_to?(:to_h)
47
+ convert_map(value.to_h)
48
+ elsif value.respond_to?(:to_hash)
49
+ convert_map(value.to_hash)
50
+ else
51
+ value
52
+ end
53
+ end
54
+
55
+ def underscore_key(key)
56
+ str = key.to_s
57
+ underscored = str.gsub(/([A-Z]+)/) { "_#{$1.downcase}" }
58
+ underscored.gsub(/__+/, '_').sub(/^_/, '')
59
+ end
60
+
61
+ def primitive?(value)
62
+ value.is_a?(String) ||
63
+ value.is_a?(Numeric) ||
64
+ value == true ||
65
+ value == false ||
66
+ value.nil?
67
+ end
26
68
  end
27
69
  end
28
70
  end
@@ -0,0 +1,34 @@
1
+ module Alipay
2
+ module EasySDK
3
+ module Kernel
4
+ module Util
5
+ class PageUtil
6
+ def build_form(action_url, parameters)
7
+ charset = Alipay::EasySDK::Kernel::AlipayConstants::DEFAULT_CHARSET
8
+ form = "<form id='alipaysubmit' name='alipaysubmit' action='#{action_url}?charset=#{charset}' method='POST'>"
9
+ params_enum(parameters).each do |key, val|
10
+ next if check_empty(val)
11
+
12
+ escaped = val.to_s.gsub("'", "&apos;")
13
+ form += "<input type='hidden' name='#{key}' value='#{escaped}'/>"
14
+ end
15
+ form += "<input type='submit' value='ok' style='display:none;'></form>"
16
+ form += "<script>document.forms['alipaysubmit'].submit();</script>"
17
+ form
18
+ end
19
+
20
+ private
21
+
22
+ def params_enum(parameters)
23
+ return [] if parameters.nil?
24
+ parameters.to_a
25
+ end
26
+
27
+ def check_empty(value)
28
+ !value || value.to_s.strip == ''
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -7,7 +7,12 @@ module Alipay
7
7
  return true if response.respond_to?(:code) && response.code.to_s == '10000'
8
8
 
9
9
  code_blank = !response.respond_to?(:code) || blank?(response.code)
10
- sub_code_blank = !response.respond_to?(:sub_code) || blank?(response.sub_code)
10
+ sub_code_value = if response.respond_to?(:subCode)
11
+ response.subCode
12
+ elsif response.respond_to?(:sub_code)
13
+ response.sub_code
14
+ end
15
+ sub_code_blank = blank?(sub_code_value)
11
16
 
12
17
  code_blank && sub_code_blank
13
18
  end
@@ -49,7 +49,7 @@ module Alipay
49
49
  # 调用openssl内置方法验签,完全模仿PHP的openssl_verify
50
50
  public_key = OpenSSL::PKey::RSA.new(res)
51
51
  digest = OpenSSL::Digest::SHA256.new
52
- decoded_sign = Base64.decode64(sign)
52
+ decoded_sign = Base64.decode64(sign.to_s)
53
53
 
54
54
  result = public_key.verify(digest, decoded_sign, content)
55
55
  return result
@@ -59,14 +59,16 @@ module Alipay
59
59
  end
60
60
 
61
61
  def verify_params(parameters, public_key)
62
- sign = parameters['sign']
63
- content = get_sign_content(parameters)
64
- return verify(content, sign, public_key)
62
+ normalized = stringify_keys(parameters)
63
+ sign = normalized['sign'] || ''
64
+ content = get_sign_content(normalized)
65
+ verify(content, sign, public_key)
65
66
  end
66
67
 
67
68
  def get_sign_content(params)
69
+ normalized = stringify_keys(params)
68
70
  # 模拟PHP的ksort
69
- sorted_params = params.sort_by { |k, _| k.to_s }.to_h
71
+ sorted_params = normalized.sort.to_h
70
72
 
71
73
  # 移除sign和sign_type字段
72
74
  sorted_params.delete('sign')
@@ -89,6 +91,14 @@ module Alipay
89
91
 
90
92
  private
91
93
 
94
+ def stringify_keys(hash)
95
+ return {} if hash.nil?
96
+
97
+ hash.each_with_object({}) do |(key, value), acc|
98
+ acc[key.to_s] = value
99
+ end
100
+ end
101
+
92
102
  # 完全复制PHP的wordwrap函数
93
103
  def wordwrap(str, width = 75, break_str = "\n", cut = false)
94
104
  if str.nil? || str.empty?
@@ -0,0 +1,75 @@
1
+ require_relative '../../kernel/easy_sdk_kernel'
2
+ require_relative 'models/alipay_trade_app_pay_response'
3
+
4
+ module Alipay
5
+ module EasySDK
6
+ module Payment
7
+ module App
8
+ class Client
9
+ def initialize(kernel)
10
+ @kernel = kernel
11
+ end
12
+
13
+ def pay(subject, out_trade_no, total_amount)
14
+ system_params = {
15
+ 'method' => 'alipay.trade.app.pay',
16
+ 'app_id' => @kernel.get_config('appId'),
17
+ 'timestamp' => @kernel.get_timestamp,
18
+ 'format' => 'json',
19
+ 'version' => '1.0',
20
+ 'alipay_sdk' => @kernel.get_sdk_version,
21
+ 'charset' => 'UTF-8',
22
+ 'sign_type' => @kernel.get_config('signType'),
23
+ 'app_cert_sn' => @kernel.get_merchant_cert_sn,
24
+ 'alipay_root_cert_sn' => @kernel.get_alipay_root_cert_sn
25
+ }
26
+ biz_params = {
27
+ 'subject' => subject,
28
+ 'out_trade_no' => out_trade_no,
29
+ 'total_amount' => total_amount
30
+ }
31
+ text_params = {}
32
+
33
+ sign = @kernel.sign(system_params, biz_params, text_params, @kernel.get_config('merchantPrivateKey'))
34
+ response = {
35
+ Alipay::EasySDK::Kernel::AlipayConstants::BODY_FIELD => @kernel.generate_order_string(system_params, biz_params, text_params, sign)
36
+ }
37
+ Models::AlipayTradeAppPayResponse.from_map(response)
38
+ end
39
+
40
+ def agent(app_auth_token)
41
+ @kernel.inject_text_param('app_auth_token', app_auth_token)
42
+ self
43
+ end
44
+
45
+ def auth(auth_token)
46
+ @kernel.inject_text_param('auth_token', auth_token)
47
+ self
48
+ end
49
+
50
+ def async_notify(url)
51
+ @kernel.inject_text_param('notify_url', url)
52
+ self
53
+ end
54
+
55
+ def route(test_url)
56
+ @kernel.inject_text_param('ws_service_url', test_url)
57
+ self
58
+ end
59
+
60
+ def optional(key, value)
61
+ @kernel.inject_biz_param(key, value)
62
+ self
63
+ end
64
+
65
+ def batch_optional(optional_args)
66
+ optional_args.each do |opt_key, opt_value|
67
+ @kernel.inject_biz_param(opt_key, opt_value)
68
+ end
69
+ self
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,19 @@
1
+ module Alipay
2
+ module EasySDK
3
+ module Payment
4
+ module App
5
+ module Models
6
+ class AlipayTradeAppPayResponse
7
+ attr_accessor :body
8
+
9
+ def self.from_map(response)
10
+ new.tap do |instance|
11
+ instance.body = response[Alipay::EasySDK::Kernel::AlipayConstants::BODY_FIELD]
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -17,6 +17,27 @@ module Alipay
17
17
  module Payment
18
18
  module Common
19
19
  class Client
20
+ class TeaError < StandardError
21
+ attr_reader :data, :code
22
+
23
+ def initialize(data = {}, message = '', code = nil, cause = nil)
24
+ super(message)
25
+ @data = data
26
+ @code = code
27
+ set_backtrace(cause.backtrace) if cause
28
+ end
29
+ end
30
+
31
+ class TeaUnableRetryError < StandardError
32
+ attr_reader :last_request, :last_exception
33
+
34
+ def initialize(last_request, last_exception)
35
+ super(last_exception&.message)
36
+ @last_request = last_request
37
+ @last_exception = last_exception
38
+ end
39
+ end
40
+
20
41
  def initialize(kernel)
21
42
  @kernel = kernel
22
43
  end
@@ -72,7 +93,7 @@ module Alipay
72
93
 
73
94
  def verify_notify(parameters)
74
95
  if @kernel.is_cert_mode
75
- @kernel.verify_params(parameters, @kernel.extract_alipay_public_key(@kernel.get_alipay_cert_sn('')))
96
+ @kernel.verify_params(parameters, @kernel.extract_alipay_public_key(''))
76
97
  else
77
98
  @kernel.verify_params(parameters, @kernel.get_config('alipayPublicKey'))
78
99
  end
@@ -118,7 +139,7 @@ module Alipay
118
139
  sign = @kernel.sign(system_params, biz_params, text_params, @kernel.get_config('merchantPrivateKey'))
119
140
 
120
141
  text_query = (@kernel.text_params || {}).dup
121
- query_params = { 'sign' => sign }.merge(system_params)
142
+ query_params = { Alipay::EasySDK::Kernel::AlipayConstants::SIGN_FIELD => sign }.merge(system_params)
122
143
  query_params.merge!(text_query) unless text_query.empty?
123
144
 
124
145
  uri = build_gateway_uri(query_params)
@@ -131,11 +152,15 @@ module Alipay
131
152
  resp_map = @kernel.read_as_json(response, api_method)
132
153
 
133
154
  unless verify_response(resp_map)
134
- raise '验签失败,请检查支付宝公钥设置是否正确。'
155
+ raise TeaError.new({}, '验签失败,请检查支付宝公钥设置是否正确。')
135
156
  end
136
157
 
137
158
  map = @kernel.to_resp_model(resp_map)
138
159
  response_class.from_map(map)
160
+ rescue TeaError => e
161
+ raise e
162
+ rescue StandardError => e
163
+ raise TeaError.new({}, e.message, nil, e)
139
164
  ensure
140
165
  clear_optional_params
141
166
  end
@@ -156,8 +181,8 @@ module Alipay
156
181
  end
157
182
 
158
183
  def build_gateway_uri(query_params)
159
- host = @kernel.get_config('gatewayHost').to_s.sub(%r{/gateway\.do$}, '')
160
- scheme = @kernel.get_config('protocol') || 'https'
184
+ host = @kernel.get_config(Alipay::EasySDK::Kernel::AlipayConstants::HOST_CONFIG_KEY).to_s.sub(%r{/gateway\.do$}, '')
185
+ scheme = @kernel.get_config(Alipay::EasySDK::Kernel::AlipayConstants::PROTOCOL_CONFIG_KEY) || 'https'
161
186
  uri = URI("#{scheme}://#{host}/gateway.do")
162
187
  uri.query = URI.encode_www_form(query_params.sort.to_h)
163
188
  uri
@@ -20,20 +20,6 @@ module Alipay
20
20
  end
21
21
  end
22
22
  end
23
-
24
- def success?
25
- respond_to?('code') ? public_send('code') == '10000' : false
26
- end
27
-
28
- def error_message
29
- if respond_to?('sub_msg') && public_send('sub_msg')
30
- public_send('sub_msg')
31
- elsif respond_to?('msg')
32
- public_send('msg')
33
- else
34
- nil
35
- end
36
- end
37
23
  end
38
24
  end
39
25
  end
@@ -36,7 +36,7 @@ module Alipay
36
36
  sign = @kernel.sign(system_params, biz_params, text_params, @kernel.get_config('merchantPrivateKey'))
37
37
 
38
38
  response = {
39
- 'body' => @kernel.generate_page('POST', system_params, biz_params, text_params, sign),
39
+ Alipay::EasySDK::Kernel::AlipayConstants::BODY_FIELD => @kernel.generate_page('POST', system_params, biz_params, text_params, sign),
40
40
  'payment_url' => @kernel.generate_payment_url(system_params, biz_params, text_params, sign)
41
41
  }
42
42
 
@@ -8,26 +8,10 @@ module Alipay
8
8
 
9
9
  def self.from_map(response)
10
10
  new.tap do |instance|
11
- instance.body = response['body']
11
+ instance.body = response[Alipay::EasySDK::Kernel::AlipayConstants::BODY_FIELD]
12
12
  instance.payment_url = response['payment_url']
13
13
  end
14
14
  end
15
-
16
- def success?
17
- !body.nil? && !body.to_s.empty?
18
- end
19
-
20
- def form
21
- body
22
- end
23
-
24
- def error_message
25
- nil
26
- end
27
-
28
- def to_s
29
- body || ''
30
- end
31
15
  end
32
16
  end
33
17
  end
@@ -39,7 +39,7 @@ module Alipay
39
39
  sign = @kernel.sign(system_params, biz_params, text_params, @kernel.get_config("merchantPrivateKey"))
40
40
 
41
41
  response = {
42
- "body" => @kernel.generate_page("POST", system_params, biz_params, text_params, sign),
42
+ Alipay::EasySDK::Kernel::AlipayConstants::BODY_FIELD => @kernel.generate_page("POST", system_params, biz_params, text_params, sign),
43
43
  "payment_url" => @kernel.generate_payment_url(system_params, biz_params, text_params, sign)
44
44
  }
45
45
  return Alipay::EasySDK::Payment::Wap::Models::AlipayTradeWapPayResponse.from_map(response)
@@ -8,26 +8,10 @@ module Alipay
8
8
 
9
9
  def self.from_map(response)
10
10
  new.tap do |instance|
11
- instance.body = response['body']
11
+ instance.body = response[Alipay::EasySDK::Kernel::AlipayConstants::BODY_FIELD]
12
12
  instance.payment_url = response['payment_url']
13
13
  end
14
14
  end
15
-
16
- def success?
17
- !body.nil? && !body.to_s.empty?
18
- end
19
-
20
- def form
21
- body
22
- end
23
-
24
- def error_message
25
- nil
26
- end
27
-
28
- def to_s
29
- body || ''
30
- end
31
15
  end
32
16
  end
33
17
  end
@@ -1,5 +1,5 @@
1
1
  module Alipay
2
2
  module EasySDK
3
- VERSION = '1.0.2'
3
+ VERSION = '1.0.3'
4
4
  end
5
5
  end