zai_payment 2.4.0 → 2.6.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.
@@ -0,0 +1,295 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ZaiPayment
4
+ module Resources
5
+ # BankAccount resource for managing Zai bank accounts
6
+ #
7
+ # @see https://developer.hellozai.com/reference/createbankaccount
8
+ class BankAccount
9
+ attr_reader :client
10
+
11
+ # Map of attribute keys to API field names
12
+ FIELD_MAPPING = {
13
+ user_id: :user_id,
14
+ bank_name: :bank_name,
15
+ account_name: :account_name,
16
+ routing_number: :routing_number,
17
+ account_number: :account_number,
18
+ account_type: :account_type,
19
+ holder_type: :holder_type,
20
+ country: :country,
21
+ payout_currency: :payout_currency,
22
+ currency: :currency
23
+ }.freeze
24
+
25
+ # Map of UK-specific attribute keys to API field names
26
+ UK_FIELD_MAPPING = {
27
+ user_id: :user_id,
28
+ bank_name: :bank_name,
29
+ account_name: :account_name,
30
+ routing_number: :routing_number,
31
+ account_number: :account_number,
32
+ account_type: :account_type,
33
+ holder_type: :holder_type,
34
+ country: :country,
35
+ payout_currency: :payout_currency,
36
+ currency: :currency,
37
+ iban: :iban,
38
+ swift_code: :swift_code
39
+ }.freeze
40
+
41
+ # Valid account types
42
+ VALID_ACCOUNT_TYPES = %w[savings checking].freeze
43
+
44
+ # Valid holder types
45
+ VALID_HOLDER_TYPES = %w[personal business].freeze
46
+
47
+ def initialize(client: nil)
48
+ @client = client || Client.new
49
+ end
50
+
51
+ # Get a specific bank account by ID
52
+ #
53
+ # @param bank_account_id [String] the bank account ID
54
+ # @param include_decrypted_fields [Boolean] if true, the API will decrypt and return
55
+ # sensitive bank account fields (for example, the full account number). Defaults to false
56
+ # @return [Response] the API response containing bank account details
57
+ #
58
+ # @example
59
+ # bank_accounts = ZaiPayment::Resources::BankAccount.new
60
+ # response = bank_accounts.show("bank_account_id")
61
+ # response.data # => {"id" => "bank_account_id", "active" => true, ...}
62
+ #
63
+ # @example with decrypted fields
64
+ # response = bank_accounts.show("bank_account_id", include_decrypted_fields: true)
65
+ # # Returns full account number instead of masked version
66
+ #
67
+ # @see https://developer.hellozai.com/reference/showbankaccount
68
+ def show(bank_account_id, include_decrypted_fields: false)
69
+ validate_id!(bank_account_id, 'bank_account_id')
70
+
71
+ params = {}
72
+ params[:include_decrypted_fields] = include_decrypted_fields if include_decrypted_fields
73
+
74
+ client.get("/bank_accounts/#{bank_account_id}", params: params)
75
+ end
76
+
77
+ # Create a new bank account for Australia
78
+ #
79
+ # @param attributes [Hash] bank account attributes
80
+ # @option attributes [String] :user_id (Required) User ID
81
+ # @option attributes [String] :bank_name (Required) Bank name (defaults to Bank of Australia)
82
+ # @option attributes [String] :account_name (Required) Account name (defaults to Samuel Seller)
83
+ # @option attributes [String] :routing_number (Required) Routing number / BSB number
84
+ # (defaults to 111123)
85
+ # @option attributes [String] :account_number (Required) Account number
86
+ # (defaults to 111234)
87
+ # @option attributes [String] :account_type (Required) Account type
88
+ # ('savings' or 'checking', defaults to checking)
89
+ # @option attributes [String] :holder_type (Required) Holder type ('personal' or 'business', defaults to personal)
90
+ # @option attributes [String] :country (Required) Country code (ISO 3166-1 alpha-3, max 3 chars, defaults to AUS)
91
+ # @option attributes [String] :payout_currency Optional currency code for payouts (ISO 4217 alpha-3)
92
+ # @option attributes [String] :currency Optional currency code (ISO 4217 alpha-3)
93
+ # @return [Response] the API response containing created bank account
94
+ #
95
+ # @example Create an Australian bank account
96
+ # bank_accounts = ZaiPayment::Resources::BankAccount.new
97
+ # response = bank_accounts.create_au(
98
+ # user_id: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
99
+ # bank_name: 'Bank of Australia',
100
+ # account_name: 'Samuel Seller',
101
+ # routing_number: '111123',
102
+ # account_number: '111234',
103
+ # account_type: 'checking',
104
+ # holder_type: 'personal',
105
+ # country: 'AUS',
106
+ # payout_currency: 'AUD',
107
+ # currency: 'AUD'
108
+ # )
109
+ #
110
+ # @see https://developer.hellozai.com/reference/createbankaccount
111
+ def create_au(**attributes)
112
+ validate_create_au_attributes!(attributes)
113
+
114
+ body = build_bank_account_body(attributes, :au)
115
+ client.post('/bank_accounts', body: body)
116
+ end
117
+
118
+ # Create a new bank account for UK
119
+ #
120
+ # @param attributes [Hash] bank account attributes
121
+ # @option attributes [String] :user_id (Required) User ID
122
+ # @option attributes [String] :bank_name (Required) Bank name (defaults to Bank of UK)
123
+ # @option attributes [String] :account_name (Required) Account name (defaults to Samuel Seller)
124
+ # @option attributes [String] :routing_number (Required) Routing number / Sort Code / BSB
125
+ # number (defaults to 111123)
126
+ # @option attributes [String] :account_number (Required) Account number
127
+ # (defaults to 111234)
128
+ # @option attributes [String] :account_type (Required) Account type
129
+ # ('savings' or 'checking', defaults to checking)
130
+ # @option attributes [String] :holder_type (Required) Holder type ('personal' or 'business', defaults to personal)
131
+ # @option attributes [String] :country (Required) Country code (ISO 3166-1 alpha-3, max 3 chars, defaults to GBR)
132
+ # @option attributes [String] :payout_currency Optional currency code for payouts (ISO 4217 alpha-3)
133
+ # @option attributes [String] :currency Optional currency code (ISO 4217 alpha-3)
134
+ # @option attributes [String] :iban (Required for UK) IBAN number
135
+ # @option attributes [String] :swift_code (Required for UK) SWIFT Code / BIC
136
+ # @return [Response] the API response containing created bank account
137
+ #
138
+ # @example Create a UK bank account
139
+ # bank_accounts = ZaiPayment::Resources::BankAccount.new
140
+ # response = bank_accounts.create_uk(
141
+ # user_id: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
142
+ # bank_name: 'Bank of UK',
143
+ # account_name: 'Samuel Seller',
144
+ # routing_number: '111123',
145
+ # account_number: '111234',
146
+ # account_type: 'checking',
147
+ # holder_type: 'personal',
148
+ # country: 'GBR',
149
+ # payout_currency: 'GBP',
150
+ # currency: 'GBP',
151
+ # iban: 'GB25QHWM02498765432109',
152
+ # swift_code: 'BUKBGB22'
153
+ # )
154
+ #
155
+ # @see https://developer.hellozai.com/reference/createbankaccount
156
+ def create_uk(**attributes)
157
+ validate_create_uk_attributes!(attributes)
158
+
159
+ body = build_bank_account_body(attributes, :uk)
160
+ client.post('/bank_accounts', body: body)
161
+ end
162
+
163
+ # Redact a bank account
164
+ #
165
+ # Redacts a bank account using the given bank_account_id. Redacted bank accounts
166
+ # can no longer be used as a funding source or a disbursement destination.
167
+ #
168
+ # @param bank_account_id [String] the bank account ID
169
+ # @return [Response] the API response
170
+ #
171
+ # @example
172
+ # bank_accounts = ZaiPayment::Resources::BankAccount.new
173
+ # response = bank_accounts.redact("bank_account_id")
174
+ #
175
+ # @see https://developer.hellozai.com/reference/redactbankaccount
176
+ def redact(bank_account_id)
177
+ validate_id!(bank_account_id, 'bank_account_id')
178
+ client.delete("/bank_accounts/#{bank_account_id}")
179
+ end
180
+
181
+ # Validate a US bank routing number
182
+ #
183
+ # Validates a US bank routing number before creating an account. This can be used to
184
+ # provide on-demand verification and further information of the bank information a user
185
+ # is providing.
186
+ #
187
+ # @param routing_number [String] the US bank routing number
188
+ # @return [Response] the API response containing routing number details
189
+ #
190
+ # @example
191
+ # bank_accounts = ZaiPayment::Resources::BankAccount.new
192
+ # response = bank_accounts.validate_routing_number("122235821")
193
+ # response.data # => {"routing_number" => "122235821", "customer_name" => "US BANK NA", ...}
194
+ #
195
+ # @see https://developer.hellozai.com/reference/validateroutingnumber
196
+ def validate_routing_number(routing_number)
197
+ validate_presence!(routing_number, 'routing_number')
198
+
199
+ params = { routing_number: routing_number }
200
+ client.get('/tools/routing_number', params: params)
201
+ end
202
+
203
+ private
204
+
205
+ def validate_id!(value, field_name)
206
+ return unless value.nil? || value.to_s.strip.empty?
207
+
208
+ raise Errors::ValidationError, "#{field_name} is required and cannot be blank"
209
+ end
210
+
211
+ def validate_presence!(value, field_name)
212
+ return unless value.nil? || value.to_s.strip.empty?
213
+
214
+ raise Errors::ValidationError, "#{field_name} is required and cannot be blank"
215
+ end
216
+
217
+ def validate_create_au_attributes!(attributes)
218
+ validate_required_au_attributes!(attributes)
219
+ validate_account_type!(attributes[:account_type]) if attributes[:account_type]
220
+ validate_holder_type!(attributes[:holder_type]) if attributes[:holder_type]
221
+ validate_country!(attributes[:country]) if attributes[:country]
222
+ end
223
+
224
+ def validate_create_uk_attributes!(attributes)
225
+ validate_required_uk_attributes!(attributes)
226
+ validate_account_type!(attributes[:account_type]) if attributes[:account_type]
227
+ validate_holder_type!(attributes[:holder_type]) if attributes[:holder_type]
228
+ validate_country!(attributes[:country]) if attributes[:country]
229
+ end
230
+
231
+ def validate_required_au_attributes!(attributes)
232
+ required_fields = %i[user_id bank_name account_name routing_number account_number
233
+ account_type holder_type country]
234
+
235
+ missing_fields = required_fields.select do |field|
236
+ attributes[field].nil? || attributes[field].to_s.strip.empty?
237
+ end
238
+
239
+ return if missing_fields.empty?
240
+
241
+ raise Errors::ValidationError,
242
+ "Missing required fields: #{missing_fields.join(', ')}"
243
+ end
244
+
245
+ def validate_required_uk_attributes!(attributes)
246
+ required_fields = %i[user_id bank_name account_name routing_number account_number
247
+ account_type holder_type country iban swift_code]
248
+
249
+ missing_fields = required_fields.select do |field|
250
+ attributes[field].nil? || attributes[field].to_s.strip.empty?
251
+ end
252
+
253
+ return if missing_fields.empty?
254
+
255
+ raise Errors::ValidationError,
256
+ "Missing required fields: #{missing_fields.join(', ')}"
257
+ end
258
+
259
+ def validate_account_type!(account_type)
260
+ return if VALID_ACCOUNT_TYPES.include?(account_type.to_s.downcase)
261
+
262
+ raise Errors::ValidationError,
263
+ "account_type must be one of: #{VALID_ACCOUNT_TYPES.join(', ')}"
264
+ end
265
+
266
+ def validate_holder_type!(holder_type)
267
+ return if VALID_HOLDER_TYPES.include?(holder_type.to_s.downcase)
268
+
269
+ raise Errors::ValidationError,
270
+ "holder_type must be one of: #{VALID_HOLDER_TYPES.join(', ')}"
271
+ end
272
+
273
+ def validate_country!(country)
274
+ # Country should be ISO 3166-1 alpha-3 code (3 letters)
275
+ return if country.to_s.match?(/\A[A-Z]{3}\z/i)
276
+
277
+ raise Errors::ValidationError, 'country must be a valid ISO 3166-1 alpha-3 code (e.g., AUS, GBR)'
278
+ end
279
+
280
+ def build_bank_account_body(attributes, region)
281
+ body = {}
282
+ field_mapping = region == :uk ? UK_FIELD_MAPPING : FIELD_MAPPING
283
+
284
+ attributes.each do |key, value|
285
+ next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
286
+
287
+ api_field = field_mapping[key]
288
+ body[api_field] = value if api_field
289
+ end
290
+
291
+ body
292
+ end
293
+ end
294
+ end
295
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ZaiPayment
4
+ module Resources
5
+ # BpayAccount resource for managing Zai BPay accounts
6
+ #
7
+ # @see https://developer.hellozai.com/reference/createbpayaccount
8
+ class BpayAccount
9
+ attr_reader :client
10
+
11
+ # Map of attribute keys to API field names
12
+ FIELD_MAPPING = {
13
+ user_id: :user_id,
14
+ account_name: :account_name,
15
+ biller_code: :biller_code,
16
+ bpay_crn: :bpay_crn
17
+ }.freeze
18
+
19
+ def initialize(client: nil)
20
+ @client = client || Client.new
21
+ end
22
+
23
+ # Get a specific BPay account by ID
24
+ #
25
+ # @param bpay_account_id [String] the BPay account ID
26
+ # @return [Response] the API response containing BPay account details
27
+ #
28
+ # @example
29
+ # bpay_accounts = ZaiPayment::Resources::BpayAccount.new
30
+ # response = bpay_accounts.show("bpay_account_id")
31
+ # response.data # => {"id" => "bpay_account_id", "active" => true, ...}
32
+ #
33
+ # @see https://developer.hellozai.com/reference/showbpayaccount
34
+ def show(bpay_account_id)
35
+ validate_id!(bpay_account_id, 'bpay_account_id')
36
+ client.get("/bpay_accounts/#{bpay_account_id}")
37
+ end
38
+
39
+ # Redact a BPay account
40
+ #
41
+ # Redacts a BPay account using the given bpay_account_id. Redacted BPay accounts
42
+ # can no longer be used as a disbursement destination.
43
+ #
44
+ # @param bpay_account_id [String] the BPay account ID
45
+ # @return [Response] the API response
46
+ #
47
+ # @example
48
+ # bpay_accounts = ZaiPayment::Resources::BpayAccount.new
49
+ # response = bpay_accounts.redact("bpay_account_id")
50
+ #
51
+ # @see https://developer.hellozai.com/reference/redactbpayaccount
52
+ def redact(bpay_account_id)
53
+ validate_id!(bpay_account_id, 'bpay_account_id')
54
+ client.delete("/bpay_accounts/#{bpay_account_id}")
55
+ end
56
+
57
+ # Get the user associated with a BPay account
58
+ #
59
+ # Show the User the BPay Account is associated with using a given bpay_account_id.
60
+ #
61
+ # @param bpay_account_id [String] the BPay account ID
62
+ # @return [Response] the API response containing user details
63
+ #
64
+ # @example
65
+ # bpay_accounts = ZaiPayment::Resources::BpayAccount.new
66
+ # response = bpay_accounts.show_user("bpay_account_id")
67
+ # response.data # => {"id" => "user_id", "full_name" => "Samuel Seller", ...}
68
+ #
69
+ # @see https://developer.hellozai.com/reference/showbpayaccountuser
70
+ def show_user(bpay_account_id)
71
+ validate_id!(bpay_account_id, 'bpay_account_id')
72
+ client.get("/bpay_accounts/#{bpay_account_id}/users")
73
+ end
74
+
75
+ # Create a new BPay account
76
+ #
77
+ # Create a BPay Account to be used as a Disbursement destination.
78
+ #
79
+ # @param attributes [Hash] BPay account attributes
80
+ # @option attributes [String] :user_id (Required) User ID
81
+ # @option attributes [String] :account_name (Required) Name assigned by the platform/marketplace
82
+ # to identify the account (similar to a nickname). Defaults to "My Water Bill Company"
83
+ # @option attributes [Integer] :biller_code (Required) The Biller Code for the biller that will
84
+ # receive the payment. The Biller Code must be a numeric value with 3 to 10 digits.
85
+ # @option attributes [String] :bpay_crn (Required) Customer reference number (crn) to be used for
86
+ # this bpay account. The CRN must contain between 2 and 20 digits. Defaults to "987654321"
87
+ # @return [Response] the API response containing created BPay account
88
+ #
89
+ # @example Create a BPay account
90
+ # bpay_accounts = ZaiPayment::Resources::BpayAccount.new
91
+ # response = bpay_accounts.create(
92
+ # user_id: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
93
+ # account_name: 'My Water Bill Company',
94
+ # biller_code: 123456,
95
+ # bpay_crn: '987654321'
96
+ # )
97
+ #
98
+ # @see https://developer.hellozai.com/reference/createbpayaccount
99
+ def create(**attributes)
100
+ validate_create_attributes!(attributes)
101
+
102
+ body = build_bpay_account_body(attributes)
103
+ client.post('/bpay_accounts', body: body)
104
+ end
105
+
106
+ private
107
+
108
+ def validate_id!(value, field_name)
109
+ return unless value.nil? || value.to_s.strip.empty?
110
+
111
+ raise Errors::ValidationError, "#{field_name} is required and cannot be blank"
112
+ end
113
+
114
+ def validate_create_attributes!(attributes)
115
+ validate_required_attributes!(attributes)
116
+ validate_biller_code!(attributes[:biller_code]) if attributes[:biller_code]
117
+ validate_bpay_crn!(attributes[:bpay_crn]) if attributes[:bpay_crn]
118
+ end
119
+
120
+ def validate_required_attributes!(attributes)
121
+ required_fields = %i[user_id account_name biller_code bpay_crn]
122
+
123
+ missing_fields = required_fields.select do |field|
124
+ attributes[field].nil? || (attributes[field].respond_to?(:to_s) && attributes[field].to_s.strip.empty?)
125
+ end
126
+
127
+ return if missing_fields.empty?
128
+
129
+ raise Errors::ValidationError,
130
+ "Missing required fields: #{missing_fields.join(', ')}"
131
+ end
132
+
133
+ def validate_biller_code!(biller_code)
134
+ # Biller code must be a numeric value with 3 to 10 digits
135
+ biller_code_str = biller_code.to_s
136
+
137
+ return if biller_code_str.match?(/\A\d{3,10}\z/)
138
+
139
+ raise Errors::ValidationError,
140
+ 'biller_code must be a numeric value with 3 to 10 digits'
141
+ end
142
+
143
+ def validate_bpay_crn!(bpay_crn)
144
+ # CRN must contain between 2 and 20 digits
145
+ bpay_crn_str = bpay_crn.to_s
146
+
147
+ return if bpay_crn_str.match?(/\A\d{2,20}\z/)
148
+
149
+ raise Errors::ValidationError,
150
+ 'bpay_crn must contain between 2 and 20 digits'
151
+ end
152
+
153
+ def build_bpay_account_body(attributes)
154
+ body = {}
155
+
156
+ attributes.each do |key, value|
157
+ next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
158
+
159
+ api_field = FIELD_MAPPING[key]
160
+ body[api_field] = value if api_field
161
+ end
162
+
163
+ body
164
+ end
165
+ end
166
+ end
167
+ end
@@ -8,6 +8,7 @@ module ZaiPayment
8
8
  RESPONSE_DATA_KEYS = %w[
9
9
  webhooks users items fees transactions
10
10
  batch_transactions bpay_accounts bank_accounts card_accounts
11
+ routing_number
11
12
  ].freeze
