cmb_pay 0.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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +5 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.rubocop.yml +23 -0
- data/.travis.yml +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +61 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/certs/Eric-Guo.pem +21 -0
- data/checksum/cmb_pay-0.0.3.gem.sha512 +1 -0
- data/lib/cmb_pay.rb +143 -0
- data/lib/cmb_pay/merchant_code.rb +51 -0
- data/lib/cmb_pay/message.rb +68 -0
- data/lib/cmb_pay/public.key +0 -0
- data/lib/cmb_pay/service.rb +42 -0
- data/lib/cmb_pay/sign.rb +47 -0
- data/lib/cmb_pay/util.rb +11 -0
- data/lib/cmb_pay/version.rb +3 -0
- metadata +168 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 973b9cf0d5d2974860b0d7fff0cd75d570adc8d6
|
4
|
+
data.tar.gz: 192ec7d563ace627bcd1e832d5691a8f76268ecf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3cd712678f799430ce5c34b9382405448f837fe0752c28bfff61aea7b4f8af51043574c946c8f8231d024f9acf157c8753b50943e1a043932de9d2c70c1ea278
|
7
|
+
data.tar.gz: 50472f3eabe787fe14e9a107404775d31b26108fa4a40fd56b77b80681e5ea4867480595a6312e733c804eb53e2629009ec73d7d7d7d39bc3616c5dc0f5def04
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.1
|
3
|
+
|
4
|
+
Documentation:
|
5
|
+
Enabled: false
|
6
|
+
|
7
|
+
Metrics/LineLength:
|
8
|
+
Max: 150
|
9
|
+
|
10
|
+
Metrics/AbcSize:
|
11
|
+
Max: 57
|
12
|
+
|
13
|
+
Metrics/ClassLength:
|
14
|
+
Max: 150
|
15
|
+
|
16
|
+
Metrics/MethodLength:
|
17
|
+
Max: 20
|
18
|
+
|
19
|
+
Style/NumericLiterals:
|
20
|
+
MinDigits: 7
|
21
|
+
|
22
|
+
Style/AsciiComments:
|
23
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 guochunzhong
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
CMB Pay [![Gem Version][version-badge]][rubygems] [![Build Status][travis-badge]][travis] [![Code Climate][codeclimate-badge]][codeclimate] [![Code Coverage][codecoverage-badge]][codecoverage]
|
2
|
+
=======
|
3
|
+
|
4
|
+
An unofficial cmb (China Merchants Bank) pay ruby gem, inspired from [alipay](https://github.com/chloerei/alipay), [wx_pay](https://github.com/jasl/wx_pay) and [cmbchina](https://github.com/yellong/cmbchina) a lot.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'cmb_pay'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install cmb_pay
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Config
|
25
|
+
|
26
|
+
Create `config/initializers/cmb_pay.rb` and put following configurations into it.
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
# required
|
30
|
+
CmbPay.branch_id = '0755' # 支付商户开户分行号,4位
|
31
|
+
CmbPay.co_no = '000056' # 支付商户号/收单商户号,6位长数字,由银行在商户开户时确定
|
32
|
+
CmbPay.environment = 'test' if Rails.env.development? || Rails.env.staging?
|
33
|
+
CmbPay.co_key = '' # 商户校验码,测试环境为空
|
34
|
+
# only require by uri_of_pre_pay_euserp
|
35
|
+
CmbPay.mch_no = 'P0019844' # 协议商户企业编号,或者说是8位虚拟企业网银编号
|
36
|
+
CmbPay.default_payee_id = '1' # 默认收款方的用户标识
|
37
|
+
```
|
38
|
+
|
39
|
+
## Development
|
40
|
+
|
41
|
+
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.
|
42
|
+
|
43
|
+
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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
44
|
+
|
45
|
+
## Contributing
|
46
|
+
|
47
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/bayetech/cmb_pay. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
48
|
+
|
49
|
+
|
50
|
+
## License
|
51
|
+
|
52
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
53
|
+
|
54
|
+
[version-badge]: https://badge.fury.io/rb/cmb_pay.svg
|
55
|
+
[rubygems]: https://rubygems.org/gems/cmb_pay
|
56
|
+
[travis-badge]: https://travis-ci.org/bayetech/cmb_pay.svg
|
57
|
+
[travis]: https://travis-ci.org/bayetech/cmb_pay
|
58
|
+
[codeclimate-badge]: https://codeclimate.com/github/bayetech/cmb_pay/badges/gpa.svg
|
59
|
+
[codeclimate]: https://codeclimate.com/github/bayetech/cmb_pay
|
60
|
+
[codecoverage-badge]: https://codeclimate.com/github/bayetech/cmb_pay/badges/coverage.svg
|
61
|
+
[codecoverage]: https://codeclimate.com/github/bayetech/cmb_pay/coverage
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'cmb_pay'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/certs/Eric-Guo.pem
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBBMRMwEQYDVQQDDAplcmlj
|
3
|
+
Lmd1b2N6MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNj
|
4
|
+
b20wHhcNMTYwNzExMTUyMTU2WhcNMTcwNzExMTUyMTU2WjBBMRMwEQYDVQQDDApl
|
5
|
+
cmljLmd1b2N6MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
|
6
|
+
FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6LSiHb79c6Krm
|
7
|
+
TeoHq5fko/QJownbKLtlzHmWCGb6B38mIPqYZBSmfaK9EZr6DTf7TJWB7e/u2+Ep
|
8
|
+
bXCqdUxRDbQZig52DvKfljfgOD/YzALHKAckuk/sskvM2pdtRIowJSYAG/Hz2j1d
|
9
|
+
mngRZaIDd/09CNttRsgCDXZl+qqsNJKCQWZ3T8OdqmlpwkIxo7llKEQ578fQwgZ3
|
10
|
+
8uE4RLMv8fOdFv0EzGXbRyFLCT2WEAH5Ns2Jv12KKbvYCTICdJ36cSV8hOUZT0fG
|
11
|
+
y4FxEXtV9uJVJv3BJ5LE84TWdNiMI1lbFul72p09vebUe2N0Hru3XDnfq69XJ+9e
|
12
|
+
nZC9MFk/AgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
13
|
+
BBR/fjIribDcJvWvxPh+zv2kFGRAejAfBgNVHREEGDAWgRRlcmljLmd1b2N6QGdt
|
14
|
+
YWlsLmNvbTAfBgNVHRIEGDAWgRRlcmljLmd1b2N6QGdtYWlsLmNvbTANBgkqhkiG
|
15
|
+
9w0BAQUFAAOCAQEAIA++aoEJOSbTwQml1cQ+z2psV2R18HKYR7ZM8bGEm9PxGyLt
|
16
|
+
DcpqGIL65VctVjqVzD4RTvDrsFhVICL5o0rLYpC4Qm5jBpx+E2s/akJSDsQcOO12
|
17
|
+
qk+IDlQHJjhf7DOg0+XAIj1/QTvq8s3QrrD8NRoIvGcAXVzqAafcG9NOGIXpqFS1
|
18
|
+
ZdwgixrYZK4p/Z+qIJFiuGvBauegUn0x7WDln52cWXgb+a/oYPBjGtBAZznhSy+R
|
19
|
+
R5k6Ma92sW8jupX4cqbSu9rntdVQkNRpoHIrfU0MZT0cKsg/D1zMteylxrO3KMsz
|
20
|
+
SPQRv+nrI1J0zevFqb8010heoR8SDyUA0Mm3+Q==
|
21
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1 @@
|
|
1
|
+
49dec10bce7bd6a94b0b80e6539e209a2297bcc6a6fc23699d78d5f2cdd222b36a7ce4aa6f18cfd27afed3d326891938d7279c5e41fecad986f9ea5f6af49443
|
data/lib/cmb_pay.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'uri'
|
3
|
+
require 'active_support/core_ext/hash'
|
4
|
+
require 'cmb_pay/version'
|
5
|
+
require 'cmb_pay/util'
|
6
|
+
require 'cmb_pay/sign'
|
7
|
+
require 'cmb_pay/merchant_code'
|
8
|
+
require 'cmb_pay/message'
|
9
|
+
require 'cmb_pay/service'
|
10
|
+
|
11
|
+
module CmbPay
|
12
|
+
class << self
|
13
|
+
attr_accessor :branch_id # 开户分行号
|
14
|
+
attr_accessor :co_no # 支付商户号/收单商户号
|
15
|
+
attr_accessor :co_key # 商户校验码,测试环境为空
|
16
|
+
attr_accessor :mch_no # 协议商户企业编号,或者说是8位虚拟企业网银编号
|
17
|
+
attr_accessor :expire_in_minutes # 会话有效时间
|
18
|
+
attr_accessor :environment # 调用的招商银行支付环境,默认生产,测试填test
|
19
|
+
attr_accessor :default_payee_id # 默认收款方的用户标识
|
20
|
+
end
|
21
|
+
@co_key = ''
|
22
|
+
@mch_no = ''
|
23
|
+
@expire_in_minutes = 30
|
24
|
+
@environment = :production
|
25
|
+
|
26
|
+
SUPPORTED_BANK = {
|
27
|
+
'招商银行' => nil,
|
28
|
+
'兴业银行' => 309,
|
29
|
+
'中信银行' => 302,
|
30
|
+
'中国民生银行' => 305,
|
31
|
+
'光大银行' => 303,
|
32
|
+
'华夏银行' => 304,
|
33
|
+
'北京农村商业银行' => 1418,
|
34
|
+
'深圳发展银行' => 307,
|
35
|
+
'中国银行' => 104,
|
36
|
+
'北京银行' => 403,
|
37
|
+
'中国邮政储蓄银行' => 100,
|
38
|
+
'上海浦东发展银行' => 310,
|
39
|
+
'东亚银行' => 2502,
|
40
|
+
'广东发展银行' => 306,
|
41
|
+
'南京银行' => 424,
|
42
|
+
'上海交通银行' => 301,
|
43
|
+
'平安银行' => 410,
|
44
|
+
'中国工商银行' => 102,
|
45
|
+
'杭州银行' => 423,
|
46
|
+
'中国建设银行' => 105,
|
47
|
+
'宁波银行' => 408,
|
48
|
+
'中国农业银行' => 103,
|
49
|
+
'浙商银行' => 316,
|
50
|
+
'渤海银行' => 318,
|
51
|
+
'上海农村商业银行' => 402,
|
52
|
+
'上海银行' => 313
|
53
|
+
}.freeze
|
54
|
+
|
55
|
+
def self.uri_of_pre_pay_euserp(payer_id:, bill_no:, amount_in_cents:, merchant_url:, merchant_para: nil,
|
56
|
+
protocol:, merchant_ret_url:, merchant_ret_para: nil,
|
57
|
+
options: {})
|
58
|
+
generate_pay_link_of('PrePayEUserP',
|
59
|
+
payer_id, bill_no, amount_in_cents, merchant_url, merchant_para,
|
60
|
+
protocol, merchant_ret_url, merchant_ret_para, nil,
|
61
|
+
options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.uri_of_pre_pay_c2(bill_no:, amount_in_cents:, merchant_url:, merchant_para: nil,
|
65
|
+
merchant_ret_url:, merchant_ret_para: nil, card_bank: nil,
|
66
|
+
options: {})
|
67
|
+
generate_pay_link_of('PrePayC2',
|
68
|
+
nil, bill_no, amount_in_cents, merchant_url, merchant_para,
|
69
|
+
nil, merchant_ret_url, merchant_ret_para, card_bank,
|
70
|
+
options)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.uri_of_pre_pay_wap(bill_no:, amount_in_cents:, merchant_url:, merchant_para: nil,
|
74
|
+
card_bank: nil,
|
75
|
+
options: {})
|
76
|
+
generate_pay_link_of('PrePayWAP',
|
77
|
+
nil, bill_no, amount_in_cents, merchant_url, merchant_para,
|
78
|
+
nil, nil, nil, card_bank,
|
79
|
+
options)
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.cmb_pay_message(query_string)
|
83
|
+
CmbPay::Message.new query_string
|
84
|
+
end
|
85
|
+
|
86
|
+
private_class_method
|
87
|
+
|
88
|
+
def self.generate_pay_link_of(pay_type, payer_id, bill_no, amount_in_cents, merchant_url, merchant_para,
|
89
|
+
protocol, merchant_ret_url, merchant_ret_para, card_bank, options)
|
90
|
+
branch_id = options.delete(:branch_id) || CmbPay.branch_id
|
91
|
+
co_no = options.delete(:co_no) || CmbPay.co_no
|
92
|
+
co_key = options.delete(:co_key) || CmbPay.co_key
|
93
|
+
# 定单号,6位或10位长数字,由商户系统生成,一天内不能重复;
|
94
|
+
cmb_bill_no = format('%010d', bill_no.to_i % 10_000_000_000)
|
95
|
+
expire_in_minutes = options.delete(:expire_in_minutes) || CmbPay.expire_in_minutes
|
96
|
+
pay_in_yuan, pay_in_cent = amount_in_cents.to_i.divmod(100)
|
97
|
+
pay_amount = "#{pay_in_yuan}.#{format('%02d', pay_in_cent)}"
|
98
|
+
cmb_merchant_para = Service.encode_merchant_para(merchant_para)
|
99
|
+
trade_date = options.delete(:trade_date) || Time.now.strftime('%Y%m%d')
|
100
|
+
payee_id = options.delete(:payee_id) || CmbPay.default_payee_id
|
101
|
+
random = options.delete(:random)
|
102
|
+
if protocol.is_a?(Hash) && !payer_id.nil?
|
103
|
+
cmb_reserved_xml = {
|
104
|
+
'PNo' => protocol['PNo'],
|
105
|
+
'TS' => protocol['TS'] || Time.now.strftime('%Y%m%d%H%M%S'),
|
106
|
+
'MchNo' => CmbPay.mch_no,
|
107
|
+
'Seq' => protocol['Seq'],
|
108
|
+
'MUID' => payer_id,
|
109
|
+
'URL' => merchant_url,
|
110
|
+
'Para' => cmb_merchant_para
|
111
|
+
}.to_xml(root: 'Protocol', skip_instruct: true, skip_types: true, indent: 0)
|
112
|
+
else
|
113
|
+
cmb_reserved_xml = generate_cmb_card_bank_xml(card_bank)
|
114
|
+
payee_id = nil
|
115
|
+
end
|
116
|
+
m_code = MerchantCode.generate(random: random, strkey: co_key, date: trade_date,
|
117
|
+
branch_id: branch_id, co_no: co_no,
|
118
|
+
bill_no: cmb_bill_no, amount: pay_amount,
|
119
|
+
merchant_para: cmb_merchant_para, merchant_url: merchant_url,
|
120
|
+
payer_id: payer_id, payee_id: payee_id,
|
121
|
+
reserved: cmb_reserved_xml)
|
122
|
+
uri_params = {
|
123
|
+
'BranchID' => branch_id,
|
124
|
+
'CoNo' => co_no,
|
125
|
+
'BillNo' => cmb_bill_no,
|
126
|
+
'Amount' => pay_amount,
|
127
|
+
'Date' => trade_date,
|
128
|
+
'ExpireTimeSpan' => expire_in_minutes,
|
129
|
+
'MerchantUrl' => merchant_url,
|
130
|
+
'MerchantPara' => cmb_merchant_para,
|
131
|
+
'MerchantCode' => m_code
|
132
|
+
}
|
133
|
+
uri_params['MerchantRetUrl'] = merchant_ret_url unless merchant_ret_url.nil?
|
134
|
+
uri_params['MerchantRetPara'] = merchant_ret_para unless merchant_ret_para.nil?
|
135
|
+
Service.request_uri(pay_type, uri_params)
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.generate_cmb_card_bank_xml(card_bank)
|
139
|
+
return "<CardBank>#{format('%04d', card_bank.to_i)}</CardBank>" if /^\d+$/ =~ card_bank.to_s
|
140
|
+
return nil if SUPPORTED_BANK[card_bank].nil?
|
141
|
+
"<CardBank>#{format('%04d', SUPPORTED_BANK[card_bank])}</CardBank>"
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module CmbPay
|
2
|
+
module MerchantCode
|
3
|
+
class << self
|
4
|
+
attr_accessor :random_generator
|
5
|
+
end
|
6
|
+
@random_generator = Random.new
|
7
|
+
|
8
|
+
# 产生商户校验码
|
9
|
+
#
|
10
|
+
# * +random+ - 随机数
|
11
|
+
# * +strkey+ - 商户密钥
|
12
|
+
# * +date+ - 订单日期
|
13
|
+
# * +branch_id+ - 开户分行号
|
14
|
+
# * +co_no+ - 商户号
|
15
|
+
# * +bill_no+ - 订单号
|
16
|
+
# * +amount+ - 订单金额
|
17
|
+
# * +merchant_para+ - 商户自定义参数
|
18
|
+
# * +merchant_url+ - 商户接受通知的URL
|
19
|
+
# * +payer_id+ - 付款方用户标识。用来唯一标识商户的一个用户。长度限制为40字节以内。
|
20
|
+
# * +payee_id+ - 收款方的用户标识。生成规则同上。不要求商户提供用户的注册名称,但需要保证一个用户对应一个UserID。
|
21
|
+
# * +client_ips+ - 商户取得的客户端IP,如果有多个IP用逗号','分隔。长度限制为64字节。
|
22
|
+
# * +goods_type+ - 商品类型编码,长度限制为8字节。
|
23
|
+
# * +reserved+ - 保留字段,长度限制为1024字节。
|
24
|
+
def self.generate(random: nil, strkey:, date:, branch_id:, co_no:, bill_no:,
|
25
|
+
amount:, merchant_para:, merchant_url:,
|
26
|
+
payer_id:, payee_id:, client_ips: nil, goods_type: nil, reserved: nil)
|
27
|
+
random = random_generator.rand if random.nil?
|
28
|
+
last_3 = optional_last_3(client_ips: client_ips, goods_type: goods_type, reserved: reserved)
|
29
|
+
combine_part1 = pay_to_in_rc4(random: random, strkey: strkey, payer_id: payer_id, payee_id: payee_id, opt_last_3: last_3)
|
30
|
+
combine_part2 = "#{strkey}#{combine_part1}#{date}#{branch_id}#{co_no}#{bill_no}#{amount}#{merchant_para}#{merchant_url}"
|
31
|
+
"|#{combine_part1}|#{Sign.sha1_digest(combine_part2)}"
|
32
|
+
end
|
33
|
+
|
34
|
+
private_class_method
|
35
|
+
|
36
|
+
def self.pay_to_in_rc4(random:, strkey:, payer_id:, payee_id:, opt_last_3:)
|
37
|
+
in_data = "#{random}|#{payer_id}<$CmbSplitter$>#{payee_id}#{opt_last_3}"
|
38
|
+
key_md5_digest = Sign.md5_key_digest(strkey)
|
39
|
+
encrypted = Sign.rc4_encrypt(key_md5_digest, in_data)
|
40
|
+
Sign.encode_base64(encrypted)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.optional_last_3(client_ips: nil, goods_type: nil, reserved: nil)
|
44
|
+
r = ''
|
45
|
+
r << "<$ClientIP$>#{client_ips}</$ClientIP$>" unless client_ips.to_s == ''
|
46
|
+
r << "<$GoodsType$>#{goods_type}</$GoodsType$>" unless goods_type.to_s == ''
|
47
|
+
r << "<$Reserved$>#{reserved}</$Reserved$>" unless reserved.to_s == ''
|
48
|
+
r
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module CmbPay
|
2
|
+
class Message
|
3
|
+
attr_accessor :succeed # 消息成功失败,成功为'Y',失败为'N'
|
4
|
+
attr_accessor :co_no # 商户号,6位长数字,由银行在商户开户时确定
|
5
|
+
attr_accessor :bill_no # 订单号(由支付命令送来);
|
6
|
+
attr_accessor :amount # 实际支付金额(由支付命令送来)
|
7
|
+
attr_accessor :date # 订单下单日期(由支付命令送来)
|
8
|
+
attr_accessor :merchant_para # 商户自定义传递参数(由支付命令送来)
|
9
|
+
attr_accessor :msg # 银行通知用户支付结构消息
|
10
|
+
|
11
|
+
attr_accessor :branch_id # 分行号
|
12
|
+
attr_accessor :bank_date # 银行主机交易日期
|
13
|
+
attr_accessor :bank_serial_no # 银行流水号
|
14
|
+
|
15
|
+
attr_accessor :signature # 通知命令签名
|
16
|
+
|
17
|
+
attr_reader :query_string # 原始的query_string
|
18
|
+
|
19
|
+
def initialize(query_string)
|
20
|
+
query_string = URI.encode_www_form(query_string) if query_string.is_a? Hash
|
21
|
+
@query_string = query_string
|
22
|
+
|
23
|
+
params = URI.decode_www_form(query_string).to_h
|
24
|
+
|
25
|
+
@succeed = params['Succeed']
|
26
|
+
@co_no = params['CoNo']
|
27
|
+
@bill_no = params['BillNo']
|
28
|
+
@amount = params['Amount']
|
29
|
+
@date = params['Date']
|
30
|
+
@merchant_para = params['MerchantPara']
|
31
|
+
@msg = params['Msg']
|
32
|
+
|
33
|
+
# 银行通知用户的支付结果消息。信息的前38个字符格式为:4位分行号+6位商户号+8位银行接受交易的日期+20位银行流水号;
|
34
|
+
# 可以利用交易日期+银行流水号+订单号对该订单进行结帐处理
|
35
|
+
msg = params['Msg'][0..37]
|
36
|
+
@branch_id = msg[0..3]
|
37
|
+
@co_no ||= msg[4..9]
|
38
|
+
@bank_date = msg[10..17]
|
39
|
+
@bank_serial_no = msg[18..37]
|
40
|
+
|
41
|
+
@signature = params['Signature']
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid?
|
45
|
+
Sign::Sha1WithRsa.verify(query_string)
|
46
|
+
end
|
47
|
+
|
48
|
+
def succeed?
|
49
|
+
succeed == 'Y'
|
50
|
+
end
|
51
|
+
|
52
|
+
def amount_cents
|
53
|
+
(amount.to_f * 100).to_i
|
54
|
+
end
|
55
|
+
|
56
|
+
def order_date
|
57
|
+
Date.strptime(date, '%Y%m%d')
|
58
|
+
end
|
59
|
+
|
60
|
+
def payment_date
|
61
|
+
Date.strptime(bank_date, '%Y%m%d')
|
62
|
+
end
|
63
|
+
|
64
|
+
def merchant_params
|
65
|
+
URI.decode_www_form(merchant_para.tr('|', '&')).to_h
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
Binary file
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module CmbPay
|
2
|
+
module Service
|
3
|
+
GATEWAY_URL = {
|
4
|
+
production: {
|
5
|
+
NP_BindCard: 'https://mobile.cmbchina.com/mobilehtml/DebitCard/M_NetPay/OneNetRegister/NP_BindCard.aspx',
|
6
|
+
PrePayEUserP: 'https://netpay.cmbchina.com/netpayment/BaseHttp.dll?PrePayEUserP',
|
7
|
+
PrePayC2: 'https://netpay.cmbchina.com/netpayment/BaseHttp.dll?PrePayC2',
|
8
|
+
PrePayWAP: 'https://netpay.cmbchina.com/netpayment/BaseHttp.dll?PrePayWAP'
|
9
|
+
},
|
10
|
+
test: {
|
11
|
+
NP_BindCard: 'http://61.144.248.29:801/mobilehtml/DebitCard/M_NetPay/OneNetRegister/NP_BindCard.aspx',
|
12
|
+
PrePayEUserP: 'http://61.144.248.29:801/netpayment/BaseHttp.dll?PrePayEUserP',
|
13
|
+
PrePayC2: 'https://netpay.cmbchina.com/netpayment/BaseHttp.dll?TestPrePayC2',
|
14
|
+
PrePayWAP: 'https://netpay.cmbchina.com/netpayment/BaseHttp.dll?TestPrePayWAP'
|
15
|
+
}
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def self.request_gateway_url(api_action)
|
19
|
+
GATEWAY_URL[CmbPay.environment.to_sym][api_action.to_sym]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.request_uri(api_action, params)
|
23
|
+
uri = URI(request_gateway_url(api_action))
|
24
|
+
uri.query = if CmbPay.environment.to_sym == :test && api_action.to_sym == :PrePayC2
|
25
|
+
"TestPrePayC2?#{URI.encode_www_form(params)}"
|
26
|
+
elsif CmbPay.environment.to_sym == :test && api_action.to_sym == :PrePayWAP
|
27
|
+
"TestPrePayWAP?#{URI.encode_www_form(params)}"
|
28
|
+
else
|
29
|
+
"#{uri.query}?#{URI.encode_www_form(params)}"
|
30
|
+
end
|
31
|
+
uri
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.encode_merchant_para(para)
|
35
|
+
if para.nil?
|
36
|
+
''
|
37
|
+
else
|
38
|
+
para.to_a.collect { |c| "#{c[0]}=#{c[1]}" }.join '|'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/cmb_pay/sign.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'openssl' # For RC4/RSA/SHA1
|
4
|
+
|
5
|
+
module CmbPay
|
6
|
+
module Sign
|
7
|
+
CMB_PUBLIC_KEY = File.read(File.expand_path('./public.key', __dir__)).freeze
|
8
|
+
|
9
|
+
module Sha1WithRsa
|
10
|
+
def self.verify(param_string)
|
11
|
+
pub = OpenSSL::PKey::RSA.new(CMB_PUBLIC_KEY)
|
12
|
+
pub.verify('sha1', signature(param_string), plain_text(param_string))
|
13
|
+
end
|
14
|
+
|
15
|
+
private_class_method
|
16
|
+
|
17
|
+
def self.plain_text(param_string)
|
18
|
+
param_string[0, param_string.index('&Signature=')]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.signature(param_string)
|
22
|
+
sign = param_string[param_string.index('&Signature=') + 11, param_string.length - 1]
|
23
|
+
sign.split('|').map { |ascii_code| ascii_code.to_i.chr }.join('')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# CMB replace '+' with '*' according to standard Base64
|
28
|
+
def self.encode_base64(bin)
|
29
|
+
::Base64.strict_encode64(bin).tr('+', '*')
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.md5_key_digest(str_key)
|
33
|
+
Digest::MD5.hexdigest(str_key.encode('gb2312')).upcase
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.rc4_encrypt(md5_hash, data)
|
37
|
+
cipher = OpenSSL::Cipher.new('RC4')
|
38
|
+
cipher.encrypt
|
39
|
+
cipher.key = Util.hex_to_binary(md5_hash)
|
40
|
+
cipher.update(data.encode('gb2312')) + cipher.final
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.sha1_digest(str)
|
44
|
+
OpenSSL::Digest::SHA1.hexdigest(str)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/cmb_pay/util.rb
ADDED
metadata
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cmb_pay
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Guo
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBBMRMwEQYDVQQDDAplcmlj
|
14
|
+
Lmd1b2N6MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNj
|
15
|
+
b20wHhcNMTYwNzExMTUyMTU2WhcNMTcwNzExMTUyMTU2WjBBMRMwEQYDVQQDDApl
|
16
|
+
cmljLmd1b2N6MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
|
17
|
+
FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6LSiHb79c6Krm
|
18
|
+
TeoHq5fko/QJownbKLtlzHmWCGb6B38mIPqYZBSmfaK9EZr6DTf7TJWB7e/u2+Ep
|
19
|
+
bXCqdUxRDbQZig52DvKfljfgOD/YzALHKAckuk/sskvM2pdtRIowJSYAG/Hz2j1d
|
20
|
+
mngRZaIDd/09CNttRsgCDXZl+qqsNJKCQWZ3T8OdqmlpwkIxo7llKEQ578fQwgZ3
|
21
|
+
8uE4RLMv8fOdFv0EzGXbRyFLCT2WEAH5Ns2Jv12KKbvYCTICdJ36cSV8hOUZT0fG
|
22
|
+
y4FxEXtV9uJVJv3BJ5LE84TWdNiMI1lbFul72p09vebUe2N0Hru3XDnfq69XJ+9e
|
23
|
+
nZC9MFk/AgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
24
|
+
BBR/fjIribDcJvWvxPh+zv2kFGRAejAfBgNVHREEGDAWgRRlcmljLmd1b2N6QGdt
|
25
|
+
YWlsLmNvbTAfBgNVHRIEGDAWgRRlcmljLmd1b2N6QGdtYWlsLmNvbTANBgkqhkiG
|
26
|
+
9w0BAQUFAAOCAQEAIA++aoEJOSbTwQml1cQ+z2psV2R18HKYR7ZM8bGEm9PxGyLt
|
27
|
+
DcpqGIL65VctVjqVzD4RTvDrsFhVICL5o0rLYpC4Qm5jBpx+E2s/akJSDsQcOO12
|
28
|
+
qk+IDlQHJjhf7DOg0+XAIj1/QTvq8s3QrrD8NRoIvGcAXVzqAafcG9NOGIXpqFS1
|
29
|
+
ZdwgixrYZK4p/Z+qIJFiuGvBauegUn0x7WDln52cWXgb+a/oYPBjGtBAZznhSy+R
|
30
|
+
R5k6Ma92sW8jupX4cqbSu9rntdVQkNRpoHIrfU0MZT0cKsg/D1zMteylxrO3KMsz
|
31
|
+
SPQRv+nrI1J0zevFqb8010heoR8SDyUA0Mm3+Q==
|
32
|
+
-----END CERTIFICATE-----
|
33
|
+
date: 2016-07-11 00:00:00.000000000 Z
|
34
|
+
dependencies:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: http
|
37
|
+
requirement: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 1.0.4
|
42
|
+
- - "<"
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '3'
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: 1.0.4
|
52
|
+
- - "<"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 4.2.6
|
62
|
+
- - "<"
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 5.1.x
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 4.2.6
|
72
|
+
- - "<"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 5.1.x
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: bundler
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '1.12'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '1.12'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rake
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '11.2'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '11.2'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: rspec
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '3.5'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '3.5'
|
117
|
+
description: Helping rubyist integration with cmb (China Merchants Bank) payment service
|
118
|
+
(招商银行一网通支付) easier.
|
119
|
+
email:
|
120
|
+
- eric.guocz@gmail.com
|
121
|
+
executables: []
|
122
|
+
extensions: []
|
123
|
+
extra_rdoc_files: []
|
124
|
+
files:
|
125
|
+
- ".gitignore"
|
126
|
+
- ".idea/vcs.xml"
|
127
|
+
- ".rspec"
|
128
|
+
- ".rubocop.yml"
|
129
|
+
- ".travis.yml"
|
130
|
+
- LICENSE.txt
|
131
|
+
- README.md
|
132
|
+
- bin/console
|
133
|
+
- bin/setup
|
134
|
+
- certs/Eric-Guo.pem
|
135
|
+
- checksum/cmb_pay-0.0.3.gem.sha512
|
136
|
+
- lib/cmb_pay.rb
|
137
|
+
- lib/cmb_pay/merchant_code.rb
|
138
|
+
- lib/cmb_pay/message.rb
|
139
|
+
- lib/cmb_pay/public.key
|
140
|
+
- lib/cmb_pay/service.rb
|
141
|
+
- lib/cmb_pay/sign.rb
|
142
|
+
- lib/cmb_pay/util.rb
|
143
|
+
- lib/cmb_pay/version.rb
|
144
|
+
homepage: https://github.com/bayetech/cmb_pay
|
145
|
+
licenses:
|
146
|
+
- MIT
|
147
|
+
metadata: {}
|
148
|
+
post_install_message:
|
149
|
+
rdoc_options: []
|
150
|
+
require_paths:
|
151
|
+
- lib
|
152
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - "~>"
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '2.1'
|
157
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
requirements: []
|
163
|
+
rubyforge_project:
|
164
|
+
rubygems_version: 2.6.4
|
165
|
+
signing_key:
|
166
|
+
specification_version: 4
|
167
|
+
summary: An unofficial cmb (China Merchants Bank) pay gem.
|
168
|
+
test_files: []
|
metadata.gz.sig
ADDED
Binary file
|