dpay 0.1.0
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/CHANGELOG.md +29 -0
- data/LICENSE +201 -0
- data/README.md +191 -0
- data/lib/dpay/bank/bank.rb +28 -0
- data/lib/dpay/bank/bank_service.rb +28 -0
- data/lib/dpay/blik/blik_alias.rb +27 -0
- data/lib/dpay/blik/blik_alias_registration.rb +21 -0
- data/lib/dpay/blik/blik_alias_type.rb +18 -0
- data/lib/dpay/blik/blik_app.rb +17 -0
- data/lib/dpay/blik/blik_recurring_registration.rb +74 -0
- data/lib/dpay/blik/blik_recurring_registration_info.rb +26 -0
- data/lib/dpay/blik/blik_recurring_status.rb +28 -0
- data/lib/dpay/blik/blik_service.rb +51 -0
- data/lib/dpay/card/apple_pay_request.rb +38 -0
- data/lib/dpay/card/card_data.rb +30 -0
- data/lib/dpay/card/card_encryptor.rb +27 -0
- data/lib/dpay/card/card_payment_request.rb +59 -0
- data/lib/dpay/card/card_payment_result.rb +58 -0
- data/lib/dpay/card/card_recurring_frequency.rb +23 -0
- data/lib/dpay/card/card_recurring_operation.rb +19 -0
- data/lib/dpay/card/card_recurring_registration.rb +69 -0
- data/lib/dpay/card/card_service.rb +47 -0
- data/lib/dpay/card/dcc_decision.rb +18 -0
- data/lib/dpay/card/dcc_markup.rb +18 -0
- data/lib/dpay/card/dcc_offer.rb +36 -0
- data/lib/dpay/card/google_pay_request.rb +35 -0
- data/lib/dpay/card/redirect_type.rb +20 -0
- data/lib/dpay/client.rb +26 -0
- data/lib/dpay/config.rb +39 -0
- data/lib/dpay/currency.rb +19 -0
- data/lib/dpay/errors.rb +77 -0
- data/lib/dpay/http/client.rb +11 -0
- data/lib/dpay/http/net_http_client.rb +52 -0
- data/lib/dpay/http/request.rb +17 -0
- data/lib/dpay/http/response.rb +29 -0
- data/lib/dpay/internal/api_requestor.rb +65 -0
- data/lib/dpay/internal/base_urls.rb +38 -0
- data/lib/dpay/internal/checksum_calculator.rb +26 -0
- data/lib/dpay/internal/coerce.rb +46 -0
- data/lib/dpay/internal/error_mapper.rb +90 -0
- data/lib/dpay/internal/php.rb +71 -0
- data/lib/dpay/ipn/ipn_event.rb +84 -0
- data/lib/dpay/ipn/ipn_type.rb +19 -0
- data/lib/dpay/ipn/ipn_verifier.rb +57 -0
- data/lib/dpay/money.rb +85 -0
- data/lib/dpay/payment/device_info.rb +42 -0
- data/lib/dpay/payment/invoice_details.rb +48 -0
- data/lib/dpay/payment/payer.rb +26 -0
- data/lib/dpay/payment/payment_service.rb +35 -0
- data/lib/dpay/payment/payout_fee_mode.rb +18 -0
- data/lib/dpay/payment/payout_instruction.rb +26 -0
- data/lib/dpay/payment/payout_position.rb +23 -0
- data/lib/dpay/payment/register_payment_request.rb +246 -0
- data/lib/dpay/payment/registered_payment.rb +42 -0
- data/lib/dpay/payment/return_urls.rb +20 -0
- data/lib/dpay/payment/transaction.rb +54 -0
- data/lib/dpay/payment/transaction_refund.rb +21 -0
- data/lib/dpay/payment/transaction_status.rb +21 -0
- data/lib/dpay/payment/transaction_type.rb +23 -0
- data/lib/dpay/payout/payout_details.rb +50 -0
- data/lib/dpay/payout/payout_receiver.rb +22 -0
- data/lib/dpay/payout/payout_service.rb +23 -0
- data/lib/dpay/refund/refund.rb +22 -0
- data/lib/dpay/refund/refund_availability.rb +23 -0
- data/lib/dpay/refund/refund_service.rb +46 -0
- data/lib/dpay/testing/mock_http_client.rb +51 -0
- data/lib/dpay/testing.rb +4 -0
- data/lib/dpay/version.rb +5 -0
- data/lib/dpay.rb +68 -0
- data/sig/dpay/bank/bank.rbs +17 -0
- data/sig/dpay/bank/bank_service.rbs +13 -0
- data/sig/dpay/blik/blik_alias.rbs +17 -0
- data/sig/dpay/blik/blik_alias_registration.rbs +6 -0
- data/sig/dpay/blik/blik_alias_type.rbs +11 -0
- data/sig/dpay/blik/blik_app.rbs +10 -0
- data/sig/dpay/blik/blik_recurring_registration.rbs +24 -0
- data/sig/dpay/blik/blik_recurring_registration_info.rbs +17 -0
- data/sig/dpay/blik/blik_recurring_status.rbs +17 -0
- data/sig/dpay/blik/blik_service.rbs +13 -0
- data/sig/dpay/card/apple_pay_request.rbs +13 -0
- data/sig/dpay/card/card_data.rbs +15 -0
- data/sig/dpay/card/card_encryptor.rbs +9 -0
- data/sig/dpay/card/card_payment_request.rbs +17 -0
- data/sig/dpay/card/card_payment_result.rbs +22 -0
- data/sig/dpay/card/card_recurring_frequency.rbs +16 -0
- data/sig/dpay/card/card_recurring_operation.rbs +12 -0
- data/sig/dpay/card/card_recurring_registration.rbs +22 -0
- data/sig/dpay/card/card_service.rbs +16 -0
- data/sig/dpay/card/dcc_decision.rbs +11 -0
- data/sig/dpay/card/dcc_markup.rbs +10 -0
- data/sig/dpay/card/dcc_offer.rbs +21 -0
- data/sig/dpay/card/google_pay_request.rbs +12 -0
- data/sig/dpay/card/redirect_type.rbs +13 -0
- data/sig/dpay/client.rbs +21 -0
- data/sig/dpay/config.rbs +25 -0
- data/sig/dpay/currency.rbs +11 -0
- data/sig/dpay/errors.rbs +86 -0
- data/sig/dpay/http/client.rbs +7 -0
- data/sig/dpay/http/net_http_client.rbs +15 -0
- data/sig/dpay/http/request.rbs +12 -0
- data/sig/dpay/http/response.rbs +15 -0
- data/sig/dpay/internal/api_requestor.rbs +23 -0
- data/sig/dpay/internal/base_urls.rbs +13 -0
- data/sig/dpay/internal/checksum_calculator.rbs +9 -0
- data/sig/dpay/internal/coerce.rbs +12 -0
- data/sig/dpay/internal/error_mapper.rbs +16 -0
- data/sig/dpay/internal/php.rbs +16 -0
- data/sig/dpay/ipn/ipn_event.rbs +28 -0
- data/sig/dpay/ipn/ipn_type.rbs +12 -0
- data/sig/dpay/ipn/ipn_verifier.rbs +11 -0
- data/sig/dpay/money.rbs +25 -0
- data/sig/dpay/payment/device_info.rbs +25 -0
- data/sig/dpay/payment/invoice_details.rbs +17 -0
- data/sig/dpay/payment/payer.rbs +14 -0
- data/sig/dpay/payment/payment_service.rbs +11 -0
- data/sig/dpay/payment/payout_fee_mode.rbs +11 -0
- data/sig/dpay/payment/payout_instruction.rbs +8 -0
- data/sig/dpay/payment/payout_position.rbs +10 -0
- data/sig/dpay/payment/register_payment_request.rbs +56 -0
- data/sig/dpay/payment/registered_payment.rbs +20 -0
- data/sig/dpay/payment/return_urls.rbs +11 -0
- data/sig/dpay/payment/transaction.rbs +25 -0
- data/sig/dpay/payment/transaction_refund.rbs +14 -0
- data/sig/dpay/payment/transaction_status.rbs +14 -0
- data/sig/dpay/payment/transaction_type.rbs +16 -0
- data/sig/dpay/payout/payout_details.rbs +24 -0
- data/sig/dpay/payout/payout_receiver.rbs +15 -0
- data/sig/dpay/payout/payout_service.rbs +6 -0
- data/sig/dpay/refund/refund.rbs +11 -0
- data/sig/dpay/refund/refund_availability.rbs +12 -0
- data/sig/dpay/refund/refund_service.rbs +14 -0
- data/sig/dpay/testing/mock_http_client.rbs +16 -0
- data/sig/dpay/testing.rbs +4 -0
- data/sig/dpay/version.rbs +3 -0
- data/sig/dpay.rbs +2 -0
- metadata +180 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class BlikRecurringStatus
|
|
5
|
+
ACTIVE = "ACTIVE"
|
|
6
|
+
|
|
7
|
+
attr_reader :alias_value, :alias_type, :status, :expiration_date, :registration, :raw
|
|
8
|
+
|
|
9
|
+
def self.from_api(data)
|
|
10
|
+
new(data)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(data)
|
|
14
|
+
@raw = data
|
|
15
|
+
@alias_value = Internal::Coerce.string(data["alias_value"]) || ""
|
|
16
|
+
@alias_type = Internal::Coerce.string(data["alias_type"]) || BlikAliasType::PAYID
|
|
17
|
+
@status = data["status"].is_a?(String) ? data["status"] : nil
|
|
18
|
+
@expiration_date = data["expiration_date"].is_a?(String) ? data["expiration_date"] : nil
|
|
19
|
+
registration = data["registration"]
|
|
20
|
+
@registration = registration.is_a?(Hash) ? BlikRecurringRegistrationInfo.from_api(registration) : nil
|
|
21
|
+
freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def active?
|
|
25
|
+
@status == ACTIVE
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class BlikService
|
|
5
|
+
def initialize(api)
|
|
6
|
+
@api = api
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def alias(alias_value, alias_type = BlikAliasType::UID)
|
|
10
|
+
data = @api.post_json(
|
|
11
|
+
Internal::BaseUrls::API_PAYMENTS,
|
|
12
|
+
"/api/v1_0/payments/blik/aliases",
|
|
13
|
+
alias_body(alias_value, alias_type)
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
BlikAlias.from_api(payload(data))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def unregister_alias(alias_value, alias_type = BlikAliasType::UID, reason = nil)
|
|
20
|
+
body = alias_body(alias_value, alias_type, reason)
|
|
21
|
+
@api.post_json(Internal::BaseUrls::API_PAYMENTS, "/api/v1_0/payments/blik/aliases/unregister", body)
|
|
22
|
+
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def recurring_status(alias_value)
|
|
27
|
+
body = { "service" => @api.service, "alias_value" => alias_value }
|
|
28
|
+
body["checksum"] = @api.checksum.secret_second(@api.service, [alias_value])
|
|
29
|
+
|
|
30
|
+
data = @api.post_json(Internal::BaseUrls::API_PAYMENTS, "/api/v1_0/payments/blik/recurring/status", body)
|
|
31
|
+
|
|
32
|
+
BlikRecurringStatus.from_api(payload(data))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def alias_body(alias_value, alias_type, reason = nil)
|
|
38
|
+
BlikAliasType.assert_valid(alias_type)
|
|
39
|
+
|
|
40
|
+
body = { "service" => @api.service, "alias_value" => alias_value, "alias_type" => alias_type }
|
|
41
|
+
body["reason"] = reason unless reason.nil?
|
|
42
|
+
body["checksum"] = @api.checksum.secret_second(@api.service, [alias_value])
|
|
43
|
+
|
|
44
|
+
body
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def payload(data)
|
|
48
|
+
data["data"].is_a?(Hash) ? data["data"] : {}
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class ApplePayRequest
|
|
5
|
+
INIT = "APPLE_PAY_INIT"
|
|
6
|
+
PAY = "APPLE_PAY"
|
|
7
|
+
|
|
8
|
+
def self.init(device_info)
|
|
9
|
+
new(INIT, nil, device_info)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.pay(token, device_info)
|
|
13
|
+
new(PAY, token, device_info)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(x_pay_type, token, device_info)
|
|
17
|
+
@x_pay_type = x_pay_type
|
|
18
|
+
@token = token
|
|
19
|
+
@device_info = device_info
|
|
20
|
+
@channel_id = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def with_channel_id(channel_id)
|
|
24
|
+
@channel_id = channel_id
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_body
|
|
29
|
+
body = {}
|
|
30
|
+
body["channelId"] = @channel_id unless @channel_id.nil?
|
|
31
|
+
body["xPayType"] = @x_pay_type
|
|
32
|
+
body["xPayToken"] = @token unless @token.nil?
|
|
33
|
+
body["deviceInfo"] = @device_info.to_h
|
|
34
|
+
|
|
35
|
+
body
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class CardData
|
|
5
|
+
PAN = /\A\d{12,19}\z/
|
|
6
|
+
CVV = /\A\d{3,4}\z/
|
|
7
|
+
EXPIRY = %r{\A(0[1-9]|1[0-2])/\d{2}\z}
|
|
8
|
+
|
|
9
|
+
attr_reader :pan, :cvv, :expiry
|
|
10
|
+
|
|
11
|
+
def initialize(pan, cvv, expiry)
|
|
12
|
+
normalized = pan.to_s.delete(" ")
|
|
13
|
+
|
|
14
|
+
raise InvalidArgumentError, "Card number must be 12-19 digits" unless PAN.match?(normalized)
|
|
15
|
+
raise InvalidArgumentError, "CVV must be 3-4 digits" unless CVV.match?(cvv.to_s)
|
|
16
|
+
raise InvalidArgumentError, "Expiry must be in MM/YY format" unless EXPIRY.match?(expiry.to_s)
|
|
17
|
+
|
|
18
|
+
@pan = normalized
|
|
19
|
+
@cvv = cvv
|
|
20
|
+
@expiry = expiry
|
|
21
|
+
freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def inspect
|
|
25
|
+
"#<DPay::CardData pan=[FILTERED] cvv=[FILTERED] expiry=[FILTERED]>"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
alias to_s inspect
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
|
|
5
|
+
module DPay
|
|
6
|
+
class CardEncryptor
|
|
7
|
+
def encrypt(card, transaction_id, public_key_pem, timestamp: Time.now.to_i)
|
|
8
|
+
key = load_key(public_key_pem)
|
|
9
|
+
payload = Internal::PHP.json_encode(
|
|
10
|
+
{ "PN" => card.pan, "SC" => card.cvv, "DT" => card.expiry, "ID" => transaction_id, "TX" => timestamp },
|
|
11
|
+
escape_slashes: true
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
[key.public_encrypt(payload, OpenSSL::PKey::RSA::PKCS1_PADDING)].pack("m0")
|
|
15
|
+
rescue OpenSSL::PKey::PKeyError => e
|
|
16
|
+
raise CardEncryptionError, "Card data encryption failed: #{e.message}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def load_key(public_key_pem)
|
|
22
|
+
OpenSSL::PKey::RSA.new(public_key_pem.to_s)
|
|
23
|
+
rescue OpenSSL::PKey::PKeyError, ArgumentError, TypeError
|
|
24
|
+
raise CardEncryptionError, "Invalid RSA public key"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class CardPaymentRequest
|
|
5
|
+
def self.create(device_info)
|
|
6
|
+
new(device_info)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize(device_info)
|
|
10
|
+
@device_info = device_info
|
|
11
|
+
@leading = {}
|
|
12
|
+
@trailing = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def with_email(email)
|
|
16
|
+
@leading["email"] = email
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def with_channel_id(channel_id)
|
|
21
|
+
@leading["channelId"] = channel_id
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def with_card_holder(first_name, last_name)
|
|
26
|
+
@leading["cardHolderFirstName"] = first_name
|
|
27
|
+
@leading["cardHolderLastName"] = last_name
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def with_encrypted_card_data(encrypted_card_data)
|
|
32
|
+
@leading["encryptedCardData"] = encrypted_card_data
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def with_three_ds_confirmed(confirmed)
|
|
37
|
+
@trailing["threeDsConfirmed"] = confirmed
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def with_dcc_decision(decision)
|
|
42
|
+
DccDecision.assert_valid(decision)
|
|
43
|
+
@trailing["dccDecision"] = decision
|
|
44
|
+
self
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_body
|
|
48
|
+
body = {}
|
|
49
|
+
|
|
50
|
+
%w[email channelId cardHolderFirstName cardHolderLastName encryptedCardData].each do |key|
|
|
51
|
+
body[key] = @leading[key] if @leading.key?(key)
|
|
52
|
+
end
|
|
53
|
+
body["deviceInfo"] = @device_info.to_h
|
|
54
|
+
%w[threeDsConfirmed dccDecision].each { |key| body[key] = @trailing[key] if @trailing.key?(key) }
|
|
55
|
+
|
|
56
|
+
body
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class CardPaymentResult
|
|
5
|
+
attr_reader :redirect_type, :redirect_text, :dcc_offer, :raw
|
|
6
|
+
|
|
7
|
+
def self.from_api(data)
|
|
8
|
+
new(data)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(data)
|
|
12
|
+
@raw = data
|
|
13
|
+
message = data["message"].is_a?(Hash) ? data["message"] : {}
|
|
14
|
+
|
|
15
|
+
@redirect_type = message["redirectType"].is_a?(String) ? message["redirectType"] : nil
|
|
16
|
+
text = message["redirectText"]
|
|
17
|
+
@redirect_text = text.is_a?(String) && !text.empty? ? text : nil
|
|
18
|
+
@dcc_offer = message["dccOffer"].is_a?(Hash) ? DccOffer.from_api(message["dccOffer"]) : nil
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def success?
|
|
23
|
+
@redirect_type == RedirectType::SUCCESS
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def three_ds_form?
|
|
27
|
+
@redirect_type == RedirectType::FORM
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def requires_redirect?
|
|
31
|
+
@redirect_type == RedirectType::URL
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def dcc_offer?
|
|
35
|
+
@redirect_type == RedirectType::DCC_OFFER
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def three_ds_form_html
|
|
39
|
+
three_ds_form? ? decode : nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def redirect_url
|
|
43
|
+
requires_redirect? ? decode : nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def decode
|
|
49
|
+
text = @redirect_text
|
|
50
|
+
return nil if text.nil?
|
|
51
|
+
|
|
52
|
+
decoded = text.unpack1("m0")
|
|
53
|
+
decoded.is_a?(String) ? decoded : nil
|
|
54
|
+
rescue ArgumentError
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
module CardRecurringFrequency
|
|
5
|
+
DAILY = "DAILY"
|
|
6
|
+
WEEKLY = "WEEKLY"
|
|
7
|
+
BIWEEKLY = "BIWEEKLY"
|
|
8
|
+
MONTHLY = "MONTHLY"
|
|
9
|
+
QUARTERLY = "QUARTERLY"
|
|
10
|
+
SEMIANNUAL = "SEMIANNUAL"
|
|
11
|
+
ANNUAL = "ANNUAL"
|
|
12
|
+
|
|
13
|
+
ALL = [DAILY, WEEKLY, BIWEEKLY, MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL].freeze
|
|
14
|
+
|
|
15
|
+
def self.valid?(value)
|
|
16
|
+
ALL.include?(value)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.assert_valid(value)
|
|
20
|
+
raise InvalidArgumentError, %(Invalid card recurring frequency "#{value}") unless valid?(value)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
module CardRecurringOperation
|
|
5
|
+
ADD_CARD = "add_card"
|
|
6
|
+
COF_INITIAL = "cof_initial"
|
|
7
|
+
CHARGE = "charge"
|
|
8
|
+
|
|
9
|
+
ALL = [ADD_CARD, COF_INITIAL, CHARGE].freeze
|
|
10
|
+
|
|
11
|
+
def self.valid?(value)
|
|
12
|
+
ALL.include?(value)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.assert_valid(value)
|
|
16
|
+
raise InvalidArgumentError, %(Invalid card recurring operation "#{value}") unless valid?(value)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class CardRecurringRegistration
|
|
5
|
+
DATE = /\A\d{4}-\d{2}-\d{2}\z/
|
|
6
|
+
|
|
7
|
+
def self.create(label)
|
|
8
|
+
new(label)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(label)
|
|
12
|
+
unless label.is_a?(String) && !label.empty? && label.length <= 50
|
|
13
|
+
raise InvalidArgumentError, "Mandate label must be 1-50 characters"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@label = label
|
|
17
|
+
@optional = {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def with_frequency(frequency)
|
|
21
|
+
CardRecurringFrequency.assert_valid(frequency)
|
|
22
|
+
@optional["frequency"] = frequency
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def with_limit_amt(limit_amt)
|
|
27
|
+
@optional["limit_amt"] = limit_amt.minor
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def with_tot_limit_amt(tot_limit_amt)
|
|
32
|
+
@optional["tot_limit_amt"] = tot_limit_amt.minor
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def with_limit_amt_fixed(fixed)
|
|
37
|
+
@optional["is_limit_amt_fixed"] = fixed
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def with_expiration_date(expiration_date)
|
|
42
|
+
@optional["expiration_date"] = assert_date(expiration_date)
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def with_init_date(init_date)
|
|
47
|
+
@optional["init_date"] = assert_date(init_date)
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_h
|
|
52
|
+
base = { "label" => @label }
|
|
53
|
+
|
|
54
|
+
%w[frequency limit_amt tot_limit_amt is_limit_amt_fixed expiration_date init_date].each do |key|
|
|
55
|
+
base[key] = @optional[key] if @optional.key?(key)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
base
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def assert_date(date)
|
|
64
|
+
raise InvalidArgumentError, %(Date "#{date}" must be in YYYY-MM-DD format) unless DATE.match?(date.to_s)
|
|
65
|
+
|
|
66
|
+
date
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class CardService
|
|
5
|
+
def initialize(api)
|
|
6
|
+
@api = api
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def public_key
|
|
10
|
+
@api.get_text(Internal::BaseUrls::API_PAYMENTS, "/api/v1_0/cards/public-key").strip
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def pay_otp(transaction_id, request)
|
|
14
|
+
post(transaction_id, "/pay/card-otp", request.to_body)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def pre_auth(transaction_id, request)
|
|
18
|
+
post(transaction_id, "/pay/card-pre-auth", request.to_body)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def capture(transaction_id, amount)
|
|
22
|
+
post(transaction_id, "/capture", { "amount" => amount.to_decimal.to_f })
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def cancel(transaction_id, amount = nil)
|
|
26
|
+
post(transaction_id, "/cancellation", amount.nil? ? {} : { "amount" => amount.to_decimal.to_f })
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def google_pay(transaction_id, request)
|
|
30
|
+
post(transaction_id, "/pay/google-pay", request.to_body)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def apple_pay(transaction_id, request)
|
|
34
|
+
post(transaction_id, "/pay/apple-pay", request.to_body)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def post(transaction_id, suffix, body)
|
|
40
|
+
path = "/api/v1_0/cards/payment/#{Internal::PHP.raw_url_encode(transaction_id)}#{suffix}"
|
|
41
|
+
data = @api.post_json(Internal::BaseUrls::API_PAYMENTS, path, body)
|
|
42
|
+
raise CardPaymentError.from_api(data) unless data["success"] == true
|
|
43
|
+
|
|
44
|
+
CardPaymentResult.from_api(data)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
module DccDecision
|
|
5
|
+
ACCEPT = "accept"
|
|
6
|
+
REJECT = "reject"
|
|
7
|
+
|
|
8
|
+
ALL = [ACCEPT, REJECT].freeze
|
|
9
|
+
|
|
10
|
+
def self.valid?(value)
|
|
11
|
+
ALL.include?(value)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.assert_valid(value)
|
|
15
|
+
raise InvalidArgumentError, %(Invalid DCC decision "#{value}") unless valid?(value)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class DccMarkup
|
|
5
|
+
attr_reader :rate, :additional_info
|
|
6
|
+
|
|
7
|
+
def self.from_api(data)
|
|
8
|
+
new(data)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(data)
|
|
12
|
+
rate = data["rate"]
|
|
13
|
+
@rate = rate.is_a?(Integer) || rate.is_a?(Float) ? rate.to_f : 0.0
|
|
14
|
+
@additional_info = data["additionalInfo"].is_a?(String) ? data["additionalInfo"] : nil
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class DccOffer
|
|
5
|
+
attr_reader :currency_conversion_id, :original_amount, :converted_amount, :exchange_rate,
|
|
6
|
+
:valid_until, :declaration_text, :markup, :raw
|
|
7
|
+
|
|
8
|
+
def self.from_api(data)
|
|
9
|
+
new(data)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(data)
|
|
13
|
+
@raw = data
|
|
14
|
+
@currency_conversion_id = Internal::Coerce.string(data["currencyConversionId"]) || ""
|
|
15
|
+
@original_amount = Internal::Coerce.money(data["originalAmount"], currency(data["originalCurrency"]))
|
|
16
|
+
@converted_amount = Internal::Coerce.money(data["convertedAmount"], currency(data["convertedCurrency"]))
|
|
17
|
+
exchange_rate = data["exchangeRate"]
|
|
18
|
+
@exchange_rate = exchange_rate.is_a?(Integer) || exchange_rate.is_a?(Float) ? exchange_rate.to_f : 0.0
|
|
19
|
+
@valid_until = Internal::Coerce.string(data["validUntil"]) || ""
|
|
20
|
+
@declaration_text = Internal::Coerce.string(data["declarationText"]) || ""
|
|
21
|
+
@european_economic_area = Internal::Coerce.boolean(data["europeanEconomicArea"])
|
|
22
|
+
@markup = Internal::Coerce.list(data["markup"]).map { |markup| DccMarkup.from_api(markup) }
|
|
23
|
+
freeze
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def european_economic_area?
|
|
27
|
+
@european_economic_area
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def currency(value)
|
|
33
|
+
Currency.valid?(value) ? value : Currency::PLN
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class GooglePayRequest
|
|
5
|
+
def self.create(token, device_info)
|
|
6
|
+
new(token, device_info)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize(token, device_info)
|
|
10
|
+
@token = token
|
|
11
|
+
@device_info = device_info
|
|
12
|
+
@leading = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def with_email(email)
|
|
16
|
+
@leading["email"] = email
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def with_channel_id(channel_id)
|
|
21
|
+
@leading["channelId"] = channel_id
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_body
|
|
26
|
+
body = {}
|
|
27
|
+
%w[email channelId].each { |key| body[key] = @leading[key] if @leading.key?(key) }
|
|
28
|
+
body["xPayType"] = "GOOGLE_PAY"
|
|
29
|
+
body["xPayToken"] = @token
|
|
30
|
+
body["deviceInfo"] = @device_info.to_h
|
|
31
|
+
|
|
32
|
+
body
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
module RedirectType
|
|
5
|
+
SUCCESS = "SUCCESS"
|
|
6
|
+
FORM = "FORM"
|
|
7
|
+
URL = "URL"
|
|
8
|
+
DCC_OFFER = "DCC_OFFER"
|
|
9
|
+
|
|
10
|
+
ALL = [SUCCESS, FORM, URL, DCC_OFFER].freeze
|
|
11
|
+
|
|
12
|
+
def self.valid?(value)
|
|
13
|
+
ALL.include?(value)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.assert_valid(value)
|
|
17
|
+
raise InvalidArgumentError, %(Invalid redirect type "#{value}") unless valid?(value)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/dpay/client.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class Client
|
|
5
|
+
VERSION = DPay::VERSION
|
|
6
|
+
|
|
7
|
+
attr_reader :config, :payments, :refunds, :banks, :blik, :cards, :payouts
|
|
8
|
+
|
|
9
|
+
def initialize(service:, secret_hash:, timeout: Config::DEFAULT_TIMEOUT, http_client: nil, base_urls: nil)
|
|
10
|
+
@config = Config.new(
|
|
11
|
+
service: service, secret_hash: secret_hash, timeout: timeout,
|
|
12
|
+
http_client: http_client, base_urls: base_urls
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
api = Internal::ApiRequestor.new(@config, @config.http_client || HTTP::NetHTTPClient.new(@config.timeout))
|
|
16
|
+
|
|
17
|
+
@payments = PaymentService.new(api)
|
|
18
|
+
@refunds = RefundService.new(api)
|
|
19
|
+
@banks = BankService.new(api)
|
|
20
|
+
@blik = BlikService.new(api)
|
|
21
|
+
@cards = CardService.new(api)
|
|
22
|
+
@payouts = PayoutService.new(api)
|
|
23
|
+
freeze
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/dpay/config.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
class Config
|
|
5
|
+
DEFAULT_TIMEOUT = 30
|
|
6
|
+
|
|
7
|
+
attr_reader :service, :secret_hash, :timeout, :http_client, :base_urls
|
|
8
|
+
|
|
9
|
+
def initialize(service:, secret_hash:, timeout: DEFAULT_TIMEOUT, http_client: nil, base_urls: nil)
|
|
10
|
+
@service = non_empty_string(service, "service")
|
|
11
|
+
@secret_hash = non_empty_string(secret_hash, "secret_hash")
|
|
12
|
+
@timeout = positive_integer(timeout)
|
|
13
|
+
@http_client = transport(http_client)
|
|
14
|
+
@base_urls = Internal::BaseUrls.new(base_urls || {})
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def non_empty_string(value, name)
|
|
21
|
+
raise InvalidArgumentError, "#{name} must be a non-empty String" unless value.is_a?(String) && !value.empty?
|
|
22
|
+
|
|
23
|
+
value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def positive_integer(value)
|
|
27
|
+
raise InvalidArgumentError, "timeout must be an Integer >= 1" unless value.is_a?(Integer) && value >= 1
|
|
28
|
+
|
|
29
|
+
value
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def transport(value)
|
|
33
|
+
return nil if value.nil?
|
|
34
|
+
raise InvalidArgumentError, "http_client must respond to #request" unless value.respond_to?(:request)
|
|
35
|
+
|
|
36
|
+
value
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DPay
|
|
4
|
+
module Currency
|
|
5
|
+
PLN = "PLN"
|
|
6
|
+
EUR = "EUR"
|
|
7
|
+
CZK = "CZK"
|
|
8
|
+
|
|
9
|
+
CODE = /\A[A-Z]{3}\z/
|
|
10
|
+
|
|
11
|
+
def self.valid?(code)
|
|
12
|
+
code.is_a?(String) && CODE.match?(code)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.assert_valid(code)
|
|
16
|
+
raise InvalidArgumentError, %(Invalid currency code "#{code}") unless valid?(code)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|