alipay_global 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +250 -0
- data/Rakefile +9 -0
- data/alipay_global.gemspec +27 -0
- data/keys/alipay_public_key.pem +6 -0
- data/keys/test_private_key.pem +27 -0
- data/keys/test_public_key.pem +9 -0
- data/lib/alipay_global.rb +32 -0
- data/lib/alipay_global/service.rb +35 -0
- data/lib/alipay_global/service/exchange.rb +34 -0
- data/lib/alipay_global/service/notification.rb +18 -0
- data/lib/alipay_global/service/reconciliation.rb +73 -0
- data/lib/alipay_global/service/trade.rb +67 -0
- data/lib/alipay_global/sign.rb +47 -0
- data/lib/alipay_global/sign/dsa.rb +13 -0
- data/lib/alipay_global/sign/md5.rb +16 -0
- data/lib/alipay_global/sign/rsa.rb +24 -0
- data/lib/alipay_global/utils.rb +30 -0
- data/lib/alipay_global/version.rb +3 -0
- data/test/alipay_global_test.rb +31 -0
- data/test/service/exchange_test.rb +19 -0
- data/test/service/notification_test.rb +21 -0
- data/test/service/reconciliation_test.rb +83 -0
- data/test/service/trade_test.rb +70 -0
- data/test/service_test.rb +33 -0
- data/test/sign/dsa_test.rb +23 -0
- data/test/sign/md5_test.rb +31 -0
- data/test/sign/rsa_test.rb +64 -0
- data/test/sign_test.rb +32 -0
- data/test/test_config.rb +4 -0
- data/test/utils_test.rb +25 -0
- metadata +172 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module AlipayGlobal
|
4
|
+
module Service
|
5
|
+
module Exchange
|
6
|
+
|
7
|
+
def self.current_rates
|
8
|
+
exchange_rates_resp = {}
|
9
|
+
open(build_request) do |file|
|
10
|
+
file.each_line do |line|
|
11
|
+
line = line.strip
|
12
|
+
rate_results = line.split("|")
|
13
|
+
|
14
|
+
exchange_rates_resp[rate_results[2]] = {
|
15
|
+
time: DateTime.strptime("#{rate_results[0]} #{rate_results[1]}","%Y%m%d %H%M%S"),
|
16
|
+
rate: rate_results[3].to_f
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
exchange_rates_resp
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.build_request
|
24
|
+
params = {
|
25
|
+
'service' => 'forex_rate_file',
|
26
|
+
'partner' => AlipayGlobal.api_partner_id,
|
27
|
+
}
|
28
|
+
|
29
|
+
AlipayGlobal::Service.request_uri(params).to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module AlipayGlobal
|
4
|
+
module Service
|
5
|
+
module Notification
|
6
|
+
|
7
|
+
def self.check(params)
|
8
|
+
params = {
|
9
|
+
'service' => 'notify_verify',
|
10
|
+
'partner' => AlipayGlobal.api_partner_id,
|
11
|
+
}.merge(params)
|
12
|
+
|
13
|
+
Net::HTTP.get(AlipayGlobal::Service.request_uri(params,false))
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module AlipayGlobal
|
4
|
+
module Service
|
5
|
+
module Reconciliation
|
6
|
+
|
7
|
+
RECONCILIATION_REQUIRED_PARAMS = %w( start_date end_date )
|
8
|
+
|
9
|
+
RECONCILIATION_ERROR_MESSAGES = [
|
10
|
+
"Over 10 days to Date period",
|
11
|
+
"Over limit balance amount record",
|
12
|
+
"System exception",
|
13
|
+
"<alipay><is_success>F</is_success><error>ILLEGAL_PARTNER</error></alipay>",
|
14
|
+
"Finish date not ahead of today",
|
15
|
+
"Finish date ahead of begin date",
|
16
|
+
"Illegal Date period!",
|
17
|
+
"Date format incorrect YYYYMMDD",
|
18
|
+
"Internet connected exception ,please try later"
|
19
|
+
]
|
20
|
+
|
21
|
+
def self.request(params)
|
22
|
+
reconciliation_resp = []
|
23
|
+
open(build_request(params)) do |data|
|
24
|
+
results = data.read
|
25
|
+
|
26
|
+
return false if results.include? "No balance account data in the period"
|
27
|
+
raise ArgumentError, "#{results}" if error_check(results)
|
28
|
+
|
29
|
+
results.each_line do |line|
|
30
|
+
line = line.strip
|
31
|
+
transaction = line.split("|")
|
32
|
+
|
33
|
+
reconciliation_resp << {
|
34
|
+
partner_transaction_id: transaction[0],
|
35
|
+
amount: transaction[1],
|
36
|
+
currency: transaction[2],
|
37
|
+
transaction_time: valid_alipay_date(transaction[3]) ? DateTime.strptime(transaction[3],"%Y%m%d%H%M%S") : '',
|
38
|
+
settlement_time: valid_alipay_date(transaction[4]) ? DateTime.strptime(transaction[4],"%Y%m%d%H%M%S") : '',
|
39
|
+
transaction_type: transaction[5],
|
40
|
+
service_charge: transaction[6],
|
41
|
+
status: transaction[7],
|
42
|
+
remarks: transaction[8] ? transaction[8] : ''
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
reconciliation_resp
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.build_request(params)
|
51
|
+
AlipayGlobal::Service.check_required_params(params, RECONCILIATION_REQUIRED_PARAMS)
|
52
|
+
|
53
|
+
params = AlipayGlobal::Utils.stringify_keys(params)
|
54
|
+
|
55
|
+
params = {
|
56
|
+
'service' => 'forex_compare_file',
|
57
|
+
'partner' => AlipayGlobal.api_partner_id,
|
58
|
+
}.merge(params)
|
59
|
+
|
60
|
+
AlipayGlobal::Service.request_uri(params).to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.error_check(data)
|
64
|
+
RECONCILIATION_ERROR_MESSAGES.any? { |w| data =~ /#{w}/ }
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.valid_alipay_date(raw_date)
|
68
|
+
raw_date && raw_date.length > 0
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "uri"
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
module AlipayGlobal
|
6
|
+
module Service
|
7
|
+
module Trade
|
8
|
+
|
9
|
+
CREATE_TRADE_REQUIRED_PARAMS = %w( notify_url subject out_trade_no currency )
|
10
|
+
CREATE_TRADE_OPTIONAL_PARAMS = %w( return_url body total_fee rmb_fee order_gmt_create order_valid_time timeout_rule auth_token supplier seller_id seller_name seller_industry )
|
11
|
+
|
12
|
+
def self.create(params)
|
13
|
+
is_mobile = params.delete(:mobile) || params.delete("mobile")
|
14
|
+
service_type = is_mobile ? "create_forex_trade_wap" : "create_forex_trade"
|
15
|
+
|
16
|
+
params = AlipayGlobal::Utils.stringify_keys(params)
|
17
|
+
AlipayGlobal::Service.check_required_params(params, CREATE_TRADE_REQUIRED_PARAMS)
|
18
|
+
AlipayGlobal::Service.check_optional_params(params, CREATE_TRADE_OPTIONAL_PARAMS)
|
19
|
+
|
20
|
+
params = {
|
21
|
+
'service' => service_type,
|
22
|
+
'_input_charset' => 'utf-8',
|
23
|
+
'partner' => AlipayGlobal.api_partner_id
|
24
|
+
}.merge(params)
|
25
|
+
|
26
|
+
AlipayGlobal::Service.request_uri(params).to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.refund(refunds)
|
30
|
+
file = build_refund_file(refunds)
|
31
|
+
|
32
|
+
params = {
|
33
|
+
'service' => 'forex_refund_file',
|
34
|
+
'partner' => AlipayGlobal.api_partner_id
|
35
|
+
}
|
36
|
+
|
37
|
+
uri = AlipayGlobal::Service.request_uri(params)
|
38
|
+
|
39
|
+
#p uri.to_s
|
40
|
+
|
41
|
+
form_params = { 'partner' => AlipayGlobal.api_partner_id, 'service' => 'forex_refund_file', 'refund_file' => IO.read(file.path) }
|
42
|
+
|
43
|
+
#p form_params
|
44
|
+
|
45
|
+
resp = Net::HTTP.post_form(uri, form_params)
|
46
|
+
|
47
|
+
#p resp.body
|
48
|
+
|
49
|
+
alipay_resp = Nokogiri::XML(resp.body)
|
50
|
+
|
51
|
+
alipay_results = alipay_resp.at_xpath('//alipay')
|
52
|
+
#puts "success = " + alipay_results.at_xpath('//is_success').content
|
53
|
+
#puts "error = " + alipay_results.at_xpath('//error').content
|
54
|
+
|
55
|
+
file.unlink
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.build_refund_file(refunds)
|
59
|
+
file = Tempfile.new(['refund_file','.txt'])
|
60
|
+
refund_content = AlipayGlobal::Utils.write_refund_content(refunds)
|
61
|
+
file.write(refund_content)
|
62
|
+
file.close
|
63
|
+
file
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module AlipayGlobal
|
2
|
+
module Sign
|
3
|
+
@alipay_rsa_public_key = File.read("#{File.dirname __dir__}/../keys/alipay_public_key.pem")
|
4
|
+
|
5
|
+
def self.generate(params)
|
6
|
+
params = Utils.stringify_keys(params)
|
7
|
+
sign_type = AlipayGlobal.sign_type.upcase
|
8
|
+
key = AlipayGlobal.api_secret_key
|
9
|
+
string = params_to_string(params)
|
10
|
+
|
11
|
+
case sign_type
|
12
|
+
when 'MD5'
|
13
|
+
MD5.sign(string, key)
|
14
|
+
when 'RSA'
|
15
|
+
RSA.sign(string)
|
16
|
+
when 'DSA'
|
17
|
+
DSA.sign(string, key)
|
18
|
+
else
|
19
|
+
raise ArgumentError, "invalid sign_type #{sign_type}, allow value: 'MD5', 'RSA', 'DSA'"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.verify?(params)
|
24
|
+
params = Utils.stringify_keys(params)
|
25
|
+
|
26
|
+
sign_type = params.delete('sign_type')
|
27
|
+
signature = params.delete('sign')
|
28
|
+
string = params_to_string(params)
|
29
|
+
|
30
|
+
case sign_type
|
31
|
+
when 'MD5'
|
32
|
+
key = AlipayGlobal.api_secret_key
|
33
|
+
MD5.verify?(string, key, signature)
|
34
|
+
when 'RSA'
|
35
|
+
RSA.verify?(string, @alipay_rsa_public_key, signature)
|
36
|
+
when 'DSA'
|
37
|
+
DSA.verify?(string, signature)
|
38
|
+
else
|
39
|
+
false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.params_to_string(params)
|
44
|
+
params.sort.map { |item| item.join('=') }.join('&')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module AlipayGlobal
|
2
|
+
module Sign
|
3
|
+
class DSA
|
4
|
+
def self.sign(string, private_key)
|
5
|
+
raise NotImplementedError, 'DSA is not implemented'
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.verify?(string, sign)
|
9
|
+
raise NotImplementedError, 'DSA is not implemented'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module AlipayGlobal
|
4
|
+
module Sign
|
5
|
+
class MD5
|
6
|
+
#pre-signed string should not be url encoded
|
7
|
+
def self.sign(string, secret_key)
|
8
|
+
Digest::MD5.hexdigest("#{string}#{secret_key}")
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.verify?(string, secret_key, sign)
|
12
|
+
sign == sign(string, secret_key)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module AlipayGlobal
|
5
|
+
module Sign
|
6
|
+
class RSA
|
7
|
+
def self.private_key
|
8
|
+
raise ArgumentError, "Assign valid location for RSA private key location :: #AlipayGlobal.private_key_location = #{AlipayGlobal.private_key_location}" if !AlipayGlobal.private_key_location
|
9
|
+
File.read(AlipayGlobal.private_key_location)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.sign(string, supplied_private_key = nil)
|
13
|
+
key = supplied_private_key || private_key
|
14
|
+
rsa = OpenSSL::PKey::RSA.new(key)
|
15
|
+
Base64.encode64(rsa.sign(OpenSSL::Digest::SHA256.new, string))
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.verify?(string, public_key, sign)
|
19
|
+
rsa = OpenSSL::PKey::RSA.new(public_key)
|
20
|
+
rsa.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(sign), string)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module AlipayGlobal
|
2
|
+
module Utils
|
3
|
+
def self.stringify_keys(hash)
|
4
|
+
new_hash = {}
|
5
|
+
hash.each do |key, value|
|
6
|
+
case key
|
7
|
+
when :total_fee, :rmb_fee, :refund_sum
|
8
|
+
value = value.round(2)
|
9
|
+
value = '%.2f' % value
|
10
|
+
end
|
11
|
+
new_hash[(key.to_s rescue key) || key] = value
|
12
|
+
end
|
13
|
+
new_hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.write_refund_content(refunds)
|
17
|
+
raise ArgumentException, "Refund content should contain at least 1 refund object" if refunds.length == 0
|
18
|
+
file_content = ""
|
19
|
+
refunds.each do |refund|
|
20
|
+
refund = stringify_keys(refund)
|
21
|
+
refund_reason = refund['refund_reason'] || ""
|
22
|
+
line_ending = stringify_keys(refunds.last) == refund ? "" : "\n"
|
23
|
+
raise ArgumentException, "Refund reason (#{refund_reason} cannot be more than 255 characters long)" if refund_reason.length > 255
|
24
|
+
file_content += "#{AlipayGlobal.api_partner_id}|#{refund['new_transaction_id']}|#{refund['old_transaction_id']}|#{refund['currency']}|#{refund['refund_sum']}|#{refund['refund_time']}|#{refund_reason}#{line_ending}"
|
25
|
+
end
|
26
|
+
file_content
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'test_config'
|
2
|
+
|
3
|
+
describe "AlipayGlobal", "basic gem config" do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@alipay = AlipayGlobal
|
7
|
+
end
|
8
|
+
|
9
|
+
it "has a basic debug mode default" do
|
10
|
+
assert @alipay.debug_mode?
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has a default MD5 mode" do
|
14
|
+
assert_equal 'MD5', @alipay.sign_type
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "api_partner_id & api_secret_key assignment" do
|
18
|
+
|
19
|
+
before do
|
20
|
+
@alipay.api_partner_id = '2088101122136241'
|
21
|
+
@alipay.api_secret_key = '760bdzec6y9goq7ctyx96ezkz78287de'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "has assigned api_partner_id and api_secret_key" do
|
25
|
+
@alipay.api_partner_id.wont_be_nil true
|
26
|
+
@alipay.api_secret_key.wont_be_nil true
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'test_config'
|
2
|
+
|
3
|
+
describe "AlipayGlobal::Service::Exchange", "Forex exchange rates request" do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@alipay = AlipayGlobal
|
7
|
+
@alipay.api_partner_id = '2088101122136241'
|
8
|
+
@alipay.api_secret_key = '760bdzec6y9goq7ctyx96ezkz78287de'
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#build_request" do
|
12
|
+
|
13
|
+
it "should create the correct rates url" do
|
14
|
+
assert_equal 'https://mapi.alipay.net/gateway.do?service=forex_rate_file&partner=2088101122136241&sign_type=MD5&sign=4051af1716d522b47acff927d4fb9781', @alipay::Service::Exchange.build_request()
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'test_config'
|
2
|
+
|
3
|
+
describe "AlipayGlobal::Service::Notification", "Notification request check" do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@alipay = AlipayGlobal
|
7
|
+
@alipay.api_partner_id = '2088101122136241'
|
8
|
+
@alipay.api_secret_key = '760bdzec6y9goq7ctyx96ezkz78287de'
|
9
|
+
|
10
|
+
@params = {
|
11
|
+
notify_id: 'abcdefghijklmnopqrst'
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#check" do
|
16
|
+
it "should get params correctly" do
|
17
|
+
assert_equal "false", @alipay::Service::Notification.check(@params)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'test_config'
|
2
|
+
|
3
|
+
describe "AlipayGlobal::Service::Reconciliation", "Reconciliation request check" do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@alipay = AlipayGlobal
|
7
|
+
@alipay.api_partner_id = '2088101122136241'
|
8
|
+
@alipay.api_secret_key = '760bdzec6y9goq7ctyx96ezkz78287de'
|
9
|
+
|
10
|
+
@params = {
|
11
|
+
'start_date'=> '20120202',
|
12
|
+
'end_date'=> '20120205'
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#build_request" do
|
17
|
+
it "should get params correctly" do
|
18
|
+
assert_equal "https://mapi.alipay.net/gateway.do?service=forex_compare_file&partner=2088101122136241&start_date=20120202&end_date=20120205&sign_type=MD5&sign=3d09abe0980696bb39bb72c6137266ca", @alipay::Service::Reconciliation.build_request(@params)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#request" do
|
23
|
+
#Alipay has some weird ass success parameter
|
24
|
+
describe "Success scenario" do
|
25
|
+
#this test is dependent on interfacing with alipay's test environment. The data may change/disappear over time. This test will fail if we get no data for the given parameter
|
26
|
+
it "should return the content parsed correctly" do
|
27
|
+
assert_operator 0, :<, @alipay::Service::Reconciliation.request({
|
28
|
+
'start_date'=> '20122340',
|
29
|
+
'end_date'=> '20122349'
|
30
|
+
}).length
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "Error scenarios" do
|
35
|
+
|
36
|
+
it "return false for no records in period" do
|
37
|
+
assert_equal false, @alipay::Service::Reconciliation.request(@params)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should throw an ArgumentError for date range > 10 days" do
|
41
|
+
proc{ (@alipay::Service::Reconciliation.request({
|
42
|
+
'start_date'=> '20120202',
|
43
|
+
'end_date'=> '20120305'
|
44
|
+
})).call }.must_raise(ArgumentError)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should throw an ArgumentError for an errorneous date range" do
|
48
|
+
proc{ (@alipay::Service::Reconciliation.request({
|
49
|
+
'start_date'=> '20130202',
|
50
|
+
'end_date'=> '20120305'
|
51
|
+
})).call }.must_raise(ArgumentError)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should throw an ArgumentError for no dates supplied (or nil)" do
|
55
|
+
proc{ (@alipay::Service::Reconciliation.request({})).call }.must_raise(ArgumentError)
|
56
|
+
proc{ (@alipay::Service::Reconciliation.request(nil)).call }.must_raise(NoMethodError)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should throw an ArgumentError for finish date ahead of today" do
|
60
|
+
proc{ (@alipay::Service::Reconciliation.request({
|
61
|
+
'start_date'=> '20151300',
|
62
|
+
'end_date'=> '20151333'
|
63
|
+
})).call }.must_raise(ArgumentError)
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "Incorrect Merchant" do
|
67
|
+
before do
|
68
|
+
@alipay.api_partner_id = 'daftpunk'
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should throw an ArgumentError for Merchant ID incorrect" do
|
72
|
+
proc{ (@alipay::Service::Reconciliation.request(@params)).call }.must_raise(ArgumentError)
|
73
|
+
end
|
74
|
+
|
75
|
+
after do
|
76
|
+
@alipay.api_partner_id = '2088101122136241'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|