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.
Files changed (137) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +29 -0
  3. data/LICENSE +201 -0
  4. data/README.md +191 -0
  5. data/lib/dpay/bank/bank.rb +28 -0
  6. data/lib/dpay/bank/bank_service.rb +28 -0
  7. data/lib/dpay/blik/blik_alias.rb +27 -0
  8. data/lib/dpay/blik/blik_alias_registration.rb +21 -0
  9. data/lib/dpay/blik/blik_alias_type.rb +18 -0
  10. data/lib/dpay/blik/blik_app.rb +17 -0
  11. data/lib/dpay/blik/blik_recurring_registration.rb +74 -0
  12. data/lib/dpay/blik/blik_recurring_registration_info.rb +26 -0
  13. data/lib/dpay/blik/blik_recurring_status.rb +28 -0
  14. data/lib/dpay/blik/blik_service.rb +51 -0
  15. data/lib/dpay/card/apple_pay_request.rb +38 -0
  16. data/lib/dpay/card/card_data.rb +30 -0
  17. data/lib/dpay/card/card_encryptor.rb +27 -0
  18. data/lib/dpay/card/card_payment_request.rb +59 -0
  19. data/lib/dpay/card/card_payment_result.rb +58 -0
  20. data/lib/dpay/card/card_recurring_frequency.rb +23 -0
  21. data/lib/dpay/card/card_recurring_operation.rb +19 -0
  22. data/lib/dpay/card/card_recurring_registration.rb +69 -0
  23. data/lib/dpay/card/card_service.rb +47 -0
  24. data/lib/dpay/card/dcc_decision.rb +18 -0
  25. data/lib/dpay/card/dcc_markup.rb +18 -0
  26. data/lib/dpay/card/dcc_offer.rb +36 -0
  27. data/lib/dpay/card/google_pay_request.rb +35 -0
  28. data/lib/dpay/card/redirect_type.rb +20 -0
  29. data/lib/dpay/client.rb +26 -0
  30. data/lib/dpay/config.rb +39 -0
  31. data/lib/dpay/currency.rb +19 -0
  32. data/lib/dpay/errors.rb +77 -0
  33. data/lib/dpay/http/client.rb +11 -0
  34. data/lib/dpay/http/net_http_client.rb +52 -0
  35. data/lib/dpay/http/request.rb +17 -0
  36. data/lib/dpay/http/response.rb +29 -0
  37. data/lib/dpay/internal/api_requestor.rb +65 -0
  38. data/lib/dpay/internal/base_urls.rb +38 -0
  39. data/lib/dpay/internal/checksum_calculator.rb +26 -0
  40. data/lib/dpay/internal/coerce.rb +46 -0
  41. data/lib/dpay/internal/error_mapper.rb +90 -0
  42. data/lib/dpay/internal/php.rb +71 -0
  43. data/lib/dpay/ipn/ipn_event.rb +84 -0
  44. data/lib/dpay/ipn/ipn_type.rb +19 -0
  45. data/lib/dpay/ipn/ipn_verifier.rb +57 -0
  46. data/lib/dpay/money.rb +85 -0
  47. data/lib/dpay/payment/device_info.rb +42 -0
  48. data/lib/dpay/payment/invoice_details.rb +48 -0
  49. data/lib/dpay/payment/payer.rb +26 -0
  50. data/lib/dpay/payment/payment_service.rb +35 -0
  51. data/lib/dpay/payment/payout_fee_mode.rb +18 -0
  52. data/lib/dpay/payment/payout_instruction.rb +26 -0
  53. data/lib/dpay/payment/payout_position.rb +23 -0
  54. data/lib/dpay/payment/register_payment_request.rb +246 -0
  55. data/lib/dpay/payment/registered_payment.rb +42 -0
  56. data/lib/dpay/payment/return_urls.rb +20 -0
  57. data/lib/dpay/payment/transaction.rb +54 -0
  58. data/lib/dpay/payment/transaction_refund.rb +21 -0
  59. data/lib/dpay/payment/transaction_status.rb +21 -0
  60. data/lib/dpay/payment/transaction_type.rb +23 -0
  61. data/lib/dpay/payout/payout_details.rb +50 -0
  62. data/lib/dpay/payout/payout_receiver.rb +22 -0
  63. data/lib/dpay/payout/payout_service.rb +23 -0
  64. data/lib/dpay/refund/refund.rb +22 -0
  65. data/lib/dpay/refund/refund_availability.rb +23 -0
  66. data/lib/dpay/refund/refund_service.rb +46 -0
  67. data/lib/dpay/testing/mock_http_client.rb +51 -0
  68. data/lib/dpay/testing.rb +4 -0
  69. data/lib/dpay/version.rb +5 -0
  70. data/lib/dpay.rb +68 -0
  71. data/sig/dpay/bank/bank.rbs +17 -0
  72. data/sig/dpay/bank/bank_service.rbs +13 -0
  73. data/sig/dpay/blik/blik_alias.rbs +17 -0
  74. data/sig/dpay/blik/blik_alias_registration.rbs +6 -0
  75. data/sig/dpay/blik/blik_alias_type.rbs +11 -0
  76. data/sig/dpay/blik/blik_app.rbs +10 -0
  77. data/sig/dpay/blik/blik_recurring_registration.rbs +24 -0
  78. data/sig/dpay/blik/blik_recurring_registration_info.rbs +17 -0
  79. data/sig/dpay/blik/blik_recurring_status.rbs +17 -0
  80. data/sig/dpay/blik/blik_service.rbs +13 -0
  81. data/sig/dpay/card/apple_pay_request.rbs +13 -0
  82. data/sig/dpay/card/card_data.rbs +15 -0
  83. data/sig/dpay/card/card_encryptor.rbs +9 -0
  84. data/sig/dpay/card/card_payment_request.rbs +17 -0
  85. data/sig/dpay/card/card_payment_result.rbs +22 -0
  86. data/sig/dpay/card/card_recurring_frequency.rbs +16 -0
  87. data/sig/dpay/card/card_recurring_operation.rbs +12 -0
  88. data/sig/dpay/card/card_recurring_registration.rbs +22 -0
  89. data/sig/dpay/card/card_service.rbs +16 -0
  90. data/sig/dpay/card/dcc_decision.rbs +11 -0
  91. data/sig/dpay/card/dcc_markup.rbs +10 -0
  92. data/sig/dpay/card/dcc_offer.rbs +21 -0
  93. data/sig/dpay/card/google_pay_request.rbs +12 -0
  94. data/sig/dpay/card/redirect_type.rbs +13 -0
  95. data/sig/dpay/client.rbs +21 -0
  96. data/sig/dpay/config.rbs +25 -0
  97. data/sig/dpay/currency.rbs +11 -0
  98. data/sig/dpay/errors.rbs +86 -0
  99. data/sig/dpay/http/client.rbs +7 -0
  100. data/sig/dpay/http/net_http_client.rbs +15 -0
  101. data/sig/dpay/http/request.rbs +12 -0
  102. data/sig/dpay/http/response.rbs +15 -0
  103. data/sig/dpay/internal/api_requestor.rbs +23 -0
  104. data/sig/dpay/internal/base_urls.rbs +13 -0
  105. data/sig/dpay/internal/checksum_calculator.rbs +9 -0
  106. data/sig/dpay/internal/coerce.rbs +12 -0
  107. data/sig/dpay/internal/error_mapper.rbs +16 -0
  108. data/sig/dpay/internal/php.rbs +16 -0
  109. data/sig/dpay/ipn/ipn_event.rbs +28 -0
  110. data/sig/dpay/ipn/ipn_type.rbs +12 -0
  111. data/sig/dpay/ipn/ipn_verifier.rbs +11 -0
  112. data/sig/dpay/money.rbs +25 -0
  113. data/sig/dpay/payment/device_info.rbs +25 -0
  114. data/sig/dpay/payment/invoice_details.rbs +17 -0
  115. data/sig/dpay/payment/payer.rbs +14 -0
  116. data/sig/dpay/payment/payment_service.rbs +11 -0
  117. data/sig/dpay/payment/payout_fee_mode.rbs +11 -0
  118. data/sig/dpay/payment/payout_instruction.rbs +8 -0
  119. data/sig/dpay/payment/payout_position.rbs +10 -0
  120. data/sig/dpay/payment/register_payment_request.rbs +56 -0
  121. data/sig/dpay/payment/registered_payment.rbs +20 -0
  122. data/sig/dpay/payment/return_urls.rbs +11 -0
  123. data/sig/dpay/payment/transaction.rbs +25 -0
  124. data/sig/dpay/payment/transaction_refund.rbs +14 -0
  125. data/sig/dpay/payment/transaction_status.rbs +14 -0
  126. data/sig/dpay/payment/transaction_type.rbs +16 -0
  127. data/sig/dpay/payout/payout_details.rbs +24 -0
  128. data/sig/dpay/payout/payout_receiver.rbs +15 -0
  129. data/sig/dpay/payout/payout_service.rbs +6 -0
  130. data/sig/dpay/refund/refund.rbs +11 -0
  131. data/sig/dpay/refund/refund_availability.rbs +12 -0
  132. data/sig/dpay/refund/refund_service.rbs +14 -0
  133. data/sig/dpay/testing/mock_http_client.rbs +16 -0
  134. data/sig/dpay/testing.rbs +4 -0
  135. data/sig/dpay/version.rbs +3 -0
  136. data/sig/dpay.rbs +2 -0
  137. metadata +180 -0
