forest_admin_datasource_mambu_payments 1.33.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/.rspec +3 -0
- data/Rakefile +6 -0
- data/forest_admin_datasource_mambu_payments.gemspec +37 -0
- data/lib/forest_admin_datasource_mambu_payments/client/reads.rb +66 -0
- data/lib/forest_admin_datasource_mambu_payments/client/writes.rb +42 -0
- data/lib/forest_admin_datasource_mambu_payments/client.rb +166 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/account_holder.rb +64 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/balance.rb +75 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/base_collection.rb +254 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/claim.rb +98 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/connected_account.rb +103 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/direct_debit_mandate.rb +125 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/event.rb +133 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/expected_payment.rb +132 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/external_account.rb +121 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/file.rb +89 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/incoming_payment.rb +120 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/internal_account.rb +136 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/payee_verification_request.rb +88 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/payment_capture.rb +136 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/payment_order.rb +132 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/reconciliation.rb +93 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/return.rb +132 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/transaction.rb +113 -0
- data/lib/forest_admin_datasource_mambu_payments/configuration.rb +36 -0
- data/lib/forest_admin_datasource_mambu_payments/datasource.rb +35 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/disable_search.rb +31 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/helpers.rb +94 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/messages.rb +30 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/holder_link_plugin.rb +56 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_account_holder_to_direct_debit_mandates.rb +14 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_account_holder_to_incoming_payments.rb +14 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_external_account_to_direct_debit_mandates.rb +13 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_external_account_to_incoming_payments.rb +13 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_external_account_to_payment_orders.rb +13 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_events.rb +13 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_expected_payments.rb +21 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_returns.rb +12 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_transactions.rb +20 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_internal_account_to_balances.rb +17 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_internal_account_to_incoming_payments.rb +13 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_internal_account_to_payment_orders.rb +17 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_payment_order_to_events.rb +13 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_payment_order_to_receiving_account_holder.rb +15 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_payment_order_to_returns.rb +12 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_payment_order_to_transactions.rb +51 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/one_to_many_link_plugin.rb +35 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/pivot_resolution.rb +73 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_connected_account_filter.rb +38 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_cross_reconciliation_filter.rb +55 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_holder_filter.rb +32 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_link_plugin.rb +64 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_reconciliation_filter.rb +39 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/approve_payment_order.rb +56 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/cancel_payment_order.rb +66 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/create_account_holder.rb +44 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/create_external_account.rb +54 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/create_internal_account.rb +58 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/create_payment_order.rb +66 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/trigger_payee_verification.rb +58 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/update_account_holder.rb +67 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/update_external_account.rb +75 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/update_internal_account.rb +75 -0
- data/lib/forest_admin_datasource_mambu_payments/query/condition_tree_translator.rb +115 -0
- data/lib/forest_admin_datasource_mambu_payments/version.rb +3 -0
- data/lib/forest_admin_datasource_mambu_payments.rb +44 -0
- metadata +170 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# rubocop:disable Metrics/ClassLength, Metrics/MethodLength
|
|
2
|
+
module ForestAdminDatasourceMambuPayments
|
|
3
|
+
module Collections
|
|
4
|
+
class Return < BaseCollection
|
|
5
|
+
ManyToOneSchema = ForestAdminDatasourceToolkit::Schema::Relations::ManyToOneSchema
|
|
6
|
+
|
|
7
|
+
ENUM_DIRECTION = %w[credit debit].freeze
|
|
8
|
+
ENUM_TYPE = %w[sepa sepa_instant].freeze
|
|
9
|
+
ENUM_RETURN_TYPE = %w[return refund reversal].freeze
|
|
10
|
+
ENUM_STATUS = %w[pending sent processing executed received rejected].freeze
|
|
11
|
+
ENUM_RELATED_PAYMENT = %w[payment_order incoming_payment].freeze
|
|
12
|
+
|
|
13
|
+
client_resource :return
|
|
14
|
+
|
|
15
|
+
def initialize(datasource)
|
|
16
|
+
super(datasource, 'MambuReturn')
|
|
17
|
+
define_schema
|
|
18
|
+
define_relations
|
|
19
|
+
reconcile_filter_operators!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create(_caller, data)
|
|
23
|
+
serialize(datasource.client.create_return(build_payload(data)))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def update(caller, filter, patch)
|
|
27
|
+
payload = build_payload(patch)
|
|
28
|
+
ids_for(caller, filter).each { |id| datasource.client.update_return(id, payload) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def serialize(record)
|
|
32
|
+
a = attrs_of(record)
|
|
33
|
+
{
|
|
34
|
+
'id' => a['id'],
|
|
35
|
+
'object' => a['object'],
|
|
36
|
+
'connected_account_id' => a['connected_account_id'],
|
|
37
|
+
'related_payment_id' => a['related_payment_id'],
|
|
38
|
+
'related_payment_type' => a['related_payment_type'],
|
|
39
|
+
'related_payment_suspended' => a['related_payment_suspended'],
|
|
40
|
+
'return_type' => a['return_type'],
|
|
41
|
+
'type' => a['type'],
|
|
42
|
+
'direction' => a['direction'],
|
|
43
|
+
'status' => a['status'],
|
|
44
|
+
'status_details' => a['status_details'],
|
|
45
|
+
'return_reason' => a['return_reason'],
|
|
46
|
+
'amount' => a['amount'],
|
|
47
|
+
'currency' => a['currency'],
|
|
48
|
+
'reconciliation_status' => a['reconciliation_status'],
|
|
49
|
+
'reconciled_amount' => a['reconciled_amount'],
|
|
50
|
+
'value_date' => a['value_date'],
|
|
51
|
+
'booking_date' => a['booking_date'],
|
|
52
|
+
'originating_account' => a['originating_account'],
|
|
53
|
+
'receiving_account' => a['receiving_account'],
|
|
54
|
+
'aggregation_reference' => a['aggregation_reference'],
|
|
55
|
+
'file_id' => a['file_id'],
|
|
56
|
+
'metadata' => a['metadata'],
|
|
57
|
+
'created_at' => a['created_at']
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
protected
|
|
62
|
+
|
|
63
|
+
def collection_filters
|
|
64
|
+
{
|
|
65
|
+
'connected_account_id' => { ops: [Operators::EQUAL, Operators::IN] },
|
|
66
|
+
'related_payment_id' => { ops: [Operators::EQUAL, Operators::IN] },
|
|
67
|
+
'status' => { ops: [Operators::EQUAL, Operators::IN] },
|
|
68
|
+
'type' => { ops: [Operators::EQUAL, Operators::IN] }
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def many_to_one_embeds
|
|
73
|
+
[
|
|
74
|
+
{ foreign_key: 'connected_account_id', relation_name: 'connected_account',
|
|
75
|
+
collection: 'MambuConnectedAccount' }
|
|
76
|
+
]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def define_schema
|
|
82
|
+
add_field('id', ColumnSchema.new(column_type: 'String', is_primary_key: true,
|
|
83
|
+
is_read_only: true, is_sortable: true))
|
|
84
|
+
add_field('object', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
|
|
85
|
+
add_field('connected_account_id', ColumnSchema.new(column_type: 'String',
|
|
86
|
+
is_read_only: true, is_sortable: true))
|
|
87
|
+
# related_payment_id can target a payment_order OR an incoming_payment
|
|
88
|
+
# depending on related_payment_type — we expose it as a plain string
|
|
89
|
+
# rather than a typed relation (Forest can't model the polymorphism).
|
|
90
|
+
add_field('related_payment_id', ColumnSchema.new(column_type: 'String',
|
|
91
|
+
is_read_only: false, is_sortable: false))
|
|
92
|
+
add_field('related_payment_type', ColumnSchema.new(column_type: 'Enum', enum_values: ENUM_RELATED_PAYMENT,
|
|
93
|
+
is_read_only: true, is_sortable: false))
|
|
94
|
+
add_field('related_payment_suspended', ColumnSchema.new(column_type: 'Boolean',
|
|
95
|
+
is_read_only: false, is_sortable: false))
|
|
96
|
+
add_field('return_type', ColumnSchema.new(column_type: 'Enum', enum_values: ENUM_RETURN_TYPE,
|
|
97
|
+
is_read_only: true, is_sortable: true))
|
|
98
|
+
add_field('type', ColumnSchema.new(column_type: 'Enum', enum_values: ENUM_TYPE,
|
|
99
|
+
is_read_only: true, is_sortable: true))
|
|
100
|
+
add_field('direction', ColumnSchema.new(column_type: 'Enum', enum_values: ENUM_DIRECTION,
|
|
101
|
+
is_read_only: true, is_sortable: false))
|
|
102
|
+
add_field('status', ColumnSchema.new(column_type: 'Enum', enum_values: ENUM_STATUS,
|
|
103
|
+
is_read_only: false, is_sortable: true))
|
|
104
|
+
add_field('status_details', ColumnSchema.new(column_type: 'String', is_read_only: false, is_sortable: false))
|
|
105
|
+
add_field('return_reason', ColumnSchema.new(column_type: 'String', is_read_only: false, is_sortable: false))
|
|
106
|
+
add_field('amount', ColumnSchema.new(column_type: 'Number', is_read_only: true, is_sortable: false))
|
|
107
|
+
add_field('currency', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
|
|
108
|
+
add_field('reconciliation_status', ColumnSchema.new(column_type: 'String', is_read_only: true,
|
|
109
|
+
is_sortable: false))
|
|
110
|
+
add_field('reconciled_amount', ColumnSchema.new(column_type: 'Number', is_read_only: true, is_sortable: false))
|
|
111
|
+
add_field('value_date', ColumnSchema.new(column_type: 'Date', is_read_only: true, is_sortable: true))
|
|
112
|
+
add_field('booking_date', ColumnSchema.new(column_type: 'Date', is_read_only: true, is_sortable: true))
|
|
113
|
+
add_field('originating_account', ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
|
|
114
|
+
add_field('receiving_account', ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
|
|
115
|
+
add_field('aggregation_reference', ColumnSchema.new(column_type: 'String', is_read_only: true,
|
|
116
|
+
is_sortable: false))
|
|
117
|
+
add_field('file_id', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
|
|
118
|
+
add_field('metadata', ColumnSchema.new(column_type: 'Json', is_read_only: false, is_sortable: false))
|
|
119
|
+
add_field('created_at', ColumnSchema.new(column_type: 'Date', is_read_only: true, is_sortable: true))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def define_relations
|
|
123
|
+
add_field('connected_account', ManyToOneSchema.new(
|
|
124
|
+
foreign_collection: 'MambuConnectedAccount',
|
|
125
|
+
foreign_key: 'connected_account_id',
|
|
126
|
+
foreign_key_target: 'id'
|
|
127
|
+
))
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
# rubocop:enable Metrics/ClassLength, Metrics/MethodLength
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Collections
|
|
3
|
+
class Transaction < BaseCollection
|
|
4
|
+
ManyToOneSchema = ForestAdminDatasourceToolkit::Schema::Relations::ManyToOneSchema
|
|
5
|
+
|
|
6
|
+
ENUM_DIRECTION = %w[debit credit].freeze
|
|
7
|
+
|
|
8
|
+
client_resource :transaction
|
|
9
|
+
|
|
10
|
+
def initialize(datasource)
|
|
11
|
+
super(datasource, 'MambuTransaction')
|
|
12
|
+
define_schema
|
|
13
|
+
define_relations
|
|
14
|
+
reconcile_filter_operators!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def serialize(record)
|
|
18
|
+
a = attrs_of(record)
|
|
19
|
+
{
|
|
20
|
+
'id' => a['id'],
|
|
21
|
+
'object' => a['object'],
|
|
22
|
+
'connected_account_id' => a['connected_account_id'],
|
|
23
|
+
'category' => a['category'],
|
|
24
|
+
'direction' => a['direction'],
|
|
25
|
+
'amount' => a['amount'],
|
|
26
|
+
'currency' => a['currency'],
|
|
27
|
+
'description' => a['description'],
|
|
28
|
+
'structured_reference' => a['structured_reference'],
|
|
29
|
+
'value_date' => a['value_date'],
|
|
30
|
+
'booking_date' => a['booking_date'],
|
|
31
|
+
'internal_account_id' => a['internal_account_id'],
|
|
32
|
+
'external_account_id' => a['external_account_id'],
|
|
33
|
+
'uetr' => a['uetr'],
|
|
34
|
+
'bank_data' => a['bank_data'],
|
|
35
|
+
'reconciliation_status' => a['reconciliation_status'],
|
|
36
|
+
'reconciled_amount' => a['reconciled_amount'],
|
|
37
|
+
'custom_fields' => a['custom_fields'],
|
|
38
|
+
'created_at' => a['created_at']
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
protected
|
|
43
|
+
|
|
44
|
+
def collection_filters
|
|
45
|
+
{
|
|
46
|
+
'connected_account_id' => { ops: [Operators::EQUAL, Operators::IN] },
|
|
47
|
+
'internal_account_id' => { ops: [Operators::EQUAL, Operators::IN] },
|
|
48
|
+
'external_account_id' => { ops: [Operators::EQUAL, Operators::IN] }
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def many_to_one_embeds
|
|
53
|
+
[
|
|
54
|
+
{ foreign_key: 'connected_account_id', relation_name: 'connected_account',
|
|
55
|
+
collection: 'MambuConnectedAccount' },
|
|
56
|
+
{ foreign_key: 'internal_account_id', relation_name: 'internal_account',
|
|
57
|
+
collection: 'MambuInternalAccount' },
|
|
58
|
+
{ foreign_key: 'external_account_id', relation_name: 'external_account',
|
|
59
|
+
collection: 'MambuExternalAccount' }
|
|
60
|
+
]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def define_schema
|
|
66
|
+
add_field('id', ColumnSchema.new(column_type: 'String', is_primary_key: true,
|
|
67
|
+
is_read_only: true, is_sortable: true))
|
|
68
|
+
add_field('object', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
|
|
69
|
+
add_field('connected_account_id', ColumnSchema.new(column_type: 'String',
|
|
70
|
+
is_read_only: true, is_sortable: true))
|
|
71
|
+
add_field('category', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: true))
|
|
72
|
+
add_field('direction', ColumnSchema.new(column_type: 'Enum', enum_values: ENUM_DIRECTION,
|
|
73
|
+
is_read_only: true, is_sortable: false))
|
|
74
|
+
add_field('amount', ColumnSchema.new(column_type: 'Number', is_read_only: true, is_sortable: false))
|
|
75
|
+
add_field('currency', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
|
|
76
|
+
add_field('description', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
|
|
77
|
+
add_field('structured_reference', ColumnSchema.new(column_type: 'String', is_read_only: true,
|
|
78
|
+
is_sortable: false))
|
|
79
|
+
add_field('value_date', ColumnSchema.new(column_type: 'Date', is_read_only: true, is_sortable: true))
|
|
80
|
+
add_field('booking_date', ColumnSchema.new(column_type: 'Date', is_read_only: true, is_sortable: true))
|
|
81
|
+
add_field('internal_account_id', ColumnSchema.new(column_type: 'String',
|
|
82
|
+
is_read_only: true, is_sortable: false))
|
|
83
|
+
add_field('external_account_id', ColumnSchema.new(column_type: 'String',
|
|
84
|
+
is_read_only: true, is_sortable: false))
|
|
85
|
+
add_field('uetr', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
|
|
86
|
+
add_field('bank_data', ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
|
|
87
|
+
add_field('reconciliation_status', ColumnSchema.new(column_type: 'String', is_read_only: true,
|
|
88
|
+
is_sortable: true))
|
|
89
|
+
add_field('reconciled_amount', ColumnSchema.new(column_type: 'Number', is_read_only: true, is_sortable: false))
|
|
90
|
+
add_field('custom_fields', ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
|
|
91
|
+
add_field('created_at', ColumnSchema.new(column_type: 'Date', is_read_only: true, is_sortable: true))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def define_relations
|
|
95
|
+
add_field('connected_account', ManyToOneSchema.new(
|
|
96
|
+
foreign_collection: 'MambuConnectedAccount',
|
|
97
|
+
foreign_key: 'connected_account_id',
|
|
98
|
+
foreign_key_target: 'id'
|
|
99
|
+
))
|
|
100
|
+
add_field('internal_account', ManyToOneSchema.new(
|
|
101
|
+
foreign_collection: 'MambuInternalAccount',
|
|
102
|
+
foreign_key: 'internal_account_id',
|
|
103
|
+
foreign_key_target: 'id'
|
|
104
|
+
))
|
|
105
|
+
add_field('external_account', ManyToOneSchema.new(
|
|
106
|
+
foreign_collection: 'MambuExternalAccount',
|
|
107
|
+
foreign_key: 'external_account_id',
|
|
108
|
+
foreign_key_target: 'id'
|
|
109
|
+
))
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
class Configuration
|
|
3
|
+
DEFAULT_BASE_URL = 'https://api.numeral.io'.freeze
|
|
4
|
+
SANDBOX_BASE_URL = 'https://api.sandbox.numeral.io'.freeze
|
|
5
|
+
API_VERSION = 'v1'.freeze
|
|
6
|
+
|
|
7
|
+
attr_reader :api_key, :base_url, :open_timeout, :timeout
|
|
8
|
+
|
|
9
|
+
def initialize(api_key:, base_url: nil, sandbox: false, open_timeout: 5, timeout: 30)
|
|
10
|
+
@api_key = api_key
|
|
11
|
+
@base_url = base_url || (sandbox ? SANDBOX_BASE_URL : DEFAULT_BASE_URL)
|
|
12
|
+
@open_timeout = open_timeout
|
|
13
|
+
@timeout = timeout
|
|
14
|
+
validate!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def url
|
|
18
|
+
"#{@base_url.chomp("/")}/#{API_VERSION}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def validate!
|
|
24
|
+
missing = []
|
|
25
|
+
missing << 'api_key' if blank?(@api_key)
|
|
26
|
+
return if missing.empty?
|
|
27
|
+
|
|
28
|
+
raise ConfigurationError,
|
|
29
|
+
"ForestAdminDatasourceMambuPayments missing required config: #{missing.join(", ")}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def blank?(value)
|
|
33
|
+
value.nil? || value.to_s.strip.empty?
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
class Datasource < ForestAdminDatasourceToolkit::Datasource
|
|
3
|
+
attr_reader :client, :configuration
|
|
4
|
+
|
|
5
|
+
def initialize(api_key:, base_url: nil, sandbox: false)
|
|
6
|
+
super()
|
|
7
|
+
@configuration = Configuration.new(api_key: api_key, base_url: base_url, sandbox: sandbox)
|
|
8
|
+
@client = Client.new(@configuration)
|
|
9
|
+
|
|
10
|
+
register_collections
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def register_collections
|
|
16
|
+
add_collection(Collections::ConnectedAccount.new(self))
|
|
17
|
+
add_collection(Collections::PaymentOrder.new(self))
|
|
18
|
+
add_collection(Collections::Transaction.new(self))
|
|
19
|
+
add_collection(Collections::Balance.new(self))
|
|
20
|
+
add_collection(Collections::AccountHolder.new(self))
|
|
21
|
+
add_collection(Collections::ExternalAccount.new(self))
|
|
22
|
+
add_collection(Collections::InternalAccount.new(self))
|
|
23
|
+
add_collection(Collections::IncomingPayment.new(self))
|
|
24
|
+
add_collection(Collections::DirectDebitMandate.new(self))
|
|
25
|
+
add_collection(Collections::ExpectedPayment.new(self))
|
|
26
|
+
add_collection(Collections::Event.new(self))
|
|
27
|
+
add_collection(Collections::File.new(self))
|
|
28
|
+
add_collection(Collections::Return.new(self))
|
|
29
|
+
add_collection(Collections::Claim.new(self))
|
|
30
|
+
add_collection(Collections::Reconciliation.new(self))
|
|
31
|
+
add_collection(Collections::PaymentCapture.new(self))
|
|
32
|
+
add_collection(Collections::PayeeVerificationRequest.new(self))
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Plugins
|
|
3
|
+
# Numeral's list endpoints have no free-text search. Forest emulates search
|
|
4
|
+
# by OR-ing a condition over every searchable column, which the
|
|
5
|
+
# ConditionTreeTranslator deliberately rejects (it cannot push an OR to
|
|
6
|
+
# Numeral) — so an operator typing in the search bar would get an error.
|
|
7
|
+
#
|
|
8
|
+
# This plugin turns the search bar off on every Mambu collection, which is
|
|
9
|
+
# the honest behaviour: the API can filter (per `api_filters`) but not
|
|
10
|
+
# search. Install it once at the datasource level:
|
|
11
|
+
#
|
|
12
|
+
# @agent.use(ForestAdminDatasourceMambuPayments::Plugins::DisableSearch, {})
|
|
13
|
+
class DisableSearch < ForestAdminDatasourceCustomizer::Plugins::Plugin
|
|
14
|
+
COLLECTIONS = %w[
|
|
15
|
+
MambuConnectedAccount MambuPaymentOrder MambuTransaction MambuBalance
|
|
16
|
+
MambuAccountHolder MambuExternalAccount MambuInternalAccount
|
|
17
|
+
MambuIncomingPayment MambuDirectDebitMandate MambuExpectedPayment
|
|
18
|
+
MambuEvent MambuFile MambuReturn MambuClaim MambuReconciliation
|
|
19
|
+
MambuPaymentCapture MambuPayeeVerificationRequest
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
def run(datasource_customizer, _collection_customizer = nil, _options = {})
|
|
23
|
+
Helpers.require_datasource!(datasource_customizer, self.class)
|
|
24
|
+
|
|
25
|
+
COLLECTIONS.each do |name|
|
|
26
|
+
datasource_customizer.customize_collection(name, &:disable_search)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Plugins
|
|
3
|
+
# Shared helpers for Mambu Payments plugins: input normalization, host
|
|
4
|
+
# record id resolution, and per-id rescue logic for bulk transitions.
|
|
5
|
+
module Helpers
|
|
6
|
+
ActionScope = ForestAdminDatasourceCustomizer::Decorators::Action::Types::ActionScope
|
|
7
|
+
|
|
8
|
+
SCOPE_KEYS = %i[single bulk].freeze
|
|
9
|
+
SCOPES = { single: ActionScope::SINGLE, bulk: ActionScope::BULK }.freeze
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
# Relation plugins must be installed at the datasource level
|
|
14
|
+
# (`@agent.use(plugin, {})`) so they can customize several collections at
|
|
15
|
+
# once. Raises a clear error when a caller installs one on a single
|
|
16
|
+
# collection instead.
|
|
17
|
+
def require_datasource!(datasource_customizer, plugin_class)
|
|
18
|
+
return if datasource_customizer
|
|
19
|
+
|
|
20
|
+
name = plugin_class.is_a?(Class) ? plugin_class.name.split('::').last : plugin_class
|
|
21
|
+
raise ArgumentError,
|
|
22
|
+
"#{name} must be installed at the datasource level via @agent.use(plugin, {})"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def normalize_scopes(value)
|
|
26
|
+
list = Array(value).map(&:to_sym).uniq
|
|
27
|
+
list = SCOPE_KEYS if list.empty?
|
|
28
|
+
unknown = list - SCOPE_KEYS
|
|
29
|
+
return list if unknown.empty?
|
|
30
|
+
|
|
31
|
+
raise ForestAdminDatasourceToolkit::Exceptions::ForestException,
|
|
32
|
+
"Unknown scopes: #{unknown.join(", ")}. Allowed: #{SCOPE_KEYS.join(", ")}."
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resolve_ids(context, field)
|
|
36
|
+
records = context.get_records([field])
|
|
37
|
+
records = [records].compact unless records.is_a?(Array)
|
|
38
|
+
records.filter_map { |r| r[field] || r[field.to_sym] }
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
ForestAdminDatasourceMambuPayments.logger.warn(
|
|
41
|
+
"[forest_admin_datasource_mambu_payments] failed to resolve ids from '#{field}': " \
|
|
42
|
+
"#{e.class}: #{e.message}"
|
|
43
|
+
)
|
|
44
|
+
[]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Per-id rescue so a single API failure doesn't abort the remaining ids.
|
|
48
|
+
def each_with_rescue(ids, label)
|
|
49
|
+
succeeded = []
|
|
50
|
+
failed = []
|
|
51
|
+
ids.each do |id|
|
|
52
|
+
yield id
|
|
53
|
+
succeeded << id
|
|
54
|
+
rescue StandardError => e
|
|
55
|
+
ForestAdminDatasourceMambuPayments.logger.warn(
|
|
56
|
+
"[forest_admin_datasource_mambu_payments] #{label} failed for ##{id}: #{e.class}: #{e.message}"
|
|
57
|
+
)
|
|
58
|
+
failed << [id, "#{e.class}: #{e.message}"]
|
|
59
|
+
end
|
|
60
|
+
[succeeded, failed]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def present?(value)
|
|
64
|
+
!value.nil? && value.to_s != ''
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def to_int(value)
|
|
68
|
+
return nil unless present?(value)
|
|
69
|
+
|
|
70
|
+
Integer(value.to_s)
|
|
71
|
+
rescue ArgumentError, TypeError
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def write_back(context, field, value)
|
|
76
|
+
return :skipped if field.nil? || value.nil?
|
|
77
|
+
|
|
78
|
+
context.collection.update(context.filter, { field => value })
|
|
79
|
+
:ok
|
|
80
|
+
rescue StandardError => e
|
|
81
|
+
ForestAdminDatasourceMambuPayments.logger.warn(
|
|
82
|
+
"[forest_admin_datasource_mambu_payments] write-back to '#{field}' failed: #{e.class}: #{e.message}"
|
|
83
|
+
)
|
|
84
|
+
[:failed, "#{e.class}: #{e.message}"]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def write_back_warning(writeback)
|
|
88
|
+
return nil unless writeback.is_a?(Array) && writeback.first == :failed
|
|
89
|
+
|
|
90
|
+
" (warning: could not write the id back to the host record: #{writeback.last})"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Plugins
|
|
3
|
+
module Messages
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
def success(succeeded, failed, noun:, verb_past:)
|
|
7
|
+
[succeeded_phrase(succeeded, noun, verb_past), failed_phrase(failed, noun)].compact.join(' ')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def all_failed(failed, noun:, verb:)
|
|
11
|
+
return "Failed to #{verb} #{noun} ##{failed.first.first}: #{failed.first.last}" if failed.size == 1
|
|
12
|
+
|
|
13
|
+
"Failed to #{verb} all #{failed.size} #{noun}s. First error: #{failed.first.last}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def succeeded_phrase(succeeded, noun, verb_past)
|
|
17
|
+
return nil if succeeded.empty?
|
|
18
|
+
return "#{noun.capitalize} ##{succeeded.first} #{verb_past}." if succeeded.size == 1
|
|
19
|
+
|
|
20
|
+
"#{succeeded.size} #{noun}s #{verb_past}."
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def failed_phrase(failed, _noun)
|
|
24
|
+
return nil if failed.empty?
|
|
25
|
+
|
|
26
|
+
"#{failed.size} failed: #{failed.map(&:first).join(", ")}."
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Plugins
|
|
3
|
+
module Relations
|
|
4
|
+
# Base for AccountHolder links reached transitively through an account
|
|
5
|
+
# relation. The `host` imports `account_holder_id` from a related account,
|
|
6
|
+
# exposes a ManyToOne to AccountHolder, and gets a TwoStepHolderFilter
|
|
7
|
+
# (which rewrites holder filters onto `local_fk`); AccountHolder gets the
|
|
8
|
+
# reciprocal OneToMany. Subclasses configure it declaratively:
|
|
9
|
+
#
|
|
10
|
+
# class LinkAccountHolderToIncomingPayments < HolderLinkPlugin
|
|
11
|
+
# link host: 'MambuIncomingPayment', name: 'incoming_payments',
|
|
12
|
+
# local_fk: 'internal_account_id', intermediate: 'MambuInternalAccount',
|
|
13
|
+
# import_path: 'internal_account:account_holder_id'
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# Install at the datasource level: @agent.use(plugin, {}).
|
|
17
|
+
class HolderLinkPlugin < ForestAdminDatasourceCustomizer::Plugins::Plugin
|
|
18
|
+
ACCOUNT_HOLDER = 'MambuAccountHolder'.freeze
|
|
19
|
+
FK_NAME = 'account_holder_id'.freeze
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
attr_reader :config
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# rubocop:disable Metrics/ParameterLists
|
|
26
|
+
def self.link(host:, name:, local_fk:, intermediate:, import_path:, many_to_one_name: 'account_holder')
|
|
27
|
+
@config = {
|
|
28
|
+
host: host, name: name, local_fk: local_fk, intermediate: intermediate,
|
|
29
|
+
import_path: import_path, many_to_one_name: many_to_one_name
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
# rubocop:enable Metrics/ParameterLists
|
|
33
|
+
|
|
34
|
+
def run(datasource_customizer, _collection_customizer = nil, _options = {})
|
|
35
|
+
Plugins::Helpers.require_datasource!(datasource_customizer, self.class)
|
|
36
|
+
|
|
37
|
+
cfg = self.class.config
|
|
38
|
+
datasource_customizer.customize_collection(cfg[:host]) do |c|
|
|
39
|
+
c.import_field(FK_NAME, path: cfg[:import_path], readonly: true)
|
|
40
|
+
c.add_many_to_one_relation(cfg[:many_to_one_name], ACCOUNT_HOLDER,
|
|
41
|
+
foreign_key: FK_NAME, foreign_key_target: 'id')
|
|
42
|
+
TwoStepHolderFilter.install(c,
|
|
43
|
+
fk_name: FK_NAME,
|
|
44
|
+
local_fk: cfg[:local_fk],
|
|
45
|
+
intermediate_collection: cfg[:intermediate])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
datasource_customizer.customize_collection(ACCOUNT_HOLDER) do |c|
|
|
49
|
+
c.add_one_to_many_relation(cfg[:name], cfg[:host],
|
|
50
|
+
origin_key: FK_NAME, origin_key_target: 'id')
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Plugins
|
|
3
|
+
module Relations
|
|
4
|
+
# AccountHolder <-> DirectDebitMandate, transitive via the external account
|
|
5
|
+
# (DirectDebitMandate.external_account.account_holder_id).
|
|
6
|
+
# Install at the datasource level: @agent.use(plugin, {}).
|
|
7
|
+
class LinkAccountHolderToDirectDebitMandates < HolderLinkPlugin
|
|
8
|
+
link host: 'MambuDirectDebitMandate', name: 'direct_debit_mandates',
|
|
9
|
+
local_fk: 'external_account_id', intermediate: 'MambuExternalAccount',
|
|
10
|
+
import_path: 'external_account:account_holder_id'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Plugins
|
|
3
|
+
module Relations
|
|
4
|
+
# AccountHolder <-> IncomingPayment, transitive via the internal account
|
|
5
|
+
# (IncomingPayment.internal_account.account_holder_id).
|
|
6
|
+
# Install at the datasource level: @agent.use(plugin, {}).
|
|
7
|
+
class LinkAccountHolderToIncomingPayments < HolderLinkPlugin
|
|
8
|
+
link host: 'MambuIncomingPayment', name: 'incoming_payments',
|
|
9
|
+
local_fk: 'internal_account_id', intermediate: 'MambuInternalAccount',
|
|
10
|
+
import_path: 'internal_account:account_holder_id'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Plugins
|
|
3
|
+
module Relations
|
|
4
|
+
# Reciprocal OneToMany on MambuExternalAccount for the native
|
|
5
|
+
# DirectDebitMandate.external_account ManyToOne.
|
|
6
|
+
# Install at the datasource level: @agent.use(plugin, {}).
|
|
7
|
+
class LinkExternalAccountToDirectDebitMandates < OneToManyLinkPlugin
|
|
8
|
+
link host: 'MambuExternalAccount', to: 'MambuDirectDebitMandate',
|
|
9
|
+
name: 'direct_debit_mandates', origin_key: 'external_account_id'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Plugins
|
|
3
|
+
module Relations
|
|
4
|
+
# Reciprocal OneToMany on MambuExternalAccount for the native
|
|
5
|
+
# IncomingPayment.external_account ManyToOne.
|
|
6
|
+
# Install at the datasource level: @agent.use(plugin, {}).
|
|
7
|
+
class LinkExternalAccountToIncomingPayments < OneToManyLinkPlugin
|
|
8
|
+
link host: 'MambuExternalAccount', to: 'MambuIncomingPayment',
|
|
9
|
+
name: 'incoming_payments', origin_key: 'external_account_id'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Plugins
|
|
3
|
+
module Relations
|
|
4
|
+
# Reciprocal OneToMany on MambuExternalAccount for the native
|
|
5
|
+
# PaymentOrder.external_account ManyToOne (receiving account).
|
|
6
|
+
# Install at the datasource level: @agent.use(plugin, {}).
|
|
7
|
+
class LinkExternalAccountToPaymentOrders < OneToManyLinkPlugin
|
|
8
|
+
link host: 'MambuExternalAccount', to: 'MambuPaymentOrder',
|
|
9
|
+
name: 'payment_orders', origin_key: 'receiving_account_id'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_events.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Plugins
|
|
3
|
+
module Relations
|
|
4
|
+
# OneToMany on MambuIncomingPayment over Event.related_object_id
|
|
5
|
+
# (events emitted for this incoming payment).
|
|
6
|
+
# Install at the datasource level: @agent.use(plugin, {}).
|
|
7
|
+
class LinkIncomingPaymentToEvents < OneToManyLinkPlugin
|
|
8
|
+
link host: 'MambuIncomingPayment', to: 'MambuEvent',
|
|
9
|
+
name: 'events', origin_key: 'related_object_id'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Plugins
|
|
3
|
+
module Relations
|
|
4
|
+
# IncomingPayment <-> ExpectedPayment matched through two reconciliations
|
|
5
|
+
# sharing a transaction (cross-pivot resolution).
|
|
6
|
+
# Install at the datasource level: @agent.use(plugin, {}).
|
|
7
|
+
class LinkIncomingPaymentToExpectedPayments < TwoStepLinkPlugin
|
|
8
|
+
link owner: 'MambuIncomingPayment', filtered: 'MambuExpectedPayment',
|
|
9
|
+
name: 'matched_expected_payments', foreign_key: 'incoming_payment_id'
|
|
10
|
+
|
|
11
|
+
def install_source_filter(collection)
|
|
12
|
+
TwoStepCrossReconciliationFilter.install(collection,
|
|
13
|
+
fk_name: 'incoming_payment_id',
|
|
14
|
+
src_payment_type: 'incoming_payment',
|
|
15
|
+
dst_payment_type: 'expected_payment',
|
|
16
|
+
target_field: 'id')
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module ForestAdminDatasourceMambuPayments
|
|
2
|
+
module Plugins
|
|
3
|
+
module Relations
|
|
4
|
+
# OneToMany on MambuIncomingPayment over Return.related_payment_id.
|
|
5
|
+
# Install at the datasource level: @agent.use(plugin, {}).
|
|
6
|
+
class LinkIncomingPaymentToReturns < OneToManyLinkPlugin
|
|
7
|
+
link host: 'MambuIncomingPayment', to: 'MambuReturn',
|
|
8
|
+
name: 'returns', origin_key: 'related_payment_id'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|