12
13
 
13
14
  def initialize(faraday_response)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZaiPayment
4
- VERSION = '2.4.0'
4
+ VERSION = '2.6.0'
5
5
  end
data/lib/zai_payment.rb CHANGED
@@ -14,6 +14,8 @@ require_relative 'zai_payment/resources/webhook'
14
14
  require_relative 'zai_payment/resources/user'
15
15
  require_relative 'zai_payment/resources/item'
16
16
  require_relative 'zai_payment/resources/token_auth'
17
+ require_relative 'zai_payment/resources/bank_account'
18
+ require_relative 'zai_payment/resources/bpay_account'
17
19
 
18
20
  module ZaiPayment
19
21
  class << self
@@ -57,5 +59,15 @@ module ZaiPayment
57
59
  def token_auths
58
60
  @token_auths ||= Resources::TokenAuth.new(client: Client.new(base_endpoint: :core_base))
59
61
  end
62
+
63
+ # @return [ZaiPayment::Resources::BankAccount] bank_account resource instance
64
+ def bank_accounts
65
+ @bank_accounts ||= Resources::BankAccount.new(client: Client.new(base_endpoint: :core_base))
66
+ end
67
+
68
+ # @return [ZaiPayment::Resources::BpayAccount] bpay_account resource instance
69
+ def bpay_accounts
70
+ @bpay_accounts ||= Resources::BpayAccount.new(client: Client.new(base_endpoint: :core_base))
71
+ end
60
72
  end
61
73
  end