data/lib/dpay/money.rb ADDED
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ class Money
5
+ DECIMAL = /\A(-?)(\d+)(?:\.(\d{1,2}))?\z/
6
+
7
+ attr_reader :minor, :currency
8
+
9
+ def self.pln(minor)
10
+ new(minor, Currency::PLN)
11
+ end
12
+
13
+ def self.of(minor, currency)
14
+ Currency.assert_valid(currency)
15
+ new(minor, currency)
16
+ end
17
+
18
+ def self.from_decimal(decimal, currency)
19
+ Currency.assert_valid(currency)
20
+ parsed = parse_decimal(decimal)
21
+ raise InvalidArgumentError, %(Invalid money amount "#{decimal}") if parsed.nil?
22
+
23
+ new(parsed, currency)
24
+ end
25
+
26
+ def self.from_api_number(value, currency)
27
+ case value
28
+ when Integer then of(value * 100, currency)
29
+ when Float then of(Internal::PHP.round(value * 100), currency)
30
+ when String then from_decimal(value, currency)
31
+ else raise InvalidArgumentError, "Money value must be Integer, Float or String"
32
+ end
33
+ end
34
+
35
+ def self.try_from_api_number(value, currency)
36
+ return nil unless Currency.valid?(currency)
37
+
38
+ case value
39
+ when Integer then new(value * 100, currency)
40
+ when Float then new(Internal::PHP.round(value * 100), currency)
41
+ when String
42
+ parsed = parse_decimal(value)
43
+ parsed.nil? ? nil : new(parsed, currency)
44
+ end
45
+ end
46
+
47
+ def self.parse_decimal(decimal)
48
+ match = DECIMAL.match(decimal.to_s)
49
+ return nil if match.nil?
50
+
51
+ minor = (match[2].to_i * 100) + (match[3] || "").ljust(2, "0").to_i
52
+ match[1] == "-" ? -minor : minor
53
+ end
54
+ private_class_method :parse_decimal
55
+
56
+ def initialize(minor, currency)
57
+ @minor = minor
58
+ @currency = currency
59
+ freeze
60
+ end
61
+
62
+ def to_decimal
63
+ absolute = @minor.abs
64
+ sign = @minor.negative? ? "-" : ""
65
+ format("%<sign>s%<dollars>d.%<cents>02d", sign: sign, dollars: absolute / 100, cents: absolute % 100)
66
+ end
67
+
68
+ def negative?
69
+ @minor.negative?
70
+ end
71
+
72
+ def ==(other)
73
+ other.is_a?(Money) && other.minor == @minor && other.currency == @currency
74
+ end
75
+ alias eql? ==
76
+
77
+ def hash
78
+ [@minor, @currency].hash
79
+ end
80
+
81
+ def to_s
82
+ "#{to_decimal} #{@currency}"
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ class DeviceInfo
5
+ def self.create(browser_accept_header:, browser_language:, browser_color_depth:, browser_screen_height:,
6
+ browser_screen_width:, browser_tz:, browser_user_agent:, system_family:, geo_localization:,
7
+ device_id:, application_name:)
8
+ new(
9
+ { "browserAcceptHeader" => browser_accept_header, "browserLanguage" => browser_language,
10
+ "browserColorDepth" => browser_color_depth, "browserScreenHeight" => browser_screen_height,
11
+ "browserScreenWidth" => browser_screen_width, "browserTZ" => browser_tz,
12
+ "browserUserAgent" => browser_user_agent, "systemFamily" => system_family,
13
+ "geoLocalization" => geo_localization, "deviceID" => bounded(device_id, "Device ID"),
14
+ "applicationName" => bounded(application_name, "Application name") }
15
+ )
16
+ end
17
+
18
+ def self.bounded(value, label)
19
+ raise InvalidArgumentError, "#{label} must be 1-64 characters" unless value.is_a?(String) &&
20
+ !value.empty? && value.length <= 64
21
+
22
+ value
23
+ end
24
+ private_class_method :bounded
25
+
26
+ def initialize(fields)
27
+ @fields = fields
28
+ @browser_java_enabled = nil
29
+ end
30
+
31
+ def with_browser_java_enabled(enabled)
32
+ @browser_java_enabled = enabled
33
+ self
34
+ end
35
+
36
+ def to_h
37
+ data = @fields.dup
38
+ data["browserJavaEnabled"] = @browser_java_enabled ? "true" : "false" unless @browser_java_enabled.nil?
39
+ data
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ class InvoiceDetails
5
+ DATE = /\A\d{4}-\d{2}-\d{2}\z/
6
+
7
+ def self.create
8
+ new
9
+ end
10
+
11
+ def initialize
12
+ @data = {}
13
+ end
14
+
15
+ def with_payer_nip(payer_nip)
16
+ @data["payer_nip"] = payer_nip
17
+ self
18
+ end
19
+
20
+ def with_payer_name(payer_name)
21
+ @data["payer_name"] = payer_name
22
+ self
23
+ end
24
+
25
+ def with_invoice_number(invoice_number)
26
+ @data["invoice_number"] = invoice_number
27
+ self
28
+ end
29
+
30
+ def with_payment_due_date(payment_due_date)
31
+ unless payment_due_date.is_a?(String) && DATE.match?(payment_due_date)
32
+ raise InvalidArgumentError, "Payment due date must be in YYYY-MM-DD format"
33
+ end
34
+
35
+ @data["payment_due_date"] = payment_due_date
36
+ self
37
+ end
38
+
39
+ def with_vat_amount(vat_amount)
40
+ @data["vat_amount"] = vat_amount.minor
41
+ self
42
+ end
43
+
44
+ def to_h
45
+ @data.dup
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ class Payer
5
+ EMAIL = /\A[^@\s]+@[^@\s]+\.[^@\s]+\z/
6
+
7
+ attr_reader :email, :first_name, :last_name
8
+
9
+ def self.create
10
+ new
11
+ end
12
+
13
+ def with_email(email)
14
+ raise InvalidArgumentError, %(Invalid email "#{email}") unless email.is_a?(String) && EMAIL.match?(email)
15
+
16
+ @email = email
17
+ self
18
+ end
19
+
20
+ def with_name(first_name, last_name)
21
+ @first_name = first_name
22
+ @last_name = last_name
23
+ self
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ class PaymentService
5
+ def initialize(api)
6
+ @api = api
7
+ end
8
+
9
+ def register(request)
10
+ body = request.to_body(@api.service)
11
+ body["checksum"] = @api.checksum.secret_second(
12
+ @api.service,
13
+ [body["value"], body["url_success"], body["url_fail"], body["url_ipn"]]
14
+ )
15
+
16
+ data = @api.post_json(Internal::BaseUrls::API_PAYMENTS, "/api/v1_0/payments/register", body)
17
+ raise PaymentRejectedError.from_api(data) if rejected?(data)
18
+
19
+ RegisteredPayment.from_api(data)
20
+ end
21
+
22
+ def details(transaction_id)
23
+ body = { "service" => @api.service, "transaction_id" => transaction_id }
24
+ body["checksum"] = @api.checksum.ordered_body(body.values)
25
+
26
+ Transaction.from_api(@api.post_json(Internal::BaseUrls::PANEL, "/api/v1/pbl/details", body))
27
+ end
28
+
29
+ private
30
+
31
+ def rejected?(data)
32
+ data["error"] == true || data["status"] == false
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ module PayoutFeeMode
5
+ NET = "net"
6
+ GROSS = "gross"
7
+
8
+ ALL = [NET, GROSS].freeze
9
+
10
+ def self.valid?(value)
11
+ ALL.include?(value)
12
+ end
13
+
14
+ def self.assert_valid(value)
15
+ raise InvalidArgumentError, %(Invalid payout fee mode "#{value}") unless valid?(value)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ class PayoutInstruction
5
+ def self.create(positions, fee_mode = PayoutFeeMode::NET)
6
+ new(positions, fee_mode)
7
+ end
8
+
9
+ def initialize(positions, fee_mode = PayoutFeeMode::NET)
10
+ raise InvalidArgumentError, "Payout instruction requires at least one position" if positions.empty?
11
+ unless positions.all?(PayoutPosition)
12
+ raise InvalidArgumentError, "Positions must be DPay::PayoutPosition instances"
13
+ end
14
+
15
+ PayoutFeeMode.assert_valid(fee_mode)
16
+
17
+ @positions = positions.dup.freeze
18
+ @fee_mode = fee_mode
19
+ freeze
20
+ end
21
+
22
+ def to_h
23
+ { "fee_mode" => @fee_mode, "positions" => @positions.map(&:to_h) }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ class PayoutPosition
5
+ attr_reader :iban, :title, :amount
6
+
7
+ def initialize(iban, title, amount)
8
+ raise InvalidArgumentError, "Payout IBAN must not be empty" unless iban.is_a?(String) && !iban.empty?
9
+ unless title.is_a?(String) && !title.empty? && title.length <= 255
10
+ raise InvalidArgumentError, "Payout title must be 1-255 characters"
11
+ end
12
+
13
+ @iban = iban
14
+ @title = title
15
+ @amount = amount
16
+ freeze
17
+ end
18
+
19
+ def to_h
20
+ { "iban" => @iban, "title" => @title, "amount" => @amount.to_decimal.to_f }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ class RegisterPaymentRequest
5
+ BLIK_CODE = /\A\d{6}\z/
6
+ PARTNER_PLATFORM = /\A[A-Z0-9]{1,64}\z/
7
+ HTTP_URL = %r{\Ahttps?://[^\s]+\z}
8
+ TOGGLES = { "creditcard" => :@credit_card, "paysafecard" => :@paysafecard, "blik" => :@blik,
9
+ "installment" => :@installment, "paypal" => :@paypal, "nobanks" => :@no_banks }.freeze
10
+
11
+ attr_reader :amount
12
+
13
+ def self.create(amount, transaction_type, urls)
14
+ new(amount, transaction_type, urls)
15
+ end
16
+
17
+ def initialize(amount, transaction_type, urls)
18
+ TransactionType.assert_valid(transaction_type)
19
+
20
+ @amount = amount
21
+ @transaction_type = transaction_type
22
+ @urls = urls
23
+ @optional = {}
24
+ end
25
+
26
+ def with_description(description)
27
+ set("description", description)
28
+ end
29
+
30
+ def with_custom(custom)
31
+ set("custom", custom)
32
+ end
33
+
34
+ def with_payer(payer)
35
+ @payer = payer
36
+ self
37
+ end
38
+
39
+ def with_accept_tos(accept_tos)
40
+ set("accept_tos", accept_tos)
41
+ end
42
+
43
+ def with_channel(channel)
44
+ set("channel", channel)
45
+ end
46
+
47
+ def with_credit_card(enabled)
48
+ @credit_card = enabled
49
+ self
50
+ end
51
+
52
+ def with_paysafecard(enabled)
53
+ @paysafecard = enabled
54
+ self
55
+ end
56
+
57
+ def with_blik(enabled)
58
+ @blik = enabled
59
+ self
60
+ end
61
+
62
+ def with_installment(enabled)
63
+ @installment = enabled
64
+ self
65
+ end
66
+
67
+ def with_paypal(enabled)
68
+ @paypal = enabled
69
+ self
70
+ end
71
+
72
+ def with_no_banks(disabled)
73
+ @no_banks = disabled
74
+ self
75
+ end
76
+
77
+ def with_phone_number(phone_number, currency_code)
78
+ Currency.assert_valid(currency_code)
79
+ set("phone_number", phone_number)
80
+ set("currency_code", currency_code)
81
+ end
82
+
83
+ def with_currency_code(currency_code)
84
+ Currency.assert_valid(currency_code)
85
+ set("currency_code", currency_code)
86
+ end
87
+
88
+ def with_partner_platform(partner_platform)
89
+ unless partner_platform.is_a?(String) && PARTNER_PLATFORM.match?(partner_platform)
90
+ raise InvalidArgumentError, "Partner platform must match ^[A-Z0-9]{1,64}$"
91
+ end
92
+
93
+ set("partner_platform", partner_platform)
94
+ end
95
+
96
+ def with_blik_code(blik_code, user_agent, user_ip)
97
+ raise InvalidArgumentError, "BLIK code must be exactly 6 digits" unless BLIK_CODE.match?(blik_code.to_s)
98
+ raise InvalidArgumentError, "blik_code cannot be combined with blik_alias" if @optional.key?("blik_alias")
99
+
100
+ set("blik_code", blik_code)
101
+ set("user_agent", user_agent)
102
+ set("user_ip", user_ip)
103
+ end
104
+
105
+ def with_blik_alias(alias_value, user_agent, user_ip)
106
+ if @optional.key?("blik_code") || @optional.key?("register_blik_alias") ||
107
+ @optional.key?("register_blik_recurring_alias")
108
+ raise InvalidArgumentError, "blik_alias cannot be combined with blik_code or alias registration"
109
+ end
110
+
111
+ set("blik_alias", alias_value)
112
+ set("user_agent", user_agent)
113
+ set("user_ip", user_ip)
114
+ end
115
+
116
+ def with_register_blik_alias(registration)
117
+ if @optional.key?("blik_alias")
118
+ raise InvalidArgumentError, "register_blik_alias cannot be combined with blik_alias"
119
+ end
120
+
121
+ set("register_blik_alias", registration.to_h)
122
+ end
123
+
124
+ def with_register_blik_recurring_alias(registration)
125
+ if @optional.key?("blik_alias")
126
+ raise InvalidArgumentError, "register_blik_recurring_alias cannot be combined with blik_alias"
127
+ end
128
+
129
+ set("register_blik_recurring_alias", registration.to_h)
130
+ end
131
+
132
+ def with_alias_ipn_url(url)
133
+ raise InvalidArgumentError, %(Invalid alias IPN URL "#{url}") unless url.is_a?(String) && HTTP_URL.match?(url)
134
+
135
+ set("alias_ipn_url", url)
136
+ end
137
+
138
+ def with_no_delay(no_delay)
139
+ set("no_delay", no_delay)
140
+ end
141
+
142
+ def with_card_recurring(registration)
143
+ if @optional.key?("card_recurring_alias")
144
+ raise InvalidArgumentError, "register_card_recurring cannot be combined with card_recurring_alias"
145
+ end
146
+
147
+ set("register_card_recurring", registration.to_h)
148
+ end
149
+
150
+ def with_card_recurring_alias(alias_value)
151
+ if @optional.key?("register_card_recurring")
152
+ raise InvalidArgumentError, "card_recurring_alias cannot be combined with register_card_recurring"
153
+ end
154
+
155
+ set("card_recurring_alias", alias_value)
156
+ end
157
+
158
+ def with_authorize_only(authorize_only)
159
+ set("authorize_only", authorize_only)
160
+ end
161
+
162
+ def with_card_recurring_operation(operation)
163
+ CardRecurringOperation.assert_valid(operation)
164
+ set("card_recurring_operation", operation)
165
+ end
166
+
167
+ def with_payout(payout)
168
+ set("payout", payout.to_h)
169
+ end
170
+
171
+ def with_billing_address(billing_address)
172
+ set("billing_address", billing_address)
173
+ end
174
+
175
+ def with_shipping_address(shipping_address)
176
+ set("shipping_address", shipping_address)
177
+ end
178
+
179
+ def with_device_info(device_info)
180
+ set("device_info", device_info.to_h)
181
+ end
182
+
183
+ def with_products(products)
184
+ set("products", products)
185
+ end
186
+
187
+ def with_efaktura(invoice = nil)
188
+ unless @transaction_type == TransactionType::TRANSFERS
189
+ raise InvalidArgumentError, %(efaktura is allowed only for transactionType "transfers")
190
+ end
191
+
192
+ set("efaktura", true)
193
+ invoice.nil? ? self : set("invoice", invoice.to_h)
194
+ end
195
+
196
+ def to_body(service)
197
+ body = {
198
+ "service" => service,
199
+ "value" => @amount.to_decimal,
200
+ "transactionType" => @transaction_type,
201
+ "url_success" => @urls.success,
202
+ "url_fail" => @urls.fail,
203
+ "url_ipn" => @urls.ipn
204
+ }
205
+
206
+ append(body, "description")
207
+ append(body, "custom")
208
+ append_payer(body)
209
+ append(body, "accept_tos")
210
+ append(body, "channel")
211
+ append_toggles(body)
212
+ %w[phone_number currency_code partner_platform user_agent user_ip blik_code blik_alias register_blik_alias
213
+ register_blik_recurring_alias alias_ipn_url no_delay register_card_recurring card_recurring_alias
214
+ authorize_only card_recurring_operation payout billing_address shipping_address device_info products
215
+ efaktura invoice].each { |key| append(body, key) }
216
+
217
+ body
218
+ end
219
+
220
+ private
221
+
222
+ def set(key, value)
223
+ @optional[key] = value
224
+ self
225
+ end
226
+
227
+ def append(body, key)
228
+ body[key] = @optional[key] if @optional.key?(key)
229
+ end
230
+
231
+ def append_payer(body)
232
+ return if @payer.nil?
233
+
234
+ body["email"] = @payer.email unless @payer.email.nil?
235
+ body["client_name"] = @payer.first_name unless @payer.first_name.nil?
236
+ body["client_surname"] = @payer.last_name unless @payer.last_name.nil?
237
+ end
238
+
239
+ def append_toggles(body)
240
+ TOGGLES.each do |key, variable|
241
+ flag = instance_variable_defined?(variable) ? instance_variable_get(variable) : nil
242
+ body[key] = flag ? 1 : 0 unless flag.nil?
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ class RegisteredPayment
5
+ REDIRECT = %r{\Ahttps?://}
6
+ PAID = "Transaction paid"
7
+ INTERNAL_PROCESSING = "Internal processing"
8
+
9
+ attr_reader :transaction_id, :message, :ipksef, :raw
10
+
11
+ def self.from_api(data)
12
+ new(data)
13
+ end
14
+
15
+ def initialize(data)
16
+ @raw = data
17
+ @transaction_id = Internal::Coerce.string(data["transactionId"]) || ""
18
+ @message = Internal::Coerce.string(data["msg"]) || ""
19
+ @ipksef = data["ipksef"].is_a?(String) ? data["ipksef"] : nil
20
+ freeze
21
+ end
22
+
23
+ def redirect_url
24
+ REDIRECT.match?(@message) ? @message : nil
25
+ end
26
+
27
+ def paid?
28
+ @message == PAID
29
+ end
30
+
31
+ def internal_processing?
32
+ @message == INTERNAL_PROCESSING
33
+ end
34
+
35
+ def card_recurring_alias
36
+ additional = @raw["additionalInfo"]
37
+ return nil unless additional.is_a?(Hash) && additional["card_recurring_alias"].is_a?(String)
38
+
39
+ additional["card_recurring_alias"]
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ class ReturnUrls
5
+ HTTP_URL = %r{\Ahttps?://[^\s]+\z}
6
+
7
+ attr_reader :success, :fail, :ipn
8
+
9
+ def initialize(success, fail, ipn)
10
+ { "success" => success, "fail" => fail, "ipn" => ipn }.each do |name, url|
11
+ raise InvalidArgumentError, %(Invalid #{name} URL "#{url}") unless url.is_a?(String) && HTTP_URL.match?(url)
12
+ end
13
+
14
+ @success = success
15
+ @fail = fail
16
+ @ipn = ipn
17
+ freeze
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ class Transaction
5
+ attr_reader :id, :value, :status, :payment_method, :creation_date, :payment_date,
6
+ :refunded_amount, :available_refund_amount, :gateway_id, :payer, :refunds, :raw
7
+
8
+ def self.from_api(data)
9
+ new(data)
10
+ end
11
+
12
+ def initialize(data)
13
+ @raw = data
14
+ tx = data["transaction"].is_a?(Hash) ? data["transaction"] : {}
15
+
16
+ @id = Internal::Coerce.string(tx["id"]) || ""
17
+ @value = Internal::Coerce.money(tx["value"])
18
+ @status = Internal::Coerce.string(tx["status"]) || ""
19
+ @payment_method = Internal::Coerce.string(tx["payment_method"])
20
+ @creation_date = Internal::Coerce.string(tx["creation_date"])
21
+ @payment_date = tx["payment_date"].is_a?(String) ? tx["payment_date"] : nil
22
+ @settled = Internal::Coerce.boolean(tx["settled"])
23
+ @refunded = Internal::Coerce.boolean(tx["refunded"])
24
+ @refunded_amount = Internal::Coerce.money(tx["refunded_amount"])
25
+ @available_refund_amount = Internal::Coerce.money(tx["available_refund_amount"])
26
+ @fully_refunded = Internal::Coerce.boolean(tx["fully_refunded"])
27
+ @direct = Internal::Coerce.boolean(tx["direct"])
28
+ @gateway_id = tx["gateway_id"].is_a?(String) ? tx["gateway_id"] : nil
29
+ @payer = data["payer"].is_a?(Hash) ? data["payer"] : {}
30
+ @refunds = Internal::Coerce.list(data["refunds"]).map { |refund| TransactionRefund.from_api(refund) }
31
+ freeze
32
+ end
33
+
34
+ def paid?
35
+ @status == TransactionStatus::PAID || @status == TransactionStatus::CAPTURED
36
+ end
37
+
38
+ def settled?
39
+ @settled
40
+ end
41
+
42
+ def refunded?
43
+ @refunded
44
+ end
45
+
46
+ def fully_refunded?
47
+ @fully_refunded
48
+ end
49
+
50
+ def direct?
51
+ @direct
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DPay
4
+ class TransactionRefund
5
+ attr_reader :payment_id, :value, :status, :creation_date, :payment_date, :raw
6
+
7
+ def self.from_api(data)
8
+ new(data)
9
+ end
10
+
11
+ def initialize(data)
12
+ @raw = data
13
+ @payment_id = Internal::Coerce.string(data["payment_id"]) || ""
14
+ @value = Internal::Coerce.money(data["value"])
15
+ @status = Internal::Coerce.string(data["status"]) || TransactionStatus::PAID
16
+ @creation_date = Internal::Coerce.string(data["creation_date"])
17
+ @payment_date = Internal::Coerce.string(data["payment_date"])
18
+ freeze
19
+ end
20
+ end
21
+ end