allinpay_cnp 0.1.4 → 0.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 761b711ff19735de05c502045113231b46fcfeb9d19846250332b7f0ac5796b8
4
- data.tar.gz: 2989b6abb28969795fb0bac8ba1f5d70a5dca67e9763df79dc70dc9353e02932
3
+ metadata.gz: fafe64e400a551831d156112734279e2fb85e79be6501313be8207a1cfec73f7
4
+ data.tar.gz: 2622d961e762fab513b071796956dc60475e43271541f862c08f3a11b813b725
5
5
  SHA512:
6
- metadata.gz: 3ab98fa0152a023bc8f81154d8cc5d3318f2068f0581d34b0fbde9a87ed83b92d1409818e5ea7284d8352df49dfe2089983d4db0fcc2b32e6f37a64ab7007e73
7
- data.tar.gz: fcd529d01f205b5e2801e61ae68bfb1ba16645513cdb54c81e3dee009468d5a0d7ec0ae5fe64a4f6ccd63e8776bd522ccab767fd83e902afddc851a08c493012
6
+ metadata.gz: eb660034680377320d589e3608ff84df6f2225276ae57bf7711648600b3df5d6d61973c539a925cb7b05aa986a161e293c62481bffd3471d04f7ddc7a1dd385b
7
+ data.tar.gz: 5da018a9be520a162b9dbcfa76807ca15fe2b63baa607aff17d0b17a3ac65d50cc8f914296a59fb52a031e7cf884446f67e77c0734ccf3db95b572dda0380ab8
data/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  ### Gemfile
8
8
 
9
9
  ```ruby
10
- gem 'allinpay_cnp', git: 'https://github.com/your-org/allinpay_cnp'
10
+ gem 'allinpay_cnp', '~> 0.1.6'
11
11
  ```
12
12
 
13
13
  ### 本地安装
@@ -15,7 +15,7 @@ gem 'allinpay_cnp', git: 'https://github.com/your-org/allinpay_cnp'
15
15
  ```bash
16
16
  cd allinpay_cnp
17
17
  gem build allinpay_cnp.gemspec
18
- gem install allinpay_cnp-0.1.0.gem
18
+ gem install allinpay_cnp-0.1.6.gem
19
19
  ```
20
20
 
21
21
  ## 配置
@@ -29,6 +29,7 @@ AllinpayCnp.configure do |config|
29
29
  config.merchant_id = Rails.application.credentials.dig(:allinpay, :merchant_id)
30
30
  config.private_key = Rails.application.credentials.dig(:allinpay, :private_key)
31
31
  config.public_key = Rails.application.credentials.dig(:allinpay, :public_key)
32
+ config.inst_no = Rails.application.credentials.dig(:allinpay, :inst_no)
32
33
  config.environment = Rails.env.production? ? :production : :test
33
34
  config.timeout = 30
34
35
  config.logger = Rails.logger
@@ -44,7 +45,8 @@ AllinpayCnp.configure do |config|
44
45
  config.merchant_id = '086310030670001'
45
46
  config.private_key = File.read('private_key.pem')
46
47
  config.public_key = File.read('public_key.pem')
47
- config.environment = :test # :test 或 :production
48
+ config.inst_no = '00000001' # 机构模式下必填,直连商户模式不填
49
+ config.environment = :test # :test 或 :production
48
50
  config.timeout = 30
49
51
  config.logger = Logger.new($stdout)
50
52
  end
@@ -54,13 +56,24 @@ end
54
56
 
55
57
  | 配置项 | 类型 | 必填 | 说明 |
56
58
  |--------|------|------|------|
