├── .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 | # # => '
) 108 | params.each do |key, value| 109 | html << %Q() 110 | end 111 | html << "
" 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)}" }.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 = <