├── .github
└── workflows
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── alipay.gemspec
├── bin
└── console
├── config.yml.example
├── doc
├── legacy_api.md
├── quick_start_cn.md
├── quick_start_en.md
├── rsa_key_cn.md
└── rsa_key_en.md
├── lib
├── alipay.rb
└── alipay
│ ├── client.rb
│ ├── mobile
│ ├── service.rb
│ └── sign.rb
│ ├── notify.rb
│ ├── service.rb
│ ├── sign.rb
│ ├── sign
│ ├── dsa.rb
│ ├── md5.rb
│ ├── rsa.rb
│ └── rsa2.rb
│ ├── utils.rb
│ ├── version.rb
│ └── wap
│ ├── notify.rb
│ ├── service.rb
│ └── sign.rb
└── test
├── alipay
├── client_test.rb
├── mobile
│ ├── service_test.rb
│ └── sign_test.rb
├── notify_test.rb
├── service_test.rb
├── sign
│ ├── md5_test.rb
│ ├── rsa2_test.rb
│ └── rsa_test.rb
├── sign_test.rb
├── utils_test.rb
└── wap
│ ├── notify_test.rb
│ ├── service_test.rb
│ └── sign_test.rb
├── alipay_test.rb
└── test_helper.rb
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | build_and_push:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v3
12 | - uses: ruby/setup-ruby@v1
13 | with:
14 | ruby-version: 3.2
15 | bundler-cache: true
16 | - run: bundle exec rake
17 | - name: Publish to RubyGems
18 | run: |
19 | mkdir -p $HOME/.gem
20 | touch $HOME/.gem/credentials
21 | chmod 0600 $HOME/.gem/credentials
22 | printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials
23 | gem build *.gemspec
24 | gem push *.gem
25 | env:
26 | RUBYGEMS_API_KEY: "${{secrets.RUBYGEMS_API_KEY}}"
27 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | strategy:
8 | fail-fast: false
9 | matrix:
10 | os: [ubuntu-latest]
11 | ruby: ['3.0', '3.1', '3.2']
12 | runs-on: ${{ matrix.os }}
13 | steps:
14 | - uses: actions/checkout@v3
15 | - uses: ruby/setup-ruby@v1
16 | with:
17 | ruby-version: ${{ matrix.ruby }}
18 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically
19 | - run: bundle exec rake
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | .idea
7 | Gemfile.lock
8 | InstalledFiles
9 | _yardoc
10 | coverage
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 | config.yml
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.5.1
4 | - 2.4.4
5 | - 2.3.6
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## master
2 |
3 | Newer release changelog, see https://github.com/chloerei/alipay/releases
4 |
5 | ## v0.16.0 (2020-05-15)
6 |
7 | - Support certificate signature, thanks @moohao #92
8 |
9 | ## v0.15.2 (2019-08-02)
10 |
11 | - page_execute_form invalid-signature charset, thanks @lingceng #90
12 |
13 | ## v0.15.1 (2018-06-16)
14 |
15 | - Add `partner` and `seller_id` options for legacy API, thanks @KQyongzhang #83
16 |
17 | ## v0.15.0 (2017-08-27)
18 |
19 | - Add `Alipay::Client` for open API.
20 | - Stop adding new feature to Alipay::Service, Alipay::App::Service, Alipay::Wap::Service, Alipay::Mobile::Service.
21 |
22 | ## v0.14.0 (2016-12-28)
23 |
24 | - Add `Alipay::App::Service.create_forex_trade_wap_url` method, thanks @xiaohesong #61
25 |
26 | ## v0.13.0 (2016-12-24)
27 |
28 | - Add `Alipay::App::Service.alipay_trade_app_pay` method, thanks @FX-HAO #64
29 |
30 | ## v0.12.0 (2016-03-07)
31 |
32 | - Add `Alipay::Service::batch_trans_notify_url` method, thanks @ryancheung #58
33 |
34 | ## v0.11.0 (2015-12-04)
35 |
36 | - Add `Alipay::Wap::Service.security_risk_detect` method, thanks @jasl #55
37 |
38 | ## v0.10.0 (2015-11-03)
39 |
40 | - Add `Alipay::Service.account_page_query` method, thanks @xjz19901211 #53
41 |
42 | ## v0.9.0 (2015-10-15)
43 |
44 | - Add `Alipay::Service.create_direct_pay_by_user_wap_url` method, thanks @serco-chen #52
45 |
46 | ## v0.8.0 (2015-07-20)
47 |
48 | - Add `Alipay::Mobile::Service.mobile_security_pay_string` method, thanks @Sen #49
49 | - Remove `Alipay::Service.mobile_security_pay_url` #49
50 |
51 | ## v0.7.1 (2015-04-10)
52 |
53 | - Don't warning when `rmb_fee` is used in forex_refund_url method.
54 |
55 | ## v0.7.0 (2015-04-07)
56 |
57 | - Remove Alipay.seller_email setting, it can be replaced by seller_id, than same with pid.
58 | - Alipay::Wap::Service.trade_create_direct_token add a required params: seller_account_name.
59 |
60 |
61 | ## v0.6.0 (2015-04-06)
62 |
63 | New API:
64 |
65 | - Add Alipay::Service.mobile_security_pay_url.
66 | - All service methods accept `options` args to config pid, seller_email and key.
67 |
68 | New Config:
69 |
70 | - Add `Alipay.sign_type`, default is 'MD5', allow: 'MD5', 'RSA'. 'DSA' is not implemented yet.
71 |
72 | Break Changes:
73 |
74 | - Move `Alipay::Service::Wap` to `Alipay::Wap::Service`
75 | - Move `Alipay::Sign::Wap` to `Alipay::Wap::Sign`
76 | - Move `Alipay::Notify::Wap` to `Alipay::Wap::Notify`
77 | - Rename `Alipay::Service.create_forex_single_refund_url` to `Alipay::Service.forex_refund_url`
78 | - Rename `Alipay::Service.create_forex_trade` to `Alipay::Service.create_forex_trade_url`
79 | - Rename `Alipay::Service.create_refund_url` to `Alipay::Service.refund_fastpay_by_platform_pwd_url`
80 | - Rename `Service::Wap.auth_and_execute` to `Service::Wap::Service.auth_and_execute_url`
81 |
82 | Now `Alipay::Sign.verify?` and `Alipay::Wap::Sign.verify?` detect sign_type by params.
83 |
84 | Development:
85 |
86 | - Update Test::Unit to Minitest.
87 | - Use fxied test data.
88 |
89 | ## v0.5.0 (2015-03-09)
90 |
91 | - Add `forex_single_refund` service.
92 | - Add `debug_mode` config:
93 |
94 | set `Alipay.debug_mode = false` to disable options check warning in production env.
95 |
96 | ## v0.4.1 (2015-03-03)
97 |
98 | - Fix `single_trade_query` check options typo.
99 |
100 | ## v0.4.0 (2015-01-23)
101 |
102 | - Add `single_trade_query` service. #19
103 |
104 | ## v0.3.1 (2015-01-15)
105 |
106 | - Fix xml encoding #18
107 |
108 | ## v0.3.0 (2014-12-10)
109 |
110 | - Add `close_trade` service. by @linjunpop #16
111 |
112 | ## v0.2.0 (2014-12-03)
113 |
114 | - Add `create_forex_trade` service. by @christophe-dufour #15
115 |
116 | ## v0.1.0 (2014-06-15)
117 |
118 | - Add Wap API by @HungYuHei
119 |
120 | - Add App API by @HungYuHei
121 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in alipay.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Rei
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Alipay
2 |
3 | Unofficial Alipay ruby gem.
4 |
5 | Note: Alipay::Client API does not have enough feedback in production yet, please fully test in your staging environment before production. You can find legacy API document [here](doc/legacy_api.md).
6 |
7 | You should read [https://doc.open.alipay.com](https://doc.open.alipay.com) before using this gem.
8 |
9 | ## Installation
10 |
11 | To install using [Bundler](http://bundler.io/). Add this line to your
12 | application's Gemfile:
13 |
14 | ```ruby
15 | gem 'alipay'
16 | ```
17 |
18 | Then run:
19 | ```bash
20 | $ bundle
21 | ```
22 |
23 | Or you can manually install using [RubyGems](http://rubygems.org/):
24 | ```bash
25 | $ gem install alipay
26 | ```
27 |
28 | ## Getting Started
29 |
30 | This gem needs to be configured with your application's private key for Alipay and Alipay's public key. Here is a [quick guide](doc/rsa_key_en.md) on generating RSA key for use with this gem to get you started.
31 |
32 | ### Setup
33 | ```ruby
34 | require 'alipay'
35 |
36 | # setup the client to communicate with either production API or sandbox API
37 | # https://openapi.alipay.com/gateway.do (Production)
38 | # https://openapi.alipaydev.com/gateway.do (Sandbox)
39 | API_URL = 'https://openapi.alipaydev.com/gateway.do'
40 |
41 | # setup your own credentials and certificates
42 | APP_ID = '2016xxxxxxxxxxxx'
43 | APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nxkbt...4Wt7tl\n-----END RSA PRIVATE KEY-----\n"
44 | ALIPAY_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nTq43T5...OVUAQb3R\n-----END PUBLIC KEY-----\n"
45 |
46 | # initialize a client to communicate with the Alipay API
47 | @alipay_client = Alipay::Client.new(
48 | url: API_URL,
49 | app_id: APP_ID,
50 | app_private_key: APP_PRIVATE_KEY,
51 | alipay_public_key: ALIPAY_PUBLIC_KEY
52 | )
53 | ```
54 |
55 | ### Create a payment
56 | ```ruby
57 | @alipay_client.page_execute_url(
58 | method: 'alipay.trade.page.pay',
59 | biz_content: JSON.generate({
60 | out_trade_no: '20160401000000',
61 | product_code: 'FAST_INSTANT_TRADE_PAY',
62 | total_amount: '0.01',
63 | subject: 'test'
64 | }, ascii_only: true), # ascii_only is important!
65 | timestamp: '2016-04-01 00:00:00'
66 | )
67 |
68 | # This method will then return a payment url
69 | # => 'https://openapi.alipaydev.com/gateway.do?app_id=201600...'
70 | ```
71 |
72 | Read [Alipay::Client](lib/alipay/client.rb) or the [Quick Start Guide](doc/quick_start_en.md) for usage detail.
73 |
74 | ## Contributing
75 |
76 | Bug report or pull request are welcome.
77 |
78 | ### Make a pull request
79 |
80 | 1. Fork it
81 | 2. Create your feature branch (`git checkout -b my-new-feature`)
82 | 3. Commit your changes (`git commit -am 'Add some feature'`)
83 | 4. Push to the branch (`git push origin my-new-feature`)
84 | 5. Create new Pull Request
85 |
86 | Please write unit test with your code if necessary.
87 |
88 | ## License
89 |
90 | MIT License
91 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rake/testtask"
3 |
4 | Rake::TestTask.new do |t|
5 | t.libs << "test"
6 | t.test_files = FileList['test/**/*_test.rb']
7 | end
8 |
9 | task :default => :test
10 |
--------------------------------------------------------------------------------
/alipay.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'alipay/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "alipay"
8 | spec.version = Alipay::VERSION
9 | spec.authors = ["Rei"]
10 | spec.email = ["chloerei@gmail.com"]
11 | spec.description = %q{An unofficial simple alipay gem}
12 | spec.summary = %q{An unofficial simple alipay gem}
13 | spec.homepage = "https://github.com/chloerei/alipay"
14 | spec.license = "MIT"
15 |
16 | spec.files = `git ls-files -z`.split("\x0").reject do |f|
17 | f.match(%r{^(test|spec|features)/})
18 | end
19 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20 | spec.require_paths = ["lib"]
21 |
22 | spec.add_development_dependency "bundler"
23 | spec.add_development_dependency "rake"
24 | spec.add_development_dependency "minitest"
25 | spec.add_development_dependency "webmock"
26 | end
27 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'bundler/setup'
4 | require 'alipay'
5 | require 'yaml'
6 | require 'json'
7 |
8 | if File.exists?('config.yml')
9 | $alipay_client = Alipay::Client.new(YAML.load_file('config.yml'))
10 | end
11 |
12 | require 'irb'
13 |
14 | IRB.start(__FILE__)
15 |
--------------------------------------------------------------------------------
/config.yml.example:
--------------------------------------------------------------------------------
1 | # Dev config for bin/console
2 |
3 | url:
4 | app_id:
5 | app_private_key:
6 | alipay_public_key:
7 |
--------------------------------------------------------------------------------
/doc/legacy_api.md:
--------------------------------------------------------------------------------
1 | # Alipay Legacy API
2 |
3 | *It's recommended to use new alipay api - Open API, read [Alipay::Client](../lib/alipay/client.rb) for more detail.*
4 |
5 | A unofficial alipay ruby gem.
6 |
7 | Alipay official document: https://b.alipay.com/order/techService.htm .
8 |
9 | ## Installation
10 |
11 | Add this line to your application's Gemfile:
12 |
13 | ```ruby
14 | gem 'alipay'
15 | ```
16 |
17 | And then execute:
18 |
19 | ```console
20 | $ bundle
21 | ```
22 |
23 | ## Configuration
24 |
25 | ```ruby
26 | Alipay.pid = 'YOUR_PID'
27 | Alipay.key = 'YOUR_KEY'
28 |
29 | #Alipay.sign_type = 'MD5' # Available values: MD5, RSA. Default is MD5
30 | #Alipay.debug_mode = true # Enable parameter check. Default is true.
31 | ```
32 |
33 | You can set default key, or pass a key directly to service method:
34 |
35 | ```ruby
36 | Service.create_partner_trade_by_buyer_url({
37 | out_trade_no: 'OUT_TRADE_NO',
38 | # Order params...
39 | }, {
40 | pid: 'ANOTHER_PID',
41 | key: 'ANOTHER_KEY',
42 | })
43 | ```
44 |
45 | ## Service
46 |
47 | ### 担保交易收款接口
48 |
49 | #### Name
50 |
51 | ```ruby
52 | create_partner_trade_by_buyer
53 | ```
54 |
55 | #### Definition
56 |
57 | ```ruby
58 | Alipay::Service.create_partner_trade_by_buyer_url({ARGUMENTS}, {OPTIONS})
59 | ```
60 |
61 | #### Example
62 |
63 | ```ruby
64 | Alipay::Service.create_partner_trade_by_buyer_url(
65 | out_trade_no: '20150401000-0001',
66 | subject: 'Order Name',
67 | price: '10.00',
68 | quantity: 12,
69 | logistics_type: 'DIRECT',
70 | logistics_fee: '0',
71 | logistics_payment: 'SELLER_PAY',
72 | return_url: 'https://example.com/orders/20150401000-0001',
73 | notify_url: 'https://example.com/orders/20150401000-0001/notify'
74 | )
75 | # => 'https://mapi.alipay.com/gateway.do?service=create_partner_trade_by_buyer&...'
76 | ```
77 |
78 | Guide consumer to this address to complete payment
79 |
80 | #### Arguments
81 |
82 | | Key | Requirement | Description |
83 | | --- | ----------- | ----------- |
84 | | out_order_no | required | Order id in your application. |
85 | | subject | required | Order subject. |
86 | | price | required | Order item's price. |
87 | | quantity | required | Order item's quantity, total price is price * quantity. |
88 | | logistics_type | required | Logistics type. Available values: POST, EXPRESS, EMS, DIRECT. |
89 | | logistics_fee | required | Logistics fee. |
90 | | logistics_payment | required | Who pay the logistics fee. Available values: BUYER_PAY, SELLER_PAY, BUYER_PAY_AFTER_RECEIVE. |
91 | | return_url | optional | Redirect customer to this url after payment. |
92 | | notify_url | optional | Alipay asyn notify url. |
93 |
94 | This is not a complete list of arguments, please read official document: http://download.alipay.com/public/api/base/alipayescow.zip .
95 |
96 | ### 确认发货接口
97 |
98 | #### Name
99 |
100 | ```ruby
101 | send_goods_confirm_by_platform
102 | ```
103 |
104 | #### Definition
105 |
106 | ```ruby
107 | Alipay::Service.send_goods_confirm_by_platform({ARGUMENTS}, {OPTIONS})
108 | ```
109 |
110 | #### Example
111 |
112 | ```ruby
113 | Alipay::Service.send_goods_confirm_by_platform(
114 | trade_no: '201504010000001',
115 | logistics_name: 'example.com',
116 | transport_type: 'DIRECT'
117 | )
118 | # => 'T'
119 | ```
120 |
121 | #### Arguments
122 |
123 | | Key | Requirement | Description |
124 | | --- | ----------- | ----------- |
125 | | trade_no | required | Trade number in Alipay system, should get from notify message. |
126 | | logistics_name | required | Logistics Name. |
127 | | transport_type/create_transport_type | required | Allowed values: POST, EXPRESS, EMS, DIRECT. |
128 |
129 | This is not a complete list of arguments, please read official document: http://download.alipay.com/public/api/base/alipayescow.zip .
130 |
131 | ### 即时到账收款接口
132 |
133 | #### Name
134 |
135 | ```ruby
136 | create_direct_pay_by_user
137 | ```
138 |
139 | #### Definition
140 |
141 | ```ruby
142 | Alipay::Service.create_direct_pay_by_user_url({ARGUMENTS}, {OPTIONS})
143 | ```
144 |
145 | #### Example
146 |
147 | ```ruby
148 | Alipay::Service.create_direct_pay_by_user_url(
149 | out_trade_no: '20150401000-0001',
150 | subject: 'Order Name',
151 | total_fee: '10.00',
152 | return_url: 'https://example.com/orders/20150401000-0001',
153 | notify_url: 'https://example.com/orders/20150401000-0001/notify'
154 | )
155 | ```
156 |
157 | #### Arguments
158 |
159 | | Key | Requirement | Description |
160 | | --- | ----------- | ----------- |
161 | | out_order_no | required | Order id in your application. |
162 | | subject | required | Order subject. |
163 | | total_fee | required | Order's total fee. |
164 | | return_url | optional | Redirect customer to this url after payment. |
165 | | notify_url | optional | Alipay asyn notify url. |
166 |
167 | This is not a complete list of arguments, please read official document: http://download.alipay.com/public/api/base/alipaydirect.zip .
168 |
169 | ### 手机网站支付接口
170 |
171 | #### Name
172 |
173 | ```ruby
174 | alipay.wap.create.direct.pay.by.user
175 | ```
176 |
177 | #### Definition
178 |
179 | ```ruby
180 | Alipay::Service.create_direct_pay_by_user_wap_url({ARGUMENTS}, {OPTIONS})
181 | ```
182 |
183 | #### Example
184 |
185 | ```ruby
186 | Alipay::Service.create_direct_pay_by_user_wap_url(
187 | out_trade_no: '20150401000-0001',
188 | subject: 'Order Name',
189 | total_fee: '10.00',
190 | return_url: 'https://example.com/orders/20150401000-0001',
191 | notify_url: 'https://example.com/orders/20150401000-0001/notify'
192 | )
193 | ```
194 |
195 | #### Arguments
196 |
197 | | Key | Requirement | Description |
198 | | --- | ----------- | ----------- |
199 | | out_order_no | required | Order id in your application. |
200 | | subject | required | Order subject. |
201 | | total_fee | required | Order's total fee. |
202 | | return_url | optional | Redirect customer to this url after payment. |
203 | | notify_url | optional | Alipay asyn notify url. |
204 |
205 | This is not a complete list of arguments, please read official document: http://download.alipay.com/public/api/base/alipaywapdirect.zip .
206 |
207 |
208 |
209 |
210 | ### 国际支付宝移动接口
211 |
212 | #### Name
213 |
214 | ```ruby
215 | create_forex_trade_wap
216 | ```
217 |
218 | #### Definition
219 |
220 | ```ruby
221 | Alipay::Service.create_forex_trade_wap_url({ARGUMENTS}, {OPTIONS})
222 | ```
223 |
224 | #### Example
225 |
226 | ```ruby
227 | Alipay::Service.create_forex_trade_wap_url(
228 | out_trade_no: '20150401000-0001',
229 | subject: 'Order Name',
230 | merchant_url: 'http://example.com/itemback',
231 | total_fee: '10.00', #or rmb_fee, only one
232 | currency: 'USD',
233 | return_url: 'https://example.com/orders/20150401000-0001',
234 | notify_url: 'https://example.com/orders/20150401000-0001/notify'
235 | )
236 | ```
237 |
238 | #### Arguments
239 |
240 | | Key | Requirement | Description |
241 | | --- | ----------- | ----------- |
242 | | out_order_no | required | Order id in your application. |
243 | | subject | required | Order subject. |
244 | | merchant_url | required | The link which customer could jump back to merchant page from Alipay cashier. |
245 | | total_fee or rmb_fee | required | Order's total fee. |
246 | | currency | required | currency type. |
247 | | return_url | optional | Redirect customer to this url after payment. |
248 | | notify_url | optional | Alipay asyn notify url. |
249 |
250 | This is not a complete list of arguments, please read official document: https://global.alipay.com/product/mobilepayments.html .
251 |
252 |
253 | ### 即时到账批量退款有密接口
254 |
255 | #### Name
256 |
257 | ```ruby
258 | refund_fastpay_by_platform_pwd
259 | ```
260 |
261 | #### Definition
262 |
263 | ```ruby
264 | Alipay::Service.refund_fastpay_by_platform_pwd_url
265 | ```
266 |
267 | #### Example
268 |
269 | ```ruby
270 | batch_no = Alipay::Utils.generate_batch_no # refund batch no, you SHOULD store it to db to avoid alipay duplicate refund
271 | Alipay::Service.refund_fastpay_by_platform_pwd_url(
272 | batch_no: batch_no,
273 | data: [{
274 | trade_no: '201504010000001',
275 | amount: '10.0',
276 | reason: 'REFUND_REASON'
277 | }],
278 | notify_url: 'https://example.com/orders/20150401000-0001/refund_notify'
279 | )
280 | # => https://mapi.alipay.com/gateway.do?service=refund_fastpay_by_platform_pwd&...
281 | ```
282 |
283 | #### Arguments
284 |
285 | | Key | Requirement | Description |
286 | | --- | ----------- | ----------- |
287 | | batch_no | required | Refund batch no, you should store it to db to avoid alipay duplicate refund. |
288 | | data | required | Refund data, a hash array. |
289 | | notify_url | required | Alipay notify url. |
290 |
291 | ##### Data Item
292 |
293 | | Key | Requirement | Description |
294 | | --- | ----------- | ----------- |
295 | | trade_no | required | Trade number in alipay system. |
296 | | amount | required | Refund amount. |
297 | | reason | required | Refund reason. Less than 256 bytes, could not contain special characters: ^ $ | #. |
298 |
299 | This is not a complete list of arguments, please read official document: http://download.alipay.com/public/api/base/alipaydirect.zip .
300 |
301 | ### 关闭交易接口
302 |
303 | #### Name
304 |
305 | ```ruby
306 | close_trade
307 | ```
308 |
309 | #### Definition
310 |
311 | ```ruby
312 | Alipay::Service.close_trade({ARGUMENTS}, {OPTIONS})
313 | ```
314 |
315 | #### Example
316 |
317 | ```ruby
318 | Alipay::Service.close_trade(
319 | trade_no: '201504010000001'
320 | )
321 | # => 'T'
322 |
323 | # When fail
324 | # => 'F TRADE_STATUS_NOT_AVAILD'
325 | ```
326 |
327 | #### ARGUMENTS
328 |
329 | | Key | Requirement | Description |
330 | | --- | ----------- | ----------- |
331 | | out_order_no | optional * | Order number in your application. |
332 | | trade_no | optional * | Trade number in alipay system. |
333 |
334 | \* out_order_no and trade_no must have one.
335 |
336 | ### 单笔交易查询接口
337 |
338 | #### Name
339 |
340 | ```ruby
341 | single_trade_query
342 | ```
343 |
344 | #### Definition
345 |
346 | ```ruby
347 | Alipay::Service.single_trade_query({ARGUMENTS}, {OPTIONS})
348 | ```
349 |
350 | #### Example
351 |
352 | ```ruby
353 | Alipay::Service.single_trade_query(
354 | trade_no: '201504010000001'
355 | )
356 | # => 'T...
357 | ```
358 |
359 | #### ARGUMENTS
360 |
361 | | Key | Requirement | Description |
362 | | --- | ----------- | ----------- |
363 | | out_trade_no | optional * | Order number in your application. |
364 | | trade_no | optional * | Trade number in alipay system. |
365 |
366 | \* out_trade_no and trade_no must have one.
367 |
368 | ### 境外收单接口
369 |
370 | #### Name
371 |
372 | ```ruby
373 | create_forex_trade
374 | ```
375 |
376 | #### Definition
377 |
378 | ```ruby
379 | Alipay::Service.create_forex_trade_url({ARGUMENTS}, {OPTIONS})
380 | ```
381 |
382 | #### Example
383 |
384 | ```ruby
385 | Alipay::Service.create_forex_trade_url(
386 | out_trade_no: '20150401000-0001',
387 | subject: 'Subject',
388 | currency: 'USD',
389 | total_fee: '10.00',
390 | notify_url: 'https://example.com/orders/20150401000-0001/notify'
391 | )
392 | # => 'https://mapi.alipay.com/gateway.do?service=create_forex_trade...'
393 | ```
394 |
395 | #### ARGUMENTS
396 |
397 | | Key | Requirement | Description |
398 | | --- | ----------- | ----------- |
399 | | out_trade_no | required | Order number in your application. |
400 | | subject | required | Order subject. |
401 | | currency | required | Abbreviated currency name. |
402 | | total_fee | required * | Order total price. |
403 | | notify_url | optional | Alipay asyn notify url. |
404 |
405 | \* total_fee can be replace by rmb_fee.
406 |
407 | ### 境外收单单笔退款接口
408 |
409 | #### Name
410 |
411 | ```ruby
412 | forex_refund
413 | ```
414 |
415 | #### Definition
416 |
417 | ```ruby
418 | Alipay::Service.forex_refund_url({ARGUMENTS}, {OPTIONS})
419 | ```
420 |
421 | #### Example
422 |
423 | ```ruby
424 | Alipay::Service.forex_refund_url(
425 | out_return_no: '20150401000-0001',
426 | out_trade_no: '201504010000001',
427 | return_amount: '10.00',
428 | currency: 'USD',
429 | reason: 'Reason',
430 | gmt_return: '20150401000000'
431 | )
432 | ```
433 |
434 | #### ARGUMENTS
435 |
436 | | Key | Requirement | Description |
437 | | --- | ----------- | ----------- |
438 | | out_return_no | required | Refund no, you should store it to db to avoid alipay duplicate refund. |
439 | | out_trade_no | required | Order number in your application. |
440 | | return_amount | required | Refund amount. |
441 | | currency | required | Abbreviated currency name. |
442 | | reason | required | Refun reason. |
443 | | gmt_return | required * | YYYYMMDDHHMMSS Beijing Time. |
444 |
445 | \* Auto set Time.now if not set.
446 |
447 | ### 账单明细分页查询接口
448 |
449 | #### Name
450 |
451 | ```ruby
452 | account.page.query
453 | ```
454 |
455 | #### Definition
456 |
457 | ```ruby
458 | Alipay::Service::account_page_query({PARAMS}, {OPTIONS})
459 | ```
460 |
461 | #### Example
462 |
463 | ```ruby
464 | Alipay::Service.account_page_query(
465 | page_no: 1,
466 | gmt_start_time: '2015-10-25 00:00:00',
467 | gmt_end_time: '2015-10-26 00:00:00'
468 | )
469 | ```
470 |
471 | #### Arguments
472 |
473 | It's an unpublic api, contact support for permission and document.
474 |
475 | ### 批量付款到支付宝账户接口
476 |
477 | #### Name
478 |
479 | ```ruby
480 | batch_trans_notify
481 | ```
482 |
483 | #### Definition
484 |
485 | ```ruby
486 | Alipay::Service::batch_trans_notify_url({PARAMS}, {OPTIONS})
487 | ```
488 |
489 | #### Example
490 |
491 | ```ruby
492 | Alipay::Service.batch_trans_notify_url(
493 | notify_url: 'https://example.com/orders/20150401000-0001/notify',
494 | account_name: '毛毛',
495 | detail_data: '0315006^testture0002@126.com^常炜买家^1000.00^hello',
496 | batch_no: '20080107001',
497 | batch_num: 1,
498 | batch_fee: 1000.00,
499 | email: 'biz_932@alitest.com'
500 | )
501 | #=> 'https://mapi.alipay.com/gateway.do?service=batch_trans_notify&...'
502 | ```
503 |
504 | #### Arguments
505 |
506 | | Key | Requirement | Description |
507 | | --- | ----------- | ----------- |
508 | | notify_url | required | Alipay asyn notify url. |
509 | | account_name | required | Alipay account name of payer. |
510 | | detail_data | required | Payment data. |
511 | | batch_no | required | Batch transaction number. |
512 | | batch_num | required | Batch transaction count. |
513 | | batch_fee | required | Batch transaction total amount. |
514 | | email | required | Alipay email account of payer. |
515 |
516 | Document: https://doc.open.alipay.com/doc2/detail?treeId=64&articleId=103773&docType=1
517 |
518 | ### 验证通知
519 |
520 | #### Name
521 |
522 | ```ruby
523 | notify_verify
524 | ```
525 |
526 | #### Definition
527 |
528 | ```ruby
529 | Alipay::Notify.verify?({PARAMS}, {OPTIONS})
530 | ```
531 |
532 | #### Example
533 |
534 | ```ruby
535 | # Rails
536 | # params except :controller_name, :action_name, :host, etc.
537 | notify_params = params.except(*request.path_parameters.keys)
538 |
539 | Alipay::Notify.verify?(notify_params, options = {})
540 | ```
541 |
542 | ### QR Code 生成二维码
543 |
544 | #### Name
545 |
546 | ```ruby
547 | alipay.commerce.qrcode.create
548 | ```
549 |
550 | #### Definition
551 |
552 | ```ruby
553 | Alipay::Service.create_merchant_qr_code({PARAMS}, {OPTIONS})
554 | ```
555 |
556 | #### Example
557 |
558 | ```ruby
559 | create_qr_code_params = {
560 | biz_type: "OVERSEASHOPQRCODE",
561 | biz_data: {
562 | address: "No.278, Road YinCheng, Shanghai, China",
563 | country_code: "CN",
564 | currency: "USD",
565 | secondary_merchant_id: "xxx001",
566 | secondary_merchant_industry: "7011",
567 | secondary_merchant_name: "xxx Store",
568 | store_id: "0001",
569 | store_name: "Apple store",
570 | trans_currency: "USD"
571 | }
572 | }
573 |
574 | Alipay::Service.create_merchant_qr_code(create_qr_code_params)
575 | # => 'https://mapi.alipay.com/gateway.do?service=alipay.commerce.qrcode.create...'
576 | ```
577 |
578 | #### ARGUMENTS
579 |
580 | | Key | Requirement | Description |
581 | | --- | ----------- | ----------- |
582 | | notify_url | optional | Alipay asyn notify url. |
583 | | biz_type | required | Business type that is defined by Alipay, this case is “OVERSEASHOPQRCODE” |
584 | | biz_data | required | Business data. Format:JSON |
585 |
586 | #### BIZ_DATA ARGUMENTS (required)
587 |
588 | | Key | Requirement | Description |
589 | | --- | ----------- | ----------- |
590 | | secondary_merchant_industry | required | Business category code of the secondary merchant. |
591 | | secondary_merchant_id | required | The unique ID assigned by the partner to identify a secondary merchant. |
592 | | secondary_merchant_name | required | Registration legal name of the secondary merchant, shown in the Alipay Wallet and the reconciliation file to identify a secondary merchant. |
593 | | store_id | required | The unique ID that is assigned by the partner to identify a store of a merchant. |
594 | | store_name | required | The name of the store. |
595 | | trans_currency | required | The pricing currency |
596 | | currency | required | The currency to settle with the merchant. The default value is CNY. If the pricing currency is not CNY, then the settlement currency must be either CNY or the pricing currency. |
597 | | country_code | required | The country code that consists of two letters (alpha-2 code) |
598 | | address | required | The address of the store where the code is created. |
599 |
600 | This is not a complete list of arguments, please read official document: https://global.alipay.com/docs/ac/global/qrcode_create#biz_data
601 |
602 | ### QR Code 修改二维码
603 |
604 | #### Name
605 |
606 | ```ruby
607 | alipay.commerce.qrcode.modify
608 | ```
609 |
610 | #### Definition
611 |
612 | ```ruby
613 | Alipay::Service.update_merchant_qr_code({PARAMS}, {OPTIONS})
614 | ```
615 |
616 | #### Example
617 |
618 | ```ruby
619 | update_qr_code_params = {
620 | biz_type: "OVERSEASHOPQRCODE",
621 | qrcode: "https://qr.alipay.com/baxxxxx",
622 | biz_data: {
623 | address: "No.278, Road YinCheng, Shanghai, China",
624 | country_code: "CN",
625 | currency: "USD",
626 | secondary_merchant_id: "xxx001",
627 | secondary_merchant_industry: "7011",
628 | secondary_merchant_name: "xxx Store",
629 | store_id: "0001",
630 | store_name: "Apple store",
631 | trans_currency: "USD"
632 | }
633 | }
634 |
635 | Alipay::Service.update_merchant_qr_code(update_qr_code_params)
636 | # => 'https://mapi.alipay.com/gateway.do?service=alipay.commerce.qrcode.modify...'
637 | ```
638 |
639 | #### ARGUMENTS
640 |
641 | | Key | Requirement | Description |
642 | | --- | ----------- | ----------- |
643 | | notify_url | optional | Alipay asyn notify url. |
644 | | biz_type | required | Business type that is defined by Alipay, this case is “OVERSEASHOPQRCODE” |
645 | | biz_data | required | Business data. Format:JSON |
646 | | qrcode | required | The returned QR code value after the code is generated successfully. |
647 |
648 | #### BIZ_DATA ARGUMENTS (required)
649 |
650 | | Key | Requirement | Description |
651 | | --- | ----------- | ----------- |
652 | | secondary_merchant_industry | required | Business category code of the secondary merchant. |
653 | | secondary_merchant_id | required | The unique ID assigned by the partner to identify a secondary merchant. |
654 | | secondary_merchant_name | required | Registration legal name of the secondary merchant, shown in the Alipay Wallet and the reconciliation file to identify a secondary merchant. |
655 | | store_id | required | The unique ID that is assigned by the partner to identify a store of a merchant. |
656 | | store_name | required | The name of the store. |
657 | | trans_currency | required | The pricing currency |
658 | | currency | required | The currency to settle with the merchant. The default value is CNY. If the pricing currency is not CNY, then the settlement currency must be either CNY or the pricing currency. |
659 | | country_code | required | The country code that consists of two letters (alpha-2 code) |
660 | | address | required | The address of the store where the code is created. |
661 |
662 | This is not a complete list of arguments, please read official document: https://global.alipay.com/docs/ac/global/qrcode_modify#Qb0Hc
663 |
664 | ### 境外线下交易查询接口
665 |
666 | #### Name
667 |
668 | ```ruby
669 | alipay.acquire.overseas.query
670 | ```
671 |
672 | #### Definition
673 |
674 | ```ruby
675 | Alipay::Service.acquirer_overseas_query({PARAMS}, {OPTIONS})
676 | ```
677 |
678 | #### Example
679 |
680 | ```ruby
681 | acquirer_overseas_query_params = {
682 | partner_trans_id: "2010121000000002"
683 | }
684 |
685 | Alipay::Service.acquirer_overseas_query(acquirer_overseas_query_params)
686 | # => 'https://mapi.alipay.com/gateway.do?service=alipay.acquire.overseas.query...'
687 | ```
688 |
689 | #### ARGUMENTS
690 |
691 | | Key | Requirement | Description |
692 | | --- | ----------- | ----------- |
693 | | partner_trans_id | required | The original partner transaction ID given in the payment request |
694 | | alipay_trans_id | optional | The transaction ID assigned by Alipay for the partner's payment request, which follows a mapping relation with the partner field plus the partner_trans_id field. When both of the fields are specified, alipay_trans_id will be verified first. |
695 |
696 | Document: https://global.alipay.com/docs/ac/global/overseas_query
697 |
698 | ### 境外线下单笔退款接口
699 |
700 | #### Name
701 |
702 | ```ruby
703 | alipay.acquire.overseas.spot.refund
704 | ```
705 |
706 | #### Definition
707 |
708 | ```ruby
709 | Alipay::Service.acquirer_overseas_spot_refund_url({PARAMS}, {OPTIONS})
710 | ```
711 |
712 | #### Example
713 |
714 | ```ruby
715 | acquirer_overseas_spot_refund_params = {
716 | partner_trans_id: "2010121000000002",
717 | partner_refund_id: "301012133000002",
718 | currency: "USD",
719 | refund_amount: "0.01"
720 | }
721 |
722 | Alipay::Service.acquirer_overseas_spot_refund_url(acquirer_overseas_spot_refund_params)
723 | # => 'https://mapi.alipay.com/gateway.do?service=alipay.acquire.overseas.spot.refund...'
724 | ```
725 |
726 | #### ARGUMENTS
727 |
728 | | Key | Requirement | Description |
729 | | --- | ----------- | ----------- |
730 | | partner_trans_id | required | The original partner transaction ID given in the payment request |
731 | | partner_refund_id | required | The refund order ID in the partner system. The value of partner_refund_id cannot be the same as that of partner_trans_id. The partner_refund_id field plus the partner field identifies a refund transaction. |
732 | | currency | required | The currency of the refund amount. |
733 | | refund_amount | required | Refund amount, which must be less than or equal to the original transaction amount or the left transaction amount if ever refunded. |
734 | | is_sync | optional | Indicates that the refund request is processed synchronously or asynchronously with a value of Y or N. The default value is N, which means an asynchronous notification from Alipay is returned to the merchant if the merchant has set the value of the notify_url field when sending the refund request. If the value is set as Y, it means only a synchronous response is returned to the merchant. |
735 |
736 | This is not a complete list of arguments, please read official document: https://global.alipay.com/docs/ac/global/spot_refund#92fa0c95
737 |
738 | ## Mobile::Service
739 |
740 | ### 移动支付接口
741 |
742 | #### Name
743 |
744 | ```ruby
745 | mobile.securitypay.pay
746 | ```
747 |
748 | #### Definition
749 |
750 | ```ruby
751 | Alipay::Mobile::Service.mobile_securitypay_pay_string({ARGUMENTS}, {OPTIONS})
752 | ```
753 |
754 | #### Example
755 |
756 | ```ruby
757 | Alipay::Mobile::Service.mobile_securitypay_pay_string(
758 | out_trade_no: '20150401000-0001',
759 | notify_url: 'https://example.com/orders/20150401000-0001/notify'
760 | subject: 'subject',
761 | total_fee: '10.00',
762 | body: 'text'
763 | )
764 | # => service="mobile.securitypay.pay"&_input_charset="utf-8"&partner=...
765 | ```
766 |
767 | #### ARGUMENTS
768 |
769 | | Key | Requirement | Description |
770 | | --- | ----------- | ----------- |
771 | | out_trade_no | required | Order number in your application. |
772 | | notify_url | required | Alipay asyn notify url. |
773 | | subject | required | Order subject. |
774 | | total_fee | required | Order total price. |
775 | | body | required | Order body, less than 512 bytes. |
776 |
777 | \* This service only support RSA sign_type.
778 |
779 | This is not a complete list of arguments, please read official document: http://download.alipay.com/public/api/base/WS_MOBILE_PAY_SDK_BASE.zip .
780 |
781 | ## Wap::Service
782 |
783 | ### 授权接口
784 |
785 | #### Name
786 |
787 | ```ruby
788 | alipay.wap.trade.create.direct
789 | ```
790 |
791 | #### Definition
792 |
793 | ```ruby
794 | Alipay::Wap::Service.trade_create_direct_token({ARGUMENTS}, {OPTIONS}}
795 | ```
796 |
797 | #### Example
798 |
799 | ```ruby
800 | token = Alipay::Wap::Service.trade_create_direct_token(
801 | req_data: {
802 | seller_account_name: 'account@example.com',
803 | out_trade_no: '20150401000-0001',
804 | subject: 'Subject',
805 | total_fee: '10.0',
806 | call_back_url: 'https://example.com/orders/20150401000-0001',
807 | notify_url: 'https://example.com/orders/20150401000-0001/notify'
808 | }
809 | )
810 | ```
811 |
812 | #### ARGUMENTS
813 |
814 | | Key | Requirement | Description |
815 | | --- | ----------- | ----------- |
816 | | req_data | required | See req_data ARGUMENTS |
817 |
818 | ##### req_data ARGUMENTS
819 |
820 | | Key | Requirement | Description |
821 | | --- | ----------- | ----------- |
822 | | seller_account_name | required | Alipay seller account. |
823 | | out_order_no | required | Order id in your application. |
824 | | subject | required | Order subject. |
825 | | total_fee | required | Order total price. |
826 | | return_url | optional | Redirect customer to this url after payment. |
827 | | notify_url | optional | Alipay asyn notify url. |
828 |
829 | This is not a complete list of arguments, please read official document: http://download.alipay.com/public/api/base/WS_WAP_PAYWAP.zip .
830 |
831 | ### 交易接口
832 |
833 | #### Name
834 |
835 | ```ruby
836 | alipay.wap.auth.authAndExecute
837 | ```
838 |
839 | #### Definition
840 |
841 | ```ruby
842 | Alipay::Wap::Service.auth_and_execute_url({ARGUMENTS}, {OPTIONS})
843 | ```
844 |
845 | #### Example
846 |
847 | ```ruby
848 | Alipay::Wap::Service.auth_and_execute_url(request_token: token)
849 | ```
850 | #### ARGUMENTS
851 |
852 | | Key | Requirement | Description |
853 | | --- | ----------- | ----------- |
854 | | request_token | required | Get from trade_create_direct_token |
855 |
856 | ### 风险探测服务接口
857 |
858 | #### Name
859 |
860 | ```ruby
861 | alipay.security.risk.detect
862 | ```
863 |
864 | #### Definition
865 |
866 | ```ruby
867 | Alipay::Wap::Service.security_risk_detect({ARGUMENTS}, {OPTIONS})
868 | ```
869 |
870 | #### Example
871 |
872 | ```ruby
873 | Alipay::Wap::Service.security_risk_detect({
874 | order_no: '1',
875 | order_credate_time: '1970-01-01 00:00:00',
876 | order_category: 'TestCase^AlipayGem^Ruby',
877 | order_item_name: 'item',
878 | order_amount: '0.01',
879 | buyer_account_no: '2088123123',
880 | buyer_bind_mobile: '13600000000',
881 | buyer_reg_date: '1970-01-01 00:00:00',
882 | terminal_type: 'WAP'
883 | }, {
884 | sign_type: 'RSA',
885 | key: RSA_PRIVATE_KEY
886 | })
887 | ```
888 | #### ARGUMENTS
889 |
890 | | Key | Requirement | Description |
891 | | --- | ----------- | ----------- |
892 | | order_no | optional | Order id in your application. |
893 | | order_credate_time | optional | Order created time. |
894 | | order_category | optional | Categories of Order's items. using `^` as splitter. |
895 | | order_item_name | optional | Order subject. |
896 | | order_amount | optional | Order item's price. |
897 | | buyer_account_no | optional | User id in your application. |
898 | | buyer_reg_date | optional | User created time. |
899 | | buyer_bind_mobile | optional | User mobile phone. |
900 | | terminal_type | optional | The terminal type which user are using to request the payment, can be `MOBILE` for App, `WAP` for mobile, `WEB` for PC. |
901 |
902 | ### 验证通知
903 |
904 | #### Name
905 |
906 | ```ruby
907 | notify_verify
908 | ```
909 |
910 | #### Definition
911 |
912 | ```ruby
913 | Alipay::Wap::Notify.verify?({PARAMS}, {OPTIONS})
914 | ```
915 |
916 | #### Example
917 |
918 | ```ruby
919 | # Rails
920 | # params except :controller_name, :action_name, :host, etc.
921 | notify_params = params.except(*request.path_parameters.keys)
922 |
923 | Alipay::Wap::Notify.verify?(notify_params)
924 | ```
925 |
926 | ## Contributing
927 |
928 | Bug report or pull request are welcome.
929 |
930 | ### Make a pull request
931 |
932 | 1. Fork it
933 | 2. Create your feature branch (`git checkout -b my-new-feature`)
934 | 3. Commit your changes (`git commit -am 'Add some feature'`)
935 | 4. Push to the branch (`git push origin my-new-feature`)
936 | 5. Create new Pull Request
937 |
938 | Please write unit test with your code if necessary.
939 |
940 | ## Donate
941 |
942 | Donate to maintainer let him make this gem better.
943 |
944 | Alipay donate link: http://chloerei.com/donate/ .
945 |
--------------------------------------------------------------------------------
/doc/quick_start_cn.md:
--------------------------------------------------------------------------------
1 | 简易入门指南
2 | =================
3 |
4 | ### [English](quick_start_en.md)
5 |
6 | ## 导航
7 | * [设置客户端](#设置客户端)
8 | * [Ruby](#ruby)
9 | * [Ruby on Rails](#ruby-on-rails)
10 | * [创建支付订单](#创建支付订单)
11 | * [电脑网站支付](#电脑网站支付)
12 | * [手机网站支付](#手机网站支付)
13 | * [扫码支付](#扫码支付)
14 | * [分期付款](#分期付款)
15 | * [验证回调数据](#验证回调数据)
16 | * [查询支付状态](#查询支付状态)
17 | * [终止支付订单](#终止支付订单)
18 | * [关闭交易](#关闭交易)
19 | * [撤销交易](#撤销交易)
20 | * [交易退款](#交易退款)
21 | * [发起退款](#发起退款)
22 | * [查询退款状态](#查询退款状态)
23 | * [转帐](#转帐)
24 | * [转帐到顾客支付宝账户](#转帐到顾客支付宝账户)
25 | * [查询转帐状态](#查询转帐状态)
26 |
27 |
28 |
29 | ## 设置客户端
30 |
31 | **指南内所有的示例会假设你已设置好客户端**
32 |
33 | ### Ruby
34 | ```ruby
35 | # 保存好应用ID,密钥,API接口地址等设置
36 | API_URL: 'https://openapi.alipaydev.com/gateway.do'
37 | APP_ID: '2016000000000000'
38 | APP_PRIVATE_KEY: "-----BEGIN RSA PRIVATE KEY-----\nxkbt...4Wt7tl\n-----END RSA PRIVATE KEY-----\n"
39 | ALIPAY_PUBLIC_KEY: "-----BEGIN PUBLIC KEY-----\nTq43T5...OVUAQb3R\n-----END PUBLIC KEY-----\n"
40 |
41 | # 建立一个客户端以便快速调用API
42 | @client = Alipay::Client.new(
43 | url: API_URL,
44 | app_id: APP_ID,
45 | app_private_key: APP_PRIVATE_KEY,
46 | alipay_public_key: ALIPAY_PUBLIC_KEY
47 | )
48 | ```
49 |
50 | ### Ruby on Rails
51 | 你可以保存你的支付宝设置为环境变量,或者建立一个文档在Rails初始化时导入。
52 |
53 | 在这个范例里,我们将使用 `dotenv-rails` gem 把支付宝所需设置保存为环境变量.
54 |
55 | 在你的 Gemfile
56 | ```ruby
57 | # Gemfile
58 | gem 'alipay', '~> 0.15.0'
59 | gem 'dotenv-rails', '~> 2.2', '>= 2.2.1'
60 | ```
61 | 命令行运行
62 | ```bash
63 | $ bundle install
64 | ```
65 |
66 | Rails 根目录下创建一个 .env 文件
67 | ```ruby
68 | # .env
69 | APP_ID=2016000000000000
70 | ALIPAY_API=https://openapi.alipaydev.com/gateway.do
71 | APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nxkbt...4Wt7tl\n-----END RSA PRIVATE KEY-----\n"
72 | ALIPAY_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nTq43T5...OVUAQb3R\n-----END PUBLIC KEY-----\n"
73 | ```
74 |
75 | 设后置好,你可以在你的 Ruby on Rails 应用里使用环境变量创建一个支付宝 API 客户端
76 | ```ruby
77 | @client = Alipay::Client.new(
78 | url: ENV['ALIPAY_API'],
79 | app_id: ENV['APP_ID'],
80 | app_private_key: ENV['APP_PRIVATE_KEY'],
81 | alipay_public_key: ENV['ALIPAY_PUBLIC_KEY']
82 | )
83 | ```
84 |
85 | ## 创建支付订单
86 |
87 | ### 电脑网站支付
88 | #### API 接口
89 | ```
90 | alipay.trade.page.pay
91 | ```
92 | 这个 API 接口主要是用于在顾客在电脑网站下单并发起支付。
93 |
94 | #### 客户端函数
95 | ```ruby
96 | Alipay::Client.page_execute_url
97 | ```
98 | 这个客户端函数调用后会返回一条重定向顾客用的支付地址。
99 |
100 | #### 示例
101 | ```ruby
102 | @client.page_execute_url(
103 | method: 'alipay.trade.page.pay',
104 | return_url: 'https://mystore.com/orders/20160401000000/return',
105 | notify_url: 'https://mystore.com/orders/20160401000000/notify',
106 | biz_content: JSON.generate({
107 | out_trade_no: '20160401000000',
108 | product_code: 'FAST_INSTANT_TRADE_PAY',
109 | total_amount: '0.01',
110 | subject: 'Example #123'
111 | }, ascii_only: true)
112 | )
113 | # => 'https://openapi.alipaydev.com/gateway.do?app_id=2016...'
114 | ```
115 |
116 | #### 值得注意的参数
117 | * `return_url` 是提供给支付宝的同步返回地址。客户支付成功后,支付宝会使用 GET 请求将用户重定向到这个地址。这个一个*选填*参数,可不提供。
118 | * `notify_url` 是提供给支付宝的异步返回地址。客户支付成功后,支付宝会以 POST 请求方式把交易信息发到这个地址上。这时一个*选填*参数。可不提供。
119 | * `out_trade_no` 是你作为商户提供给支付宝的订单号。建议引用你应用内相应模型的 ID,并不能重复。
120 | * `total_amount` 是支付订单的金额,精确到小数点后两位。例如金额 5,123.99 元的参数值应为 '5123.99'。
121 |
122 | > 如需 API 接口的完整参数列表,请参考官方的 [API 文档](https://docs.open.alipay.com/270/alipay.trade.page.pay/).
123 |
124 |
125 | ### 手机网站支付
126 | #### API 接口
127 | ```
128 | alipay.trade.wap.pay
129 | ```
130 | 这个 API 接口适用于顾客在手机网站环境下创建支付订单。如顾客手机内装有支付宝钱包,重定向到这个接口将会唤起手机上支付宝钱包。如顾客未安装支付宝应用,则将会重新向到移动版支付页面。
131 |
132 | #### 客户端函数
133 | ```ruby
134 | Alipay::Client.page_execute_url
135 | ```
136 | 这个客户端函数调用后会返回一条重定向顾客用的支付地址。
137 |
138 | #### 示例
139 | ```ruby
140 | @client.page_execute_url(
141 | method: 'alipay.trade.wap.pay',
142 | return_url: 'https://mystore.com/orders/20160401000000/return',
143 | notify_url: 'https://mystore.com/orders/20160401000000/notify',
144 | biz_content: JSON.generate({
145 | out_trade_no: '20160401000000',
146 | product_code: 'QUICK_WAP_WAY',
147 | total_amount: '0.01',
148 | subject: 'Example: 456'
149 | quit_url: 'https://mystore.com/orders/20160401000000/'
150 | }, ascii_only: true)
151 | )
152 | # => 'https://openapi.alipaydev.com/gateway.do?app_id=2016...'
153 | ```
154 | #### 值得注意的参数
155 | * `quit_url` 顾客在移动版支付页面时,支付宝会以这个参数所提供的地址生成一个返回按钮。
156 |
157 | > 如需 API 接口的完整参数列表,请参考官方的 [API 文档](https://docs.open.alipay.com/203/107090/).
158 |
159 | ### 扫码支付
160 | #### API 接口
161 | ```
162 | alipay.trade.precreate
163 | ```
164 | 这个 API 接口在提供支付订单数据后,将会创建订单并返回一个生成二维码用的支付地址。支付成功后支付宝会向 `notify_url` 设定的异步通知地址发送交易数据。
165 |
166 | #### 客户端函数
167 | ```ruby
168 | Alipay::Client.execute
169 | ```
170 |
171 | #### 示例
172 | ```ruby
173 | # 创建支付订单并取得订单信息
174 | response = @client.execute(
175 | method: 'alipay.trade.precreate',
176 | notify_url: 'https://mystore.com/orders/20160401000000/notify',
177 | biz_content: JSON.generate({
178 | out_trade_no: '20160401000000',
179 | total_amount: '50.00',
180 | subject: 'QR Code Test'
181 | }, ascii_only: true)
182 | )
183 | # => '{\"alipay_trade_precreate_response\":{\"code\"...'
184 |
185 | # 提取二维码地址
186 | qr_code = JSON.parse(response)["alipay_trade_precreate_response"]["qr_code"]
187 | # => 'https://qr.alipay.com/abcdefggfedcba'
188 | ```
189 |
190 | > 如需 API 接口的完整参数列表,请参考官方的 [API 文档](https://docs.open.alipay.com/api_1/alipay.trade.precreate).
191 |
192 | ### 分期付款
193 | #### API 接口
194 | ```
195 | alipay.trade.page.pay (电脑网站)
196 | alipay.trade.wap.pay (手机网站)
197 | alipay.trade.precreate (扫码支付)
198 | ```
199 |
200 | #### 客户端函数
201 | ```ruby
202 | Alipay::Client.page_execute_url (电脑网站 / 手机网站)
203 | Alipay::Client.execute (扫码支付)
204 | ```
205 |
206 | #### 示例
207 | 情景:顾客在商城网站上预先选择好分6期付款的方案。商城和支付宝创建支付订单时同时提供分期参数。
208 |
209 | ```ruby
210 | @client.page_execute_url(
211 | method: 'alipay.trade.page.pay',
212 | return_url: 'https://mystore.com/orders/20160401000000/return',
213 | notify_url: 'https://mystore.com/orders/20160401000000/notify',
214 | biz_content: {
215 | out_trade_no: '20160401000000',
216 | product_code: 'FAST_INSTANT_TRADE_PAY',
217 | total_amount: '0.01',
218 | subject: 'Example #654',
219 | enable_pay_channels: 'balance,pcreditpayInstallment',
220 | extend_params: JSON.generate({
221 | hb_fq_num: '6'',
222 | hb_fq_seller_percent: '0'
223 | }
224 | }, ascii_only: true)
225 | )
226 | ```
227 | 情景:商城网站不提供分期选项,但允许客户在支付宝的支付过程中自行决定分期付款。
228 | ```ruby
229 | @client.page_execute_url(
230 | method: 'alipay.trade.page.pay',
231 | return_url: 'https://mystore.com/orders/20160401000000/return',
232 | notify_url: 'https://mystore.com/orders/20160401000000/notify',
233 | biz_content: JSON.generate({
234 | out_trade_no: '20160401000000',
235 | product_code: 'FAST_INSTANT_TRADE_PAY',
236 | total_amount: '0.01',
237 | subject: 'Example #654',
238 | enable_pay_channels: 'balance,pcreditpayInstallment',
239 | }, ascii_only: true)
240 | )
241 | ```
242 | #### 值得注意的参数
243 | * `enable_pay_channels` 这个参数指定用户可使用的付款渠道。 `pcreditpayInstallment` 是分期付款的参数值。可同时指定多个参数值。
244 | * `hb_fq_num` 这个参数指定分期数. 有效的参数值为 `3`,`6`, 和 `12`。
245 | * `hb_fq_seller_percent` 这个参数指定商户分担分期手续费的比例。有效参数值为`0`或`100`。
246 |
247 | > 如需 API 接口的完整参数列表,请参考官方的 [API 文档](https://docs.open.alipay.com/277/106748/).
248 |
249 |
250 | ### 验证回调数据
251 | #### 客户端函数
252 | ```
253 | Alipay::Client.verify?
254 | ```
255 | 这个客户端函数将会使用支付宝所提供的公钥来验证回调数据。
256 |
257 | #### 示例
258 | ```ruby
259 | params = {
260 | out_trade_no: '20160401000000',
261 | trade_status: 'TRADE_SUCCESS'
262 | sign_type: 'RSA2',
263 | sign: '...'
264 | }
265 | @client.verify?(params)
266 | # => true / false
267 | ```
268 |
269 | ##### 验证 GET return_url 同步通知
270 | 支付宝会在顾客支付成功后,将客户以 GET 请求的方式重定向顾客到你指定的 `return_url` 地址。以下将简单地示范如何在你应用的 Controller 里验证回调数据。
271 |
272 | ```ruby
273 | @client.verify?(request.query_parameters)
274 | # => true / false
275 | ```
276 |
277 | ##### 验证 POST notify_url 异步通知
278 | 支付宝在顾客支付成功后,会向你所指定的 `notify_url` 以 POST 请求方式发送异步通知。你的应用对应的Controller需以纯文本方式返回 `success` 这7个字符。否则支付宝会继续尝试发送异步通知。
279 |
280 | ```ruby
281 | if @client.verify?(request.request_parameters)
282 | render plain: 'success'
283 | end
284 | ```
285 |
286 | ### 查询支付状态
287 | #### API 接口
288 | ```
289 | alipay.trade.query
290 | ```
291 | 这个 API 接口适用于在未接收到同步/异步通知的情况下查询订单支付状态。这个接口同时也适用于查询未设定同步/异步通知地址的订单。
292 |
293 | #### 客户端函数
294 | ```ruby
295 | Alipay::Client.execute
296 | ```
297 |
298 | #### 示例
299 | ```ruby
300 | response = @client.execute(
301 | method: 'alipay.trade.query',
302 | biz_content: JSON.generate({
303 | trade_no: '2013112611001004680073956707',
304 | }, ascii_only: true)
305 | )
306 | # => '{\"alipay_trade_query_response\":{\"code\"...'
307 |
308 | # Get payment status
309 | result_status = JSON.parse(response)["alipay_trade_query_response"]["trade_status"]
310 | # => 'TRADE_SUCCESS'
311 | ```
312 |
313 | #### 值得注意的参数
314 | * `trade_no` 是创建支付订单后,支付宝返回的交易号。如未取得交易号,可以使用创建订单时所传入的商户订单号来代替。
315 |
316 | > 如需 API 接口的完整参数列表,请参考官方的 [API 文档](https://docs.open.alipay.com/api_1/alipay.trade.query).
317 |
318 | ## 终止支付订单
319 | ### 关闭交易
320 | #### API 接口
321 | ```
322 | alipay.trade.close
323 | ```
324 | 这个 API 接口适用于用户在一定时间内未支付,但未在支付宝系统内超时的情况下对未付款的建议进行关闭。
325 |
326 | #### 客户端函数
327 | ```ruby
328 | Alipay::Client.execute
329 | ```
330 |
331 | #### 示例
332 | ```ruby
333 | response = @client.execute(
334 | method: 'alipay.trade.close',
335 | notify_url: 'https://mystore.com/orders/20160401000000/notify',
336 | biz_content: JSON.generate({
337 | trade_no: '2013112611001004680073956707',
338 | }, ascii_only: true)
339 | )
340 | # => '{\"alipay_trade_close_response\":{\"code\"...'
341 |
342 | # 取得请求结果代码
343 | result_code = JSON.parse(response)["alipay_trade_close_response"]["code"]
344 | # => '10000'
345 | ```
346 |
347 | #### 值得注意的参数
348 | * `trade_no` 是创建支付订单后,支付宝返回的交易号。如未取得交易号,可以使用创建订单时所传入的商户订单号来代替。
349 |
350 | > 如需 API 接口的完整参数列表,请参考官方的 [API 文档](https://docs.open.alipay.com/api_1/alipay.trade.close).
351 |
352 | ### 撤销交易
353 | #### API 接口
354 | ```
355 | alipay.trade.cancel
356 | ```
357 | 这个 API 接口适用于支付交易返回失败或支付系统超时的情况下撤销交易订单。如果顾客已成功付款,款项会以原渠道退回给顾客。并返回 `action` 值 `refund`。如客户尚未付款,订单会被关闭并返回 `action` 值 `close`。
358 |
359 | #### 客户端函数
360 | ```ruby
361 | Alipay::Client.execute
362 | ```
363 |
364 | #### 示例
365 | ```ruby
366 | response = @client.execute(
367 | method: 'alipay.trade.cancel',
368 | biz_content: JSON.generate({
369 | out_trade_no: '20160401000000',
370 | }, ascii_only: true)
371 | )
372 | # => '{\"alipay_trade_cancel_response\":{\"code\"...'
373 |
374 | # 取得撤销结果
375 | result_action = JSON.parse(response)["alipay_trade_cancel_response"]["action"]
376 | # => 'close'
377 | ```
378 |
379 | #### 值得注意的参数
380 | * `trade_no` 是创建支付订单后,支付宝返回的交易号。如未取得交易号,可以使用创建订单时所传入的商户订单号来代替。
381 |
382 | > 如需 API 接口的完整参数列表,请参考官方的 [API 文档](https://docs.open.alipay.com/api_1/alipay.trade.cancel/).
383 |
384 | ## 交易退款
385 |
386 | ### 发起退款
387 | #### API 接口
388 | ```
389 | alipay.trade.refund
390 | ```
391 | 这个 API 接口适用于对已成功的交易订单发起退款。退款可拆分成多笔操作。如支付订单已超支付宝的退款期限,退款请求会失败。
392 |
393 | #### 客户端函数
394 | ```ruby
395 | Alipay::Client.execute
396 | ```
397 |
398 | #### 示例
399 | 情景:顾客请求总额为 210.85 元的订单退款 10.12 元。
400 | ```ruby
401 | response = @client.execute(
402 | method: 'alipay.trade.refund',
403 | biz_content: JSON.generate({
404 | out_trade_no: '6c50789a0610',
405 | out_request_no: '6c50789a0610-1',
406 | refund_amount: '10.12'
407 | }, ascii_only: true)
408 | )
409 | # => '{\"alipay_trade_refund_response\":{\"code\"...'
410 |
411 | # 取得结果
412 | result_code = JSON.parse(response)["alipay_trade_refund_response"]["code"]
413 | # => '10000'
414 |
415 | result_fund_change = JSON.parse(response)["alipay_trade_refund_response"]["fund_change"]
416 | # => 'Y'
417 | ```
418 |
419 | #### 值得注意的参数
420 | * `out_request_no` 是 *条件可选* 参数. 如果你打算以同一张支付订单发起分多次退款,这则是*必选*参数。如你计划对支付订单进行一次性全额退款,这则是*可选*参数。
421 | * `refund_amount` 是你退款单的金额。该金额可小于支付订单金额。但如之前以同一支付订单已产生退款,所有退款单的总额不能超出支付订单的总额。
422 |
423 | > 如需 API 接口的完整参数列表,请参考官方的 [API 文档](https://docs.open.alipay.com/api_1/alipay.trade.refund/).
424 |
425 | ### 查询退款状态
426 | #### API 接口
427 | ```
428 | alipay.trade.fastpay.refund.query
429 | ```
430 | 这个 API 接口适用于查询退款状态和一些退款的参数。
431 |
432 | #### 客户端函数
433 | ```ruby
434 | Alipay::Client.execute
435 | ```
436 |
437 | #### 示例
438 | ```ruby
439 | response = @client.execute(
440 | method: 'alipay.trade.fastpay.refund.query',
441 | biz_content: JSON.generate({
442 | out_trade_no: '6c50789a0610',
443 | out_request_no: '6c50789a0610-1'
444 | }, ascii_only: true)
445 | )
446 | # => '{\"alipay_trade_fastpay_refund_query_response\":{\"code\"...'
447 |
448 | # 取得退款金额
449 | result_refund_amount = JSON.parse(response)["alipay_trade_fastpay_refund_query_response"]["refund_amount"]
450 | # => '10.12'
451 | ```
452 |
453 | #### 值得注意的参数
454 | * `out_request_no` 是你创建退款单时所提供给支付宝的单号。如支付订单仅有一笔退款,则可使用 `out_trade_no` 来代替。
455 |
456 | > 如需 API 接口的完整参数列表,请参考官方的 [API 文档](https://docs.open.alipay.com/api_1/alipay.trade.fastpay.refund.query/).
457 |
458 |
459 | ## 转帐
460 | ### 转帐到顾客支付宝账户
461 | #### API 接口
462 | ```
463 | alipay.fund.trans.toaccount.transfer
464 | ```
465 | 这个 API 接口适用于从你的商户支付宝账户转帐到顾客支付宝帐号。
466 |
467 | #### 客户端函数
468 | ```
469 | Alipay::Client.execute
470 | ```
471 |
472 | #### 示例
473 | ```ruby
474 | response = @client.execute(
475 | method: 'alipay.fund.trans.toaccount.transfer',
476 | biz_content: JSON.generate({
477 | out_biz_no: '3142321423432',
478 | payee_type: 'ALIPAY_LOGONID',
479 | payee_account: 'customer@example.com',
480 | amount: '12.23'
481 | }, ascii_only: true)
482 | )
483 | # => '{\"alipay_fund_trans_toaccount_transfer_response\":{\"code\"...'
484 |
485 | # 取得转帐ID
486 | result_order_id = JSON.parse(response)["alipay_fund_trans_toaccount_transfer_response"]["order_id"]
487 | # => '20160627110070001502260006780837'
488 | ```
489 |
490 | #### 值得注意的参数
491 | * `out_biz_no` 是你提供给支付宝的唯一转帐ID
492 | * `payee_type` 是用于识别 `payee_account` 提供的账户类型。 有效值为 `ALIPAY_USERID` ,适用于提供开头为 `2088` 的支付宝用户号。另一有效值 `ALIPAY_LOGONID` 适用于以用户邮箱,手机,和登录号来识别。
493 | * `payee_account` 是用户的支付宝ID,邮箱地址,登录号,和手机号码。这个参数必须匹配所提供 `payee_type` 值。
494 | * `amount` 是转帐金额,精确到小数点后两位数。 (例如: ¥123.50 值为 `123.50`)
495 |
496 | > 如需 API 接口的完整参数列表,请参考官方的 [API 文档](https://docs.open.alipay.com/api_28/alipay.fund.trans.toaccount.transfer).
497 |
498 | ### 查询转帐状态
499 | #### API 接口
500 | ```
501 | alipay.fund.trans.order.query
502 | ```
503 | 这个 API 接口适用于查询转帐状态
504 |
505 | #### 客户端函数
506 | ```
507 | Alipay::Client.execute
508 | ```
509 |
510 | #### 示例
511 | ```ruby
512 | response = @client.execute(
513 | method: 'alipay.fund.trans.order.query',
514 | biz_content: JSON.generate({
515 | out_biz_no: '3142321423432',
516 | }, ascii_only: true)
517 | )
518 | # => '{\"alipay_fund_trans_order_query_response\":{\"code\"...'
519 |
520 | # 取得转帐状态
521 | refund_status = JSON.parse(response)["alipay_fund_trans_order_query_response"]["status"]
522 | # => 'SUCCESS'
523 |
524 | # 取得预计到帐时间
525 | refund_status = JSON.parse(response)["alipay_fund_trans_order_query_response"]["arrival_time_end"]
526 | # => '2018-01-01 08:08:08'
527 | ```
528 |
529 | #### 值得注意的参数
530 | * 'order_id' 是创建转帐交易时所返回的ID。如没有取得该ID,则可使用 `out_biz_no` 来代替。
531 |
532 | > 如需 API 接口的完整参数列表,请参考官方的 [API 文档](https://docs.open.alipay.com/api_28/alipay.fund.trans.order.query/).
533 |
--------------------------------------------------------------------------------
/doc/quick_start_en.md:
--------------------------------------------------------------------------------
1 | Quick Start Guide
2 | =================
3 |
4 | ### [中文](quick_start_cn.md)
5 |
6 | ## Table of Contents
7 | * [Client Setup](#client-setup)
8 | * [Ruby](#ruby)
9 | * [Ruby on Rails](#ruby-on-rails)
10 | * [Create Payment](#create-payment)
11 | * [Desktop](#desktop)
12 | * [Mobile](#mobile)
13 | * [QR Code](#qr-code)
14 | * [Installment Plan](#installment-plan)
15 | * [Verify Callback](#verify-callback)
16 | * [Query Payment Status](#query-payment-status)
17 | * [Stop Payment](#stop-payment)
18 | * [Close Payment](#close-payment)
19 | * [Cancel Payment](#cancel-payment)
20 | * [Refund Payment](#refund-payment)
21 | * [Initiate Refund](#initiate-refund)
22 | * [Query Refund Status](#query-refund-status)
23 | * [Fund Transfer](#fund-transfer)
24 | * [Transfer Fund to Customer](#transfer-fund-to-customer)
25 | * [Query Fund Transfer](#query-fund-transfer)
26 |
27 | ## Client Setup
28 |
29 | **All examples in the quick start guide assume you have properly setup the client.**
30 |
31 | ### Ruby
32 | ```ruby
33 | # put your Alipay credentials here
34 | API_URL: 'https://openapi.alipaydev.com/gateway.do'
35 | APP_ID: '2016000000000000'
36 | APP_PRIVATE_KEY: "-----BEGIN RSA PRIVATE KEY-----\nxkbt...4Wt7tl\n-----END RSA PRIVATE KEY-----\n"
37 | ALIPAY_PUBLIC_KEY: "-----BEGIN PUBLIC KEY-----\nTq43T5...OVUAQb3R\n-----END PUBLIC KEY-----\n"
38 |
39 | # set up a client to talk to the Alipay API
40 | @client = Alipay::Client.new(
41 | url: API_URL,
42 | app_id: APP_ID,
43 | app_private_key: APP_PRIVATE_KEY,
44 | alipay_public_key: ALIPAY_PUBLIC_KEY
45 | )
46 | ```
47 |
48 | ### Ruby on Rails
49 | You can save your Alipay credentials as environment variables or set them up using initializer.
50 |
51 | This guide will demonstrate setting up the Alipay client with the `dotenv-rails` gem.
52 |
53 | In your Gemfile
54 | ```ruby
55 | # Gemfile
56 | gem 'alipay', '~> 0.15.0'
57 | gem 'dotenv-rails', '~> 2.2', '>= 2.2.1'
58 | ```
59 | Then run
60 | ```bash
61 | $ bundle install
62 | ```
63 |
64 | Create an .env file
65 | ```ruby
66 | # .env
67 | APP_ID=2016000000000000
68 | ALIPAY_API=https://openapi.alipaydev.com/gateway.do
69 | APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nxkbt...4Wt7tl\n-----END RSA PRIVATE KEY-----\n"
70 | ALIPAY_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nTq43T5...OVUAQb3R\n-----END PUBLIC KEY-----\n"
71 | ```
72 |
73 | In your Ruby on Rails application, you can create an Alipay client instance like this:
74 | ```ruby
75 | @client = Alipay::Client.new(
76 | url: ENV['ALIPAY_API'],
77 | app_id: ENV['APP_ID'],
78 | app_private_key: ENV['APP_PRIVATE_KEY'],
79 | alipay_public_key: ENV['ALIPAY_PUBLIC_KEY']
80 | )
81 | ```
82 |
83 | ## Create Payment
84 |
85 | ### Desktop
86 | #### API Method
87 | ```
88 | alipay.trade.page.pay
89 | ```
90 | This API method is for creating payment transaction that is suitable for customers using various desktop (PC) environment.
91 |
92 | #### Client Method
93 | ```ruby
94 | Alipay::Client.page_execute_url
95 | ```
96 | This client method will generate a payment URL for redirecting customers to.
97 |
98 | #### Example
99 | ```ruby
100 | @client.page_execute_url(
101 | method: 'alipay.trade.page.pay',
102 | return_url: 'https://mystore.com/orders/20160401000000/return',
103 | notify_url: 'https://mystore.com/orders/20160401000000/notify',
104 | biz_content: JSON.generate({
105 | out_trade_no: '20160401000000',
106 | product_code: 'FAST_INSTANT_TRADE_PAY',
107 | total_amount: '0.01',
108 | subject: 'Example #123'
109 | }, ascii_only: true)
110 | )
111 | # => 'https://openapi.alipaydev.com/gateway.do?app_id=2016...'
112 | ```
113 |
114 | #### Notable Parameters
115 | * `return_url` is where Alipay will redirect customers to after a successful payment is made. The redirect
116 | will be sent via a `GET` request. This is an *optional* parameter.
117 | * `notify_url` is where Alipay will send async callbacks to via `POST` request after a successful
118 | payment. This is an *optional* parameter.
119 | * `out_trade_no` is a unique string set by you. It is expected to be in reference to a model ID
120 | in your application. Although it is not a strict requirement.
121 | * `total_amount` should be in decimal form with a maximum scale of 2. For example `5123.99` will
122 | be ¥5,123.99 in text form. `5123.999` is not a valid parameter.
123 |
124 | > For a complete list of the available parameters, please refer to the [API documentation](https://docs.open.alipay.com/270/alipay.trade.page.pay/).
125 |
126 |
127 | ### Mobile
128 | #### API Method
129 | ```
130 | alipay.trade.wap.pay
131 | ```
132 | This API method is for creating payment transaction for customers on a mobile device. It has the ability to transfer the payment process over to the native Alipay application if the application is installed on the customer's device. If not, the customer will be redirected to a mobile HTML version of the payment page.
133 |
134 | #### Client Method
135 | ```ruby
136 | Alipay::Client.page_execute_url
137 | ```
138 | This method will generate a payment URL for redirecting customers to.
139 |
140 | #### Example
141 | ```ruby
142 | @client.page_execute_url(
143 | method: 'alipay.trade.wap.pay',
144 | return_url: 'https://mystore.com/orders/20160401000000/return',
145 | notify_url: 'https://mystore.com/orders/20160401000000/notify',
146 | biz_content: JSON.generate({
147 | out_trade_no: '20160401000000',
148 | product_code: 'QUICK_WAP_WAY',
149 | total_amount: '0.01',
150 | subject: 'Example: 456'
151 | quit_url: 'https://mystore.com/orders/20160401000000/'
152 | }, ascii_only: true)
153 | )
154 | # => 'https://openapi.alipaydev.com/gateway.do?app_id=2016...'
155 | ```
156 | #### Notebale Parameters
157 | * `quit_url` is where Alipay will redirect customer to if the customer have completed the payment or have exited the payment process. This redirect only applies to the mobile html verison. This is an *optional* parameter.
158 |
159 | > For a complete list of the available parameters, please refer to the
160 | [API documentation](https://docs.open.alipay.com/203/107090/).
161 |
162 | ### QR Code
163 | #### API Method
164 | ```
165 | alipay.trade.precreate
166 | ```
167 | This API method generates a payment URL that can be transformed into a QR code for customers to scan. A callback will be issued to the defined `notify_url` if payment is successful.
168 |
169 | #### Client Method
170 | ```ruby
171 | Alipay::Client.execute
172 | ```
173 |
174 | #### Example
175 | ```ruby
176 | # Create a QR code based payment
177 | response = @client.execute(
178 | method: 'alipay.trade.precreate',
179 | notify_url: 'https://mystore.com/orders/20160401000000/notify',
180 | biz_content: JSON.generate({
181 | out_trade_no: '20160401000000',
182 | total_amount: '50.00',
183 | subject: 'QR Code Test'
184 | }, ascii_only: true)
185 | )
186 | # => '{\"alipay_trade_precreate_response\":{\"code\"...'
187 |
188 | # Get payment url to render as QR code for customer
189 | qr_code = JSON.parse(response)["alipay_trade_precreate_response"]["qr_code"]
190 | # => 'https://qr.alipay.com/abcdefggfedcba'
191 | ```
192 |
193 | > For a complete list of the available parameters, please refer to the [API documentation](https://docs.open.alipay.com/api_1/alipay.trade.precreate).
194 |
195 | ### Installment Plan
196 | #### API Methods
197 | ```
198 | alipay.trade.page.pay (Desktop / PC)
199 | alipay.trade.wap.pay (Mobile)
200 | alipay.trade.precreate (QR Code)
201 | ```
202 |
203 | #### Client Method
204 | ```ruby
205 | Alipay::Client.page_execute_url (Desktop / PC / Mobile)
206 | Alipay::Client.execute (QR Code)
207 | ```
208 |
209 | #### Example
210 | Scenario: Customer pre-select a six-installment payment plan before going through the payment process at Alipay.
211 |
212 | ```ruby
213 | @client.page_execute_url(
214 | method: 'alipay.trade.page.pay',
215 | return_url: 'https://mystore.com/orders/20160401000000/return',
216 | notify_url: 'https://mystore.com/orders/20160401000000/notify',
217 | biz_content: JSON.generate({
218 | out_trade_no: '20160401000000',
219 | product_code: 'FAST_INSTANT_TRADE_PAY',
220 | total_amount: '0.01',
221 | subject: 'Example #654',
222 | enable_pay_channels: 'balance,pcreditpayInstallment',
223 | extend_params: {
224 | hb_fq_num: '6'',
225 | hb_fq_seller_percent: '0'
226 | }
227 | }, ascii_only: true)
228 | )
229 | ```
230 | Scenario: Customer select an installment plan or their choice at Alipay's payment page.
231 | ```ruby
232 | @client.page_execute_url(
233 | method: 'alipay.trade.page.pay',
234 | return_url: 'https://mystore.com/orders/20160401000000/return',
235 | notify_url: 'https://mystore.com/orders/20160401000000/notify',
236 | biz_content: JSON.generate({
237 | out_trade_no: '20160401000000',
238 | product_code: 'FAST_INSTANT_TRADE_PAY',
239 | total_amount: '0.01',
240 | subject: 'Example #654',
241 | enable_pay_channels: 'balance,pcreditpayInstallment',
242 | }, ascii_only: true)
243 | )
244 | ```
245 | #### Notebale Parameters
246 | * `enable_pay_channels` defines the funding methods that the customer can use to
247 | pay for your order. `pcreditpayInstallment` is the value to enable installment plans. Mutiple
248 | values can be enabled by sperating the values by `,` like the example above.
249 | * `hb_fq_num` defines the number of installments. Valid values are `3`, `6`, or `12`.
250 | * `hb_fq_seller_percent` defines the percentage of credit installment fee the seller is
251 | responsiable for. Valid value is `0` where the customer bare all the fees. Another valid value is
252 | `100` where you bare all the fees. However it only becomes valid if you have a special contract
253 | signed with Alipay.
254 |
255 | > For a complete list of the available parameters, please refer to the [API documentation](https://docs.open.alipay.com/277/106748/).
256 |
257 |
258 | ### Verify Callback
259 | #### Client Method
260 | ```
261 | Alipay::Client.verify?
262 | ```
263 | This client method will verify the validity of response params send by Alipay using Alipay's
264 | public key.
265 |
266 | #### Example
267 | ```ruby
268 | params = {
269 | out_trade_no: '20160401000000',
270 | trade_status: 'TRADE_SUCCESS'
271 | sign_type: 'RSA2',
272 | sign: '...'
273 | }
274 | @client.verify?(params)
275 | # => true / false
276 | ```
277 |
278 | ##### Verify GET return_url
279 | Alipay will send your customer via GET request to your specified `return_url`, if it was previously
280 | defined during the payment creation process. Here is an example of how to verify response params
281 | from Alipay in your controller.
282 |
283 | ```ruby
284 | @client.verify?(request.query_parameters)
285 | # => true / false
286 | ```
287 |
288 | ##### Verify POST notify_url
289 | Alipay will send POST request to your specified `notify_url`, if it was previously defined during
290 | the payment creation process. Your controller action should response 'success' in plain text or
291 | Alipay will keep on sending POST requests in increasing intervals.
292 |
293 | ```ruby
294 | if @client.verify?(request.request_parameters)
295 | render plain: 'success'
296 | end
297 | ```
298 |
299 | ### Query Payment Status
300 | #### API Method
301 | ```
302 | alipay.trade.query
303 | ```
304 | This API method is for querying payment status after the payment is created. It suitable for
305 | situations when callbacks fail or when the created payment did not set callback parameters.
306 |
307 | #### Client Method
308 | ```ruby
309 | Alipay::Client.execute
310 | ```
311 |
312 | #### Example
313 | ```ruby
314 | response = @client.execute(
315 | method: 'alipay.trade.query',
316 | biz_content: JSON.generate({
317 | trade_no: '2013112611001004680073956707',
318 | }, ascii_only: true)
319 | )
320 | # => '{\"alipay_trade_query_response\":{\"code\"...'
321 |
322 | # Get payment status
323 | result_status = JSON.parse(response)["alipay_trade_query_response"]["trade_status"]
324 | # => 'TRADE_SUCCESS'
325 | ```
326 |
327 | Notable Parameters
328 | * `trade_no` is the payment identification string provided by Alipay via callback after the payment is
329 | created. If you do not have this on hand, you can provide the `out_trade_no` instead.
330 |
331 | > For a complete list of the available parameters, please refer to the
332 | [API documentation](https://docs.open.alipay.com/api_1/alipay.trade.query).
333 |
334 | ## Stop Payment
335 | ### Close Payment
336 | #### API Method
337 | ```
338 | alipay.trade.close
339 | ```
340 | This API method is for closing inactive payment if you don't want to wait for Alipay to close
341 | out the payment at its default expiration time.
342 |
343 | #### Client Method
344 | ```ruby
345 | Alipay::Client.execute
346 | ```
347 |
348 | #### Example
349 | ```ruby
350 | response = @client.execute(
351 | method: 'alipay.trade.close',
352 | notify_url: 'https://mystore.com/orders/20160401000000/notify',
353 | biz_content: JSON.generate({
354 | trade_no: '2013112611001004680073956707',
355 | }, ascii_only: true)
356 | )
357 | # => '{\"alipay_trade_close_response\":{\"code\"...'
358 |
359 | # Get request result
360 | result_code = JSON.parse(response)["alipay_trade_close_response"]["code"]
361 | # => '10000'
362 | ```
363 |
364 | #### Notable Parameters
365 | * `trade_no` is the payment identification that Alipay returned to you after the payment was
366 | pre-created. If you do not have this parameter on hand, you can stub in the 'out_trade_no' that you
367 | used using the payment creation process.
368 |
369 | > For a complete list of the available parameters, please refer to the
370 | [API documentation](https://docs.open.alipay.com/api_1/alipay.trade.close).
371 |
372 | ### Cancel Payment
373 | #### API Method
374 | ```
375 | alipay.trade.cancel
376 | ```
377 | This API method is for canceling stuck payment or payment that errors out due to either Alipay
378 | or your own application. If the customer has successfully made the payment. A refund will be made.
379 | If customers had not pay yet, the payment transaction will be close. You can check if a refund
380 | had been made by getting the `action` value from the request response.
381 |
382 | #### Client Method
383 | ```ruby
384 | Alipay::Client.execute
385 | ```
386 |
387 | #### Example
388 | ```ruby
389 | response = @client.execute(
390 | method: 'alipay.trade.cancel',
391 | biz_content: JSON.generate({
392 | out_trade_no: '20160401000000',
393 | }, ascii_only: true)
394 | )
395 | # => '{\"alipay_trade_cancel_response\":{\"code\"...'
396 |
397 | # Get cancellation result
398 | result_action = JSON.parse(response)["alipay_trade_cancel_response"]["action"]
399 | # => 'close'
400 | ```
401 |
402 | #### Notable Parameters
403 | * `out_trade_no` is a unique string that you provided during the payment creation process. You must
404 | provide either `out_trade_no` or `trade_no` in order for the API call to be successful.
405 |
406 | > For a complete list of the available parameters, please refer to the
407 | [API documentation](https://docs.open.alipay.com/api_1/alipay.trade.cancel/).
408 |
409 | ## Refund Payment
410 |
411 | ### Initiate Refund
412 | #### API Method
413 | ```
414 | alipay.trade.refund
415 | ```
416 | This API method is for initiating refunds to customers. Only made this API call if the payment has not passed the refund period defined by Alipay. Multiple or partial refunds are also available through this API.
417 |
418 | #### Client Method
419 | ```ruby
420 | Alipay::Client.execute
421 | ```
422 |
423 | #### Example
424 | Secenario: Customer request refund on a ¥10.12 item on a ¥210.85 order(payment).
425 | ```ruby
426 | response = @client.execute(
427 | method: 'alipay.trade.refund',
428 | biz_content: JSON.generate({
429 | out_trade_no: '6c50789a0610',
430 | out_request_no: '6c50789a0610-1',
431 | refund_amount: '10.12'
432 | }, ascii_only: true)
433 | )
434 | # => '{\"alipay_trade_refund_response\":{\"code\"...'
435 |
436 | # Get result
437 | result_code = JSON.parse(response)["alipay_trade_refund_response"]["code"]
438 | # => '10000'
439 |
440 | result_fund_change = JSON.parse(response)["alipay_trade_refund_response"]["fund_change"]
441 | # => 'Y'
442 | ```
443 |
444 | #### Notable Parameters
445 | * `out_request_no` is an *conditional optional* parameter. It is *required* when you make a partial
446 | refund or plan to make multiple refunds. It serves as identification for the refund transaction.
447 | It is *optional* when you refund the entire payment amount.
448 | * `refund_amount` is the amount you wish to refund to the customer. It can be lower than the original
449 | payment amount. However, if you have previously issued partial refunds on a payment, the sum
450 | of `refund_amount` cannot exceed the original payment amount.
451 |
452 | > For a complete list of the available parameters, please refer to the
453 | [API documentation](https://docs.open.alipay.com/api_1/alipay.trade.refund/).
454 |
455 | ### Query Refund Status
456 | #### API Method
457 | ```
458 | alipay.trade.fastpay.refund.query
459 | ```
460 | This API method is for querying refund status.
461 |
462 | #### Client Method
463 | ```ruby
464 | Alipay::Client.execute
465 | ```
466 |
467 | #### Example
468 | ```ruby
469 | response = @client.execute(
470 | method: 'alipay.trade.fastpay.refund.query',
471 | biz_content: JSON.generate({
472 | out_trade_no: '6c50789a0610',
473 | out_request_no: '6c50789a0610-1'
474 | }, ascii_only: true)
475 | )
476 | # => '{\"alipay_trade_fastpay_refund_query_response\":{\"code\"...'
477 |
478 | # Get refund amount
479 | result_refund_amount = JSON.parse(response)["alipay_trade_fastpay_refund_query_response"]["refund_amount"]
480 | # => '10.12'
481 | ```
482 |
483 | #### Notable Parameters
484 | * `out_request_no` is the identifying string provided by you provided when you initiated the refund.
485 | If you did not provide this parameter when the refund was initiated, use the `out_trade_no` as your
486 | `out_request_no`.
487 |
488 | > For a complete list of the available parameters, please refer to the
489 | [API documentation](https://docs.open.alipay.com/api_1/alipay.trade.fastpay.refund.query/).
490 |
491 |
492 | ## Fund Transfer
493 | ### Transfer Fund to Customer
494 | #### API Method
495 | ```
496 | alipay.fund.trans.toaccount.transfer
497 | ```
498 | This API method is for creating a fund transfer to customers from your Alipay account.
499 |
500 | #### Client Method
501 | ```
502 | Alipay::Client.execute
503 | ```
504 |
505 | #### Example
506 | ```ruby
507 | response = @client.execute(
508 | method: 'alipay.fund.trans.toaccount.transfer',
509 | biz_content: JSON.generate({
510 | out_biz_no: '3142321423432',
511 | payee_type: 'ALIPAY_LOGONID',
512 | payee_account: 'customer@example.com',
513 | amount: '12.23'
514 | }, ascii_only: true)
515 | )
516 | # => '{\"alipay_fund_trans_toaccount_transfer_response\":{\"code\"...'
517 |
518 | # Get order ID
519 | result_order_id = JSON.parse(response)["alipay_fund_trans_toaccount_transfer_response"]["order_id"]
520 | # => '20160627110070001502260006780837'
521 | ```
522 |
523 | #### Notable Parameters
524 | * `out_biz_no` is a unique identifying string provided by you for reference purposes.
525 | * `payee_type` is for defining the type of `payee_account` that is provided. Valid values
526 | are `ALIPAY_USERID` for customer's Alipay user ID starting with `2088` and `ALIPAY_LOGONID` for
527 | customer cellular number or email address.
528 | * `payee_account` is the customer Alipay ID, email or cellular number. It must match the `payee_type`
529 | provided.
530 | * `amount` is the amount you wish to transfer to the customer. (e.g. ¥123.50 is `123.50`)
531 |
532 | > For a complete list of the available parameters, please refer to the
533 | [API documentation](https://docs.open.alipay.com/api_28/alipay.fund.trans.toaccount.transfer).
534 |
535 | ### Query Fund Transfer
536 | #### API Method
537 | ```
538 | alipay.fund.trans.order.query
539 | ```
540 | This API method is for querying fund transfer status.
541 |
542 | #### Client Method
543 | ```
544 | Alipay::Client.execute
545 | ```
546 |
547 | #### Example
548 | ```ruby
549 | response = @client.execute(
550 | method: 'alipay.fund.trans.order.query',
551 | biz_content: JSON.generate({
552 | out_biz_no: '3142321423432',
553 | }, ascii_only: true)
554 | )
555 | # => '{\"alipay_fund_trans_order_query_response\":{\"code\"...'
556 |
557 | # Get refund_status
558 | refund_status = JSON.parse(response)["alipay_fund_trans_order_query_response"]["status"]
559 | # => 'SUCCESS'
560 |
561 | # Get expected refund date
562 | refund_status = JSON.parse(response)["alipay_fund_trans_order_query_response"]["arrival_time_end"]
563 | # => '2018-01-01 08:08:08'
564 | ```
565 |
566 | #### Notable Parameters
567 | * 'order_id' is the order id provided by Alipay when the transfer request was created. If you
568 | do not have this on hand, you can stub in `out_biz_no` instead.
569 |
570 | > For a complete list of the available parameters, please refer to the
571 | [API documentation](https://docs.open.alipay.com/api_28/alipay.fund.trans.order.query/).
572 |
--------------------------------------------------------------------------------
/doc/rsa_key_cn.md:
--------------------------------------------------------------------------------
1 | # 配合支付宝使用RSA密钥
2 |
3 | [English](rsa_key_en.md)
4 |
5 | ## 导航
6 | * [生成应用密钥](#生成应用密钥)
7 | * [验证参数](#验证参数)
8 | * [补充格式到支付宝公钥](#补充格式到支付宝公钥)
9 | * [使用证书签名方式](#使用证书签名方式)
10 |
11 | ### 生成应用密钥
12 | #### 在 Ruby 下生成 RSA2 密钥
13 | 这个会示范在 Ruby 环境下生成RSA2密钥。支付宝推荐使用RSA2密钥来验证。
14 | ```ruby
15 | require 'openssl'
16 |
17 | @app_key = OpenSSL::PKey::RSA.new(2048)
18 | ```
19 | #### 保存密钥
20 | 你可以使用以下任一方式来保存你的私钥和公钥
21 |
22 | 将私钥保存到字符串
23 | ```ruby
24 | app_private_key = @app_key.to_s
25 | ```
26 |
27 | 将私钥保存为证书文件
28 | ```ruby
29 | open 'private_key.pem', 'w' do |io| io.write @app_key.to_pem end
30 | ```
31 |
32 | 将公钥保存到字符串
33 | ```ruby
34 | app_public_key = @app_key.public_key.to_s
35 | ```
36 | 将公钥保存为证书文件
37 | ```ruby
38 | open 'public_key.pem', 'w' do |io| io.write @app_key.public_key.to_pem end
39 | ```
40 |
41 | #### 提取钥匙内容
42 | 你需要给支付宝提供你所先前生成的公钥内容。但是提供给支付宝之前需要对 Ruby 生成的公钥进行格式清理。清理完后,将清理好的公钥内容提供给支付宝即可。
43 | ```ruby
44 | key_content = app_public_key.gsub(/(-----BEGIN PUBLIC KEY-----)|(-----END PUBLIC KEY-----)|(\n)/, "")
45 | puts key_content
46 | # => 'MII0ey6QDZLB69i0e5Q0....'
47 | ```
48 |
49 | ### 验证参数
50 | 当你提交你的应用公钥给支付宝后,有一个可选的步骤是验证你的公钥的有效性。支付宝会提供一个参数让你使用你的私钥签名。把签名结果粘贴到支付宝后,支付宝会使用你上传的公钥来解密验证。
51 | ```ruby
52 | # validate params "a=123"
53 | Base64.strict_encode64(@app_key.sign('sha256', "a=123"))
54 | # => 'FokDu5uwgmNG2O/cb0QYD....'
55 | ```
56 |
57 | ### 补充格式到支付宝公钥
58 | 你上传你的公钥后,支付宝会提供他们的公钥给你的应用来验证支付宝回调的内容有效性。但是他们提供公钥不带格式,所以 Ruby 的 OpneSSL 可能无法识别。将格式补充到支付宝所提供的公钥,你可以使用以下运行脚本。
59 |
60 | ```ruby
61 | pub_key = "MIIBI...HpwIDAQAB"
62 | pub_key.scan(/.{64}|.+$/).join("\n").insert(0, "-----BEGIN PUBLIC KEY-----\n").insert(-1, "\n-----END PUBLIC KEY-----\n")
63 | # => "-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----\n"
64 | ```
65 |
66 | # 使用证书签名方式
67 |
68 | ## 应用证书配置
69 | 按照官方文档进行新建应用配置证书签名 https://docs.open.alipay.com/291/twngcd/
70 |
71 | 配置完成后,可以得到 `xxx.com_私钥.txt alipayCertPublicKey_RSA2.crt appCertPublicKey_2019082600000001.crt alipayRootCert.crt` 四个文件。
72 |
73 | ### 应用私钥补充格式
74 | ```ruby
75 | app_private_key = File.read('xxx.com_私钥.txt')
76 | app_private_key = app_private_key.scan(/.{64}|.+$/).join("\n").insert(0, "-----BEGIN RSA PRIVATE KEY-----\n").insert(-1, "\n-----END RSA PRIVATE KEY-----\n")
77 | ```
78 | ### 处理应用阿里云公钥
79 | ```ruby
80 | alipay_public_key = File.read('alipayCertPublicKey_RSA2.crt')
81 | alipay_public_key = OpenSSL::X509::Certificate.new(alipay_public_key).public_key.to_s
82 | ```
83 | ### 得到应用公钥证书sn
84 | ```ruby
85 | app_cert = File.read('appCertPublicKey_2019082600000001.crt')
86 | app_cert_sn = Alipay::Utils.get_cert_sn(app_cert)
87 | # => "28d1147972121b91734da59aa10f3c16"
88 | ```
89 | ### 得到支付宝根证书sn
90 | ```ruby
91 | alipay_root_cert = File.read('alipayRootCert.crt')
92 | alipay_root_cert_sn = Alipay::Utils.get_root_cert_sn(alipay_root_cert)
93 | # => "28d1147972121b91734da59aa10f3c16_28d1147972121b91734da59aa10f3c16"
94 | ```
95 | ### 使用
96 | ```ruby
97 | @alipay_client = Alipay::Client.new(
98 | url: API_URL,
99 | app_id: APP_ID,
100 | app_private_key: app_private_key,
101 | alipay_public_key: alipay_public_key,
102 | app_cert_sn: app_cert_sn,
103 | alipay_root_cert_sn: alipay_root_cert_sn
104 | )
105 | ```
--------------------------------------------------------------------------------
/doc/rsa_key_en.md:
--------------------------------------------------------------------------------
1 | # RSA Key for Alipay
2 |
3 | [中文](rsa_key_cn.md)
4 |
5 | ## Table of Contents
6 |
7 | * [Generate Application Key](#generate-application-key)
8 | * [Generate RSA2 Keypair in Ruby](#generate-rsa2-keypair-in-ruby)
9 | * [Saving Key](#saving-key)
10 | * [Extract Key Content](#extract-key-content)
11 | * [Signing Parameters](#signing-parameters)
12 | * [Formatting the Public Key from Alipay](#formatting-the-public-key-from-alipay)
13 |
14 | ### Generate Application Key
15 | #### Generate RSA2 Keypair in Ruby
16 | This example creates a 2048 bits RSA2 key. It is recommended by Alipay that
17 | you use a RSA2 key.
18 | ```ruby
19 | require 'openssl'
20 |
21 | @app_key = OpenSSL::PKey::RSA.new(2048)
22 | ```
23 | #### Saving Key
24 | You can save your private and public key as any of two formats. As long as it can be loaded into the program.
25 |
26 | Saving Private Key to String
27 | ```ruby
28 | app_private_key = @app_key.to_s
29 | ```
30 |
31 | Saving Private Key to File
32 | ```ruby
33 | open 'private_key.pem', 'w' do |io| io.write @app_key.to_pem end
34 | ```
35 |
36 | Saving Public Key to String
37 | ```ruby
38 | app_public_key = @app_key.public_key.to_s
39 | ```
40 |
41 | Saving Public Key to File
42 | ```ruby
43 | open 'public_key.pem', 'w' do |io| io.write @app_key.public_key.to_pem end
44 | ```
45 |
46 | #### Extract Key Content
47 | You will need to submit the application public key that you just created
48 | to Alipay. However, you will need to strip the header, footer, and new line
49 | characters from the key and just submit the key content to Alipay.
50 | ```ruby
51 | key_content = app_public_key.gsub(/(-----BEGIN PUBLIC KEY-----)|(-----END PUBLIC KEY-----)|(\n)/, "")
52 | puts key_content
53 | # => 'MII0ey6QDZLB69i0e5Q0....'
54 | ```
55 |
56 | ### Signing Parameters
57 | After you submit your application's public key to Alipay. There is an optional
58 | step to validate the public key that you just uploaded by signing a parameter
59 | provided by Alipay.
60 |
61 | ```ruby
62 | # validate params "a=123"
63 | Base64.strict_encode64(@app_key.sign('sha256', "a=123"))
64 | # => 'FokDu5uwgmNG2O/cb0QYD....'
65 | ```
66 |
67 | ### Formatting the Public Key from Alipay
68 | The public key from Alipay does not contain any formatting. Ruby's OpenSSL
69 | library cannot import/read the public key without proper formatting. To add
70 | formatting back, run the following script.
71 |
72 | ```ruby
73 | pub_key = "MIIBI...HpwIDAQAB"
74 | pub_key.scan(/.{64}|.+$/).join("\n").insert(0, "-----BEGIN PUBLIC KEY-----\n").insert(-1, "\n-----END PUBLIC KEY-----\n")
75 | # => "-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----\n"
76 | ```
77 |
78 |
--------------------------------------------------------------------------------
/lib/alipay.rb:
--------------------------------------------------------------------------------
1 | require 'net/http'
2 | require 'cgi'
3 | require 'alipay/version'
4 | require 'alipay/utils'
5 | require 'alipay/sign'
6 | require 'alipay/sign/md5'
7 | require 'alipay/sign/rsa'
8 | require 'alipay/sign/rsa2'
9 | require 'alipay/sign/dsa'
10 | require 'alipay/service'
11 | require 'alipay/notify'
12 | require 'alipay/wap/service'
13 | require 'alipay/wap/notify'
14 | require 'alipay/wap/sign'
15 | require 'alipay/mobile/service'
16 | require 'alipay/mobile/sign'
17 | require 'alipay/client'
18 |
19 | module Alipay
20 | @debug_mode = true
21 | @sign_type = 'MD5'
22 |
23 | class << self
24 | attr_accessor :pid, :key, :sign_type, :debug_mode
25 |
26 | def debug_mode?
27 | !!@debug_mode
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/alipay/client.rb:
--------------------------------------------------------------------------------
1 | module Alipay
2 | class Client
3 | # Create a client to manage all API request.
4 | #
5 | # Example:
6 | #
7 | # alipay_client = Alipay::Client.new(
8 | # url: 'https://openapi.alipaydev.com/gateway.do',
9 | # app_id: '2016000000000000',
10 | # app_private_key: APP_PRIVATE_KEY,
11 | # alipay_public_key: ALIPAY_PUBLIC_KEY
12 | # )
13 | #
14 | # Options:
15 | #
16 | # [:url] Alipay Open API gateway,
17 | # 'https://openapi.alipaydev.com/gateway.do'(Sandbox) or
18 | # 'https://openapi.alipay.com/gateway.do'(Production).
19 | #
20 | # [:app_id] Your APP ID.
21 | #
22 | # [:app_private_key] APP private key.
23 | #
24 | # [:alipay_public_key] Alipay public key.
25 | #
26 | # [:format] default is 'json', only support 'json'.
27 | #
28 | # [:charset] default is 'UTF-8', only support 'UTF-8'.
29 | #
30 | # [:sign_type] default is 'RSA2', support 'RSA2', 'RSA', 'RSA2' is recommended.
31 | def initialize(options)
32 | options = ::Alipay::Utils.stringify_keys(options)
33 | @url = options['url']
34 | @app_id = options['app_id']
35 | @app_private_key = options['app_private_key']
36 | @alipay_public_key = options['alipay_public_key']
37 | @format = options['format'] || 'json'
38 | @charset = options['charset'] || 'UTF-8'
39 | @sign_type = options['sign_type'] || 'RSA2'
40 | @app_cert_sn = options['app_cert_sn']
41 | @alipay_root_cert_sn = options['alipay_root_cert_sn']
42 | end
43 |
44 | # Generate a query string that use for APP SDK excute.
45 | #
46 | # Example:
47 | #
48 | # alipay_client.sdk_execute(
49 | # method: 'alipay.trade.app.pay',
50 | # biz_content: {
51 | # out_trade_no: '20160401000000',
52 | # product_code: 'QUICK_MSECURITY_PAY',
53 | # total_amount: '0.01',
54 | # subject: 'test'
55 | # }.to_json(ascii_only: true),
56 | # timestamp: '2016-04-01 00:00:00'
57 | # )
58 | # # => 'app_id=2016000000000000&charset=utf-8&sig....'
59 | def sdk_execute(params)
60 | params = prepare_params(params)
61 |
62 | URI.encode_www_form(params)
63 | end
64 |
65 | # Generate a url that use to redirect user to Alipay payment page.
66 | #
67 | # Example:
68 | #
69 | # alipay_client.page_execute_url(
70 | # method: 'alipay.trade.page.pay',
71 | # biz_content: {
72 | # out_trade_no: '20160401000000',
73 | # product_code: 'FAST_INSTANT_TRADE_PAY',
74 | # total_amount: '0.01',
75 | # subject: 'test'
76 | # }.to_json(ascii_only: true),
77 | # timestamp: '2016-04-01 00:00:00'
78 | # )
79 | # # => 'https://openapi.alipaydev.com/gateway.do?app_id=2016...'
80 | def page_execute_url(params)
81 | params = prepare_params(params)
82 |
83 | uri = URI(@url)
84 | uri.query = URI.encode_www_form(params)
85 | uri.to_s
86 | end
87 |
88 | # Generate a form string that use to render in view and auto POST to
89 | # Alipay server.
90 | #
91 | # Example:
92 | #
93 | # alipay_client.page_execute_form(
94 | # method: 'alipay.trade.page.pay',
95 | # biz_content: {
96 | # out_trade_no: '20160401000000',
97 | # product_code: 'FAST_INSTANT_TRADE_PAY',
98 | # total_amount: '0.01',
99 | # subject: 'test'
100 | # }.to_json(ascii_only: true),
101 | # timestamp: '2016-04-01 00:00:00'
102 | # )
103 | # # => '"
112 | html << ""
113 | html
114 | end
115 |
116 | # Immediately make a API request to Alipay and return response body.
117 | #
118 | # Example:
119 | #
120 | # alipay_client.execute(
121 | # method: 'alipay.data.dataservice.bill.downloadurl.query',
122 | # biz_content: {
123 | # bill_type: 'trade',
124 | # bill_date: '2016-04-01'
125 | # }.to_json(ascii_only: true)
126 | # )
127 | # # => '{ "alipay_data_dataservice_bill_downloadurl_query_response":{...'
128 | def execute(params)
129 | params = prepare_params(params)
130 |
131 | Net::HTTP.post_form(URI(@url), params).body
132 | end
133 |
134 | # Generate sign for params.
135 | def sign(params)
136 | string = params_to_string(params)
137 |
138 | case @sign_type
139 | when 'RSA'
140 | ::Alipay::Sign::RSA.sign(@app_private_key, string)
141 | when 'RSA2'
142 | ::Alipay::Sign::RSA2.sign(@app_private_key, string)
143 | else
144 | raise "Unsupported sign_type: #{@sign_type}"
145 | end
146 | end
147 |
148 | # Verify Alipay notification.
149 | #
150 | # Example:
151 | #
152 | # params = {
153 | # out_trade_no: '20160401000000',
154 | # trade_status: 'TRADE_SUCCESS'
155 | # sign_type: 'RSA2',
156 | # sign: '...'
157 | # }
158 | # alipay_client.verify?(params)
159 | # # => true / false
160 | def verify?(params)
161 | params = Utils.stringify_keys(params)
162 | return false if params['sign_type'] != @sign_type
163 |
164 | sign = params.delete('sign')
165 | # sign_type does not use in notify sign
166 | params.delete('sign_type')
167 | string = params_to_string(params)
168 | case @sign_type
169 | when 'RSA'
170 | ::Alipay::Sign::RSA.verify?(@alipay_public_key, string, sign)
171 | when 'RSA2'
172 | ::Alipay::Sign::RSA2.verify?(@alipay_public_key, string, sign)
173 | else
174 | raise "Unsupported sign_type: #{@sign_type}"
175 | end
176 | end
177 |
178 | private
179 |
180 | def prepare_params(params)
181 | params = {
182 | 'app_id' => @app_id,
183 | 'charset' => @charset,
184 | 'sign_type' => @sign_type,
185 | 'version' => '1.0',
186 | 'timestamp' => Time.now.localtime('+08:00').strftime("%Y-%m-%d %H:%M:%S")
187 | }.merge(::Alipay::Utils.stringify_keys(params))
188 | if !@app_cert_sn.nil? && !@alipay_root_cert_sn.nil?
189 | params = params.merge({
190 | 'app_cert_sn' => @app_cert_sn,
191 | 'alipay_root_cert_sn' => @alipay_root_cert_sn
192 | })
193 | end
194 | params['sign'] = sign(params)
195 | params
196 | end
197 |
198 | def params_to_string(params)
199 | params.sort.map { |item| item.join('=') }.join('&')
200 | end
201 | end
202 | end
203 |
--------------------------------------------------------------------------------
/lib/alipay/mobile/service.rb:
--------------------------------------------------------------------------------
1 | module Alipay
2 | module Mobile
3 | module Service
4 | MOBILE_SECURITY_PAY_REQUIRED_PARAMS = %w( notify_url out_trade_no subject total_fee body )
5 | def self.mobile_securitypay_pay_string(params, options = {})
6 | params = Utils.stringify_keys(params)
7 | Alipay::Service.check_required_params(params, MOBILE_SECURITY_PAY_REQUIRED_PARAMS)
8 | sign_type = options[:sign_type] || Alipay.sign_type
9 | key = options[:key] || Alipay.key
10 | raise ArgumentError, "only support RSA sign_type" if sign_type != 'RSA'
11 |
12 | params = {
13 | 'service' => 'mobile.securitypay.pay',
14 | '_input_charset' => 'utf-8',
15 | 'partner' => options[:partner] || options[:pid] || Alipay.pid,
16 | 'seller_id' => options[:seller_id] || options[:pid] || Alipay.pid,
17 | 'payment_type' => '1'
18 | }.merge(params)
19 |
20 | string = Alipay::Mobile::Sign.params_to_string(params)
21 | sign = CGI.escape(Alipay::Sign::RSA.sign(key, string))
22 |
23 | %Q(#{string}&sign="#{sign}"&sign_type="RSA")
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/alipay/mobile/sign.rb:
--------------------------------------------------------------------------------
1 | module Alipay
2 | module Mobile
3 | module Sign
4 | def self.params_to_string(params)
5 | params.map { |key, value| %Q(#{key}="#{value}") }.join('&')
6 | end
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/alipay/notify.rb:
--------------------------------------------------------------------------------
1 | module Alipay
2 | module Notify
3 | def self.verify?(params, options = {})
4 | params = Utils.stringify_keys(params)
5 | pid = options[:pid] || Alipay.pid
6 | Sign.verify?(params, options) && verify_notify_id?(pid, params['notify_id'])
7 | end
8 |
9 | def self.verify_notify_id?(pid, notify_id)
10 | uri = URI("https://mapi.alipay.com/gateway.do")
11 | uri.query = URI.encode_www_form(
12 | 'service' => 'notify_verify',
13 | 'partner' => pid,
14 | 'notify_id' => notify_id
15 | )
16 | Net::HTTP.get(uri) == 'true'
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/alipay/service.rb:
--------------------------------------------------------------------------------
1 | module Alipay
2 | module Service
3 | GATEWAY_URL = 'https://mapi.alipay.com/gateway.do'
4 |
5 | CREATE_PARTNER_TRADE_BY_BUYER_REQUIRED_PARAMS = %w( out_trade_no subject logistics_type logistics_fee logistics_payment price quantity )
6 | def self.create_partner_trade_by_buyer_url(params, options = {})
7 | params = Utils.stringify_keys(params)
8 | check_required_params(params, CREATE_PARTNER_TRADE_BY_BUYER_REQUIRED_PARAMS)
9 |
10 | params = {
11 | 'service' => 'create_partner_trade_by_buyer',
12 | '_input_charset' => 'utf-8',
13 | 'partner' => options[:partner] || options[:pid] || Alipay.pid,
14 | 'seller_id' => options[:seller_id] || options[:pid] || Alipay.pid,
15 | 'payment_type' => '1'
16 | }.merge(params)
17 |
18 | request_uri(params, options).to_s
19 | end
20 |
21 | TRADE_CREATE_BY_BUYER_REQUIRED_PARAMS = %w( out_trade_no subject logistics_type logistics_fee logistics_payment price quantity )
22 | def self.trade_create_by_buyer_url(params, options = {})
23 | params = Utils.stringify_keys(params)
24 | check_required_params(params, TRADE_CREATE_BY_BUYER_REQUIRED_PARAMS)
25 |
26 | params = {
27 | 'service' => 'trade_create_by_buyer',
28 | '_input_charset' => 'utf-8',
29 | 'partner' => options[:partner] || options[:pid] || Alipay.pid,
30 | 'seller_id' => options[:seller_id] || options[:pid] || Alipay.pid,
31 | 'payment_type' => '1'
32 | }.merge(params)
33 |
34 | request_uri(params, options).to_s
35 | end
36 |
37 | CREATE_DIRECT_PAY_BY_USER_REQUIRED_PARAMS = %w( out_trade_no subject )
38 | def self.create_direct_pay_by_user_url(params, options = {})
39 | params = Utils.stringify_keys(params)
40 | check_required_params(params, CREATE_DIRECT_PAY_BY_USER_REQUIRED_PARAMS)
41 |
42 | if Alipay.debug_mode? and params['total_fee'].nil? and (params['price'].nil? || params['quantity'].nil?)
43 | warn("Alipay Warn: total_fee or (price && quantity) must be set")
44 | end
45 |
46 | params = {
47 | 'service' => 'create_direct_pay_by_user',
48 | '_input_charset' => 'utf-8',
49 | 'partner' => options[:partner] || options[:pid] || Alipay.pid,
50 | 'seller_id' => options[:seller_id] || options[:pid] || Alipay.pid,
51 | 'payment_type' => '1'
52 | }.merge(params)
53 |
54 | request_uri(params, options).to_s
55 | end
56 |
57 | CREATE_DIRECT_PAY_BY_USER_WAP_REQUIRED_PARAMS = %w( out_trade_no subject total_fee )
58 | def self.create_direct_pay_by_user_wap_url(params, options = {})
59 | params = Utils.stringify_keys(params)
60 | check_required_params(params, CREATE_DIRECT_PAY_BY_USER_WAP_REQUIRED_PARAMS)
61 |
62 | params = {
63 | 'service' => 'alipay.wap.create.direct.pay.by.user',
64 | '_input_charset' => 'utf-8',
65 | 'partner' => options[:partner] || options[:pid] || Alipay.pid,
66 | 'seller_id' => options[:seller_id] || options[:pid] || Alipay.pid,
67 | 'payment_type' => '1'
68 | }.merge(params)
69 |
70 | request_uri(params, options).to_s
71 | end
72 |
73 | CREATE_REFUND_URL_REQUIRED_PARAMS = %w( batch_no data notify_url )
74 | def self.refund_fastpay_by_platform_pwd_url(params, options = {})
75 | params = Utils.stringify_keys(params)
76 | check_required_params(params, CREATE_REFUND_URL_REQUIRED_PARAMS)
77 |
78 | data = params.delete('data')
79 | detail_data = data.map do|item|
80 | item = Utils.stringify_keys(item)
81 | "#{item['trade_no']}^#{item['amount']}^#{item['reason']}"
82 | end.join('#')
83 |
84 | params = {
85 | 'service' => 'refund_fastpay_by_platform_pwd',
86 | '_input_charset' => 'utf-8',
87 | 'partner' => options[:pid] || Alipay.pid,
88 | 'seller_user_id' => options[:pid] || Alipay.pid,
89 | 'refund_date' => Time.now.strftime('%Y-%m-%d %H:%M:%S'),
90 | 'batch_num' => data.size,
91 | 'detail_data' => detail_data
92 | }.merge(params)
93 |
94 | request_uri(params, options).to_s
95 | end
96 |
97 | CREATE_FOREX_SINGLE_REFUND_URL_REQUIRED_PARAMS = %w( out_return_no out_trade_no return_amount currency reason )
98 | def self.forex_refund_url(params, options = {})
99 | params = Utils.stringify_keys(params)
100 | check_required_params(params, CREATE_FOREX_SINGLE_REFUND_URL_REQUIRED_PARAMS)
101 |
102 | params = {
103 | 'service' => 'forex_refund',
104 | 'partner' => options[:pid] || Alipay.pid,
105 | '_input_charset' => 'utf-8',
106 | 'gmt_return' => Time.now.getlocal('+08:00').strftime('%Y%m%d%H%M%S')
107 | }.merge(params)
108 |
109 | request_uri(params, options).to_s
110 | end
111 |
112 | SEND_GOODS_CONFIRM_BY_PLATFORM_REQUIRED_PARAMS = %w( trade_no logistics_name )
113 | SEND_GOODS_CONFIRM_BY_PLATFORM_OPTIONAL_PARAMS = %w( transport_type create_transport_type )
114 | def self.send_goods_confirm_by_platform(params, options = {})
115 | params = Utils.stringify_keys(params)
116 | check_required_params(params, SEND_GOODS_CONFIRM_BY_PLATFORM_REQUIRED_PARAMS)
117 | check_optional_params(params, SEND_GOODS_CONFIRM_BY_PLATFORM_OPTIONAL_PARAMS)
118 |
119 | params = {
120 | 'service' => 'send_goods_confirm_by_platform',
121 | 'partner' => options[:pid] || Alipay.pid,
122 | '_input_charset' => 'utf-8'
123 | }.merge(params)
124 |
125 | Net::HTTP.get(request_uri(params, options))
126 | end
127 |
128 | CREATE_FOREX_TRADE_REQUIRED_PARAMS = %w( notify_url subject out_trade_no currency )
129 | CREATE_FOREX_TRADE_OPTIONAL_PARAMS = %w( total_fee rmb_fee )
130 | def self.create_forex_trade_url(params, options = {})
131 | params = Utils.stringify_keys(params)
132 | check_required_params(params, CREATE_FOREX_TRADE_REQUIRED_PARAMS)
133 | check_optional_params(params, CREATE_FOREX_TRADE_OPTIONAL_PARAMS)
134 |
135 | params = {
136 | 'service' => 'create_forex_trade',
137 | '_input_charset' => 'utf-8',
138 | 'partner' => options[:pid] || Alipay.pid,
139 | }.merge(params)
140 |
141 | request_uri(params, options).to_s
142 | end
143 |
144 | CLOSE_TRADE_REQUIRED_OPTIONAL_PARAMS = %w( trade_no out_order_no )
145 | def self.close_trade(params, options = {})
146 | params = Utils.stringify_keys(params)
147 | check_optional_params(params, CLOSE_TRADE_REQUIRED_OPTIONAL_PARAMS)
148 |
149 | params = {
150 | 'service' => 'close_trade',
151 | '_input_charset' => 'utf-8',
152 | 'partner' => options[:pid] || Alipay.pid
153 | }.merge(params)
154 |
155 | Net::HTTP.get(request_uri(params, options))
156 | end
157 |
158 | SINGLE_TRADE_QUERY_OPTIONAL_PARAMS = %w( trade_no out_trade_no )
159 | def self.single_trade_query(params, options = {})
160 | params = Utils.stringify_keys(params)
161 | check_optional_params(params, SINGLE_TRADE_QUERY_OPTIONAL_PARAMS)
162 |
163 | params = {
164 | "service" => 'single_trade_query',
165 | "_input_charset" => "utf-8",
166 | "partner" => options[:pid] || Alipay.pid,
167 | }.merge(params)
168 |
169 | Net::HTTP.get(request_uri(params, options))
170 | end
171 |
172 | def self.account_page_query(params, options = {})
173 | params = {
174 | service: 'account.page.query',
175 | _input_charset: 'utf-8',
176 | partner: options[:pid] || Alipay.pid,
177 | }.merge(params)
178 |
179 | Net::HTTP.get(request_uri(params, options))
180 | end
181 |
182 | BATCH_TRANS_NOTIFY_REQUIRED_PARAMS = %w( notify_url account_name detail_data batch_no batch_num batch_fee email )
183 | def self.batch_trans_notify_url(params, options = {})
184 | params = Utils.stringify_keys(params)
185 | check_required_params(params, BATCH_TRANS_NOTIFY_REQUIRED_PARAMS)
186 |
187 | params = {
188 | 'service' => 'batch_trans_notify',
189 | '_input_charset' => 'utf-8',
190 | 'partner' => options[:pid] || Alipay.pid,
191 | 'pay_date' => Time.now.strftime("%Y%m%d")
192 | }.merge(params)
193 |
194 | request_uri(params, options).to_s
195 | end
196 |
197 | CREATE_FOREX_TRADE_WAP_REQUIRED_PARAMS = %w( out_trade_no subject merchant_url currency )
198 | def self.create_forex_trade_wap_url(params, options = {})
199 | params = Utils.stringify_keys(params)
200 | check_required_params(params, CREATE_FOREX_TRADE_WAP_REQUIRED_PARAMS)
201 |
202 | params = {
203 | 'service' => 'create_forex_trade_wap',
204 | '_input_charset' => 'utf-8',
205 | 'partner' => options[:partner] || options[:pid] || Alipay.pid,
206 | 'seller_id' => options[:seller_id] || options[:pid] || Alipay.pid,
207 | }.merge(params)
208 |
209 | request_uri(params, options).to_s
210 | end
211 |
212 | CREATE_MERCHANT_QR_CODE_REQUIRED_PARAMS = %w( biz_type biz_data )
213 | CREATE_MERCHANT_QR_CODE_REQUIRED_BIZ_DATA_PARAMS = %w( secondary_merchant_industry secondary_merchant_id secondary_merchant_name trans_currency currency )
214 | def self.create_merchant_qr_code(params, options = {})
215 | params = Utils.stringify_keys(params)
216 | check_required_params(params, CREATE_MERCHANT_QR_CODE_REQUIRED_PARAMS)
217 | biz_data = nil
218 |
219 | if params['biz_data']
220 | params['biz_data'] = Utils.stringify_keys(params['biz_data'])
221 | check_required_params(params['biz_data'], CREATE_MERCHANT_QR_CODE_REQUIRED_BIZ_DATA_PARAMS)
222 |
223 | data = params.delete('biz_data')
224 | biz_data = data.map do |key, value|
225 | "\"#{key}\": \"#{value}\""
226 | end.join(',')
227 | end
228 |
229 | biz_data = "{#{biz_data}}"
230 |
231 | params = {
232 | 'service' => 'alipay.commerce.qrcode.create',
233 | '_input_charset' => 'utf-8',
234 | 'partner' => options[:pid] || Alipay.pid,
235 | 'timestamp' => Time.now.utc.strftime('%Y-%m-%d %H:%M:%S').to_s,
236 | 'biz_data' => biz_data
237 | }.merge(params)
238 |
239 | request_uri(params, options).to_s
240 | end
241 |
242 | UPDATE_MERCHANT_QR_CODE_REQUIRED_PARAMS = %w( biz_type biz_data qr_code )
243 | def self.update_merchant_qr_code(params, options = {})
244 | params = Utils.stringify_keys(params)
245 | check_required_params(params, UPDATE_MERCHANT_QR_CODE_REQUIRED_PARAMS)
246 | biz_data = nil
247 |
248 | if params['biz_data']
249 | params['biz_data'] = Utils.stringify_keys(params['biz_data'])
250 |
251 | data = params.delete('biz_data')
252 | biz_data = data.map do |key, value|
253 | "\"#{key}\": \"#{value}\""
254 | end.join(',')
255 | end
256 |
257 | biz_data = "{#{biz_data}}"
258 |
259 | params = {
260 | 'service' => 'alipay.commerce.qrcode.modify',
261 | '_input_charset' => 'utf-8',
262 | 'partner' => options[:pid] || Alipay.pid,
263 | 'timestamp' => Time.now.utc.strftime('%Y-%m-%d %H:%M:%S').to_s,
264 | 'biz_data' => biz_data
265 | }.merge(params)
266 |
267 | request_uri(params, options).to_s
268 | end
269 |
270 | ACQUIRER_OVERSEAS_QUERY_REQUIRED_PARAMS = %w(partner_trans_id)
271 | def self.acquirer_overseas_query(params, options = {})
272 | params = Utils.stringify_keys(params)
273 | check_required_params(params, ACQUIRER_OVERSEAS_QUERY_REQUIRED_PARAMS)
274 |
275 | params = {
276 | 'service' => 'alipay.acquire.overseas.query',
277 | '_input_charset' => 'utf-8',
278 | 'partner' => options[:pid] || Alipay.pid,
279 | }.merge(params)
280 |
281 | request_uri(params, options).to_s
282 | end
283 |
284 | ACQUIRER_OVERSEAS_SPOT_REFUND_REQUIRED_PARAMS = %w( partner_trans_id partner_refund_id refund_amount currency )
285 | def self.acquirer_overseas_spot_refund_url(params, options= {})
286 | params = Utils.stringify_keys(params)
287 | check_required_params(params, ACQUIRER_OVERSEAS_SPOT_REFUND_REQUIRED_PARAMS)
288 |
289 | params = {
290 | 'service' => 'alipay.acquire.overseas.spot.refund',
291 | '_input_charset' => 'utf-8',
292 | 'partner' => options[:pid] || Alipay.pid,
293 | }.merge(params)
294 |
295 | request_uri(params, options).to_s
296 | end
297 |
298 | def self.request_uri(params, options = {})
299 | uri = URI(GATEWAY_URL)
300 | uri.query = URI.encode_www_form(sign_params(params, options))
301 | uri
302 | end
303 |
304 | def self.sign_params(params, options = {})
305 | params.merge(
306 | 'sign_type' => (options[:sign_type] || Alipay.sign_type),
307 | 'sign' => Alipay::Sign.generate(params, options)
308 | )
309 | end
310 |
311 | def self.check_required_params(params, names)
312 | return if !Alipay.debug_mode?
313 |
314 | names.each do |name|
315 | warn("Alipay Warn: missing required option: #{name}") unless params.has_key?(name)
316 | end
317 | end
318 |
319 | def self.check_optional_params(params, names)
320 | return if !Alipay.debug_mode?
321 |
322 | warn("Alipay Warn: must specify either #{names.join(' or ')}") if names.all? {|name| params[name].nil? }
323 | end
324 | end
325 | end
326 |
--------------------------------------------------------------------------------
/lib/alipay/sign.rb:
--------------------------------------------------------------------------------
1 | module Alipay
2 | module Sign
3 | def self.generate(params, options = {})
4 | params = Utils.stringify_keys(params)
5 | sign_type = options[:sign_type] || Alipay.sign_type
6 | key = options[:key] || Alipay.key
7 | string = params_to_string(params)
8 |
9 | case sign_type
10 | when 'MD5'
11 | MD5.sign(key, string)
12 | when 'RSA'
13 | RSA.sign(key, string)
14 | when 'DSA'
15 | DSA.sign(key, string)
16 | else
17 | raise ArgumentError, "invalid sign_type #{sign_type}, allow value: 'MD5', 'RSA', 'DSA'"
18 | end
19 | end
20 |
21 | ALIPAY_RSA_PUBLIC_KEY = <<-EOF
22 | -----BEGIN PUBLIC KEY-----
23 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnxj/9qwVfgoUh/y2W89L6BkRA
24 | FljhNhgPdyPuBV64bfQNN1PjbCzkIM6qRdKBoLPXmKKMiFYnkd6rAoprih3/PrQE
25 | B/VsW8OoM8fxn67UDYuyBTqA23MML9q1+ilIZwBC2AQ2UBVOrFXfFl75p6/B5Ksi
26 | NG9zpgmLCUYuLkxpLQIDAQAB
27 | -----END PUBLIC KEY-----
28 | EOF
29 |
30 | def self.verify?(params, options = {})
31 | params = Utils.stringify_keys(params)
32 |
33 | sign_type = params.delete('sign_type')
34 | sign = params.delete('sign')
35 | string = params_to_string(params)
36 |
37 | case sign_type
38 | when 'MD5'
39 | key = options[:key] || Alipay.key
40 | MD5.verify?(key, string, sign)
41 | when 'RSA'
42 | RSA.verify?(ALIPAY_RSA_PUBLIC_KEY, string, sign)
43 | when 'DSA'
44 | DSA.verify?(string, sign)
45 | else
46 | false
47 | end
48 | end
49 |
50 | def self.params_to_string(params)
51 | params.sort.map { |item| item.join('=') }.join('&')
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/lib/alipay/sign/dsa.rb:
--------------------------------------------------------------------------------
1 | module Alipay
2 | module Sign
3 | class DSA
4 | def self.sign(key, string)
5 | raise NotImplementedError, 'DSA sign is not implemented'
6 | end
7 |
8 | def self.verify?(string, sign)
9 | raise NotImplementedError, 'DSA verify is not implemented'
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/alipay/sign/md5.rb:
--------------------------------------------------------------------------------
1 | require 'digest/md5'
2 |
3 | module Alipay
4 | module Sign
5 | class MD5
6 | def self.sign(key, string)
7 | Digest::MD5.hexdigest("#{string}#{key}")
8 | end
9 |
10 | def self.verify?(key, string, sign)
11 | sign == sign(key, string)
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/alipay/sign/rsa.rb:
--------------------------------------------------------------------------------
1 | require 'openssl'
2 | require 'base64'
3 |
4 | module Alipay
5 | module Sign
6 | class RSA
7 | def self.sign(key, string)
8 | rsa = OpenSSL::PKey::RSA.new(key)
9 | Base64.strict_encode64(rsa.sign('sha1', string))
10 | end
11 |
12 | def self.verify?(key, string, sign)
13 | rsa = OpenSSL::PKey::RSA.new(key)
14 | rsa.verify('sha1', Base64.strict_decode64(sign), string)
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/alipay/sign/rsa2.rb:
--------------------------------------------------------------------------------
1 | require 'openssl'
2 | require 'base64'
3 |
4 | module Alipay
5 | module Sign
6 | class RSA2
7 | def self.sign(key, string)
8 | rsa = OpenSSL::PKey::RSA.new(key)
9 | Base64.strict_encode64(rsa.sign('sha256', string))
10 | end
11 |
12 | def self.verify?(key, string, sign)
13 | rsa = OpenSSL::PKey::RSA.new(key)
14 | rsa.verify('sha256', Base64.strict_decode64(sign), string)
15 | end
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/alipay/utils.rb:
--------------------------------------------------------------------------------
1 | module Alipay
2 | module Utils
3 | def self.stringify_keys(hash)
4 | new_hash = {}
5 | hash.each do |key, value|
6 | new_hash[(key.to_s rescue key) || key] = value
7 | end
8 | new_hash
9 | end
10 |
11 | # 退款批次号,支付宝通过此批次号来防止重复退款操作,所以此号生成后最好直接保存至数据库,不要在显示页面的时候生成
12 | # 共 24 位(8 位当前日期 + 9 位纳秒 + 1 位随机数)
13 | def self.generate_batch_no
14 | t = Time.now
15 | batch_no = t.strftime('%Y%m%d%H%M%S') + t.nsec.to_s
16 | batch_no.ljust(24, rand(10).to_s)
17 | end
18 |
19 | # get app_cert_sn
20 | def self.get_cert_sn(str, match_algo = false)
21 | return nil if str.nil?
22 | certificate = OpenSSL::X509::Certificate.new(str)
23 | if match_algo
24 | begin
25 | return unless certificate.public_key.is_a?(OpenSSL::PKey::RSA)
26 | rescue => exception
27 | return
28 | end
29 | end
30 | issuer_arr = OpenSSL::X509::Name.new(certificate.issuer).to_a
31 | issuer = issuer_arr.reverse.map { |item| item[0..1].join('=') }.join(',')
32 | serial = OpenSSL::BN.new(certificate.serial).to_s
33 | OpenSSL::Digest::MD5.hexdigest(issuer + serial)
34 | end
35 |
36 | # get alipay_root_cert_sn
37 | def self.get_root_cert_sn(str)
38 | return nil if str.nil?
39 | arr = str.scan(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/)
40 | arr_sn = []
41 | arr.each do |item|
42 | sn = get_cert_sn(item, true)
43 | unless sn.nil?
44 | arr_sn.push(sn)
45 | end
46 | end
47 | arr_sn.join('_')
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/alipay/version.rb:
--------------------------------------------------------------------------------
1 | module Alipay
2 | VERSION = "0.17.0"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/alipay/wap/notify.rb:
--------------------------------------------------------------------------------
1 | module Alipay
2 | module Wap
3 | module Notify
4 | def self.verify?(params, options = {})
5 | params = Utils.stringify_keys(params)
6 | pid = options[:pid] || Alipay.pid
7 | notify_id = params['notify_data'].scan(/\(.*)\<\/notify_id\>/).flatten.first
8 |
9 | Sign.verify?(params, options) && ::Alipay::Notify.verify_notify_id?(pid, notify_id)
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/alipay/wap/service.rb:
--------------------------------------------------------------------------------
1 | module Alipay
2 | module Wap
3 | module Service
4 | GATEWAY_URL = 'https://wappaygw.alipay.com/service/rest.htm'
5 |
6 | TRADE_CREATE_DIRECT_TOKEN_REQUIRED_PARAMS = %w( req_data )
7 | REQ_DATA_REQUIRED_PARAMS = %w( seller_account_name subject out_trade_no total_fee call_back_url )
8 | def self.trade_create_direct_token(params, options = {})
9 | params = Utils.stringify_keys(params)
10 | Alipay::Service.check_required_params(params, TRADE_CREATE_DIRECT_TOKEN_REQUIRED_PARAMS)
11 |
12 | req_data = Utils.stringify_keys(params.delete('req_data'))
13 | Alipay::Service.check_required_params(req_data, REQ_DATA_REQUIRED_PARAMS)
14 |
15 | xml = req_data.map {|k, v| "<#{k}>#{v.encode(:xml => :text)}#{k}>" }.join
16 | req_data_xml = "#{xml}"
17 |
18 | # About req_id: http://club.alipay.com/read-htm-tid-10078020-fpage-2.html
19 | params = {
20 | 'service' => 'alipay.wap.trade.create.direct',
21 | 'req_data' => req_data_xml,
22 | 'partner' => options[:pid] || Alipay.pid,
23 | 'req_id' => Time.now.strftime('%Y%m%d%H%M%s'),
24 | 'format' => 'xml',
25 | 'v' => '2.0'
26 | }.merge(params)
27 |
28 | xml = Net::HTTP.get(request_uri(params, options))
29 | CGI.unescape(xml).scan(/\(.*)\<\/request_token\>/).flatten.first
30 | end
31 |
32 | AUTH_AND_EXECUTE_REQUIRED_PARAMS = %w( request_token )
33 | def self.auth_and_execute_url(params, options = {})
34 | params = Utils.stringify_keys(params)
35 | Alipay::Service.check_required_params(params, AUTH_AND_EXECUTE_REQUIRED_PARAMS)
36 |
37 | req_data_xml = "#{params.delete('request_token')}"
38 |
39 | params = {
40 | 'service' => 'alipay.wap.auth.authAndExecute',
41 | 'req_data' => req_data_xml,
42 | 'partner' => options[:pid] || Alipay.pid,
43 | 'format' => 'xml',
44 | 'v' => '2.0'
45 | }.merge(params)
46 |
47 | request_uri(params, options).to_s
48 | end
49 |
50 | def self.security_risk_detect(params, options)
51 | params = Utils.stringify_keys(params)
52 |
53 | params = {
54 | 'service' => 'alipay.security.risk.detect',
55 | '_input_charset' => 'utf-8',
56 | 'partner' => options[:pid] || Alipay.pid,
57 | 'timestamp' => Time.now.strftime('%Y-%m-%d %H:%M:%S'),
58 | 'scene_code' => 'PAYMENT'
59 | }.merge(params)
60 |
61 | sign_params(params, options)
62 |
63 | Net::HTTP.post_form(URI(GATEWAY_URL), params)
64 | end
65 |
66 | def self.request_uri(params, options = {})
67 | uri = URI(GATEWAY_URL)
68 | uri.query = URI.encode_www_form(sign_params(params, options))
69 | uri
70 | end
71 |
72 | SIGN_TYPE_TO_SEC_ID = {
73 | 'MD5' => 'MD5',
74 | 'RSA' => '0001'
75 | }
76 |
77 | def self.sign_params(params, options = {})
78 | sign_type = (options[:sign_type] ||= Alipay.sign_type)
79 | params = params.merge('sec_id' => SIGN_TYPE_TO_SEC_ID[sign_type])
80 | params.merge('sign' => Alipay::Sign.generate(params, options))
81 | end
82 | end
83 | end
84 | end
85 |
--------------------------------------------------------------------------------
/lib/alipay/wap/sign.rb:
--------------------------------------------------------------------------------
1 | module Alipay
2 | module Wap
3 | module Sign
4 | ALIPAY_RSA_PUBLIC_KEY = <<-EOF
5 | -----BEGIN PUBLIC KEY-----
6 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCQwpCPC4oB+clYNBkKQx3gfyFl
7 | Ut3cpRr5oErt OypLKh6j1UmTDSpfsac29h1kC0HIvLmxWbPuoxcsKDlclgRPeWn
8 | IxrpSF9k5Fu6SRy3+AOdIKrDO SHQ7VwUsNih2OnPbztMSMplGnQCBa1iec2r+38
9 | Udmh5Ua2xg6IEfk493VQIDAQAB
10 | -----END PUBLIC KEY-----
11 | EOF
12 |
13 | def self.verify?(params, options = {})
14 | params = Utils.stringify_keys(params)
15 | sign = params.delete('sign')
16 | string = params_to_string(params)
17 |
18 | case params['sec_id']
19 | when 'MD5'
20 | key = options[:key] || Alipay.key
21 | ::Alipay::Sign::MD5.verify?(key, string, sign)
22 | when '0001' # RSA
23 | ::Alipay::Sign::RSA.verify?(ALIPAY_RSA_PUBLIC_KEY, string, sign)
24 | else
25 | false
26 | end
27 | end
28 |
29 | SORTED_VERIFY_PARAMS = %w( service v sec_id notify_data )
30 | def self.params_to_string(params)
31 | SORTED_VERIFY_PARAMS.map do |key|
32 | "#{key}=#{params[key]}"
33 | end.join('&')
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/test/alipay/client_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::ClientTest < Minitest::Test
4 | def setup
5 | @client = Alipay::Client.new(
6 | url: 'https://openapi.alipaydev.com/gateway.do',
7 | app_id: '2016000000000000',
8 | app_private_key: TEST_RSA_PRIVATE_KEY,
9 | format: 'json',
10 | charset: 'UTF-8',
11 | alipay_public_key: TEST_RSA_PUBLIC_KEY,
12 | sign_type: 'RSA2',
13 | app_cert_sn: '28d1147972121b91734da59aa10f3c16',
14 | alipay_root_cert_sn: '28d1147972121b91734da59aa10f3c16_28d1147972121b91734da59aa10f3c16'
15 | )
16 | end
17 |
18 | def test_client_initialize
19 | refute_nil @client
20 | end
21 |
22 | def test_sdk_execute_for_alipay_trade_app_pay
23 | string = 'app_id=2016000000000000&charset=UTF-8&sign_type=RSA2&version=1.0×tamp=2016-04-01+00%3A00%3A00&method=alipay.trade.page.pay&biz_content=%7B%22out_trade_no%22%3A%2220160401000000%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%2C%22total_amount%22%3A%220.01%22%2C%22subject%22%3A%22test%22%7D&app_cert_sn=28d1147972121b91734da59aa10f3c16&alipay_root_cert_sn=28d1147972121b91734da59aa10f3c16_28d1147972121b91734da59aa10f3c16&sign=Zr2UkxZcW69WB8bNsERgJrd73zBdiOeb08CmoI946Fiuh2dIYZKW8MqYEa46nPbqPL4Q0gv3djc1IWFMgY9J4Ku8TltV5NRxrIrtfRxVZa59Sg%2BlAI7EiHENqJfXpZptn%2BFa7SaV4ZzLsMWaNC2RceF2unPH17%2FZr1ZpkzUFYlE%3D'
24 |
25 | assert_equal string, @client.sdk_execute(
26 | method: 'alipay.trade.page.pay',
27 | biz_content: {
28 | out_trade_no: '20160401000000',
29 | product_code: 'QUICK_MSECURITY_PAY',
30 | total_amount: '0.01',
31 | subject: 'test'
32 | }.to_json(ascii_only: true),
33 | timestamp: '2016-04-01 00:00:00'
34 | )
35 | end
36 |
37 | def test_page_execute_url_for_alipay_trade_page_pay
38 | url = 'https://openapi.alipaydev.com/gateway.do?app_id=2016000000000000&charset=UTF-8&sign_type=RSA2&version=1.0×tamp=2016-04-01+00%3A00%3A00&method=alipay.trade.page.pay&biz_content=%7B%22out_trade_no%22%3A%2220160401000000%22%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%2C%22total_amount%22%3A%220.01%22%2C%22subject%22%3A%22test%22%7D&app_cert_sn=28d1147972121b91734da59aa10f3c16&alipay_root_cert_sn=28d1147972121b91734da59aa10f3c16_28d1147972121b91734da59aa10f3c16&sign=SEOXB35dlg5c4P1dwFnZDuvI%2FAsjMx6rfC9Y2F83lLx86GK1C1bQ05HR%2Fw%2FQhAbnXVIKt1%2Fh9IrDx6q8mlalQbWiNMrWHDZHm5%2FMFYln29LDNxtOT9T3s4bSvzzwfjzmnock68H7dW%2BJm%2Bbf5q6KFzen6iiR3%2Fy9BPwgldJhRh0%3D'
39 |
40 | assert_equal url, @client.page_execute_url(
41 | method: 'alipay.trade.page.pay',
42 | biz_content: {
43 | out_trade_no: '20160401000000',
44 | product_code: 'FAST_INSTANT_TRADE_PAY',
45 | total_amount: '0.01',
46 | subject: 'test'
47 | }.to_json(ascii_only: true),
48 | timestamp: '2016-04-01 00:00:00'
49 | )
50 | end
51 |
52 | def test_page_execute_form_for_alipay_trade_page_pay
53 | form = ""
54 |
55 | assert_equal form, @client.page_execute_form(
56 | method: 'alipay.trade.page.pay',
57 | biz_content: {
58 | out_trade_no: '20160401000000',
59 | product_code: 'FAST_INSTANT_TRADE_PAY',
60 | total_amount: '0.01',
61 | subject: 'test'
62 | }.to_json(ascii_only: true),
63 | timestamp: '2016-04-01 00:00:00'
64 | )
65 | end
66 |
67 | def test_execute_for_data_dataservice_bill_downloadurl_query
68 | body = <<-EOF
69 | {
70 | "sign":"ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE",
71 | "alipay_data_dataservice_bill_downloadurl_query_response":{
72 | "code":"10000",
73 | "bill_download_url":"http://dwbillcenter.alipay.com/downloadBillFile.resource?bizType=X&userId=X&fileType=X&bizDates=X&downloadFileName=X&fileId=X",
74 | "msg":"Success"
75 | }
76 | }
77 | EOF
78 | stub_request(:post, "https://openapi.alipaydev.com/gateway.do").
79 | to_return(body: body)
80 |
81 | assert_equal body, @client.execute(
82 | method: 'alipay.data.dataservice.bill.downloadurl.query',
83 | biz_content: {
84 | bill_type: 'trade',
85 | bill_date: '2016-04-01'
86 | }.to_json(ascii_only: true)
87 | )
88 | end
89 |
90 | # Use pair rsa key so we can test it
91 | def test_verify
92 | params = {
93 | out_trade_no: '20160401000000',
94 | trade_status: 'TRADE_SUCCESS'
95 | }
96 | params[:sign] = @client.sign(params)
97 | params[:sign_type] = 'RSA2'
98 | assert @client.verify?(params)
99 | end
100 |
101 | def test_verify_when_wrong
102 | params = {
103 | out_trade_no: '20160401000000',
104 | trade_status: 'TRADE_SUCCESS',
105 | sign_type: 'RSA2',
106 | sign: Base64.strict_encode64('WrongSign')
107 | }
108 | assert !@client.verify?(params)
109 | end
110 | end
111 |
--------------------------------------------------------------------------------
/test/alipay/mobile/service_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::Mobile::ServiceTest < Minitest::Test
4 | def test_mobile_securitypay_pay_string
5 | assert_equal %q(service="mobile.securitypay.pay"&_input_charset="utf-8"&partner="1000000000000000"&seller_id="1000000000000000"&payment_type="1"&out_trade_no="1"¬ify_url="/some_url"&subject="subject"&total_fee="0.01"&body="test"&sign="if1qjK4qnT7eQ5fw%2BBddHhO1LY6iuPY9Xmhkx81YKuCdceKWBdv798j%2BrxF9ZAhNW4Y3TMURm%2BXpgxhOh8lj8vorFup%2BMJ6fe2rXRWgxFhK9B8xuP2XH%2F3878g6d8Jq2D2gINTgDDL7%2BB5%2FIWWlwInas40cQTsVngG8mWkzB788%3D"&sign_type="RSA"), Alipay::Mobile::Service.mobile_securitypay_pay_string({
6 | out_trade_no: '1',
7 | notify_url: '/some_url',
8 | subject: 'subject',
9 | total_fee: '0.01',
10 | body: 'test'
11 | }, {
12 | sign_type: 'RSA',
13 | key: TEST_RSA_PRIVATE_KEY
14 | })
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/test/alipay/mobile/sign_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::Mobile::SignTest < Minitest::Test
4 | def test_params_to_string
5 | assert_equal %q(a="1"&b="2"), Alipay::Mobile::Sign.params_to_string(a: 1, b: 2)
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/alipay/notify_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::NotifyTest < Minitest::Test
4 | def setup
5 | @params = {
6 | notify_id: '1234',
7 | }
8 | @unsign_params = @params.merge(sign_type: 'MD5', sign: 'xxxx')
9 | @sign_params = @params.merge(
10 | sign_type: 'MD5',
11 | sign: '22fc7e38e5acdfede396aa463870d111'
12 | )
13 | end
14 |
15 | def test_unsign_notify
16 | stub_request(
17 | :get, "https://mapi.alipay.com/gateway.do?service=notify_verify&partner=#{Alipay.pid}¬ify_id=1234"
18 | ).to_return(body: "true")
19 | assert !Alipay::Notify.verify?(@unsign_params)
20 | end
21 |
22 | def test_verify_notify_when_true
23 | stub_request(
24 | :get, "https://mapi.alipay.com/gateway.do?service=notify_verify&partner=#{Alipay.pid}¬ify_id=1234"
25 | ).to_return(body: "true")
26 | assert Alipay::Notify.verify?(@sign_params)
27 | end
28 |
29 | def test_verify_notify_when_false
30 | stub_request(
31 | :get, "https://mapi.alipay.com/gateway.do?service=notify_verify&partner=#{Alipay.pid}¬ify_id=1234"
32 | ).to_return(body: "false")
33 | assert !Alipay::Notify.verify?(@sign_params)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/test/alipay/service_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::ServiceTest < Minitest::Test
4 | def test_generate_create_partner_trade_by_buyer_url
5 | options = {
6 | out_trade_no: '1',
7 | subject: 'test',
8 | logistics_type: 'POST',
9 | logistics_fee: '0',
10 | logistics_payment: 'SELLER_PAY',
11 | price: '0.01',
12 | quantity: 1
13 | }
14 |
15 | assert_equal 'https://mapi.alipay.com/gateway.do?service=create_partner_trade_by_buyer&_input_charset=utf-8&partner=1000000000000000&seller_id=1000000000000000&payment_type=1&out_trade_no=1&subject=test&logistics_type=POST&logistics_fee=0&logistics_payment=SELLER_PAY&price=0.01&quantity=1&sign_type=MD5&sign=b5d30863b44acd8514a49b0320fb2aa2', Alipay::Service.create_partner_trade_by_buyer_url(options)
16 | end
17 |
18 | def test_generate_trade_create_by_buyer_url
19 | options = {
20 | out_trade_no: '1',
21 | subject: 'test',
22 | logistics_type: 'POST',
23 | logistics_fee: '0',
24 | logistics_payment: 'SELLER_PAY',
25 | price: '0.01',
26 | quantity: 1
27 | }
28 | assert_equal 'https://mapi.alipay.com/gateway.do?service=trade_create_by_buyer&_input_charset=utf-8&partner=1000000000000000&seller_id=1000000000000000&payment_type=1&out_trade_no=1&subject=test&logistics_type=POST&logistics_fee=0&logistics_payment=SELLER_PAY&price=0.01&quantity=1&sign_type=MD5&sign=2d296368fea70a127da939558c970bab', Alipay::Service.trade_create_by_buyer_url(options)
29 | end
30 |
31 | def test_generate_create_direct_pay_by_user_url
32 | options = {
33 | out_trade_no: '1',
34 | subject: 'test',
35 | price: '0.01',
36 | quantity: 1
37 | }
38 | assert_equal 'https://mapi.alipay.com/gateway.do?service=create_direct_pay_by_user&_input_charset=utf-8&partner=1000000000000000&seller_id=1000000000000000&payment_type=1&out_trade_no=1&subject=test&price=0.01&quantity=1&sign_type=MD5&sign=682ad02280fca7d4c0fd22678fdddeef', Alipay::Service.create_direct_pay_by_user_url(options)
39 | end
40 |
41 | def test_generate_create_direct_pay_by_user_wap_url
42 | options = {
43 | out_trade_no: '1',
44 | subject: 'test',
45 | total_fee: '0.01'
46 | }
47 | assert_equal 'https://mapi.alipay.com/gateway.do?service=alipay.wap.create.direct.pay.by.user&_input_charset=utf-8&partner=1000000000000000&seller_id=1000000000000000&payment_type=1&out_trade_no=1&subject=test&total_fee=0.01&sign_type=MD5&sign=6530de6e3cba153cd4ca7edc48b91f96', Alipay::Service.create_direct_pay_by_user_wap_url(options)
48 | end
49 |
50 | def test_refund_fastpay_by_platform_pwd_url
51 | data = [{
52 | trade_no: '1',
53 | amount: '0.01',
54 | reason: 'test'
55 | }]
56 | options = {
57 | batch_no: '123456789',
58 | data: data,
59 | notify_url: '/some_url',
60 | refund_date: '2015-01-01 00:00:00'
61 | }
62 | assert_equal 'https://mapi.alipay.com/gateway.do?service=refund_fastpay_by_platform_pwd&_input_charset=utf-8&partner=1000000000000000&seller_user_id=1000000000000000&refund_date=2015-01-01+00%3A00%3A00&batch_num=1&detail_data=1%5E0.01%5Etest&batch_no=123456789¬ify_url=%2Fsome_url&sign_type=MD5&sign=def57a58e1ac21f70c45e41bd3697368', Alipay::Service.refund_fastpay_by_platform_pwd_url(options)
63 | end
64 |
65 | def test_forex_refund_url
66 | options = {
67 | out_return_no: '1',
68 | out_trade_no: '12345678980',
69 | return_amount: '0.01',
70 | currency: 'USD',
71 | reason: 'reason',
72 | gmt_return: '20150101000000'
73 | }
74 | assert_equal 'https://mapi.alipay.com/gateway.do?service=forex_refund&partner=1000000000000000&_input_charset=utf-8&gmt_return=20150101000000&out_return_no=1&out_trade_no=12345678980&return_amount=0.01¤cy=USD&reason=reason&sign_type=MD5&sign=c9681fff5505fe993d1b2b8141308d0d', Alipay::Service.forex_refund_url(options)
75 | end
76 |
77 | def test_generate_create_forex_trade_url
78 | options = {
79 | notify_url: 'https://example.com/notify',
80 | subject: 'test',
81 | out_trade_no: '1',
82 | currency: 'EUR',
83 | total_fee: '0.01',
84 | }
85 | assert_equal 'https://mapi.alipay.com/gateway.do?service=create_forex_trade&_input_charset=utf-8&partner=1000000000000000¬ify_url=https%3A%2F%2Fexample.com%2Fnotify&subject=test&out_trade_no=1¤cy=EUR&total_fee=0.01&sign_type=MD5&sign=f24fd4d76acabf860263a40805138380', Alipay::Service.create_forex_trade_url(options)
86 | end
87 |
88 | def test_close_trade
89 | response_body = <<-EOF
90 |
91 |
92 | T
93 |
94 | EOF
95 | stub_request(
96 | :get,
97 | %r|https://mapi\.alipay\.com/gateway\.do.*|
98 | ).to_return(body: response_body)
99 |
100 | assert_equal response_body, Alipay::Service.close_trade(
101 | out_order_no: '1'
102 | )
103 | end
104 |
105 | def test_single_trade_query
106 | response_body = <<-EOF
107 |
108 |
109 | T
110 |
111 | 20150123123123
112 | utf-8
113 | single_trade_query
114 | PARTNER
115 |
116 |
117 |
118 | DAEMON_CONFIRM_CLOSE
119 | buyer@example.com
120 | BUYER_ID
121 | 0.00
122 | 0
123 | 2015-01-20 02:37:00
124 | 2015-01-20 02:17:00
125 | 2015-01-20 02:37:00
126 | F
127 | B
128 | 1
129 | 1
130 | 640.00
131 | 1
132 | seller@example.com
133 | SELLER_ID
134 | ORDER SUBJECT
135 | 0.00
136 | 0.00
137 | 640.00
138 | TRADE_NO
139 | TRADE_CLOSED
140 | F
141 |
142 | SIGN
143 | MD5
144 |
145 | EOF
146 | stub_request(
147 | :get,
148 | %r|https://mapi\.alipay\.com/gateway\.do.*|
149 | ).to_return(body: response_body)
150 |
151 | assert_equal response_body, Alipay::Service.single_trade_query(
152 | out_trade_no: '1'
153 | )
154 | end
155 |
156 | def test_should_send_goods_confirm_by_platform
157 | body = <<-EOF
158 |
159 |
160 | T
161 |
162 | EOF
163 | stub_request(
164 | :get,
165 | %r|https://mapi\.alipay\.com/gateway\.do.*|
166 | ).to_return(body: body)
167 |
168 | assert_equal body, Alipay::Service.send_goods_confirm_by_platform(
169 | trade_no: 'trade_no',
170 | logistics_name: 'example.com',
171 | transport_type: 'DIRECT'
172 | )
173 | end
174 |
175 | def test_account_page_query
176 | body = <<-EOF
177 |
178 |
179 | T
180 |
181 | sign_data
182 | utf-8
183 | 2015-10-26 06:20:29
184 | MD5
185 | account.page.query
186 | 2088123123
187 | 1
188 | 2015-10-25 06:20:29
189 |
190 |
191 |
192 |
193 |
194 | 1234
195 | 2088123123
196 | 123
197 | 20151025123123
198 | 商品名称
199 | 100.00
200 | 12345678910
201 |
202 | 1234567
203 | 0.00
204 | 2088123123
205 | 0.015
206 | 2088123123123
207 | xxxx有限公司
208 | 0.00
209 |
210 | 快捷手机安全支付
211 | 快速支付,支付给个人,支付宝帐户全额
212 | 100.00
213 | 20151025123123
214 | 0.00
215 | 在线支付
216 | 2015-10-25 06:33:07
217 |
218 |
219 | F
220 | 1
221 | 5000
222 |
223 |
224 | sign_data
225 | MD5
226 |
227 | EOF
228 | stub_request(
229 | :get, %r|https://mapi\.alipay\.com/gateway\.do.*|
230 | ).to_return(:body => body)
231 | assert_equal body, Alipay::Service.account_page_query(
232 | page_no: 1,
233 | gmt_start_time: (Time.now - 1).strftime('%Y-%m-%d %H:%M:%S'),
234 | gmt_end_time: Time.now.strftime('%Y-%m-%d %H:%M:%S')
235 | )
236 | end
237 |
238 | def test_create_forex_trade_wap_url
239 | options = {
240 | out_trade_no: '20150401000-0001',
241 | subject: 'Order Name',
242 | merchant_url: 'http://example.com/itemback',
243 | total_fee: '10.00', #or rmb_fee, only one
244 | currency: 'USD',
245 | return_url: 'https://example.com/orders/20150401000-0001',
246 | notify_url: 'https://example.com/orders/20150401000-0001/notify'
247 | }
248 | assert_equal 'https://mapi.alipay.com/gateway.do?service=create_forex_trade_wap&_input_charset=utf-8&partner=1000000000000000&seller_id=1000000000000000&out_trade_no=20150401000-0001&subject=Order+Name&merchant_url=http%3A%2F%2Fexample.com%2Fitemback&total_fee=10.00¤cy=USD&return_url=https%3A%2F%2Fexample.com%2Forders%2F20150401000-0001¬ify_url=https%3A%2F%2Fexample.com%2Forders%2F20150401000-0001%2Fnotify&sign_type=MD5&sign=f15d9e3d885c12f1a994048342c07bef', Alipay::Service.create_forex_trade_wap_url(options)
249 | end
250 |
251 | def test_batch_trans_notify_url
252 | options = {
253 | notify_url: 'https://example.com/orders/20150401000-0001/notify',
254 | account_name: '毛毛',
255 | detail_data: '0315006^testture0002@126.com^常炜买家^1000.00^hello',
256 | batch_no: '20080107001',
257 | batch_num: 1,
258 | batch_fee: 1000.00,
259 | email: 'biz_932@alitest.com',
260 | pay_date: '20080107'
261 | }
262 |
263 | assert_equal 'https://mapi.alipay.com/gateway.do?service=batch_trans_notify&_input_charset=utf-8&partner=1000000000000000&pay_date=20080107¬ify_url=https%3A%2F%2Fexample.com%2Forders%2F20150401000-0001%2Fnotify&account_name=%E6%AF%9B%E6%AF%9B&detail_data=0315006%5Etestture0002%40126.com%5E%E5%B8%B8%E7%82%9C%E4%B9%B0%E5%AE%B6%5E1000.00%5Ehello&batch_no=20080107001&batch_num=1&batch_fee=1000.0&email=biz_932%40alitest.com&sign_type=MD5&sign=59c611607daafd1337e96b22404bd521', Alipay::Service.batch_trans_notify_url(options)
264 | end
265 |
266 | def test_create_merchant_qr_code
267 | params = {
268 | biz_type: "OVERSEASHOPQRCODE",
269 | biz_data: {
270 | address: "No.278, Road YinCheng, Shanghai, China",
271 | country_code: "CN",
272 | currency: "USD",
273 | secondary_merchant_id: "xxx001",
274 | secondary_merchant_industry: "7011",
275 | secondary_merchant_name: "xxx Store",
276 | store_id: "0001",
277 | store_name: "Apple store",
278 | trans_currency: "USD"
279 | }
280 | }
281 |
282 | current_time = Time.utc(2023, 12, 12, 1, 1, 1)
283 | Time.stub :now, current_time do
284 | assert_equal 'https://mapi.alipay.com/gateway.do?service=alipay.commerce.qrcode.create&_input_charset=utf-8&partner=1000000000000000×tamp=2023-12-12+01%3A01%3A01&biz_data=%7B%22address%22%3A+%22No.278%2C+Road+YinCheng%2C+Shanghai%2C+China%22%2C%22country_code%22%3A+%22CN%22%2C%22currency%22%3A+%22USD%22%2C%22secondary_merchant_id%22%3A+%22xxx001%22%2C%22secondary_merchant_industry%22%3A+%227011%22%2C%22secondary_merchant_name%22%3A+%22xxx+Store%22%2C%22store_id%22%3A+%220001%22%2C%22store_name%22%3A+%22Apple+store%22%2C%22trans_currency%22%3A+%22USD%22%7D&biz_type=OVERSEASHOPQRCODE&sign_type=MD5&sign=1c8881161f5895d0e7bbc8f4f5cad06c', Alipay::Service.create_merchant_qr_code(params)
285 | end
286 | end
287 |
288 | def test_update_merchant_qr_code
289 | params = {
290 | biz_type: "OVERSEASHOPQRCODE",
291 | qr_code: "https://qr.alipay.com/baxxxxx",
292 | biz_data: {
293 | address: "No.278, Road YinCheng, Shanghai, China",
294 | country_code: "CN",
295 | currency: "USD",
296 | secondary_merchant_id: "xxx001",
297 | secondary_merchant_industry: "7011",
298 | secondary_merchant_name: "xxx Store",
299 | store_id: "0001",
300 | store_name: "Apple store",
301 | trans_currency: "USD"
302 | }
303 | }
304 |
305 | current_time = Time.utc(2023, 12, 12, 1, 1, 1)
306 | Time.stub :now, current_time do
307 | assert_equal 'https://mapi.alipay.com/gateway.do?service=alipay.commerce.qrcode.modify&_input_charset=utf-8&partner=1000000000000000×tamp=2023-12-12+01%3A01%3A01&biz_data=%7B%22address%22%3A+%22No.278%2C+Road+YinCheng%2C+Shanghai%2C+China%22%2C%22country_code%22%3A+%22CN%22%2C%22currency%22%3A+%22USD%22%2C%22secondary_merchant_id%22%3A+%22xxx001%22%2C%22secondary_merchant_industry%22%3A+%227011%22%2C%22secondary_merchant_name%22%3A+%22xxx+Store%22%2C%22store_id%22%3A+%220001%22%2C%22store_name%22%3A+%22Apple+store%22%2C%22trans_currency%22%3A+%22USD%22%7D&biz_type=OVERSEASHOPQRCODE&qr_code=https%3A%2F%2Fqr.alipay.com%2Fbaxxxxx&sign_type=MD5&sign=4d43f06b3d60cfc96750876287ef66ca', Alipay::Service.update_merchant_qr_code(params)
308 | end
309 | end
310 |
311 | def test_acquirer_overseas_query
312 | params = {
313 | partner_trans_id: "2010121000000002"
314 | }
315 |
316 | assert_equal 'https://mapi.alipay.com/gateway.do?service=alipay.acquire.overseas.query&_input_charset=utf-8&partner=1000000000000000&partner_trans_id=2010121000000002&sign_type=MD5&sign=2a7f598bbb13d02f7de819ae689f80ba', Alipay::Service.acquirer_overseas_query(params)
317 | end
318 |
319 | def test_acquirer_overseas_spot_refund_url
320 | params = {
321 | partner_trans_id: "2010121000000002",
322 | partner_refund_id: "301012133000002",
323 | currency: "USD",
324 | refund_amount: "0.01",
325 | is_sync: "Y"
326 | }
327 |
328 | assert_equal 'https://mapi.alipay.com/gateway.do?service=alipay.acquire.overseas.spot.refund&_input_charset=utf-8&partner=1000000000000000&partner_trans_id=2010121000000002&partner_refund_id=301012133000002¤cy=USD&refund_amount=0.01&is_sync=Y&sign_type=MD5&sign=397685a0c6b2d71d0d1f374ddba331a0', Alipay::Service.acquirer_overseas_spot_refund_url(params)
329 | end
330 | end
331 |
--------------------------------------------------------------------------------
/test/alipay/sign/md5_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::Sign::MD5Test < Minitest::Test
4 | def setup
5 | @string = "partner=123&service=test"
6 | @sign = 'bbd13b52823b576291595f472ebcfbc2'
7 | end
8 |
9 | def test_sign
10 | assert_equal @sign, Alipay::Sign::MD5.sign(Alipay.key, @string)
11 | end
12 |
13 | def test_verify
14 | assert Alipay::Sign::MD5.verify?(Alipay.key, @string, @sign)
15 | end
16 |
17 | def test_verify_fail_when_sign_not_true
18 | assert !Alipay::Sign::MD5.verify?(Alipay.key, "danger#{@string}", @sign)
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/test/alipay/sign/rsa2_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::Sign::RSA2Test < Minitest::Test
4 | def setup
5 | @string = "partner=123&service=test"
6 | @rsa2_pkey = OpenSSL::PKey::RSA.new(2048)
7 | @rsa2_public_key = @rsa2_pkey.public_key.export
8 | @sign = Base64.strict_encode64(@rsa2_pkey.sign('sha256', @string))
9 | end
10 |
11 | def test_sign
12 | assert_equal @sign, Alipay::Sign::RSA2.sign(@rsa2_pkey, @string)
13 | end
14 |
15 | def test_verify
16 | assert Alipay::Sign::RSA2.verify?(@rsa2_public_key, @string, @sign)
17 | end
18 |
19 | def test_verify_fail_when_sign_not_true
20 | assert !Alipay::Sign::RSA2.verify?(@rsa2_public_key, "danger#{@string}", @sign)
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/test/alipay/sign/rsa_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::Sign::RSATest < Minitest::Test
4 | def setup
5 | @string = "partner=123&service=test"
6 | @sign = "TaVXdP/0ia5NxIv9T76v6vGOrtgoaFrwnchKIWP9PQeX1UkUVxaq6ejDFmXFrFR+Plk+E/XzfV2DYJSVt0Am0qJRSgeg+PuvK+yWGCGm9GJgUJlS4Eyta3g+8DWwRWTjUyh5yzlf9JoSnbNjYpBolnMRD7B/u1JLkTMJuMx2TVM="
7 | end
8 |
9 | def test_sign
10 | assert_equal @sign, Alipay::Sign::RSA.sign(TEST_RSA_PRIVATE_KEY, @string)
11 | end
12 |
13 | def test_verify
14 | assert Alipay::Sign::RSA.verify?(TEST_RSA_PUBLIC_KEY, @string, @sign)
15 | end
16 |
17 | def test_verify_fail_when_sign_not_true
18 | assert !Alipay::Sign::RSA.verify?(TEST_RSA_PUBLIC_KEY, "danger#{@string}", @sign)
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/test/alipay/sign_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::SignTest < Minitest::Test
4 | def setup
5 | @params = {
6 | service: 'test',
7 | partner: '123'
8 | }
9 | @md5_sign = 'bbd13b52823b576291595f472ebcfbc2'
10 |
11 | @key_2 = '20000000000000000000000000000000'
12 | @md5_sign_2 = '6d581af270c023fdaaca6880491e9bf7'
13 | end
14 |
15 | def test_generate_sign
16 | assert_equal @md5_sign, Alipay::Sign.generate(@params)
17 | assert_equal @md5_sign_2, Alipay::Sign.generate(@params, {key: @key_2})
18 | end
19 |
20 | def test_verify_sign
21 | assert Alipay::Sign.verify?(@params.merge(sign_type: 'MD5', sign: @md5_sign))
22 | assert Alipay::Sign.verify?(@params.merge(sign_type: 'MD5', sign: @md5_sign_2), {key: @key_2})
23 | end
24 |
25 | def test_verify_fail_when_sign_not_true
26 | assert !Alipay::Sign.verify?(@params)
27 | assert !Alipay::Sign.verify?(@params.merge(danger: 'danger', sign_type: 'MD5', sign: @md5_sign))
28 | assert !Alipay::Sign.verify?(@params.merge(sign_type: 'MD5', sign: 'danger'))
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/test/alipay/utils_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::UtilsTest < Minitest::Test
4 | def test_stringify_keys
5 | hash = { 'a' => 1, :b => 2 }
6 | assert_equal({ 'a' => 1, 'b' => 2 }.sort, Alipay::Utils.stringify_keys(hash).sort)
7 | end
8 |
9 | def test_generate_batch_no
10 | assert_equal(24, Alipay::Utils.generate_batch_no.size)
11 | end
12 |
13 | def test_get_cert_sn
14 | assert_equal('28d1147972121b91734da59aa10f3c16', Alipay::Utils.get_cert_sn(TEST_APP_CERT))
15 | end
16 |
17 | def test_get_root_cert_sn
18 | assert_equal('28d1147972121b91734da59aa10f3c16_28d1147972121b91734da59aa10f3c16', Alipay::Utils.get_root_cert_sn(TEST_ALIPAY_ROOT_CERT))
19 | end
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/test/alipay/wap/notify_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::Wap::NotifyTest < Minitest::Test
4 | def setup
5 | @notify_id = 'notify_id_test'
6 |
7 | @notify_params = {
8 | service: 'alipay.wap.trade.create.direct',
9 | v: '1.0',
10 | sec_id: 'MD5',
11 | notify_data: "#{@notify_id}other_value"
12 | }
13 |
14 | query = [ :service, :v, :sec_id, :notify_data ].map {|key| "#{key}=#{@notify_params[key]}"}.join('&')
15 | @sign_params = @notify_params.merge(sign: Digest::MD5.hexdigest("#{query}#{Alipay.key}"))
16 | end
17 |
18 | def test_unsign_notify
19 | stub_request(
20 | :get, "https://mapi.alipay.com/gateway.do?service=notify_verify&partner=#{Alipay.pid}¬ify_id=#{@notify_id}"
21 | ).to_return(body: "true")
22 | assert !Alipay::Wap::Notify.verify?(@notify_params)
23 | end
24 |
25 | def test_verify_notify_when_true
26 | stub_request(
27 | :get, "https://mapi.alipay.com/gateway.do?service=notify_verify&partner=#{Alipay.pid}¬ify_id=#{@notify_id}"
28 | ).to_return(body: "true")
29 | assert Alipay::Wap::Notify.verify?(@sign_params)
30 | end
31 |
32 | def test_verify_notify_when_false
33 | stub_request(
34 | :get, "https://mapi.alipay.com/gateway.do?service=notify_verify&partner=#{Alipay.pid}¬ify_id=#{@notify_id}"
35 | ).to_return(body: "false")
36 | assert !Alipay::Wap::Notify.verify?(@sign_params)
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/test/alipay/wap/service_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::Wap::ServiceTest < Minitest::Test
4 | def test_trade_create_direct_token
5 | token = 'REQUEST_TOKEN'
6 | body = <<-EOS
7 | res_data=
8 |
9 |
10 | #{token}
11 |
12 | &partner=PID
13 | &req_id=REQ_ID
14 | &sec_id=MD5
15 | &service=alipay.wap.trade.create.direct
16 | &v=2.0
17 | &sign=SIGN
18 | EOS
19 |
20 | stub_request(
21 | :get,
22 | %r|https://wappaygw\.alipay\.com/service/rest\.htm.*|
23 | ).to_return(body: body)
24 |
25 | assert_equal token, Alipay::Wap::Service.trade_create_direct_token(
26 | req_data: {
27 | seller_account_name: 'account@example.com',
28 | out_trade_no: '1',
29 | subject: 'subject',
30 | total_fee: '0.01',
31 | call_back_url: 'https://example.com/call_back'
32 | }
33 | )
34 | end
35 |
36 | def test_auth_and_execute_url
37 | assert_equal 'https://wappaygw.alipay.com/service/rest.htm?service=alipay.wap.auth.authAndExecute&req_data=%3Cauth_and_execute_req%3E%3Crequest_token%3Etoken_test%3C%2Frequest_token%3E%3C%2Fauth_and_execute_req%3E&partner=1000000000000000&format=xml&v=2.0&sec_id=MD5&sign=3efe60d4a9b7960ba599da6764c959df', Alipay::Wap::Service.auth_and_execute_url(request_token: 'token_test')
38 | end
39 |
40 | def test_security_risk_detect
41 | stub_request(
42 | :post,
43 | %r|https://wappaygw\.alipay\.com/service/rest\.htm.*|
44 | ).to_return(
45 | body: ' '
46 | )
47 |
48 | params = {
49 | order_no: '1',
50 | order_credate_time: Time.now.strftime('%Y-%m-%d %H:%M:%S'),
51 | order_category: 'TestCase^AlipayGem^Ruby',
52 | order_item_name: 'item',
53 | order_amount: '0.01',
54 | buyer_account_no: '2088123123',
55 | buyer_bind_mobile: '13600000000',
56 | buyer_reg_date: '1970-01-01 00:00:00',
57 | terminal_type: 'WAP'
58 | }
59 |
60 | options = {
61 | sign_type: 'RSA',
62 | key: TEST_RSA_PRIVATE_KEY
63 | }
64 |
65 | assert_equal ' ', Alipay::Wap::Service.security_risk_detect(params, options).body
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/test/alipay/wap/sign_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class Alipay::Wap::SignTest < Minitest::Test
4 | def setup
5 | @params = {
6 | v: '1.0',
7 | sec_id: 'MD5',
8 | service: 'test',
9 | notify_data: 'notify_data'
10 | }
11 | @sign = Digest::MD5.hexdigest("service=test&v=1.0&sec_id=MD5¬ify_data=notify_data#{Alipay.key}")
12 | end
13 |
14 | def test_verify_sign
15 | assert !Alipay::Wap::Sign.verify?(@params)
16 | assert !Alipay::Wap::Sign.verify?(@params.merge(sec_id: 'unknow'))
17 | assert Alipay::Wap::Sign.verify?(@params.merge(sign: @sign, whatever: 'x'))
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/test/alipay_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class AlipayTest < Minitest::Test
4 | def test_debug_mode_default
5 | assert Alipay.debug_mode?
6 | end
7 |
8 | def test_sign_type_default
9 | assert_equal 'MD5', Alipay.sign_type
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require 'minitest/autorun'
2 | require 'alipay'
3 | require 'webmock/minitest'
4 |
5 | Alipay.pid = '1000000000000000'
6 | Alipay.key = '10000000000000000000000000000000'
7 |
8 | TEST_RSA_PUBLIC_KEY = <