57
- | `merchant_id` | String | | 商户号 |
58
- | `private_key` | String | | 商户私钥 (PEM 格式) |
59
- | `public_key` | String | 否 | CNP 公钥,用于验证回调签名 |
59
+ | `merchant_id` | String | 条件必填 | 商户号。若每次调用时都传入 `merchant_no`,可不在全局配置 |
60
+ | `private_key` | String | 条件必填 | 商户私钥 (PEM 格式),用于请求签名。若每次调用时都传入 `private_key`,可不在全局配置 |
61
+ | `public_key` | String | 否 | 系统公钥 (PEM 格式),用于验证回调/应答签名。亦可在调用时通过 `public_key` / `verify_callback(public_key:)` 传入 |
62
+ | `inst_no` | String | 否 | 机构号,机构模式下必填,直连商户模式留空 |
60
63
  | `environment` | Symbol | 否 | `:test` (默认) 或 `:production` |
61
64
  | `timeout` | Integer | 否 | 请求超时时间,默认 30 秒 |
62
65
  | `logger` | Logger | 否 | 日志对象 |
63
66
 
67
+ > 多商户模式下可以不在全局配置 `merchant_id` / `private_key`,而是在每次调用时按商户传入。详见 [多商户模式](#多商户模式-per-call-credentials)。
68
+
69
+ ### 密钥说明
70
+
71
+ | 密钥 | 用途 | 对应配置 |
72
+ |------|------|----------|
73
+ | 商户私钥 | 请求时计算签名 | `config.private_key` |
74
+ | 系统公钥 | 接收应答/异步通知时验签 | `config.public_key` |
75
+ | 商户公钥 | 上传至通联后台,通联用来验证你的请求签名 | 不需要配置到代码中 |
76
+
64
77
  ## 使用方法
65
78
 
66
79
  ### 获取客户端
@@ -84,6 +97,9 @@ response = client.unified_pay(
84
97
  },
85
98
  email: 'customer@example.com',
86
99
  language: 'zh-hant', # 可选: zh-hant, zh-hans, en
100
+ product_info: [ # 商品信息,可选,会以 JSON 字符串形式发送
101
+ { sku: 'SKU001', productName: 'Test Product', price: '100.00', quantity: '1' }
102
+ ],
87
103
  shipping: {
88
104
  first_name: 'Peter',
89
105
  last_name: 'Zhang',
@@ -114,7 +130,7 @@ end
114
130
  ### 查询订单 (Query)
115
131
 
116
132
  ```ruby
117
- response = client.query('ORIGINAL_ORDER_123')
133
+ response = client.query(ori_access_order_id: 'ORIGINAL_ORDER_123')
118
134
 
119
135
  if response.success?
120
136
  puts "状态: #{response.status}" # SUCCESS, FAIL, PROCESSING
@@ -168,6 +184,33 @@ class WebhooksController < ApplicationController
168
184
  end
169
185
  ```
170
186
 
187
+ ## 多商户模式 (Per-call credentials)
188
+
189
+ 如果你的应用需要为多个商户代发请求 (商户模式 / 非机构模式),每个商户拥有独立的密钥对。可在调用时传入 `private_key` / `public_key` / `merchant_no`,会覆盖全局配置:
190
+
191
+ ```ruby
192
+ response = client.unified_pay(
193
+ access_order_id: "ORDER_#{Time.now.to_i}",
194
+ amount: '100.00',
195
+ currency: 'HKD',
196
+ urls: { notify_url: '...', return_url: '...' },
197
+ merchant_no: merchant.allinpay_id,
198
+ private_key: merchant.allinpay_private_key,
199
+ public_key: merchant.allinpay_public_key
200
+ )
201
+
202
+ # query / refund 同样支持
203
+ client.query(ori_access_order_id: 'ORDER_123',
204
+ merchant_no: merchant.allinpay_id,
205
+ private_key: merchant.allinpay_private_key,
206
+ public_key: merchant.allinpay_public_key)
207
+
208
+ # 回调验签时使用对应商户的 public_key
209
+ client.verify_callback(params, public_key: merchant.allinpay_public_key)
210
+ ```
211
+
212
+ 未传入时会回退到 `AllinpayCnp.configure` 中的全局配置,因此单商户用法完全向后兼容。
213
+
171
214
  ## Response 对象
172
215
 
173
216
  所有 API 方法都返回 `Response` 对象:
@@ -216,4 +259,3 @@ lib/
216
259
  ├── response.rb # 响应封装
217
260
  └── client.rb # API 客户端
218
261
  ```
219
-
@@ -11,7 +11,9 @@ module AllinpayCnp
11
11
  amount = opts.fetch(:amount)
12
12
  currency = opts.fetch(:currency)
13
13
  urls = opts.fetch(:urls)
14
- options = opts.except(:access_order_id, :amount, :currency, :urls)
14
+ private_key = opts[:private_key]
15
+ public_key = opts[:public_key]
16
+ options = opts.except(:access_order_id, :amount, :currency, :urls, :private_key, :public_key)
15
17
 
16
18
  params = build_unified_pay_params(
17
19
  access_order_id: access_order_id,
@@ -21,7 +23,7 @@ module AllinpayCnp
21
23
  **options
22
24
  )
23
25
 
24
- request.post(:unified_pay, params)
26
+ request.post(:unified_pay, params, private_key: private_key, public_key: public_key)
25
27
  end
26
28
 
27
29
  def query(hash_or_opts = nil, **kwargs)
@@ -29,26 +31,33 @@ module AllinpayCnp
29
31
 
30
32
  merchant_no = opts.fetch(:merchant_no, config.merchant_id)
31
33
  ori_access_order_id = opts.fetch(:ori_access_order_id)
34
+ private_key = opts[:private_key]
35
+ public_key = opts[:public_key]
32
36
 
33
37
  params = {
34
38
  version: VERSION,
39
+ instNo: inst_no_param,
35
40
  mchtId: merchant_no,
36
41
  transType: 'Query',
42
+ accessOrderId: generate_order_id,
37
43
  oriAccessOrderId: ori_access_order_id
38
- }
39
- request.post(:quickpay, params)
44
+ }.compact
45
+ request.post(:quickpay, params, private_key: private_key, public_key: public_key)
40
46
  end
41
47
 
42
48
  def refund(hash_or_opts = nil, **kwargs)
43
49
  opts = (hash_or_opts || {}).merge(kwargs).transform_keys(&:to_sym)
44
50
 
45
- merchant_no = opts.fetch(:merchant_no)
51
+ merchant_no = opts.fetch(:merchant_no, config.merchant_id)
46
52
  ori_access_order_id = opts.fetch(:ori_access_order_id)
47
53
  refund_amount = opts.fetch(:refund_amount)
48
54
  access_order_id = opts.fetch(:access_order_id, generate_order_id)
55
+ private_key = opts[:private_key]
56
+ public_key = opts[:public_key]
49
57
 
50
58
  params = {
51
59
  version: VERSION,
60
+ instNo: inst_no_param,
52
61
  mchtId: merchant_no,
53
62
  transType: 'Refund',
54
63
  accessOrderId: access_order_id,
@@ -56,19 +65,25 @@ module AllinpayCnp
56
65
  refundAmount: refund_amount.to_s,
57
66
  notifyUrl: opts.fetch(:notify_url, nil)
58
67
  }.reject { |_, v| v.nil? }
59
- request.post(:quickpay, params)
68
+ request.post(:quickpay, params, private_key: private_key, public_key: public_key)
60
69
  end
61
70
 
62
- def verify_callback(params)
63
- return false unless config.public_key
71
+ def verify_callback(params, public_key: nil)
72
+ key = public_key || config.public_key
73
+ return false unless key
64
74
 
65
- Signature.verify(params, config.public_key)
75
+ Signature.verify(params, key)
66
76
  end
67
77
 
68
78
  private
69
79
 
70
80
  def generate_order_id
71
- Time.now.to_i.to_s
81
+ (Time.now.to_f * 1000).to_i.to_s
82
+ end
83
+
84
+ def inst_no_param
85
+ val = config.inst_no
86
+ (val.nil? || val.to_s.strip.empty?) ? nil : val.to_s.strip
72
87
  end
73
88
 
74
89
  def config
@@ -80,7 +95,7 @@ module AllinpayCnp
80
95
  end
81
96
 
82
97
  def build_unified_pay_params(access_order_id:, amount:, currency:, urls:, merchant_no: nil, **options)
83
- build_base_params(access_order_id, amount, currency, urls, options)
98
+ build_base_params(access_order_id, amount, currency, urls, options, merchant_no)
84
99
  .merge(build_shipping_params(options))
85
100
  .merge(build_billing_params(options))
86
101
  .compact
@@ -95,11 +110,12 @@ module AllinpayCnp
95
110
  def build_order_core_params(access_order_id, amount, currency, merchant_no = nil)
96
111
  {
97
112
  version: VERSION,
113
+ instNo: inst_no_param,
98
114
  mchtId: merchant_no || config.merchant_id,
99
115
  accessOrderId: access_order_id,
100
116
  amount: amount.to_s,
101
117
  currency: currency
102
- }
118
+ }.compact
103
119
  end
104
120
 
105
121
  def notify_return_urls(urls)
@@ -16,13 +16,13 @@ module AllinpayCnp
16
16
  }
17
17
  }.freeze
18
18
 
19
- def post(endpoint_type, params)
20
- sign_params(params)
19
+ def post(endpoint_type, params, private_key: nil, public_key: nil)
20
+ sign_params(params, private_key)
21
21
  url = build_url(endpoint_type)
22
22
  log_request(url, params)
23
23
  response = send_post(url, params)
24
24
  log_response(response)
25
- Response.new(response, public_key: config.public_key)
25
+ Response.new(response, public_key: public_key || config.public_key)
26
26
  rescue Faraday::Error => e
27
27
  log_error(e.message)
28
28
  Response.new(nil, error: e)
@@ -34,9 +34,9 @@ module AllinpayCnp
34
34
  AllinpayCnp.config
35
35
  end
36
36
 
37
- def sign_params(params)
37
+ def sign_params(params, private_key = nil)
38
38
  params[:signType] = 'RSA2'
39
- params[:sign] = Signature.sign(params, config.private_key)
39
+ params[:sign] = Signature.sign(params, private_key || config.private_key)
40
40
  end
41
41
 
42
42
  def build_url(endpoint_type)
@@ -16,7 +16,7 @@ module AllinpayCnp
16
16
  def http_success?
17
17
  return false if @error
18
18
 
19
- @http_response&.success?
19
+ @http_response&.success? == true
20
20
  end
21
21
 
22
22
  def success?
@@ -18,7 +18,7 @@ module AllinpayCnp
18
18
 
19
19
  sign_string = build_sign_string(params)
20
20
  rsa_verify(sign_string, signature, public_key)
21
- rescue StandardError
21
+ rescue OpenSSL::PKey::PKeyError, OpenSSL::OpenSSLError
22
22
  false
23
23
  end
24
24
 
@@ -49,16 +49,16 @@ module AllinpayCnp
49
49
  end
50
50
 
51
51
  def load_private_key(key_content)
52
- if key_content.include?('-----BEGIN')
52
+ if key_content.include?('-----BEGIN PRIVATE KEY') || key_content.include?('-----BEGIN RSA PRIVATE KEY')
53
53
  OpenSSL::PKey::RSA.new(key_content)
54
54
  else
55
- pem = '-----BEGIN PRIVATE KEY-----\n#{key_content}\n-----END PRIVATE KEY-----'
55
+ pem = "-----BEGIN PRIVATE KEY-----\n#{key_content}\n-----END PRIVATE KEY-----"
56
56
  OpenSSL::PKey::RSA.new(pem)
57
57
  end
58
58
  end
59
59
 
60
60
  def load_public_key(key_content)
61
- if key_content.include?('-----BEGIN')
61
+ if key_content.include?('-----BEGIN PUBLIC KEY')
62
62
  OpenSSL::PKey::RSA.new(key_content)
63
63
  else
64
64
  pem = "-----BEGIN PUBLIC KEY-----\n#{key_content}\n-----END PUBLIC KEY-----"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AllinpayCnp
4
- VERSION = '0.1.4'
4
+ VERSION = '0.1.6'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: allinpay_cnp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - DrinE
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-03 00:00:00.000000000 Z
11
+ date: 2026-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday