alipay_global 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- 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
|