diff --git a/docs/interoperation/specification/json/bulkTransactions_post_sync_response.json b/docs/interoperation/specification/json/bulkTransactions_post_sync_response.json new file mode 100644 index 00000000000..1bb95ab40bb --- /dev/null +++ b/docs/interoperation/specification/json/bulkTransactions_post_sync_response.json @@ -0,0 +1,7 @@ +//Request URI: +//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/bulkTransactions + +//Response Body: +{ + "bulkTransactionId": "11436b17-c690-4a30-8505-42a2c4eafb9d" // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ +} diff --git a/docs/interoperation/specification/json/bulkTransactions_put_async_response.json b/docs/interoperation/specification/json/bulkTransactions_put_async_response.json new file mode 100644 index 00000000000..c6899d75e3f --- /dev/null +++ b/docs/interoperation/specification/json/bulkTransactions_put_async_response.json @@ -0,0 +1,28 @@ +//Request URI: +//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/bulkTransactions +//GET paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/bulkTransactions/11436b17-c690-4a30-8505-42a2c4eafb9d + +//Response URI: +//PUT /interoperation/bulkTransactions/11436b17-c690-4a30-8505-42a2c4eafb9d + +//Header: +//X-Tenant-Identifier: T111 + +//Response Body: +{ + "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36) + "completedTimestamp": "2017-11-16T04:15:35.513+01:00", // optional, ISO 8601 + "bulkTransferState": "COMPLETED", // mandatory, Enum of String(1..32): RECEIVED, PENDING, ACCEPTED, PROCESSING, COMPLETED, REJECTED + "individualTransferResults": [ // mandatory, (1..1000) + { + "transferId": "11436b17-c690-4a30-8505-42a2c4eafb9d", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + "completedTimestamp": "2017-11-16T04:15:35.513+01:00", // optional, ISO 8601 + "transferState": "COMMITTED"// mandatory, Enum of String(1..32): RECEIVED, RESERVED, COMMITTED, ABORTED + }, + { + "transferId": "11436b17-c690-4a30-8505-42a2c4eafb9d", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + "completedTimestamp": "2017-11-16T04:15:35.513+01:00", // optional, ISO 8601 + "transferState": "COMMITTED"// mandatory, Enum of String(1..32): RECEIVED, RESERVED, COMMITTED, ABORTED + } + ] +} diff --git a/docs/interoperation/specification/json/bulk_payer_post_request.json b/docs/interoperation/specification/json/bulk_payer_post_request.json new file mode 100644 index 00000000000..9cfcc9ddd40 --- /dev/null +++ b/docs/interoperation/specification/json/bulk_payer_post_request.json @@ -0,0 +1,60 @@ +//Request URI: +//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/bulkTransactions +// HTTP/1.1 + +//Request Header: +//Accept: application/vnd.interoperability.quotes+json;version=1 +//Content-Type: application/vnd.interoperability.quotes+json;version=1.0 +//Content-Length: 975 +//Date: Tue, 15 Nov 2017 10: 13:40 GMT +//Origin: http://www.example.com +//X-Tenant-Identifier: T111 + +//Request Body: +{ + "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36) + "payer": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128) + "partySubIdOrType": "something" // optional, String(1..128) + } + }, + "amountType": "RECEIVE", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE + "individualTransfers": [ // mandatory, (1..1000) + { + "payee": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "IN93116000060000000012345671", // mandatory, String(1..128) + "partySubIdOrType": "nothing", // optional, String(1..128) + } + }, + "amount": { // mandatory + "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ + "currency": "IDR" // mandatory, ISO 4217 (Rupee) + } + }, + { + "payee": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "IN93116000060000000012345672", // mandatory, String(1..128) + "partySubIdOrType": "nothing", // optional, String(1..128) + } + }, + "amount": { // mandatory + "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ + "currency": "IDR" // mandatory, ISO 4217 (Rupee) + } + } + ], + "transactionType": { // mandatory + "scenario": "TRANSFER", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND + "initiator": "PAYER", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE + "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE + }, + "note": "From Mats", // optional, String(1..128) + "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601 +} + diff --git a/docs/interoperation/specification/json/merchant_payer_post_request.json b/docs/interoperation/specification/json/merchant_payer_post_request.json new file mode 100644 index 00000000000..ce5c20bc9da --- /dev/null +++ b/docs/interoperation/specification/json/merchant_payer_post_request.json @@ -0,0 +1,46 @@ +//Request URI: +//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/transactions +// HTTP/1.1 + +//Request Header: +//Accept: application/vnd.interoperability.quotes+json;version=1 +//Content-Type: application/vnd.interoperability.quotes+json;version=1.0 +//Content-Length: 975 +//Date: Tue, 15 Nov 2017 10: 13:40 GMT +//Origin: http://www.example.com +//FSPIOP-Callback: http://www.client-server.com/demo +//FSPIOP-Notification: http://www.client-server.com/demo +//X-Tenant-Identifier: T111 + +//Request Body: +{ + "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36) + "payee": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128) + "partySubIdOrType": "something" // optional, String(1..128) + }, + "merchantClassificationCode": "" // optional, String(1..4 digits) + }, + "payer": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "IN4550000000058398257466", // mandatory, String(1..128) + "partySubIdOrType": "nothing", // optional, String(1..128) + "fspId": "BankNrOne" // optional, String(1..32) + } + }, + "amountType": "RECEIVE", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE + "amount": { // mandatory + "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ + "currency": "IDR" // mandatory, ISO 4217 (Rupee) + }, + "transactionType": { // mandatory + "scenario": "PAYMENT", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND + "initiator": "PAYER", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE + "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE + }, + "note": "From Mats", // optional, String(1..128) + "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601 +} diff --git a/docs/interoperation/specification/json/p2p_payee_post_request.json b/docs/interoperation/specification/json/p2p_payee_post_request.json new file mode 100644 index 00000000000..84a96a785e6 --- /dev/null +++ b/docs/interoperation/specification/json/p2p_payee_post_request.json @@ -0,0 +1,42 @@ +//Request URI: +//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/transactions +// HTTP/1.1 + +//Request Header: +//Accept: application/vnd.interoperability.quotes+json;version=1 +//Content-Type: application/vnd.interoperability.quotes+json;version=1.0 +//Content-Length: 975 +//Date: Tue, 15 Nov 2017 10: 13:40 GMT +//Origin: http://www.example.com +//X-Tenant-Identifier: T111 + +//Request Body: +{ + "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36) + "payee": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128) + "partySubIdOrType": "something" // optional, String(1..128) + } + }, + "payer": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "MSISDN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "+12345678901", // mandatory, String(1..128) + "partySubIdOrType": "nothing", // optional, String(1..128) + } + }, + "amountType": "SEND", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE + "amount": { // mandatory + "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ + "currency": "IDR" // mandatory, ISO 4217 (Rupee) + }, + "transactionType": { // mandatory + "scenario": "TRANSFER", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND + "initiator": "PAYEE", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE + "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE + }, + "note": "From Mats", // optional, String(1..128) + "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601 +} diff --git a/docs/interoperation/specification/json/p2p_payer_post_request.json b/docs/interoperation/specification/json/p2p_payer_post_request.json new file mode 100644 index 00000000000..848211d371d --- /dev/null +++ b/docs/interoperation/specification/json/p2p_payer_post_request.json @@ -0,0 +1,42 @@ +//Request URI: +//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/transactions +// HTTP/1.1 + +//Request Header: +//Accept: application/vnd.interoperability.quotes+json;version=1 +//Content-Type: application/vnd.interoperability.quotes+json;version=1.0 +//Content-Length: 975 +//Date: Tue, 15 Nov 2017 10: 13:40 GMT +//Origin: http://www.example.com +//X-Tenant-Identifier: T111 + +//Request Body: +{ + "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36) + "payee": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "MSISDN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "+12345678901", // mandatory, String(1..128) + "partySubIdOrType": "nothing", // optional, String(1..128) + } + }, + "payer": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128) + "partySubIdOrType": "something" // optional, String(1..128) + } + }, + "amountType": "SEND", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE + "amount": { // mandatory + "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ + "currency": "IDR" // mandatory, ISO 4217 (Rupee) + }, + "transactionType": { // mandatory + "scenario": "TRANSFER", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND + "initiator": "PAYER", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE + "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE + }, + "note": "From Mats", // optional, String(1..128) + "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601 +} diff --git a/docs/interoperation/specification/json/payee_quote_notification_post_request.json b/docs/interoperation/specification/json/payee_quote_notification_post_request.json new file mode 100644 index 00000000000..142166a5b5f --- /dev/null +++ b/docs/interoperation/specification/json/payee_quote_notification_post_request.json @@ -0,0 +1,53 @@ +//Request URI: +//POST /channel/quotes/notification +//HTTP/1.1 +//X-Tenant-Identifier: T111 + +//Request Body: +{ + "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36) + "transactionId": "11436b17-c690-4a30-8505-42a2c4eafb9d", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + "requestId": "18e43330-187e-11e9-ab14-d663bd873d93", // optional, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + "quoteId": "61058cea-187e-11e9-ab14-d663bd873d93", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + "payee": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "MSISDN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "12345678901", // mandatory, String(1..128) + "partySubIdOrType": "nothing", // optional, String(1..128) + "fspId": "BankNrOne" // optional, String(1..32) + } + }, + "payer": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128) + "partySubIdOrType": "something" // optional, String(1..128) + }, + "name": "" // optional, String(1..128) ^(?!\s*$)[\w .,'-]{1,128}$ + }, + "amountType": "SEND", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE + "amount": { + "amount": "100", + "currency": "USD" + }, + "transferAmount": { + "amount": "99", + "currency": "USD" + }, + "fspFee": { // optional + "amount": "1", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ + "currency": "IDR" // mandatory, ISO 4217 (Rupee) + }, + "fspCommission": { // optional + "amount": "0", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ + "currency": "IDR" // mandatory, ISO 4217 (Rupee) + }, + "transactionRole": "PAYEE", + "transactionType": { // mandatory + "scenario": "TRANSFER", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND + "initiator": "PAYER", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE + "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE + }, + "note": "From Mats", // optional, String(1..128) + "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601 +} diff --git a/docs/interoperation/specification/json/payer_quote_notification_post_request.json b/docs/interoperation/specification/json/payer_quote_notification_post_request.json new file mode 100644 index 00000000000..6b0209488a0 --- /dev/null +++ b/docs/interoperation/specification/json/payer_quote_notification_post_request.json @@ -0,0 +1,57 @@ +//Request URI: +//POST /channel/quotes/notification +// HTTP/1.1 +//X-Tenant-Identifier: T111 + +//Request Body: +{ + "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36) + "transactionId": "11436b17-c690-4a30-8505-42a2c4eafb9d", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + "requestId": "18e43330-187e-11e9-ab14-d663bd873d93", // optional, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + "quoteId": "61058cea-187e-11e9-ab14-d663bd873d93", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + "payee": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "MSISDN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "+12345678901", // mandatory, String(1..128) + "partySubIdOrType": "nothing", // optional, String(1..128) + "fspId": "BankNrOne" // optional, String(1..32) + }, + "name": "" // optional, String(1..128) ^(?!\s*$)[\w .,'-]{1,128}$ + }, + "payer": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128) + "partySubIdOrType": "something" // optional, String(1..128) + } + }, + "amountType": "SEND", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE + "amount": { // mandatory + "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ + "currency": "IDR" // mandatory, ISO 4217 (Rupee) + }, + "transferAmount": { + "amount": "99", + "currency": "USD" + }, + "payeeReceiveAmount": { + "amount": "100", + "currency": "USD" + }, + "fspFee": { // optional + "amount": "1", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ + "currency": "IDR" // mandatory, ISO 4217 (Rupee) + }, + "fspCommission": { // optional + "amount": "0", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ + "currency": "IDR" // mandatory, ISO 4217 (Rupee) + }, + "transactionRole": "PAYER", + "transactionType": { // mandatory + "scenario": "TRANSFER", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND + "initiator": "PAYEE", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE + "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE + }, + "note": "From Mats", // optional, String(1..128) + "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601 +} diff --git a/docs/interoperation/specification/json/payer_request_notification_post_request.json b/docs/interoperation/specification/json/payer_request_notification_post_request.json new file mode 100644 index 00000000000..3a8d2ad3e0b --- /dev/null +++ b/docs/interoperation/specification/json/payer_request_notification_post_request.json @@ -0,0 +1,39 @@ +//Request URI: +//POST /channel/transactions/{transactionId}/notification +// HTTP/1.1 +//X-Tenant-Identifier: T111 + +//Request Body: +{ + "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36) + "transactionId": "11436b17-c690-4a30-8505-42a2c4eafb9d", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + "requestId": "18e43330-187e-11e9-ab14-d663bd873d93", // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ + "payee": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "MSISDN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "+12345678901", // mandatory, String(1..128) + "partySubIdOrType": "nothing", // optional, String(1..128) + "fspId": "BankNrOne" // optional, String(1..32) + }, + "name": "" // optional, String(1..128) ^(?!\s*$)[\w .,'-]{1,128}$ + }, + "payer": { // mandatory + "partyIdInfo": { // mandatory + "partyIdType": "IBAN", // mandatory, constant, type: Enum of String(1..32): MSISDN, EMAIL, PERSONAL_ID, BUSINESS, DEVICE, ACCOUNT_ID, IBAN, ALIAS + "partyIdentifier": "IN93116000060000000012345676", // mandatory, String(1..128) + "partySubIdOrType": "something" // optional, String(1..128) + } + }, + "amountType": "SEND", // mandatory, constant, type: Enum of String(1..32): SEND, RECEIVE + "amount": { // mandatory + "amount": "100", // mandatory, Number(22, 4) ^([0]|([1-9][0-9]{0,17}))([.][0-9]{0,3}[1-9])?$ + "currency": "IDR" // mandatory, ISO 4217 (Rupee) + }, + "transactionType": { // mandatory + "scenario": "TRANSFER", // mandatory, constant, type: Enum of String(1..32): DEPOSIT, WITHDRAWAL, TRANSFER, PAYMENT, REFUND + "initiator": "PAYEE", // mandatory, constant, type: Enum of String(1..32): PAYER, PAYEE + "initiatorType": "CONSUMER" // mandatory, constant, type: Enum of String(1..32): CONSUMER, AGENT, BUSINESS, DEVICE + }, + "note": "From Mats", // optional, String(1..128) + "expiration": "2017-11-15T22:17:28.985-01:00" // optional, ISO 8601 +} diff --git a/docs/interoperation/specification/json/transactions_post_sync_response.json b/docs/interoperation/specification/json/transactions_post_sync_response.json new file mode 100644 index 00000000000..c9a89f9a57d --- /dev/null +++ b/docs/interoperation/specification/json/transactions_post_sync_response.json @@ -0,0 +1,7 @@ +//Request URI: +//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/transactions + +//Response Body: +{ + "transactionId": "11436b17-c690-4a30-8505-42a2c4eafb9d" // mandatory, UUID String(36) ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ +} diff --git a/docs/interoperation/specification/json/transactions_put_async_response.json b/docs/interoperation/specification/json/transactions_put_async_response.json new file mode 100644 index 00000000000..3ddc178d9aa --- /dev/null +++ b/docs/interoperation/specification/json/transactions_put_async_response.json @@ -0,0 +1,16 @@ +//Request URI: +//POST paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/transactions +//GET paymenthub-schema:// payment-hub-domain:payment-hub-port/channel/transactions/11436b17-c690-4a30-8505-42a2c4eafb9d + +//Response URI: +//PUT /interoperation/transactions/11436b17-c690-4a30-8505-42a2c4eafb9d + +//Header: +//X-Tenant-Identifier: T111 + +//Response Body: +{ + "clientRefId": "0f4f8eb4-1d83-11e9-ab14-d663bd873d93", // String(1..36) + "completedTimestamp": "2017-11-16T04:15:35.513+01:00", // optional, ISO 8601 + "transferState": "COMMITTED" // mandatory, Enum of String(1..32): RECEIVED, RESERVED, COMMITTED, ABORTED +} diff --git a/fineract-provider/build.gradle b/fineract-provider/build.gradle index 7e59fffdc77..367dc78733c 100644 --- a/fineract-provider/build.gradle +++ b/fineract-provider/build.gradle @@ -134,6 +134,7 @@ rat { '**/docs/system-architecture/**/*.xml', '**/bootstrap-3.0.0/assets/application.js', '**/system-architecture/js/plugins.js', + '**/docs/interoperation/specification/json/*.json', //Apache License '**/bootstrap-3.0.0/assets/less.js', diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java index d2754c21234..7fd07a54ce0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/CommandProcessingResult.java @@ -118,7 +118,7 @@ private CommandProcessingResult(final Long commandId, final Long officeId, final this.subResourceId = subResourceId; } - private CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, final Map changesOnly) { + protected CommandProcessingResult(final Long resourceId, final Long officeId, final Long commandId, final Map changesOnly) { if (resourceId != null) { this.resourceIdentifier = resourceId.toString(); } else { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java index 1ee13616370..8191cbc9bc9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java @@ -51,6 +51,18 @@ public DataValidatorBuilder reset() { return new DataValidatorBuilder(this.dataValidationErrors).resource(this.resource); } + public void merge(DataValidatorBuilder other) { + dataValidationErrors.addAll(other.dataValidationErrors); + } + + public boolean hasError() { + return !dataValidationErrors.isEmpty(); + } + + public List getDataValidationErrors() { + return dataValidationErrors; + } + public DataValidatorBuilder resource(final String resource) { this.resource = resource; return this; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java index 94eb77cee2c..3718f2e6176 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java @@ -22,6 +22,7 @@ import java.math.BigDecimal; import java.security.InvalidParameterException; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -82,7 +83,7 @@ public String toJson(final Object object) { return this.gsonConverter.toJson(object); } - public void checkForUnsupportedParameters(final Type typeOfMap, final String json, final Set supportedParams) { + public void checkForUnsupportedParameters(final Type typeOfMap, final String json, final Collection supportedParams) { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } final Map requestMap = this.gsonConverter.fromJson(json, typeOfMap); @@ -97,7 +98,7 @@ public void checkForUnsupportedParameters(final Type typeOfMap, final String jso if (!unsupportedParameterList.isEmpty()) { throw new UnsupportedParameterException(unsupportedParameterList); } } - public void checkForUnsupportedParameters(final JsonObject object, final Set supportedParams) { + public void checkForUnsupportedParameters(final JsonObject object, final Collection supportedParams) { if (object == null) { throw new InvalidParameterException(); } final Set> entries = object.entrySet(); @@ -196,17 +197,26 @@ public MonthDay extractMonthDayNamed(final String parameterName, final JsonObjec } public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element) { - return this.helperDelegator.extractLocalDateNamed(parameterName, element, new HashSet()); + return this.helperDelegator.extractLocalDateNamed(parameterName, element, new HashSet<>()); } public LocalDateTime extractLocalTimeNamed(final String parameterName, final JsonElement element) { - return this.helperDelegator.extractLocalTimeNamed(parameterName, element, new HashSet()); + return this.helperDelegator.extractLocalTimeNamed(parameterName, element, new HashSet<>()); } - + + public LocalDateTime extractLocalTimeNamed(final String parameterName, final JsonElement element, final String dateFormat, + final Locale locale) { + return this.helperDelegator.extractLocalTimeNamed(parameterName, element, dateFormat, locale, new HashSet<>()); + } + + public LocalDateTime extractLocalTimeNamed(final String parameterName, final JsonElement element, String timeFormat) { + return this.helperDelegator.extractLocalTimeNamed(parameterName, element, timeFormat, new HashSet<>()); + } + public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element, final String dateFormat, final Locale locale) { return this.helperDelegator.extractLocalDateNamed(parameterName, element.getAsJsonObject(), dateFormat, locale, - new HashSet()); + new HashSet<>()); } public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element, diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java index a3e489a9d83..460a05a4e19 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java @@ -378,15 +378,25 @@ public LocalDateTime extractLocalTimeNamed(final String parameterName, final Jso if (element.isJsonObject()) { final JsonObject object = element.getAsJsonObject(); + value = extractLocalTimeNamed(parameterName, element, extractTimeFormatParameter(object), parametersPassedInCommand); + } + return value; + } - final String timeFormat = extractTimeFormatParameter(object); + public LocalDateTime extractLocalTimeNamed(final String parameterName, final JsonElement element, String timeFormat, + final Set parametersPassedInCommand) { + + LocalDateTime value = null; + + if (element.isJsonObject()) { + final JsonObject object = element.getAsJsonObject(); final Locale clientApplicationLocale = extractLocaleParameter(object); value = extractLocalTimeNamed(parameterName, object, timeFormat, clientApplicationLocale, parametersPassedInCommand); } return value; } - - public LocalDateTime extractLocalTimeNamed(final String parameterName, final JsonObject element, final String timeFormat, + + public LocalDateTime extractLocalTimeNamed(final String parameterName, final JsonElement element, final String timeFormat, final Locale clientApplicationLocale, final Set parametersPassedInCommand) { LocalDateTime value = null; String timeValueAsString=null; @@ -419,7 +429,7 @@ public LocalDateTime extractLocalTimeNamed(final String parameterName, final Jso return value; } - public LocalDate extractLocalDateNamed(final String parameterName, final JsonObject element, final String dateFormat, + public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element, final String dateFormat, final Locale clientApplicationLocale, final Set parametersPassedInCommand) { LocalDate value = null; if (element.isJsonObject()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropApiResource.java new file mode 100644 index 00000000000..1aba6a79cc3 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropApiResource.java @@ -0,0 +1,317 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.api; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.interoperation.data.InteropIdentifierRequestData; +import org.apache.fineract.interoperation.data.InteropIdentifierResponseData; +import org.apache.fineract.interoperation.data.InteropQuoteRequestData; +import org.apache.fineract.interoperation.data.InteropQuoteResponseData; +import org.apache.fineract.interoperation.data.InteropTransactionRequestData; +import org.apache.fineract.interoperation.data.InteropTransactionRequestResponseData; +import org.apache.fineract.interoperation.data.InteropTransferRequestData; +import org.apache.fineract.interoperation.data.InteropTransferResponseData; +import org.apache.fineract.interoperation.domain.InteropIdentifierType; +import org.apache.fineract.interoperation.domain.InteropTransferActionType; +import org.apache.fineract.interoperation.service.InteropService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_QUOTE; +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_REQUEST; +import static org.apache.fineract.interoperation.util.InteropUtil.ROOT_PATH; + +@Path("/interoperation") //api/v1/ +@Component +@Scope +@Api(value = ROOT_PATH, description = "") +public class InteropApiResource { + + private PlatformSecurityContext context; + private ApiRequestParameterHelper apiRequestParameterHelper; + + private DefaultToApiJsonSerializer jsonSerializer; + + private InteropService interopService; + private PortfolioCommandSourceWritePlatformService commandsSourceService; + + @Autowired + public InteropApiResource(PlatformSecurityContext context, + ApiRequestParameterHelper apiRequestParameterHelper, + DefaultToApiJsonSerializer defaultToApiJsonSerializer, + InteropService interopService, + PortfolioCommandSourceWritePlatformService portfolioCommandSourceWritePlatformService) { + this.context = context; + this.apiRequestParameterHelper = apiRequestParameterHelper; + this.jsonSerializer = defaultToApiJsonSerializer; + this.interopService = interopService; + this.commandsSourceService = portfolioCommandSourceWritePlatformService; + } + + @GET + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("health") + @ApiOperation(value = "Query Interoperation Health Request", httpMethod = "GET", notes = "") + @ApiResponses({@ApiResponse(code = 200, message = "OK")}) + public String health(@Context UriInfo uriInfo) { + return "OK"; + } + + @GET + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("parties/{idType}/{idValue}") + @ApiOperation(value = "Query Interoperation Account by secondary identifier", httpMethod = "GET", notes = "") + @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropIdentifierResponseData.class)}) + public String getAccountByIdentifier(@PathParam("idType") @ApiParam(value = "idType") InteropIdentifierType idType, + @PathParam("idValue") @ApiParam(value = "idValue") String idValue, + @Context UriInfo uriInfo) { + InteropIdentifierResponseData result = interopService.getAccountByIdentifier(idType, idValue, null); + ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + return jsonSerializer.serialize(settings, result); + } + + @GET + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("parties/{idType}/{idValue}/{subIdOrType}") + @ApiOperation(value = "Query Interoperation Account by secondary identifier", httpMethod = "GET", notes = "") + @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropIdentifierResponseData.class)}) + public String getAccountByIdentifier(@PathParam("idType") @ApiParam(value = "idType") InteropIdentifierType idType, + @PathParam("idValue") @ApiParam(value = "idValue") String idValue, + @PathParam("subIdOrType") @ApiParam(value = "subIdOrType") String subIdOrType, + @Context UriInfo uriInfo) { + InteropIdentifierResponseData result = interopService.getAccountByIdentifier(idType, idValue, subIdOrType); + ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + return jsonSerializer.serialize(settings, result); + } + + @POST + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("parties/{idType}/{idValue}") + @ApiOperation(value = "Interoperation Identifier registration", httpMethod = "POST", notes = "") + @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body", + dataTypeClass = InteropIdentifierRequestData.class)}) + @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropIdentifierResponseData.class)}) + public String registerAccountIdentifier(@PathParam("idType") @ApiParam(value = "idType") InteropIdentifierType idType, + @PathParam("idValue") @ApiParam(value = "idValue") String idValue, + @ApiParam(hidden = true) String identifierJson, @Context UriInfo uriInfo) + throws Throwable { + CommandWrapper commandRequest = new InteropWrapperBuilder().registerAccountIdentifier(idType, idValue, null).withJson(identifierJson).build(); + + InteropIdentifierResponseData result = (InteropIdentifierResponseData) commandsSourceService.logCommandSource(commandRequest); + ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + return jsonSerializer.serialize(settings, result); + } + + @POST + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("parties/{idType}/{idValue}/{subIdOrType}") + @ApiOperation(value = "Interoperation Identifier registration", httpMethod = "POST", notes = "") + @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body", + dataTypeClass = InteropIdentifierRequestData.class)}) + @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropIdentifierResponseData.class)}) + public String registerAccountIdentifier(@PathParam("idType") @ApiParam(value = "idType") InteropIdentifierType idType, + @PathParam("idValue") @ApiParam(value = "idValue") String idValue, + @PathParam("subIdOrType") @ApiParam(value = "subIdOrType") String subIdOrType, + @ApiParam(hidden = true) String identifierJson, @Context UriInfo uriInfo) + throws Throwable { + CommandWrapper commandRequest = new InteropWrapperBuilder().registerAccountIdentifier(idType, idValue, subIdOrType).withJson(identifierJson).build(); + + InteropIdentifierResponseData result = (InteropIdentifierResponseData) commandsSourceService.logCommandSource(commandRequest); + ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + return jsonSerializer.serialize(settings, result); + } + + @DELETE + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("parties/{idType}/{idValue}") + @ApiOperation(value = "Allow Interoperation Identifier registration", httpMethod = "DELETE", notes = "") + @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body", + dataTypeClass = InteropIdentifierRequestData.class)}) + @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropIdentifierResponseData.class)}) + public String deleteAccountIdentifier(@PathParam("idType") @ApiParam(value = "idType") InteropIdentifierType idType, + @PathParam("idValue") @ApiParam(value = "idValue") String idValue, + @Context UriInfo uriInfo) + throws Throwable { + CommandWrapper commandRequest = new InteropWrapperBuilder().deleteAccountIdentifier(idType, idValue, null).build(); + + InteropIdentifierResponseData result = (InteropIdentifierResponseData) commandsSourceService.logCommandSource(commandRequest); + ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + return jsonSerializer.serialize(settings, result); + } + + @DELETE + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("parties/{idType}/{idValue}/{subIdOrType}") + @ApiOperation(value = "Allow Interoperation Identifier registration", httpMethod = "DELETE", notes = "") + @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body", + dataTypeClass = InteropIdentifierRequestData.class)}) + @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropIdentifierResponseData.class)}) + public String deleteAccountIdentifier(@PathParam("idType") @ApiParam(value = "idType") InteropIdentifierType idType, + @PathParam("idValue") @ApiParam(value = "idValue") String idValue, + @PathParam("subIdOrType") @ApiParam(value = "subIdOrType") String subIdOrType, + @Context UriInfo uriInfo) + throws Throwable { + CommandWrapper commandRequest = new InteropWrapperBuilder().deleteAccountIdentifier(idType, idValue, subIdOrType).build(); + + InteropIdentifierResponseData result = (InteropIdentifierResponseData) commandsSourceService.logCommandSource(commandRequest); + ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + return jsonSerializer.serialize(settings, result); + } + + @GET + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("transactions/{transactionCode}/requests/{requestCode}") + @ApiOperation(value = "Query Interoperation Transaction Request", httpMethod = "GET", notes = "") + @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropTransactionRequestResponseData.class)}) + public String getTransactionRequest(@PathParam("transactionCode") @ApiParam(value = "transactionCode") String transactionCode, + @PathParam("requestCode") @ApiParam(value = "requestCode") String requestCode, @Context UriInfo uriInfo) { + context.authenticatedUser().validateHasReadPermission(ENTITY_NAME_REQUEST); + + InteropTransactionRequestResponseData result = interopService.getTransactionRequest(transactionCode, requestCode); + ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + return jsonSerializer.serialize(settings, result); + } + + @POST + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("requests") + @ApiOperation(value = "Allow Interoperation Transaction Request", httpMethod = "POST", notes = "") + @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body", + dataTypeClass = InteropTransactionRequestData.class)}) + @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropTransactionRequestResponseData.class)}) + public String createTransactionRequest(@ApiParam(hidden = true) String quotesJson, @Context UriInfo uriInfo) { + CommandWrapper commandRequest = new InteropWrapperBuilder().createTransactionRequest().withJson(quotesJson).build(); + + InteropTransactionRequestResponseData result = (InteropTransactionRequestResponseData) commandsSourceService.logCommandSource(commandRequest); + ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + return jsonSerializer.serialize(settings, result); + } + + @GET + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("transactions/{transactionCode}/quotes/{quoteCode}") + @ApiOperation(value = "Query Interoperation Quote", httpMethod = "GET", notes = "") + @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropQuoteResponseData.class)}) + public String getQuote(@PathParam("transactionCode") @ApiParam(value = "transactionCode") String transactionCode, + @PathParam("quoteCode") @ApiParam(value = "quoteCode") String quoteCode, + @Context UriInfo uriInfo) { + context.authenticatedUser().validateHasReadPermission(ENTITY_NAME_QUOTE); + + InteropQuoteResponseData result = interopService.getQuote(transactionCode, quoteCode); + ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + return this.jsonSerializer.serialize(settings, result); + } + + @POST + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("quotes") + @ApiOperation(value = "Calculate Interoperation Quote", httpMethod = "POST", notes = "") + @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body", + dataTypeClass = InteropQuoteRequestData.class)}) + @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropQuoteResponseData.class)}) + public String createQuote(@ApiParam(hidden = true) String quotesJson, @Context UriInfo uriInfo) { + CommandWrapper commandRequest = new InteropWrapperBuilder().createQuotes().withJson(quotesJson).build(); + + InteropQuoteResponseData result = (InteropQuoteResponseData) commandsSourceService.logCommandSource(commandRequest); + ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + return jsonSerializer.serialize(settings, result); + } + + @GET + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("transactions/{transactionCode}/transfers/{transferCode}") + @ApiOperation(value = "Query Interoperation Transfer", httpMethod = "GET", notes = "") + @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropTransferResponseData.class)}) + public String getTransfer(@PathParam("transactionCode") @ApiParam(value = "transactionCode") String transactionCode, + @PathParam("transferCode") @ApiParam(value = "transferCode") String transferCode, + @Context UriInfo uriInfo) { + context.authenticatedUser().validateHasReadPermission(ENTITY_NAME_QUOTE); + + InteropTransferResponseData result = interopService.getTransfer(transactionCode, transferCode); + ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + return this.jsonSerializer.serialize(settings, result); + } + + @POST + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + @Path("transfers") + @ApiOperation(value = "Prepare Interoperation Transfer", httpMethod = "POST", notes = "") + @ApiImplicitParams({@ApiImplicitParam(value = "body", required = true, paramType = "body", dataType = "body", format = "body", + dataTypeClass = InteropTransferRequestData.class)}) + @ApiResponses({@ApiResponse(code = 200, message = "OK", response = InteropTransferResponseData.class)}) + public String performTransfer(@QueryParam("action") @ApiParam(value = "action") String action, + @ApiParam(hidden = true) String quotesJson, @Context UriInfo uriInfo) { + CommandWrapper commandRequest = new InteropWrapperBuilder().performTransfer(InteropTransferActionType.valueOf(action)).withJson(quotesJson).build(); + + InteropTransferResponseData result = (InteropTransferResponseData) commandsSourceService.logCommandSource(commandRequest); + ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); + + return jsonSerializer.serialize(settings, result); + } +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java new file mode 100644 index 00000000000..6f7bc4ec664 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java @@ -0,0 +1,82 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.api; + +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.interoperation.domain.InteropIdentifierType; +import org.apache.fineract.interoperation.domain.InteropTransferActionType; + +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_IDENTIFIER; +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_QUOTE; +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_REQUEST; +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_TRANSFER; +import static org.apache.fineract.interoperation.util.InteropUtil.ROOT_PATH; + +public class InteropWrapperBuilder { + + private String actionName; + private String entityName; + private String href; + private String json = "{}"; + + public CommandWrapper build() { + return new CommandWrapper(null, null, null, null, null, actionName, entityName, null, null, href, json, null, null, + null, null, null); + } + + public InteropWrapperBuilder withJson(final String json) { + this.json = json; + return this; + } + + public InteropWrapperBuilder registerAccountIdentifier(InteropIdentifierType idType, String idValue, String subIdOrType) { + this.actionName = "CREATE"; + this.entityName = ENTITY_NAME_IDENTIFIER; + this.href = "/" + ROOT_PATH + "/parties/" + idType + "/" + idValue + "/" + (subIdOrType == null ? "" : subIdOrType); + return this; + } + + public InteropWrapperBuilder deleteAccountIdentifier(InteropIdentifierType idType, String idValue, String subIdOrType) { + this.actionName = "DELETE"; + this.entityName = ENTITY_NAME_IDENTIFIER; + this.href = "/" + ROOT_PATH + "/parties/" + idType + "/" + idValue + "/" + (subIdOrType == null ? "" : subIdOrType); + return this; + } + + public InteropWrapperBuilder createTransactionRequest() { + this.actionName = "CREATE"; + this.entityName = ENTITY_NAME_REQUEST; + this.href = "/" + ROOT_PATH + "/requests"; + return this; + } + + public InteropWrapperBuilder createQuotes() { + this.actionName = "CREATE"; + this.entityName = ENTITY_NAME_QUOTE; + this.href = "/" + ROOT_PATH + "/quotes"; + return this; + } + + public InteropWrapperBuilder performTransfer(InteropTransferActionType action) { + this.actionName = action.name(); + this.entityName = ENTITY_NAME_TRANSFER; + this.href = "/" + ROOT_PATH + "/transfers"; + return this; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/ExtensionData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/ExtensionData.java new file mode 100644 index 00000000000..005816a70f8 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/ExtensionData.java @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import com.google.gson.JsonObject; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; + +import javax.validation.constraints.NotNull; +import java.util.Arrays; + +import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_KEY; +import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_VALUE; + +public class ExtensionData { + + public static final String[] PARAMS = {PARAM_KEY, PARAM_VALUE}; + + @NotNull + private final String key; + + private String value; + + public ExtensionData(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public static ExtensionData validateAndParse(DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) { + if (element == null) + return null; + + jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS)); + + String key = jsonHelper.extractStringNamed(PARAM_KEY, element); + DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_KEY).value(key).notBlank(); + + String value = jsonHelper.extractStringNamed(PARAM_VALUE, element); + + dataValidator.merge(dataValidatorCopy); + return dataValidator.hasError() ? null : new ExtensionData(key, value); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/GeoCodeData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/GeoCodeData.java new file mode 100644 index 00000000000..5277fe92aa4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/GeoCodeData.java @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import com.google.gson.JsonObject; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; + +import javax.validation.constraints.NotNull; +import java.util.Arrays; + +import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_LATITUDE; +import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_LONGITUDE; + +public class GeoCodeData { + + public static final String[] PARAMS = {PARAM_LATITUDE, PARAM_LONGITUDE}; + + @NotNull + private final String latitude; + @NotNull + private final String longitude; + + public GeoCodeData(String latitude, String longitude) { + this.latitude = latitude; + this.longitude = longitude; + } + + public String getLatitude() { + return latitude; + } + + public String getLongitude() { + return longitude; + } + + public static GeoCodeData validateAndParse(DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) { + if (element == null) + return null; + + jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS)); + + String latitude = jsonHelper.extractStringNamed(PARAM_LATITUDE, element); + DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_LATITUDE).value(latitude).notBlank(); + + String longitude = jsonHelper.extractStringNamed(PARAM_LONGITUDE, element); + dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_LONGITUDE).value(longitude).notBlank(); + + dataValidator.merge(dataValidatorCopy); + return dataValidator.hasError() ? null : new GeoCodeData(latitude, longitude); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropIdentifierRequestData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropIdentifierRequestData.java new file mode 100644 index 00000000000..80fa82408f6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropIdentifierRequestData.java @@ -0,0 +1,86 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import com.google.gson.JsonObject; +import org.apache.bval.constraints.NotEmpty; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.interoperation.domain.InteropIdentifierType; +import org.apache.fineract.interoperation.domain.InteropTransactionRole; +import org.joda.time.LocalDateTime; + +import javax.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.List; + +import static org.apache.fineract.interoperation.util.InteropUtil.*; + +public class InteropIdentifierRequestData { + + static final String[] PARAMS = {PARAM_ACCOUNT_ID}; + + @NotEmpty + private final InteropIdentifierType idType; + @NotEmpty + private final String idValue; + + private final String subIdOrType; + + @NotEmpty + private final String accountId; + + public InteropIdentifierRequestData(@NotNull InteropIdentifierType idType, @NotNull String idValue, String subIdOrType, String accountId) { + this.idType = idType; + this.idValue = idValue; + this.subIdOrType = subIdOrType; + this.accountId = accountId; + } + + public InteropIdentifierType getIdType() { + return idType; + } + + public String getIdValue() { + return idValue; + } + + public String getSubIdOrType() { + return subIdOrType; + } + + public String getAccountId() { + return accountId; + } + + public static InteropIdentifierRequestData validateAndParse(final DataValidatorBuilder dataValidator, @NotNull InteropIdentifierType idType, + @NotNull String idValue, String subIdOrType, JsonObject element, + FromJsonHelper jsonHelper) { + if (element == null) + return null; + + jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS)); + + String accountId = jsonHelper.extractStringNamed(PARAM_ACCOUNT_ID, element); + DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_ACCOUNT_ID).value(accountId).notBlank(); + + dataValidator.merge(dataValidatorCopy); + return dataValidator.hasError() ? null : new InteropIdentifierRequestData(idType, idValue, subIdOrType, accountId); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropIdentifierResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropIdentifierResponseData.java new file mode 100644 index 00000000000..3623ad3da92 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropIdentifierResponseData.java @@ -0,0 +1,58 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import org.apache.bval.constraints.NotEmpty; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; + +import javax.validation.constraints.NotNull; +import java.util.Map; + +public class InteropIdentifierResponseData extends CommandProcessingResult { + + @NotEmpty + private String accountId; + + + protected InteropIdentifierResponseData(Long resourceId, Long officeId, Long commandId, Map changesOnly, @NotNull String accountId) { + super(resourceId, officeId, commandId, changesOnly); + this.accountId = accountId; + } + + protected static InteropIdentifierResponseData build(Long resourceId, Long officeId, Long commandId, Map changesOnly, @NotNull String accountId) { + return new InteropIdentifierResponseData(resourceId, officeId, commandId, changesOnly, accountId); + } + + protected static InteropIdentifierResponseData build(Long commandId, @NotNull String accountId) { + return build(null, null, commandId, null, accountId); + } + + public static InteropIdentifierResponseData build(@NotNull String accountId) { + return build(null, accountId); + } + + @NotNull + public String getAccountId() { + return accountId; + } + + protected void setAccountId(String accountId) { + this.accountId = accountId; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropQuoteRequestData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropQuoteRequestData.java new file mode 100644 index 00000000000..97093d82ed2 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropQuoteRequestData.java @@ -0,0 +1,117 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import com.google.gson.JsonObject; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.interoperation.domain.InteropAmountType; +import org.apache.fineract.interoperation.domain.InteropTransactionRole; +import org.joda.time.LocalDateTime; + +import javax.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.List; + +import static org.apache.fineract.interoperation.util.InteropUtil.*; + +public class InteropQuoteRequestData extends InteropRequestData { + + static final String[] PARAMS = {PARAM_TRANSACTION_CODE, PARAM_REQUEST_CODE, PARAM_ACCOUNT_ID, PARAM_AMOUNT, PARAM_TRANSACTION_TYPE, + PARAM_TRANSACTION_ROLE, PARAM_NOTE, PARAM_GEO_CODE, PARAM_EXPIRATION, PARAM_EXTENSION_LIST, PARAM_QUOTE_CODE, + PARAM_AMOUNT_TYPE, PARAM_FEES, PARAM_LOCALE, PARAM_DATE_FORMAT}; + @NotNull + private final String quoteCode; + @NotNull + private final InteropAmountType amountType; + + private final MoneyData fees; // only for disclosed Payer fees on the Payee side + + public InteropQuoteRequestData(@NotNull String transactionCode, String requestCode, @NotNull String accountId, @NotNull MoneyData amount, + @NotNull InteropTransactionRole transactionRole, @NotNull InteropTransactionTypeData transactionType, + String note, GeoCodeData geoCode, LocalDateTime expiration, List extensionList, + @NotNull String quoteCode, @NotNull InteropAmountType amountType, MoneyData fees) { + super(transactionCode, requestCode, accountId, amount, transactionRole, transactionType, note, geoCode, expiration, extensionList); + this.quoteCode = quoteCode; + this.amountType = amountType; + this.fees = fees; + } + + public InteropQuoteRequestData(@NotNull String transactionCode, @NotNull String accountId, @NotNull InteropAmountType amountType, + @NotNull MoneyData amount, @NotNull InteropTransactionRole transactionRole, @NotNull InteropTransactionTypeData transactionType, + @NotNull String quoteCode) { + this(transactionCode, null, accountId, amount, transactionRole, transactionType, null, null, null, null, quoteCode, + amountType, null); + } + + private InteropQuoteRequestData(@NotNull InteropRequestData other, @NotNull String quoteCode, @NotNull InteropAmountType amountType, + MoneyData fees) { + this(other.getTransactionCode(), other.getRequestCode(), other.getAccountId(), other.getAmount(), other.getTransactionRole(), + other.getTransactionType(), other.getNote(), other.getGeoCode(), other.getExpiration(), other.getExtensionList(), + quoteCode, amountType, fees); + } + + public String getQuoteCode() { + return quoteCode; + } + + public InteropAmountType getAmountType() { + return amountType; + } + + public MoneyData getFees() { + return fees; + } + + public void normalizeAmounts(@NotNull MonetaryCurrency currency) { + super.normalizeAmounts(currency); + if (fees != null) + fees.normalizeAmount(currency); + } + + public static InteropQuoteRequestData validateAndParse(final DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) { + if (element == null) + return null; + + jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS)); + + InteropRequestData interopRequestData = InteropRequestData.validateAndParse(dataValidator, element, jsonHelper); + + String quoteCode = jsonHelper.extractStringNamed(PARAM_QUOTE_CODE, element); + DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_QUOTE_CODE).value(quoteCode).notBlank(); + + String amountTypeString = jsonHelper.extractStringNamed(PARAM_AMOUNT_TYPE, element); + dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_AMOUNT_TYPE).value(amountTypeString).notBlank(); + InteropAmountType amountType = InteropAmountType.valueOf(amountTypeString); + + JsonObject feesElement = jsonHelper.extractJsonObjectNamed(PARAM_FEES, element); + dataValidator.merge(dataValidatorCopy); + MoneyData fees = MoneyData.validateAndParse(dataValidator, feesElement, jsonHelper); + + String transactionRoleString = jsonHelper.extractStringNamed(PARAM_TRANSACTION_ROLE, element); + dataValidatorCopy = dataValidator.reset().parameter(PARAM_TRANSACTION_ROLE).value(transactionRoleString).notNull(); + + JsonObject transactionTypeElement = jsonHelper.extractJsonObjectNamed(PARAM_TRANSACTION_TYPE, element); + dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_TRANSACTION_TYPE).value(transactionTypeElement).notNull(); + + dataValidator.merge(dataValidatorCopy); + return dataValidator.hasError() ? null : new InteropQuoteRequestData(interopRequestData, quoteCode, amountType, fees); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropQuoteResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropQuoteResponseData.java new file mode 100644 index 00000000000..a20576ac2ad --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropQuoteResponseData.java @@ -0,0 +1,88 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import org.apache.fineract.interoperation.domain.InteropActionState; +import org.joda.time.LocalDateTime; + +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; + +public class InteropQuoteResponseData extends InteropResponseData { + + @NotNull + private final String quoteCode; + + private MoneyData fspFee; + + private MoneyData fspCommission; + + + private InteropQuoteResponseData(Long resourceId, Long officeId, Long commandId, Map changesOnly, + @NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration, + List extensionList, @NotNull String quoteCode, MoneyData fspFee, MoneyData fspCommission) { + super(resourceId, officeId, commandId, changesOnly, transactionCode, state, expiration, extensionList); + this.quoteCode = quoteCode; + this.fspFee = fspFee; + this.fspCommission = fspCommission; + } + + public static InteropQuoteResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state, + LocalDateTime expiration, List extensionList, @NotNull String quoteCode, + MoneyData fspFee, MoneyData fspCommission) { + return new InteropQuoteResponseData(null, null, commandId, null, transactionCode, state, expiration, extensionList, + quoteCode, fspFee, fspCommission); + } + + public static InteropQuoteResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration, + List extensionList, @NotNull String quoteCode, MoneyData fspFee, + MoneyData fspCommission) { + return build(null, transactionCode, state, expiration, extensionList, quoteCode, fspFee, fspCommission); + } + + public static InteropQuoteResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state, + @NotNull String quoteCode) { + return build(commandId, transactionCode, state, null, null, quoteCode, null, null); + } + + public static InteropQuoteResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state, @NotNull String quoteCode) { + return build(null, transactionCode, state, quoteCode); + } + + public String getQuoteCode() { + return quoteCode; + } + + public MoneyData getFspFee() { + return fspFee; + } + + public void setFspFee(MoneyData fspFee) { + this.fspFee = fspFee; + } + + public MoneyData getFspCommission() { + return fspCommission; + } + + public void setFspCommission(MoneyData fspCommission) { + this.fspCommission = fspCommission; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropRequestData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropRequestData.java new file mode 100644 index 00000000000..d3be0a55d5d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropRequestData.java @@ -0,0 +1,193 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.interoperation.domain.InteropTransactionRole; +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; + +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; + +import static org.apache.fineract.interoperation.util.InteropUtil.*; + +public class InteropRequestData { + + @NotNull + private final String transactionCode; + + private final String requestCode; + @NotNull + private final String accountId; + @NotNull + private final MoneyData amount; + @NotNull + private final InteropTransactionRole transactionRole; + + private final InteropTransactionTypeData transactionType; + + private String note; + + private GeoCodeData geoCode; + + private LocalDateTime expiration; + + private List extensionList; + + protected InteropRequestData(@NotNull String transactionCode, String requestCode, @NotNull String accountId, @NotNull MoneyData amount, + @NotNull InteropTransactionRole transactionRole, InteropTransactionTypeData transactionType, String note, + GeoCodeData geoCode, LocalDateTime expiration, List extensionList) { + this.transactionCode = transactionCode; + this.requestCode = requestCode; + this.accountId = accountId; + this.amount = amount; + this.transactionType = transactionType; + this.transactionRole = transactionRole; + this.note = note; + this.geoCode = geoCode; + this.expiration = expiration; + this.extensionList = extensionList; + } + + protected InteropRequestData(@NotNull String transactionCode, @NotNull String accountId, @NotNull MoneyData amount, @NotNull InteropTransactionRole transactionRole) { + this(transactionCode, null, accountId, amount, transactionRole, null, null, null, null, null); + } + + @NotNull + public String getTransactionCode() { + return transactionCode; + } + + public String getRequestCode() { + return requestCode; + } + + @NotNull + public String getAccountId() { + return accountId; + } + + @NotNull + public MoneyData getAmount() { + return amount; + } + + public InteropTransactionTypeData getTransactionType() { + return transactionType; + } + + @NotNull + public InteropTransactionRole getTransactionRole() { + return transactionRole; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + + public GeoCodeData getGeoCode() { + return geoCode; + } + + public void setGeoCode(GeoCodeData geoCode) { + this.geoCode = geoCode; + } + + public LocalDateTime getExpiration() { + return expiration; + } + + public LocalDate getExpirationLocalDate() { + return expiration == null ? null : expiration.toLocalDate(); + } + + public void setExpiration(LocalDateTime expiration) { + this.expiration = expiration; + } + + public List getExtensionList() { + return extensionList; + } + + public void setExtensionList(List extensionList) { + this.extensionList = extensionList; + } + + public void normalizeAmounts(@NotNull MonetaryCurrency currency) { + amount.normalizeAmount(currency); + } + + public static InteropRequestData validateAndParse(final DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) { + if (element == null) + return null; + + String transactionCode = jsonHelper.extractStringNamed(PARAM_TRANSACTION_CODE, element); + DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_TRANSACTION_CODE).value(transactionCode).notBlank(); + + String requestCode = jsonHelper.extractStringNamed(PARAM_REQUEST_CODE, element); + + String accountId = jsonHelper.extractStringNamed(PARAM_ACCOUNT_ID, element); + dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_ACCOUNT_ID).value(accountId).notBlank(); + + JsonObject moneyElement = jsonHelper.extractJsonObjectNamed(PARAM_AMOUNT, element); + dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_AMOUNT).value(moneyElement).notNull(); + dataValidator.merge(dataValidatorCopy); + MoneyData amount = MoneyData.validateAndParse(dataValidator, moneyElement, jsonHelper); + + JsonObject transactionTypeElement = jsonHelper.extractJsonObjectNamed(PARAM_TRANSACTION_TYPE, element); + InteropTransactionTypeData transactionType = InteropTransactionTypeData.validateAndParse(dataValidator, transactionTypeElement, jsonHelper); + + String transactionRoleString = jsonHelper.extractStringNamed(PARAM_TRANSACTION_ROLE, element); + InteropTransactionRole transactionRole = transactionRoleString == null ? InteropTransactionRole.PAYER : InteropTransactionRole.valueOf(transactionRoleString); + + String note = jsonHelper.extractStringNamed(PARAM_NOTE, element); + + JsonObject geoCodeElement = jsonHelper.extractJsonObjectNamed(PARAM_GEO_CODE, element); + GeoCodeData geoCode = GeoCodeData.validateAndParse(dataValidator, geoCodeElement, jsonHelper); + + String locale = jsonHelper.extractStringNamed(PARAM_LOCALE, element); + LocalDateTime expiration = locale == null + ? jsonHelper.extractLocalTimeNamed(PARAM_EXPIRATION, element, ISO8601_DATE_TIME_FORMAT, DEFAULT_LOCALE) + : jsonHelper.extractLocalTimeNamed(PARAM_EXPIRATION, element); // PARAM_DATE_FORMAT also must be set + + JsonArray extensionArray = jsonHelper.extractJsonArrayNamed(PARAM_EXTENSION_LIST, element); + ArrayList extensionList = null; + if (extensionArray != null) { + extensionList = new ArrayList<>(extensionArray.size()); + for (JsonElement jsonElement : extensionArray) { + if (jsonElement.isJsonObject()) + extensionList.add(ExtensionData.validateAndParse(dataValidator, jsonElement.getAsJsonObject(), jsonHelper)); + } + } + + return dataValidator.hasError() ? null : new InteropRequestData(transactionCode, requestCode, accountId, amount, + transactionRole, transactionType, note, geoCode, expiration, extensionList); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropResponseData.java new file mode 100644 index 00000000000..e31fe362c22 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropResponseData.java @@ -0,0 +1,105 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.interoperation.domain.InteropActionState; +import org.joda.time.LocalDateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +import javax.validation.constraints.NotNull; +import java.beans.Transient; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Map; + +public class InteropResponseData extends CommandProcessingResult { + + public static final String ISO_DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ"; +// public static final SimpleDateFormat ISO_DATE_TIME_FORMATTER = new SimpleDateFormat(ISO_DATE_TIME_PATTERN); // TODO: not synchronized + public static DateTimeFormatter ISO_DATE_TIME_FORMATTER = DateTimeFormat.forPattern(ISO_DATE_TIME_PATTERN); + + @NotNull + private final String transactionCode; + + @NotNull + private final InteropActionState state; + + private final String expiration; + + private final List extensionList; + + + protected InteropResponseData(Long resourceId, Long officeId, Long commandId, Map changesOnly, @NotNull String transactionCode, + @NotNull InteropActionState state, LocalDateTime expiration, List extensionList) { + super(resourceId, officeId, commandId, changesOnly); + this.transactionCode = transactionCode; + this.state = state; + this.expiration = format(expiration); + this.extensionList = extensionList; + } + + protected static InteropResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state, + LocalDateTime expiration, List extensionList) { + return new InteropResponseData(null, null, commandId, null, transactionCode, state, expiration, extensionList); + } + + public static InteropResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state, + LocalDateTime expiration, List extensionList) { + return build(null, transactionCode, state, expiration, extensionList); + } + + public static InteropResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state) { + return build(commandId, transactionCode, state, null, null); + } + + public static InteropResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state) { + return build(null, transactionCode, state); + } + + public String getTransactionCode() { + return transactionCode; + } + + public InteropActionState getState() { + return state; + } + + public String getExpiration() { + return expiration; + } + + @Transient + public LocalDateTime getExpirationDate() { + return parse(expiration); + } + + public List getExtensionList() { + return extensionList; + } + + protected static LocalDateTime parse(String date) { + return date == null ? null : LocalDateTime.parse(date, ISO_DATE_TIME_FORMATTER); + } + + protected static String format(LocalDateTime date) { + return date == null ? null : date.toString(ISO_DATE_TIME_FORMATTER); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionRequestData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionRequestData.java new file mode 100644 index 00000000000..f557166590d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionRequestData.java @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import com.google.gson.JsonObject; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.interoperation.domain.InteropTransactionRole; +import org.joda.time.LocalDateTime; + +import javax.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.List; + +import static org.apache.fineract.interoperation.util.InteropUtil.*; + +public class InteropTransactionRequestData extends InteropRequestData { + + static final String[] PARAMS = {PARAM_TRANSACTION_CODE, PARAM_REQUEST_CODE, PARAM_ACCOUNT_ID, PARAM_AMOUNT, PARAM_TRANSACTION_ROLE, + PARAM_TRANSACTION_TYPE, PARAM_NOTE, PARAM_GEO_CODE, PARAM_EXPIRATION, PARAM_EXTENSION_LIST, PARAM_LOCALE, PARAM_DATE_FORMAT}; + + + public InteropTransactionRequestData(@NotNull String transactionCode, @NotNull String requestCode, @NotNull String accountId, + @NotNull MoneyData amount, @NotNull InteropTransactionTypeData transactionType, String note, + GeoCodeData geoCode, LocalDateTime expiration, List extensionList) { + super(transactionCode, requestCode, accountId, amount, InteropTransactionRole.PAYER, transactionType, note, geoCode, expiration, extensionList); + } + + public InteropTransactionRequestData(@NotNull String transactionCode, @NotNull String requestCode, @NotNull String accountId, + @NotNull MoneyData amount, @NotNull InteropTransactionTypeData transactionType) { + this(transactionCode, requestCode, accountId, amount, transactionType, null, null, null, null); + } + + private InteropTransactionRequestData(InteropRequestData other) { + this(other.getTransactionCode(), other.getRequestCode(), other.getAccountId(), other.getAmount(), other.getTransactionType(), + other.getNote(), other.getGeoCode(), other.getExpiration(), other.getExtensionList()); + } + + public static InteropTransactionRequestData validateAndParse(final DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) { + if (element == null) + return null; + + jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS)); + + InteropRequestData interopRequestData = InteropRequestData.validateAndParse(dataValidator, element, jsonHelper); + + DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_REQUEST_CODE).value(interopRequestData.getRequestCode()).notNull(); + dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_TRANSACTION_TYPE).value(interopRequestData.getTransactionType()).notNull(); + dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_TRANSACTION_ROLE).value(interopRequestData.getTransactionRole()).ignoreIfNull() + .isOneOfTheseValues(InteropTransactionRole.PAYER); + + dataValidator.merge(dataValidatorCopy); + return dataValidator.hasError() ? null : new InteropTransactionRequestData(interopRequestData); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionRequestResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionRequestResponseData.java new file mode 100644 index 00000000000..2ea367e7a71 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionRequestResponseData.java @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import org.apache.fineract.interoperation.domain.InteropActionState; +import org.joda.time.LocalDateTime; + +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Map; + +public class InteropTransactionRequestResponseData extends InteropResponseData { + + @NotNull + private final String requestCode; + + + private InteropTransactionRequestResponseData(Long resourceId, Long officeId, Long commandId, Map changesOnly, + @NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration, + List extensionList, @NotNull String requestCode) { + super(resourceId, officeId, commandId, changesOnly, transactionCode, state, expiration, extensionList); + this.requestCode = requestCode; + } + + public static InteropTransactionRequestResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state, + LocalDateTime expiration, List extensionList, @NotNull String requestCode) { + return new InteropTransactionRequestResponseData(null, null, commandId, null, transactionCode, state, expiration, extensionList, requestCode); + } + + public static InteropTransactionRequestResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state, + LocalDateTime expiration, List extensionList, @NotNull String requestCode) { + return build(null, transactionCode, state, expiration, extensionList, requestCode); + } + + public static InteropTransactionRequestResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state, + @NotNull String requestCode) { + return build(commandId, transactionCode, state, null, null, requestCode); + } + + public static InteropTransactionRequestResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state, + @NotNull String requestCode) { + return build(null, transactionCode, state, requestCode); + } + + public String getRequestCode() { + return requestCode; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionTypeData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionTypeData.java new file mode 100644 index 00000000000..2b4788030d7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransactionTypeData.java @@ -0,0 +1,96 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import com.google.gson.JsonObject; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.interoperation.domain.InteropInitiatorType; +import org.apache.fineract.interoperation.domain.InteropTransactionRole; +import org.apache.fineract.interoperation.domain.InteropTransactionScenario; + +import javax.validation.constraints.NotNull; +import java.util.Arrays; + +import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_INITIATOR; +import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_INITIATOR_TYPE; +import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_SCENARIO; +import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_SUB_SCENARIO; + +public class InteropTransactionTypeData { + + public static final String[] PARAMS = {PARAM_SCENARIO, PARAM_SUB_SCENARIO, PARAM_INITIATOR, PARAM_INITIATOR_TYPE}; + + @NotNull + private final InteropTransactionScenario scenario; + + private final String subScenario; + @NotNull + private final InteropTransactionRole initiator; + @NotNull + private final InteropInitiatorType initiatorType; + + public InteropTransactionTypeData(InteropTransactionScenario scenario, String subScenario, InteropTransactionRole initiator, InteropInitiatorType initiatorType) { + this.scenario = scenario; + this.subScenario = subScenario; + this.initiator = initiator; + this.initiatorType = initiatorType; + } + + public InteropTransactionScenario getScenario() { + return scenario; + } + + public String getSubScenario() { + return subScenario; + } + + public InteropTransactionRole getInitiator() { + return initiator; + } + + public InteropInitiatorType getInitiatorType() { + return initiatorType; + } + + + public static InteropTransactionTypeData validateAndParse(DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) { + if (element == null) + return null; + + jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS)); + + String scenarioString = jsonHelper.extractStringNamed(PARAM_SCENARIO, element); + DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_SCENARIO).value(scenarioString).notBlank(); + InteropTransactionScenario scenario = InteropTransactionScenario.valueOf(scenarioString); + + String subScenario = jsonHelper.extractStringNamed(PARAM_SUB_SCENARIO, element); + + String initiatorString = jsonHelper.extractStringNamed(PARAM_INITIATOR, element); + dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_INITIATOR).value(initiatorString).notBlank(); + InteropTransactionRole initiator = InteropTransactionRole.valueOf(initiatorString); + + String initiatorTypeString = jsonHelper.extractStringNamed(PARAM_INITIATOR_TYPE, element); + dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_INITIATOR_TYPE).value(initiatorTypeString).notBlank(); + InteropInitiatorType initiatorType = InteropInitiatorType.valueOf(initiatorTypeString); + + dataValidator.merge(dataValidatorCopy); + return dataValidator.hasError() ? null : new InteropTransactionTypeData(scenario, subScenario, initiator, initiatorType); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransferRequestData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransferRequestData.java new file mode 100644 index 00000000000..9d68f4745c8 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransferRequestData.java @@ -0,0 +1,111 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import com.google.gson.JsonObject; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.interoperation.domain.InteropTransactionRole; +import org.joda.time.LocalDateTime; + +import javax.validation.constraints.NotNull; +import java.util.Arrays; +import java.util.List; + +import static org.apache.fineract.interoperation.util.InteropUtil.*; + +public class InteropTransferRequestData extends InteropRequestData { + + static final String[] PARAMS = {PARAM_TRANSACTION_CODE, PARAM_ACCOUNT_ID, PARAM_AMOUNT, PARAM_TRANSACTION_ROLE, PARAM_TRANSACTION_TYPE, + PARAM_NOTE, PARAM_EXPIRATION, PARAM_EXTENSION_LIST, PARAM_TRANSFER_CODE, PARAM_FSP_FEE, PARAM_FSP_COMMISSION, + PARAM_LOCALE, PARAM_DATE_FORMAT}; + + + @NotNull + private final String transferCode; + + // validation: what was specified in quotes step + private MoneyData fspFee; + + private MoneyData fspCommission; + + public InteropTransferRequestData(@NotNull String transactionCode, @NotNull String accountId, @NotNull MoneyData amount, + @NotNull InteropTransactionRole transactionRole, InteropTransactionTypeData transactionType, String note, LocalDateTime expiration, + List extensionList, @NotNull String transferCode, MoneyData fspFee, MoneyData fspCommission) { + super(transactionCode, null, accountId, amount, transactionRole, transactionType, note, null, expiration, extensionList); + this.transferCode = transferCode; + this.fspFee = fspFee; + this.fspCommission = fspCommission; + } + + public InteropTransferRequestData(@NotNull String transactionCode, @NotNull String transferCode, @NotNull String accountId, + @NotNull MoneyData amount, @NotNull InteropTransactionRole transactionRole) { + this(transactionCode, accountId, amount, transactionRole, null, null, null, null, transferCode, null, null); + } + + private InteropTransferRequestData(InteropRequestData other, @NotNull String transferCode, MoneyData fspFee, MoneyData fspCommission) { + this(other.getTransactionCode(), other.getAccountId(), other.getAmount(), other.getTransactionRole(), other.getTransactionType(), + other.getNote(), other.getExpiration(), other.getExtensionList(), transferCode, fspFee, fspCommission); + } + + public String getTransferCode() { + return transferCode; + } + + public MoneyData getFspFee() { + return fspFee; + } + + public MoneyData getFspCommission() { + return fspCommission; + } + + public void normalizeAmounts(@NotNull MonetaryCurrency currency) { + super.normalizeAmounts(currency); + if (fspFee != null) + fspFee.normalizeAmount(currency); + } + + public static InteropTransferRequestData validateAndParse(final DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) { + if (element == null) + return null; + + jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS)); + + InteropRequestData interopRequestData = InteropRequestData.validateAndParse(dataValidator, element, jsonHelper); + + String transferCode = jsonHelper.extractStringNamed(PARAM_TRANSFER_CODE, element); + DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_TRANSFER_CODE).value(transferCode).notBlank(); + + JsonObject fspFeeElement = jsonHelper.extractJsonObjectNamed(PARAM_FSP_FEE, element); + dataValidator.merge(dataValidatorCopy); + MoneyData fspFee = MoneyData.validateAndParse(dataValidator, fspFeeElement, jsonHelper); + + JsonObject fspCommissionElement = jsonHelper.extractJsonObjectNamed(PARAM_FSP_COMMISSION, element); + dataValidator.merge(dataValidatorCopy); + MoneyData fspCommission = MoneyData.validateAndParse(dataValidator, fspCommissionElement, jsonHelper); + + String transactionRoleString = jsonHelper.extractStringNamed(PARAM_TRANSACTION_ROLE, element); + dataValidatorCopy = dataValidator.reset().parameter(PARAM_TRANSACTION_ROLE).value(transactionRoleString).notNull(); + + dataValidator.merge(dataValidatorCopy); + return dataValidator.hasError() ? null : new InteropTransferRequestData(interopRequestData, transferCode, fspFee, fspCommission); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransferResponseData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransferResponseData.java new file mode 100644 index 00000000000..cc99e941870 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/InteropTransferResponseData.java @@ -0,0 +1,79 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import org.apache.fineract.interoperation.domain.InteropActionState; +import org.joda.time.LocalDateTime; + +import javax.validation.constraints.NotNull; +import java.beans.Transient; +import java.text.ParseException; +import java.util.List; +import java.util.Map; + +public class InteropTransferResponseData extends InteropResponseData { + + @NotNull + private final String transferCode; + + private String completedTimestamp; + + private InteropTransferResponseData(Long resourceId, Long officeId, Long commandId, Map changesOnly, + @NotNull String transactionCode, @NotNull InteropActionState state, LocalDateTime expiration, + List extensionList, @NotNull String transferCode, LocalDateTime completedTimestamp) { + super(resourceId, officeId, commandId, changesOnly, transactionCode, state, expiration, extensionList); + this.transferCode = transferCode; + this.completedTimestamp = format(completedTimestamp); + } + + public static InteropTransferResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state, + LocalDateTime expiration, List extensionList, @NotNull String transferCode, + LocalDateTime completedTimestamp) { + return new InteropTransferResponseData(null, null, commandId, null, transactionCode, state, expiration, extensionList, + transferCode, completedTimestamp); + } + + public static InteropTransferResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state, + List extensionList, @NotNull String transferCode, + LocalDateTime completedTimestamp) { + return build(null, transactionCode, state, null, extensionList, transferCode, completedTimestamp); + } + + public static InteropTransferResponseData build(Long commandId, @NotNull String transactionCode, @NotNull InteropActionState state, + @NotNull String transferCode) { + return build(commandId, transactionCode, state, null, null, transferCode, null); + } + + public static InteropTransferResponseData build(@NotNull String transactionCode, @NotNull InteropActionState state, @NotNull String transferCode) { + return build(null, transactionCode, state, transferCode); + } + + public String getTransferCode() { + return transferCode; + } + + public String getCompletedTimestamp() { + return completedTimestamp; + } + + @Transient + public LocalDateTime getCompletedTimestampDate() throws ParseException { + return parse(completedTimestamp); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/MoneyData.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/MoneyData.java new file mode 100644 index 00000000000..4e3cf47290b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/data/MoneyData.java @@ -0,0 +1,86 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.data; + +import com.google.gson.JsonObject; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.interoperation.util.MathUtil; + +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.util.Arrays; + +import static org.apache.fineract.interoperation.util.InteropUtil.DEFAULT_LOCALE; +import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_AMOUNT; +import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_CURRENCY; +import static org.apache.fineract.interoperation.util.InteropUtil.PARAM_LOCALE; + +public class MoneyData { + + public static final String[] PARAMS = {PARAM_AMOUNT, PARAM_CURRENCY, PARAM_LOCALE}; + + @NotNull + private final BigDecimal amount; + @NotNull + private final String currency; + + MoneyData(BigDecimal amount, String currency) { + this.amount = amount; + this.currency = currency; + } + + public static MoneyData build(BigDecimal amount, String currency) { + return new MoneyData(amount, currency); + } + + public BigDecimal getAmount() { + return amount; + } + + public String getCurrency() { + return currency; + } + + public void normalizeAmount(@NotNull MonetaryCurrency currency) { + if (!currency.getCode().equals(this.currency)) + throw new UnsupportedOperationException("Internal error: Invalid currency " + currency.getCode()); + MathUtil.normalizeAmount(amount, currency); + } + + public static MoneyData validateAndParse(DataValidatorBuilder dataValidator, JsonObject element, FromJsonHelper jsonHelper) { + if (element == null) + return null; + + jsonHelper.checkForUnsupportedParameters(element, Arrays.asList(PARAMS)); + + String locale = jsonHelper.extractStringNamed(PARAM_LOCALE, element); + BigDecimal amount = locale == null + ? jsonHelper.extractBigDecimalNamed(PARAM_AMOUNT, element, DEFAULT_LOCALE) + : jsonHelper.extractBigDecimalWithLocaleNamed(PARAM_AMOUNT, element); + DataValidatorBuilder dataValidatorCopy = dataValidator.reset().parameter(PARAM_AMOUNT).value(amount).notBlank().zeroOrPositiveAmount(); + + String currency = jsonHelper.extractStringNamed(PARAM_CURRENCY, element); + dataValidatorCopy = dataValidatorCopy.reset().parameter(PARAM_CURRENCY).value(currency).notBlank().notExceedingLengthOf(3); + + dataValidator.merge(dataValidatorCopy); + return dataValidator.hasError() ? null : new MoneyData(amount, currency); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropActionState.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropActionState.java new file mode 100644 index 00000000000..615b002fc69 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropActionState.java @@ -0,0 +1,24 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.domain; + +public enum InteropActionState { + ACCEPTED, + REJECTED +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropAmountType.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropAmountType.java new file mode 100644 index 00000000000..609667bda88 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropAmountType.java @@ -0,0 +1,24 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.domain; + +public enum InteropAmountType { + SEND, + RECEIVE +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifier.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifier.java new file mode 100644 index 00000000000..d514d529986 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifier.java @@ -0,0 +1,167 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.domain; + +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.portfolio.savings.domain.SavingsAccount; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.Date; + +@Entity +@Table(name = "interop_identifier", uniqueConstraints = { + @UniqueConstraint(name = "uk_hathor_identifier_account", columnNames = {"account_id", "type"}), + @UniqueConstraint(name = "uk_hathor_identifier_value", columnNames = {"type", "a_value", "sub_value_or_type"}) +}) +public class InteropIdentifier extends AbstractPersistableCustom { + + @ManyToOne(optional = false) + @JoinColumn(name = "account_id", nullable = false) + private SavingsAccount account; + + @Column(name = "type", nullable = false, length = 32) + @Enumerated(EnumType.STRING) + private InteropIdentifierType type; + + @Column(name = "a_value", nullable = false, length = 128) + private String value; + + @Column(name = "sub_value_or_type", length = 128) + private String subValueOrType; + + @Column(name = "created_by", nullable = false, length = 32) + private String createdBy; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "created_on", nullable = false) + private Date createdOn; + + @Column(name = "modified_by", length = 32) + private String modifiedBy; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "modified_on") + private Date modifiedOn; + + + protected InteropIdentifier() { + } + + public InteropIdentifier(@NotNull SavingsAccount account, @NotNull InteropIdentifierType type, @NotNull String value, + String subValueOrType, @NotNull String createdBy, @NotNull Date createdOn) { + this.account = account; + this.type = type; + this.value = value; + this.subValueOrType = subValueOrType; + this.createdBy = createdBy; + this.createdOn = createdOn; + } + + public InteropIdentifier(@NotNull SavingsAccount account, @NotNull InteropIdentifierType type, @NotNull String createdBy, + @NotNull Date createdOn) { + this(account, type, null, null, createdBy, createdOn); + } + + public SavingsAccount getAccount() { + return account; + } + + private void setAccount(SavingsAccount account) { + this.account = account; + } + + public InteropIdentifierType getType() { + return type; + } + + private void setType(InteropIdentifierType type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getSubValueOrType() { + return subValueOrType; + } + + public void setSubValueOrType(String subValueOrType) { + this.subValueOrType = subValueOrType; + } + + public String getCreatedBy() { + return createdBy; + } + + private void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public Date getCreatedOn() { + return createdOn; + } + + private void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + + public String geModifiedBy() { + return modifiedBy; + } + + public void setModifiedBy(String modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public Date getModifiedOn() { + return modifiedOn; + } + + public void setModifiedOn(Date modifiedOn) { + this.modifiedOn = modifiedOn; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InteropIdentifier that = (InteropIdentifier) o; + + if (!account.equals(that.account)) return false; + if (type != that.type) return false; + if (!value.equals(that.value)) return false; + return subValueOrType != null ? subValueOrType.equals(that.subValueOrType) : that.subValueOrType == null; + } + + @Override + public int hashCode() { + int result = type.hashCode(); + result = 31 * result + value.hashCode(); + result = 31 * result + (subValueOrType != null ? subValueOrType.hashCode() : 0); + return result; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifierRepository.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifierRepository.java new file mode 100644 index 00000000000..15ab03b1b03 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifierRepository.java @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.stereotype.Repository; + +@Repository +public interface InteropIdentifierRepository extends JpaRepository, JpaSpecificationExecutor { +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifierType.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifierType.java new file mode 100644 index 00000000000..ff2e1550b4e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropIdentifierType.java @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.domain; + +public enum InteropIdentifierType { + + MSISDN, + EMAIL, + PERSONAL_ID, + BUSINESS, + DEVICE, + ACCOUNT_ID, + IBAN, + ALIAS +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropInitiatorType.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropInitiatorType.java new file mode 100644 index 00000000000..41bfda59131 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropInitiatorType.java @@ -0,0 +1,26 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.domain; + +public enum InteropInitiatorType { + CONSUMER, + AGENT, + BUSINESS, + DEVICE +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransactionRole.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransactionRole.java new file mode 100644 index 00000000000..93aecc5ed48 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransactionRole.java @@ -0,0 +1,35 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.domain; + +import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType; + +public enum InteropTransactionRole { + PAYER, + PAYEE, + ; + + public boolean isWithdraw() { + return this == PAYER; + } + + public SavingsAccountTransactionType getTransactionType() { + return this == PAYER ? SavingsAccountTransactionType.WITHDRAWAL : SavingsAccountTransactionType.DEPOSIT; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransactionScenario.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransactionScenario.java new file mode 100644 index 00000000000..e84f8698cf7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransactionScenario.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.domain; + +public enum InteropTransactionScenario { + + DEPOSIT, + WITHDRAWAL, + TRANSFER, + PAYMENT, + REFUND +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransferActionType.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransferActionType.java new file mode 100644 index 00000000000..26ebd774341 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/domain/InteropTransferActionType.java @@ -0,0 +1,26 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.domain; + +public enum InteropTransferActionType { + + PREPARE, + CREATE, + ; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CommitInteropTransferHandler.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CommitInteropTransferHandler.java new file mode 100644 index 00000000000..3cf3033f2d8 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CommitInteropTransferHandler.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.interoperation.service.InteropService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static org.apache.fineract.interoperation.util.InteropUtil.ACTION_TRANSFER_COMMIT; +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_TRANSFER; + +@Service +@CommandType(entity = ENTITY_NAME_TRANSFER, action = ACTION_TRANSFER_COMMIT) +public class CommitInteropTransferHandler implements NewCommandSourceHandler { + + private final InteropService interopService; + + @Autowired + public CommitInteropTransferHandler(InteropService interopService) { + this.interopService = interopService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.interopService.commitTransfer(command); + } +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropIdentifierHandler.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropIdentifierHandler.java new file mode 100644 index 00000000000..1b888214d5d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropIdentifierHandler.java @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.handler; + +import com.google.common.base.Strings; +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.interoperation.domain.InteropIdentifierType; +import org.apache.fineract.interoperation.service.InteropService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_IDENTIFIER; +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_REQUEST; + +@Service +@CommandType(entity = ENTITY_NAME_IDENTIFIER, action = "CREATE") +public class CreateInteropIdentifierHandler implements NewCommandSourceHandler { + + private final InteropService interopService; + + @Autowired + public CreateInteropIdentifierHandler(InteropService interopService) { + this.interopService = interopService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + String[] split = command.getUrl().split("/"); + int length = split.length; + String subIdOrType = Strings.emptyToNull(split[length]); + String idValue = split[length - 1]; + InteropIdentifierType idType = InteropIdentifierType.valueOf(split[length - 2].toUpperCase()); + return this.interopService.registerAccountIdentifier(idType, idValue, subIdOrType, command); + } +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropQuoteHandler.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropQuoteHandler.java new file mode 100644 index 00000000000..e389d4be4db --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropQuoteHandler.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.interoperation.service.InteropService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_QUOTE; + +@Service +@CommandType(entity = ENTITY_NAME_QUOTE, action = "CREATE") +public class CreateInteropQuoteHandler implements NewCommandSourceHandler { + + private final InteropService interopService; + + @Autowired + public CreateInteropQuoteHandler(InteropService interopService) { + this.interopService = interopService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.interopService.createQuote(command); + } +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropRequestHandler.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropRequestHandler.java new file mode 100644 index 00000000000..98e2e2370b0 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/CreateInteropRequestHandler.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.interoperation.domain.InteropIdentifierType; +import org.apache.fineract.interoperation.service.InteropService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_REQUEST; + +@Service +@CommandType(entity = ENTITY_NAME_REQUEST, action = "CREATE") +public class CreateInteropRequestHandler implements NewCommandSourceHandler { + + private final InteropService interopService; + + @Autowired + public CreateInteropRequestHandler(InteropService interopService) { + this.interopService = interopService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.interopService.createTransactionRequest(command); + } +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/DeleteInteropRequestHandler.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/DeleteInteropRequestHandler.java new file mode 100644 index 00000000000..dda41c47e91 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/DeleteInteropRequestHandler.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.handler; + +import com.google.common.base.Strings; +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.interoperation.domain.InteropIdentifierType; +import org.apache.fineract.interoperation.service.InteropService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_REQUEST; + +@Service +@CommandType(entity = ENTITY_NAME_REQUEST, action = "DELETE") +public class DeleteInteropRequestHandler implements NewCommandSourceHandler { + + private final InteropService interopService; + + @Autowired + public DeleteInteropRequestHandler(InteropService interopService) { + this.interopService = interopService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + String[] split = command.getUrl().split("/"); + int length = split.length; + String subIdOrType = Strings.emptyToNull(split[length]); + String idValue = split[length - 1]; + InteropIdentifierType idType = InteropIdentifierType.valueOf(split[length - 2].toUpperCase()); + return this.interopService.deleteAccountIdentifier(idType, idValue, subIdOrType); + } +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/PrepareInteropTransferHandler.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/PrepareInteropTransferHandler.java new file mode 100644 index 00000000000..399464129db --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/handler/PrepareInteropTransferHandler.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.interoperation.service.InteropService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static org.apache.fineract.interoperation.util.InteropUtil.ACTION_TRANSFER_PREPARE; +import static org.apache.fineract.interoperation.util.InteropUtil.ENTITY_NAME_TRANSFER; + +@Service +@CommandType(entity = ENTITY_NAME_TRANSFER, action = ACTION_TRANSFER_PREPARE) +public class PrepareInteropTransferHandler implements NewCommandSourceHandler { + + private final InteropService interopService; + + @Autowired + public PrepareInteropTransferHandler(InteropService interopService) { + this.interopService = interopService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.interopService.prepareTransfer(command); + } +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/serialization/InteropDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/serialization/InteropDataValidator.java new file mode 100644 index 00000000000..d5ac8062045 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/serialization/InteropDataValidator.java @@ -0,0 +1,110 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.serialization; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.apache.commons.lang.StringUtils; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.interoperation.data.InteropIdentifierRequestData; +import org.apache.fineract.interoperation.data.InteropQuoteRequestData; +import org.apache.fineract.interoperation.data.InteropTransactionRequestData; +import org.apache.fineract.interoperation.data.InteropTransferRequestData; +import org.apache.fineract.interoperation.domain.InteropIdentifierType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.validation.constraints.NotNull; +import java.util.ArrayList; + +@Component +public class InteropDataValidator { + + private final FromJsonHelper jsonHelper; + + @Autowired + public InteropDataValidator(final FromJsonHelper fromJsonHelper) { + this.jsonHelper = fromJsonHelper; + } + + public InteropIdentifierRequestData validateAndParseCreateIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, + String subIdOrType, JsonCommand command) { + final DataValidatorBuilder dataValidator = new DataValidatorBuilder(new ArrayList<>()).resource("interoperation.identifier"); + JsonObject element = extractJsonObject(command); + + InteropIdentifierRequestData result = InteropIdentifierRequestData.validateAndParse(dataValidator, idType, idValue, subIdOrType, element, jsonHelper); + throwExceptionIfValidationWarningsExist(dataValidator); + + return result; + } + + public InteropTransactionRequestData validateAndParseCreateRequest(JsonCommand command) { + final DataValidatorBuilder dataValidator = new DataValidatorBuilder(new ArrayList<>()).resource("interoperation.request"); + JsonObject element = extractJsonObject(command); + + InteropTransactionRequestData result = InteropTransactionRequestData.validateAndParse(dataValidator, element, jsonHelper); + throwExceptionIfValidationWarningsExist(dataValidator); + + return result; + } + + public InteropQuoteRequestData validateAndParseCreateQuote(JsonCommand command) { + final DataValidatorBuilder dataValidator = new DataValidatorBuilder(new ArrayList<>()).resource("interoperation.quote"); + JsonObject element = extractJsonObject(command); + + InteropQuoteRequestData result = InteropQuoteRequestData.validateAndParse(dataValidator, element, jsonHelper); + throwExceptionIfValidationWarningsExist(dataValidator); + + return result; + } + + public InteropTransferRequestData validateAndParsePrepareTransfer(JsonCommand command) { + return validateAndParseCreateTransfer(command); + } + + public InteropTransferRequestData validateAndParseCreateTransfer(JsonCommand command) { + final DataValidatorBuilder dataValidator = new DataValidatorBuilder(new ArrayList<>()).resource("interoperation.transfer"); + JsonObject element = extractJsonObject(command); + + InteropTransferRequestData result = InteropTransferRequestData.validateAndParse(dataValidator, element, jsonHelper); + throwExceptionIfValidationWarningsExist(dataValidator); + + return result; + } + + private JsonObject extractJsonObject(JsonCommand command) { + String json = command.json(); + if (StringUtils.isBlank(json)) + throw new InvalidJsonException(); + + final JsonElement element = jsonHelper.parse(json); + return element.getAsJsonObject(); + } + + private void throwExceptionIfValidationWarningsExist(DataValidatorBuilder dataValidator) { + if (dataValidator.hasError()) { + throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidator.getDataValidationErrors()); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropService.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropService.java new file mode 100644 index 00000000000..e0b0e7fb849 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropService.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.service; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.interoperation.data.InteropIdentifierResponseData; +import org.apache.fineract.interoperation.data.InteropQuoteResponseData; +import org.apache.fineract.interoperation.data.InteropTransactionRequestResponseData; +import org.apache.fineract.interoperation.data.InteropTransferResponseData; +import org.apache.fineract.interoperation.domain.InteropIdentifierType; + +import javax.validation.constraints.NotNull; + +public interface InteropService { + + @NotNull + InteropIdentifierResponseData getAccountByIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, String subIdOrType); + + @NotNull + InteropIdentifierResponseData registerAccountIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, + String subIdOrType, @NotNull JsonCommand command); + + @NotNull + InteropIdentifierResponseData deleteAccountIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, + String subIdOrType); + + InteropTransactionRequestResponseData getTransactionRequest(@NotNull String transactionCode, @NotNull String requestCode); + + @NotNull + InteropTransactionRequestResponseData createTransactionRequest(@NotNull JsonCommand command); + + InteropQuoteResponseData getQuote(@NotNull String transactionCode, @NotNull String quoteCode); + + @NotNull + InteropQuoteResponseData createQuote(@NotNull JsonCommand command); + + InteropTransferResponseData getTransfer(@NotNull String transactionCode, @NotNull String transferCode); + + @NotNull + InteropTransferResponseData prepareTransfer(@NotNull JsonCommand command); + + @NotNull + InteropTransferResponseData commitTransfer(@NotNull JsonCommand command); +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java new file mode 100644 index 00000000000..51c0b250cd4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/service/InteropServiceImpl.java @@ -0,0 +1,429 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.service; + +import org.apache.commons.lang.StringUtils; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.interoperation.data.*; +import org.apache.fineract.interoperation.domain.InteropIdentifier; +import org.apache.fineract.interoperation.domain.InteropIdentifierRepository; +import org.apache.fineract.interoperation.domain.InteropIdentifierType; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepository; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.interoperation.domain.InteropActionState; +import org.apache.fineract.interoperation.serialization.InteropDataValidator; +import org.apache.fineract.interoperation.util.MathUtil; +import org.apache.fineract.portfolio.note.domain.Note; +import org.apache.fineract.portfolio.note.domain.NoteRepository; +import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; +import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService; +import org.apache.fineract.portfolio.paymenttype.domain.PaymentType; +import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepository; +import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType; +import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues; +import org.apache.fineract.portfolio.savings.domain.SavingsAccount; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountDomainService; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionSummaryWrapper; +import org.apache.fineract.portfolio.savings.domain.SavingsHelper; +import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException; +import org.apache.fineract.useradministration.domain.AppUser; +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.domain.Specifications; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Root; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import static org.apache.fineract.interoperation.util.InteropUtil.DEFAULT_LOCALE; +import static org.apache.fineract.interoperation.util.InteropUtil.DEFAULT_ROUTING_CODE; +import static org.apache.fineract.interoperation.util.InteropUtil.ISO8601_DATE_TIME_FORMAT; + +@Service +public class InteropServiceImpl implements InteropService { + + private final static Logger LOG = LoggerFactory.getLogger(InteropServiceImpl.class); + + private final PlatformSecurityContext securityContext; + private final InteropDataValidator dataValidator; + + private final SavingsAccountRepository savingsAccountRepository; + private final SavingsAccountTransactionRepository savingsAccountTransactionRepository; + private final ApplicationCurrencyRepository currencyRepository; + private final NoteRepository noteRepository; + private final PaymentTypeRepository paymentTypeRepository; + private final InteropIdentifierRepository identifierRepository; + + private final SavingsHelper savingsHelper; + private final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper; + + private final SavingsAccountDomainService savingsAccountService; + private final PaymentDetailWritePlatformService paymentDetailService; + + @Autowired + public InteropServiceImpl(PlatformSecurityContext securityContext, + InteropDataValidator interopDataValidator, + SavingsAccountRepository savingsAccountRepository, + SavingsAccountTransactionRepository savingsAccountTransactionRepository, + ApplicationCurrencyRepository applicationCurrencyRepository, + NoteRepository noteRepository, + PaymentTypeRepository paymentTypeRepository, + InteropIdentifierRepository identifierRepository, + SavingsHelper savingsHelper, + SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, + SavingsAccountDomainService savingsAccountService, + PaymentDetailWritePlatformService paymentDetailWritePlatformService) { + this.securityContext = securityContext; + this.dataValidator = interopDataValidator; + this.savingsAccountRepository = savingsAccountRepository; + this.savingsAccountTransactionRepository = savingsAccountTransactionRepository; + this.currencyRepository = applicationCurrencyRepository; + this.noteRepository = noteRepository; + this.paymentTypeRepository = paymentTypeRepository; + this.identifierRepository = identifierRepository; + this.savingsHelper = savingsHelper; + this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; + this.savingsAccountService = savingsAccountService; + this.paymentDetailService = paymentDetailWritePlatformService; + } + + @NotNull + @Transactional + public InteropIdentifierResponseData getAccountByIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, String subIdOrType) { + InteropIdentifier identifier = findIdentifier(idType, idValue, subIdOrType); + if (identifier == null) + throw new UnsupportedOperationException("Account not found for identifier " + idType + "/" + idValue + (subIdOrType == null ? "" : ("/" + subIdOrType))); + + return InteropIdentifierResponseData.build(identifier.getAccount().getExternalId()); + } + + @NotNull + @Transactional(propagation = Propagation.MANDATORY) + public InteropIdentifierResponseData registerAccountIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, + String subIdOrType, @NotNull JsonCommand command) { + InteropIdentifierRequestData request = dataValidator.validateAndParseCreateIdentifier(idType, idValue, subIdOrType, command); + //TODO: error handling + SavingsAccount savingsAccount = validateAndGetSavingAccount(request.getAccountId()); + + AppUser createdBy = getLoginUser(); + + InteropIdentifier identifier = new InteropIdentifier(savingsAccount, request.getIdType(), request.getIdValue(), + request.getSubIdOrType(), createdBy.getUsername(), DateUtils.getDateOfTenant()); + + identifierRepository.save(identifier); + + return InteropIdentifierResponseData.build(savingsAccount.getExternalId()); + } + + @NotNull + @Transactional(propagation = Propagation.MANDATORY) + public InteropIdentifierResponseData deleteAccountIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, + String subIdOrType) { + InteropIdentifier identifier = findIdentifier(idType, idValue, subIdOrType); + if (identifier == null) + throw new UnsupportedOperationException("Account not found for identifier " + idType + "/" + idValue + (subIdOrType == null ? "" : ("/" + subIdOrType))); + + String accountId = identifier.getAccount().getExternalId(); + + identifierRepository.delete(identifier); + + return InteropIdentifierResponseData.build(accountId); + } + + @Override + public InteropTransactionRequestResponseData getTransactionRequest(@NotNull String transactionCode, @NotNull String requestCode) { + // always REJECTED until request info is stored + return InteropTransactionRequestResponseData.build(transactionCode, InteropActionState.REJECTED, requestCode); + } + + @Override + @NotNull + @Transactional(propagation = Propagation.MANDATORY) + public InteropTransactionRequestResponseData createTransactionRequest(@NotNull JsonCommand command) { + // only when Payee request transaction from Payer, so here role must be always Payer + InteropTransactionRequestData request = dataValidator.validateAndParseCreateRequest(command); + + //TODO: error handling + SavingsAccount savingsAccount = validateAndGetSavingAccount(request); + + return InteropTransactionRequestResponseData.build(command.commandId(), request.getTransactionCode(), InteropActionState.ACCEPTED, + request.getExpiration(), request.getExtensionList(), request.getRequestCode()); + } + + @Override + public InteropQuoteResponseData getQuote(@NotNull String transactionCode, @NotNull String quoteCode) { + return null; + } + + @Override + @NotNull + @Transactional(propagation = Propagation.MANDATORY) + public InteropQuoteResponseData createQuote(@NotNull JsonCommand command) { + InteropQuoteRequestData request = dataValidator.validateAndParseCreateQuote(command); + + //TODO: error handling + SavingsAccount savingsAccount = validateAndGetSavingAccount(request); + SavingsAccountTransactionType transactionType = request.getTransactionRole().getTransactionType(); + + BigDecimal fee = transactionType.isDebit() ? savingsAccount.calculateWithdrawalFee(request.getAmount().getAmount()) : BigDecimal.ZERO; + + return InteropQuoteResponseData.build(command.commandId(), request.getTransactionCode(), InteropActionState.ACCEPTED, + request.getExpiration(), request.getExtensionList(), request.getQuoteCode(), MoneyData.build(fee, savingsAccount.getCurrency().getCode()), + null); + } + + @Override + public InteropTransferResponseData getTransfer(@NotNull String transactionCode, @NotNull String transferCode) { + return null; + } + + @Override + @NotNull + @Transactional(propagation = Propagation.MANDATORY) + public InteropTransferResponseData prepareTransfer(@NotNull JsonCommand command) { + InteropTransferRequestData request = dataValidator.validateAndParsePrepareTransfer(command); + + //TODO: error handling + //TODO: REVERSE + SavingsAccount savingsAccount = validateAndGetSavingAccount(request); + + BigDecimal total = validateTransfer(request, savingsAccount); + + String transferCode = request.getTransferCode(); + LocalDateTime transactionDate = DateUtils.getLocalDateTimeOfTenant(); + if (MathUtil.isGreaterThanZero(total)) { + if (MathUtil.isLessThan(savingsAccount.getWithdrawableBalance(), total)) { + throw new UnsupportedOperationException(); + } + if (findTransaction(savingsAccount, transferCode, SavingsAccountTransactionType.AMOUNT_HOLD) != null) + throw new UnsupportedOperationException("Transfer amount was already put on hold " + transferCode); + + PaymentDetail paymentDetail = PaymentDetail.instance(findPaymentType(), savingsAccount.getExternalId(), null, getRoutingCode(), transferCode, null); + AppUser appUser = getLoginUser(); + SavingsAccountTransaction transaction = SavingsAccountTransaction.holdAmount(savingsAccount, savingsAccount.office(), + paymentDetail, transactionDate.toLocalDate(), Money.of(savingsAccount.getCurrency(), total), new Date(), + appUser); + + savingsAccount.holdAmount(total); + savingsAccount.addTransaction(transaction); + + savingsAccountRepository.save(savingsAccount); + } + + return InteropTransferResponseData.build(command.commandId(), request.getTransactionCode(), InteropActionState.ACCEPTED, request.getExpiration(), + request.getExtensionList(), transferCode, transactionDate); + } + + @Override + @NotNull + @Transactional(propagation = Propagation.MANDATORY) + public InteropTransferResponseData commitTransfer(@NotNull JsonCommand command) { + InteropTransferRequestData request = dataValidator.validateAndParseCreateTransfer(command); + SavingsAccountTransactionType transactionType = request.getTransactionRole().getTransactionType(); + boolean debit = transactionType.isDebit(); + + //TODO: error handling + //TODO: REVERSE + SavingsAccount savingsAccount = validateAndGetSavingAccount(request); + + validateTransfer(request, savingsAccount); + + String transferCode = request.getTransferCode(); + if (findTransaction(savingsAccount, transferCode, debit ? SavingsAccountTransactionType.WITHDRAWAL : SavingsAccountTransactionType.DEPOSIT) != null) + throw new UnsupportedOperationException("Transfer was already committed " + transferCode); + + PaymentDetail paymentDetail = PaymentDetail.instance(findPaymentType(), savingsAccount.getExternalId(), null, getRoutingCode(), transferCode, null); + + LocalDateTime transactionDateTime = DateUtils.getLocalDateTimeOfTenant(); + LocalDate transactionDate = transactionDateTime.toLocalDate(); + Date createdDate = new Date(); + + SavingsAccountTransaction holdTransaction = findTransaction(savingsAccount, transferCode, SavingsAccountTransactionType.AMOUNT_HOLD); + if (holdTransaction != null && holdTransaction.getReleaseIdOfHoldAmountTransaction() == null) { + AppUser appUser = getLoginUser(); + + SavingsAccountTransaction releaseTransaction = SavingsAccountTransaction.releaseAmount(holdTransaction, transactionDate, createdDate, appUser); + releaseTransaction = savingsAccountTransactionRepository.saveAndFlush(releaseTransaction); + holdTransaction.updateReleaseId(releaseTransaction.getId()); + + savingsAccount.releaseAmount(holdTransaction.getAmount()); + savingsAccount.addTransaction(releaseTransaction); + + savingsAccountRepository.save(savingsAccount); + } + + BigDecimal amount = request.getAmount().getAmount(); + DateTimeFormatter fmt = getDateTimeFormatter(command); + + SavingsAccountTransaction transaction; + if (debit) { + SavingsTransactionBooleanValues transactionValues = new SavingsTransactionBooleanValues(false, true, true, false, false); + transaction = savingsAccountService.handleWithdrawal(savingsAccount, fmt, transactionDate, amount, + paymentDetail, transactionValues); + } + else { + transaction = savingsAccountService.handleDeposit(savingsAccount, fmt, transactionDate, amount, + paymentDetail, false, true); + } + + String note = request.getNote(); + if (!StringUtils.isBlank(note)) { + noteRepository.save(Note.savingsTransactionNote(savingsAccount, transaction, note)); + } + + return InteropTransferResponseData.build(command.commandId(), request.getTransactionCode(), InteropActionState.ACCEPTED, + request.getExpiration(), request.getExtensionList(), request.getTransferCode(), transactionDateTime); + } + + // Util + + private SavingsAccount validateAndGetSavingAccount(String accountId) { + SavingsAccount savingsAccount = savingsAccountRepository.findByExternalId(accountId); + if (savingsAccount == null) + throw new SavingsAccountNotFoundException(accountId); + return savingsAccount; + } + + private SavingsAccount validateAndGetSavingAccount(@NotNull InteropRequestData request) { + //TODO: error handling + SavingsAccount savingsAccount = validateAndGetSavingAccount(request.getAccountId()); + savingsAccount.setHelpers(savingsAccountTransactionSummaryWrapper, savingsHelper); + + ApplicationCurrency currency = currencyRepository.findOneByCode(request.getAmount().getCurrency()); + if (!savingsAccount.getCurrency().getCode().equals(currency.getCode())) + throw new UnsupportedOperationException(); + + SavingsAccountTransactionType transactionType = request.getTransactionRole().getTransactionType(); + if (!savingsAccount.isTransactionAllowed(transactionType, request.getExpirationLocalDate())) + throw new UnsupportedOperationException(); + + request.normalizeAmounts(savingsAccount.getCurrency()); + if (transactionType.isDebit() && MathUtil.isLessThan(savingsAccount.getWithdrawableBalance(), request.getAmount().getAmount())) + throw new UnsupportedOperationException(); + + return savingsAccount; + } + + private BigDecimal validateTransfer(@NotNull InteropTransferRequestData request, @NotNull SavingsAccount savingsAccount) { + BigDecimal amount = request.getAmount().getAmount(); + SavingsAccountTransactionType transactionType = request.getTransactionRole().getTransactionType(); + + BigDecimal total = transactionType.isDebit() ? amount : MathUtil.negate(amount); + MoneyData fspFee = request.getFspFee(); + if (fspFee != null) { + if (!savingsAccount.getCurrency().getCode().equals(fspFee.getCurrency())) + throw new UnsupportedOperationException(); + //TODO: compare with calculated quote fee + total = MathUtil.add(total, fspFee.getAmount()); + } + MoneyData fspCommission = request.getFspCommission(); + if (fspCommission != null) { + if (!savingsAccount.getCurrency().getCode().equals(fspCommission.getCurrency())) + throw new UnsupportedOperationException(); + //TODO: compare with calculated quote commission + total = MathUtil.subtractToZero(total, fspCommission.getAmount()); + } + return total; + } + + private DateTimeFormatter getDateTimeFormatter(@NotNull JsonCommand command) { + Locale locale = command.extractLocale(); + if (locale == null) + locale = DEFAULT_LOCALE; + String dateFormat = command.dateFormat(); + if (StringUtils.isEmpty(dateFormat)) + dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"; + + return DateTimeFormat.forPattern(dateFormat).withLocale(locale); + } + + PaymentType findPaymentType() { + List paymentTypes = paymentTypeRepository.findAll(); + for (PaymentType paymentType : paymentTypes) { + if (!paymentType.isCashPayment()) + return paymentType; + //TODO: for now first not cash is retured: + // 1. must be added as initial setup, + // 2. if more than one non-cashe type added then update this code + } + return null; + } + + SavingsAccountTransaction findTransaction(@NotNull SavingsAccount savingsAccount, @NotNull String transactionCode, SavingsAccountTransactionType transactionType) { + String routingCode = getRoutingCode(); + for (SavingsAccountTransaction transaction : savingsAccount.getTransactions()) { + if (transactionType != null && !transactionType.getValue().equals(transaction.getTypeOf())) + continue; + + PaymentDetail detail = transaction.getPaymentDetail(); + if (detail != null && routingCode.equals(detail.getRoutingCode()) && transactionCode.equals(detail.getReceiptNumber())) + return transaction; + } + return null; + } + + public InteropIdentifier findIdentifier(@NotNull InteropIdentifierType idType, @NotNull String idValue, String subIdOrType) { + return identifierRepository.findOne(Specifications.where(idTypeEqual(idType)).and(idValueEqual(idValue)).and(subIdOrTypeEqual(subIdOrType))); + } + + public static Specification idTypeEqual(@NotNull InteropIdentifierType idType) { + return (Root root, CriteriaQuery query, CriteriaBuilder cb) -> cb.and(cb.equal(root.get("type"), idType)); + } + + public static Specification idValueEqual(@NotNull String idValue) { + return (Root root, CriteriaQuery query, CriteriaBuilder cb) -> cb.and(cb.equal(root.get("value"), idValue)); + } + + public static Specification subIdOrTypeEqual(String subIdOrType) { + return (Root root, CriteriaQuery query, CriteriaBuilder cb) -> { + Path path = root.get("subValueOrType"); + return cb.and(subIdOrType == null ? cb.isNull(path) : cb.equal(path, subIdOrType)); + }; + } + + private AppUser getLoginUser() { + return securityContext.getAuthenticatedUserIfPresent(); + } + + @NotNull + String getRoutingCode() { + return DEFAULT_ROUTING_CODE; + } +} \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/InteropUtil.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/InteropUtil.java new file mode 100644 index 00000000000..f3ea6a8d4fd --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/InteropUtil.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.util; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import serp.util.Strings; + +import java.util.Locale; + +public class InteropUtil { + + public static final String ISO8601_DATE_TIME_FORMAT = "yyyy-MM-ddTHH:mm:ss.SSS[-HH:MM]"; + public static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd"; + + public static final Locale DEFAULT_LOCALE = Locale.US; + + public static final String ROOT_PATH = "interoperation"; + public static final String DEFAULT_ROUTING_CODE = "INTEROPERATION"; + + public static final String ENTITY_NAME_IDENTIFIER = "INTERID"; + public static final String ENTITY_NAME_REQUEST = "INTERREQUEST"; + public static final String ENTITY_NAME_QUOTE = "INTERQUOTE"; + public static final String ENTITY_NAME_TRANSFER = "INTERTRANSFER"; + + public static final String ACTION_TRANSFER_PREPARE = "PREPARE"; + public static final String ACTION_TRANSFER_COMMIT = "CREATE"; + + public static final String PARAM_LOCALE = "locale"; + public static final String PARAM_DATE_FORMAT = "dateFormat"; + + public static final String PARAM_TRANSACTION_CODE = "transactionCode"; + public static final String PARAM_REQUEST_CODE = "requestCode"; + public static final String PARAM_QUOTE_CODE = "quoteCode"; + public static final String PARAM_TRANSFER_CODE = "transferCode"; + public static final String PARAM_ACCOUNT_ID = "accountId"; + public static final String PARAM_AMOUNT_TYPE = "amountType"; + public static final String PARAM_AMOUNT = "amount"; + public static final String PARAM_FEES = "fees"; + public static final String PARAM_FSP_FEE = "fspFee"; + public static final String PARAM_FSP_COMMISSION = "fspCommission"; + public static final String PARAM_TRANSACTION_TYPE = "transactionType"; + public static final String PARAM_TRANSACTION_ROLE = "transactionRole"; + public static final String PARAM_NOTE = "note"; + public static final String PARAM_GEO_CODE = "geoCode"; + + public static final String PARAM_CURRENCY = "currency"; + + public static final String PARAM_SCENARIO = "scenario"; + public static final String PARAM_SUB_SCENARIO = "subScenario"; + public static final String PARAM_INITIATOR = "initiator"; + public static final String PARAM_INITIATOR_TYPE = "initiatorType"; + public static final String PARAM_EXPIRATION = "expiration"; + + public static final String PARAM_LATITUDE = "latitude"; + public static final String PARAM_LONGITUDE = "latitude"; + + public static final String PARAM_EXTENSION_LIST = "extensionList"; + public static final String PARAM_KEY = "key"; + public static final String PARAM_VALUE = "value"; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/MathUtil.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/MathUtil.java new file mode 100644 index 00000000000..a59d430e33b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/util/MathUtil.java @@ -0,0 +1,403 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.interoperation.util; + +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.monetary.domain.MoneyHelper; + +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.math.MathContext; + +public class MathUtil { + + public static Long nullToZero(Long value) { + return nullToDefault(value, 0L); + } + + public static Long nullToDefault(Long value, Long def) { + return value == null ? def : value; + } + + public static Long zeroToNull(Long value) { + return isEmpty(value) ? null : value; + } + + /** @return parameter value or ZERO if it is negative */ + public static Long negativeToZero(Long value) { + return isGreaterThanZero(value) ? value : 0L; + } + + public static boolean isEmpty(Long value) { + return value == null || value.equals(0L); + } + + public static boolean isGreaterThanZero(Long value) { + return value != null && value > 0L; + } + + public static boolean isLessThanZero(Long value) { + return value != null && value < 0L; + } + + public static boolean isZero(Long value) { + return value != null && value.equals(0L); + } + + public static boolean isEqualTo(Long first, Long second) { + return nullToZero(first).equals(nullToZero(second)); + } + + public static boolean isGreaterThan(Long first, Long second) { + return nullToZero(first) > nullToZero(second); + } + + public static boolean isLessThan(Long first, Long second) { + return nullToZero(first) < nullToZero(second); + } + + public static boolean isGreaterThanOrEqualTo(Long first, Long second) { + return nullToZero(first) >= nullToZero(second); + } + + public static boolean isLessThanOrEqualZero(Long value) { + return nullToZero(value) <= 0L; + } + + /** @return parameter value or negated value to positive */ + public static Long abs(Long value) { + return value == null ? 0L : Math.abs(value); + } + + /** @return calculates minimum of the two values considering null values + * @param notNull if true then null parameter is omitted, otherwise returns null */ + public static Long min(Long first, Long second, boolean notNull) { + return first == null + ? (notNull ? second : null) + : second == null ? (notNull ? first : null) : Math.min(first, second); + } + + /** @return calculates minimum of the values considering null values + * @param notNull if true then null parameter is omitted, otherwise returns null */ + public static Long min(Long first, Long second, Long third, boolean notNull) { + return min(min(first, second, notNull), third, notNull); + } + + /** @return sum the two values considering null values */ + public static Long add(Long first, Long second) { + return first == null + ? second + : second == null ? first : Math.addExact(first, second); + } + + /** @return sum the values considering null values */ + public static Long add(Long first, Long second, Long third) { + return add(add(first, second), third); + } + + /** @return sum the values considering null values */ + public static Long add(Long first, Long second, Long third, Long fourth) { + return add(add(add(first, second), third), fourth); + } + + /** @return sum the values considering null values */ + public static Long add(Long first, Long second, Long third, Long fourth, Long fifth) { + return add(add(add(add(first, second), third), fourth), fifth); + } + + /** @return first minus second considering null values, maybe negative */ + public static Long subtract(Long first, Long second) { + return first == null + ? null + : second == null ? first : Math.subtractExact(first, second); + } + + /** @return first minus the others considering null values, maybe negative */ + public static Long subtractToZero(Long first, Long second, Long third) { + return subtractToZero(subtract(first, second), third); + } + + /** @return first minus the others considering null values, maybe negative */ + public static Long subtractToZero(Long first, Long second, Long third, Long fourth) { + return subtractToZero(subtract(subtract(first, second), third), fourth); + } + + /** @return NONE negative first minus second considering null values */ + public static Long subtractToZero(Long first, Long second) { + return negativeToZero(subtract(first, second)); + } + + /** @return BigDecimal null safe negate */ + public static Long negate(Long amount) { + return isEmpty(amount) ? amount : Math.negateExact(amount); + } + + + // ----------------- BigDecimal ----------------- + + public static BigDecimal nullToZero(BigDecimal value) { + return nullToDefault(value, BigDecimal.ZERO); + } + + public static BigDecimal nullToDefault(BigDecimal value, BigDecimal def) { + return value == null ? def : value; + } + + public static BigDecimal zeroToNull(BigDecimal value) { + return isEmpty(value) ? null : value; + } + + /** @return parameter value or ZERO if it is negative */ + public static BigDecimal negativeToZero(BigDecimal value) { + return isGreaterThanZero(value) ? value : BigDecimal.ZERO; + } + + public static boolean isEmpty(BigDecimal value) { + return value == null || BigDecimal.ZERO.compareTo(value) == 0; + } + + public static boolean isGreaterThanZero(BigDecimal value) { + return value != null && value.compareTo(BigDecimal.ZERO) > 0; + } + + public static boolean isLessThanZero(BigDecimal value) { + return value != null && value.compareTo(BigDecimal.ZERO) < 0; + } + + public static boolean isZero(BigDecimal value) { + return value != null && value.compareTo(BigDecimal.ZERO) == 0; + } + + public static boolean isEqualTo(BigDecimal first, BigDecimal second) { + return nullToZero(first).compareTo(nullToZero(second)) == 0; + } + + public static boolean isGreaterThan(BigDecimal first, BigDecimal second) { + return nullToZero(first).compareTo(nullToZero(second)) > 0; + } + + public static boolean isLessThan(BigDecimal first, BigDecimal second) { + return nullToZero(first).compareTo(nullToZero(second)) < 0; + } + + public static boolean isGreaterThanOrEqualTo(BigDecimal first, BigDecimal second) { + return nullToZero(first).compareTo(nullToZero(second)) >= 0; + } + + public static boolean isLessThanOrEqualZero(BigDecimal value) { + return nullToZero(value).compareTo(BigDecimal.ZERO) <= 0; + } + + /** @return parameter value or negated value to positive */ + public static BigDecimal abs(BigDecimal value) { + return value == null ? BigDecimal.ZERO : value.abs(); + } + + /** @return calculates minimum of the two values considering null values + * @param notNull if true then null parameter is omitted, otherwise returns null */ + public static BigDecimal min(BigDecimal first, BigDecimal second, boolean notNull) { + return notNull + ? first == null + ? second + : second == null ? first : min(first, second, false) + : isLessThan(first, second) ? first : second; + } + + /** @return calculates minimum of the values considering null values + * @param notNull if true then null parameter is omitted, otherwise returns null */ + public static BigDecimal min(BigDecimal first, BigDecimal second, BigDecimal third, boolean notNull) { + return min(min(first, second, notNull), third, notNull); + } + + /** @return sum the two values considering null values */ + public static BigDecimal add(BigDecimal first, BigDecimal second) { + return add(first, second, MoneyHelper.getMathContext()); + } + + /** @return sum the two values considering null values */ + public static BigDecimal add(BigDecimal first, BigDecimal second, MathContext mc) { + return first == null + ? second + : second == null ? first : first.add(second, mc); + } + + /** @return sum the values considering null values */ + public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third) { + return add(first, second, third, MoneyHelper.getMathContext()); + } + + /** @return sum the values considering null values */ + public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, MathContext mc) { + return add(add(first, second, mc), third, mc); + } + + /** @return sum the values considering null values */ + public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth) { + return add(first, second, third, fourth, MoneyHelper.getMathContext()); + } + + /** @return sum the values considering null values */ + public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth, MathContext mc) { + return add(add(add(first, second, mc), third, mc), fourth, mc); + } + + /** @return sum the values considering null values */ + public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth, BigDecimal fifth) { + return add(first, second, third, fourth, fifth, MoneyHelper.getMathContext()); + } + + /** @return sum the values considering null values */ + public static BigDecimal add(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth, BigDecimal fifth, MathContext mc) { + return add(add(add(add(first, second, mc), third, mc), fourth, mc), fifth, mc); + } + + /** @return first minus second considering null values, maybe negative */ + public static BigDecimal subtract(BigDecimal first, BigDecimal second) { + return first == null + ? null + : second == null ? first : first.subtract(second, MoneyHelper.getMathContext()); + } + + /** @return NONE negative first minus second considering null values */ + public static BigDecimal subtractToZero(BigDecimal first, BigDecimal second) { + return negativeToZero(subtract(first, second)); + } + + /** @return first minus the others considering null values, maybe negative */ + public static BigDecimal subtractToZero(BigDecimal first, BigDecimal second, BigDecimal third) { + MathContext mc = MoneyHelper.getMathContext(); + return subtractToZero(subtract(first, second), third); + } + + /** @return first minus the others considering null values, maybe negative */ + public static BigDecimal subtractToZero(BigDecimal first, BigDecimal second, BigDecimal third, BigDecimal fourth) { + return subtractToZero(subtract(subtract(first, second), third), fourth); + } + + /** @return BigDecimal with scale set to the 'digitsAfterDecimal' of the parameter currency */ + public static BigDecimal normalizeAmount(BigDecimal amount, @NotNull MonetaryCurrency currency) { + return amount == null ? null : amount.setScale(currency.getDigitsAfterDecimal(), MoneyHelper.getRoundingMode()); + } + + /** @return BigDecimal null safe negate */ + public static BigDecimal negate(BigDecimal amount) { + return negate(amount, MoneyHelper.getMathContext()); + } + + /** @return BigDecimal null safe negate */ + public static BigDecimal negate(BigDecimal amount, MathContext mc) { + return isEmpty(amount) ? amount : amount.negate(mc); + } + + + // ----------------- Money ----------------- + + public static Money nullToZero(Money value, @NotNull MonetaryCurrency currency) { + return nullToDefault(value, Money.zero(currency)); + } + + public static Money nullToDefault(Money value, Money def) { + return value == null ? def : value; + } + + public static Money zeroToNull(Money value) { + return isEmpty(value) ? null : value; + } + + /** @return parameter value or ZERO if it is negative */ + public static Money negativeToZero(Money value) { + return value == null || isGreaterThanZero(value) ? value : Money.zero(value.getCurrency()); + } + + public static boolean isEmpty(Money value) { + return value == null || value.isZero(); + } + + public static boolean isGreaterThanZero(Money value) { + return value != null && value.isGreaterThanZero(); + } + + public static boolean isLessThanZero(Money value) { + return value != null && value.isLessThanZero(); + } + + public static boolean isEqualTo(Money first, Money second) { + return first == null ? second == null : (second != null && first.isEqualTo(second)); + } + + public static boolean isGreaterThan(Money first, Money second) { + return second == null || (first != null && first.isGreaterThan(second)); + } + + public static boolean isLessThan(Money first, Money second) { + return first == null || (second != null && first.isLessThan(second)); + } + + public static Money plus(Money first, Money second) { + return first == null + ? second + : second == null ? first : first.plus(second); + } + + public static Money plus(Money first, Money second, Money third) { + return plus(plus(first, second), third); + } + + public static Money plus(Money first, Money second, Money third, Money fourth) { + return plus(plus(plus(first, second), third), fourth); + } + + public static Money minus(Money first, Money second) { + return first == null + ? null + : second == null ? first : first.minus(second); + } + + /** @return first minus the others considering null values, maybe negative */ + public static Money minusToZero(Money first, Money second, Money third) { + return minusToZero(minus(first, second), third); + } + + /** @return first minus the others considering null values, maybe negative */ + public static Money minusToZero(Money first, Money second, Money third, Money fourth) { + return minusToZero(minus(minus(first, second), third), fourth); + } + + /** @return NONE negative first minus second considering null values */ + public static Money minusToZero(Money first, Money second) { + return negativeToZero(minus(first, second)); + } + + /** @return calculates minimum of the two values considering null values + * @param notNull if true then null parameter is omitted, otherwise returns null */ + public static Money min(Money first, Money second, boolean notNull) { + return notNull + ? first == null + ? second + : second == null ? first : min(first, second, false) + : isLessThan(first, second) ? first : second; + } + + /** @return calculates minimum of the values considering null values + * @param notNull if true then null parameter is omitted, otherwise returns null */ + public static Money min(Money first, Money second, Money third, boolean notNull) { + return min(min(first, second, notNull), third, notNull); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java index d91ebc47b9c..f5d0f522118 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java @@ -49,6 +49,11 @@ public MonetaryCurrency copy() { return new MonetaryCurrency(this.code, this.digitsAfterDecimal, this.inMultiplesOf); } + public static MonetaryCurrency fromApplicationCurrency(ApplicationCurrency applicationCurrency) { + return new MonetaryCurrency(applicationCurrency.getCode(), applicationCurrency.getDecimalPlaces(), + applicationCurrency.getCurrencyInMultiplesOf()); + } + public String getCode() { return this.code; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MoneyHelper.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MoneyHelper.java index 1d370d3d65e..678cb612827 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MoneyHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/monetary/domain/MoneyHelper.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.organisation.monetary.domain; +import java.math.MathContext; import java.math.RoundingMode; import javax.annotation.PostConstruct; @@ -30,8 +31,11 @@ public class MoneyHelper { private static RoundingMode roundingMode = null; + private static MathContext mathContext; + private static final int PRECISION = 12; + private static ConfigurationDomainService staticConfigurationDomainService; - + @Autowired private ConfigurationDomainService configurationDomainService; @@ -48,4 +52,10 @@ public static RoundingMode getRoundingMode() { return roundingMode; } + public static MathContext getMathContext() { + if (mathContext == null) { + mathContext = new MathContext(PRECISION, getRoundingMode()); + } + return mathContext; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java index be390118d45..c290221ca58 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/paymentdetail/domain/PaymentDetail.java @@ -115,5 +115,11 @@ public PaymentType getPaymentType() { return this.paymentType; } - public String getReceiptNumber() { return this.receiptNumber; } + public String getReceiptNumber() { + return this.receiptNumber; + } + + public String getRoutingCode() { + return routingCode; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index 3def00ae571..906c6774da8 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -81,6 +81,7 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.organisation.office.domain.Office; import org.apache.fineract.organisation.staff.domain.Staff; import org.apache.fineract.portfolio.accountdetails.domain.AccountType; @@ -437,6 +438,10 @@ public void setHelpers(final SavingsAccountTransactionSummaryWrapper savingsAcco this.savingsHelper = savingsHelper; } + public String getExternalId() { + return externalId; + } + public boolean isNotActive() { return !isActive(); } @@ -1023,10 +1028,22 @@ public SavingsAccountTransaction withdraw(final SavingsAccountTransactionDTO tra return transaction; } - private void payWithdrawalFee(final BigDecimal transactionAmoount, final LocalDate transactionDate, final AppUser user) { + public BigDecimal calculateWithdrawalFee(final BigDecimal transactionAmount) { + BigDecimal result = BigDecimal.ZERO; + if (isWithdrawalFeeApplicableForTransfer()) { + for (SavingsAccountCharge charge : this.charges()) { + if (charge.isWithdrawalFee() && charge.isActive()) { + result = result.add(charge.calculateWithdralFeeAmount(transactionAmount), MoneyHelper.getMathContext()); + } + } + } + return result; + } + + private void payWithdrawalFee(final BigDecimal transactionAmount, final LocalDate transactionDate, final AppUser user) { for (SavingsAccountCharge charge : this.charges()) { if (charge.isWithdrawalFee() && charge.isActive()) { - charge.updateWithdralFeeAmount(transactionAmoount); + charge.updateWithdralFeeAmount(transactionAmount); this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user); } } @@ -2712,6 +2729,28 @@ public boolean isTransactionsAllowed() { return isActive(); } + public boolean isTransactionAllowed(SavingsAccountTransactionType transactionType, LocalDate transactionDate) { + if (!isTransactionsAllowed()) + return false; + + Client client = getClient(); + if (client != null && !client.isActive()) { + return false; + } + Group group = group(); + if (group != null && !group.isActive()) { + return false; + } + + if (transactionDate == null) + return true; + if (DateUtils.isDateInTheFuture(transactionDate) || transactionDate.isBefore(getActivationLocalDate())) + return false; + if (transactionType.isCredit()) + return true; + return !isAccountLocked(transactionDate); + } + public BigDecimal minBalanceForInterestCalculation() { return this.minBalanceForInterestCalculation; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java index 2ef69a5d81b..3be7c47111f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountCharge.java @@ -38,11 +38,13 @@ import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import javax.validation.constraints.NotNull; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; @@ -704,17 +706,20 @@ public boolean equals(final Object obj) { .isEquals(); } - public BigDecimal updateWithdralFeeAmount(final BigDecimal transactionAmount) { + public BigDecimal calculateWithdralFeeAmount(@NotNull BigDecimal transactionAmount) { BigDecimal amountPaybale = BigDecimal.ZERO; if (ChargeCalculationType.fromInt(this.chargeCalculation).isFlat()) { amountPaybale = this.amount; } else if (ChargeCalculationType.fromInt(this.chargeCalculation).isPercentageOfAmount()) { - amountPaybale = transactionAmount.multiply(this.percentage).divide(BigDecimal.valueOf(100l)); + amountPaybale = transactionAmount.multiply(this.percentage).divide(BigDecimal.valueOf(100L), MoneyHelper.getRoundingMode()); } - this.amountOutstanding = amountPaybale; return amountPaybale; } + public BigDecimal updateWithdralFeeAmount(final BigDecimal transactionAmount) { + return amountOutstanding = calculateWithdralFeeAmount(transactionAmount); + } + public void updateToNextDueDateFrom(final LocalDate startingDate) { if (isAnnualFee() || isMonthlyFee() || isWeeklyFee()) { this.dueDate = getNextDueDateFrom(startingDate).toDate(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java index bb4eb22a02e..1bd9f634210 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java @@ -51,6 +51,8 @@ SavingsAccount findByIdAndDepositAccountType(@Param("accountId") Long accountId, @Query("select sa from SavingsAccount sa where sa.accountNumber = :accountNumber and sa.status in (100, 200, 300, 303, 304) ") SavingsAccount findNonClosedAccountByAccountNumber(@Param("accountNumber") String accountNumber); - + Page findByStatus(Integer status,Pageable pageable); + + SavingsAccount findByExternalId(String externalId); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java index 2f25e602ad3..0d601a45edc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java @@ -71,7 +71,7 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom + @@ -86,12 +88,11 @@ - - - + - + + @@ -101,8 +102,5 @@ - - - diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V352__interop_init.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V352__interop_init.sql new file mode 100644 index 00000000000..c2b4e5f8f91 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V352__interop_init.sql @@ -0,0 +1,83 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. +-- + +DROP TABLE IF EXISTS `interop_identifier`; +CREATE TABLE `interop_identifier` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `account_id` BIGINT(20) NOT NULL, + `type` VARCHAR(32) NOT NULL, + `a_value` VARCHAR(128) NOT NULL, + `sub_value_or_type` VARCHAR(128) NULL, + `created_by` VARCHAR(32) NOT NULL, + `created_on` TIMESTAMP NOT NULL, + `modified_by` VARCHAR(32) NULL, + `modified_on` TIMESTAMP NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `uk_interop_identifier_account` (`account_id`, `type`), + UNIQUE INDEX `uk_interop_identifier_value` (`type`, `a_value`, `sub_value_or_type`), + INDEX `fk_interop_identifier_account` (`account_id`), + CONSTRAINT `fk_interop_identifier_account` FOREIGN KEY (`account_id`) REFERENCES `m_savings_account` (`id`) +) + COLLATE = 'utf8_general_ci' + ENGINE = InnoDB; + +-- user+roles + +SET @interop_username = 'interopUser'; +INSERT INTO `m_appuser` +VALUES (NULL, 0, 1, NULL, @interop_username, 'Interop', 'User', '5787039480429368bf94732aacc771cd0a3ea02bcf504ffe1185ab94213bc63a', + 'email@email.com', b'0', b'1', b'1', b'1', b'1', CURDATE(), 0, b'0'); + +INSERT INTO `m_appuser_role` VALUES ((SELECT id FROM m_appuser WHERE username = @interop_username), 1); + +-- Interoperation permissions + +INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker) +VALUES ('interop', 'READ_INTERID', 'INTERID', 'READ', 0); +INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker) +VALUES ('interop', 'READ_INTERREQUEST', 'INTERREQUEST', 'READ', 0); +INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker) +VALUES ('interop', 'READ_INTERQUOTE', 'INTERQUOTE', 'READ', 0); +INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker) +VALUES ('interop', 'READ_INTERTRANSFER', 'INTERTRANSFER', 'READ', 0); + +INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker) +VALUES ('interop', 'PREPARE_INTERTRANSFER', 'INTERTRANSFER', 'PREPARE', 0); + +INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker) +VALUES ('interop', 'CREATE_INTERID', 'INTERID', 'CREATE', 0); +INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker) +VALUES ('interop', 'CREATE_INTERREQUEST', 'INTERREQUEST', 'CREATE', 0); +INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker) +VALUES ('interop', 'CREATE_INTERQUOTE', 'INTERQUOTE', 'CREATE', 0); +INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker) +VALUES ('interop', 'CREATE_INTERTRANSFER', 'INTERTRANSFER', 'CREATE', 0); + +INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker) +VALUES ('interop', 'DELETE_INTERID', 'INTERID', 'DELETE', 0); + + +INSERT IGNORE INTO m_code (code_name, is_system_defined) VALUES ('PaymentType', 1); + +SET @code_id = -1; +SELECT id INTO @code_id FROM m_code WHERE code_name = 'PaymentType'; + +INSERT IGNORE INTO m_code_value (code_id, code_value, order_position) VALUES (@code_id, 'Money Transfer', 1); + +INSERT IGNORE INTO m_payment_type (value, description, order_position) VALUES ('Money Transfer', 'Money Transfer', 1); \ No newline at end of file diff --git a/fineract-provider/src/main/resources/sql/migrations/list_db/V1__mifos-platform-shared-tenants.sql b/fineract-provider/src/main/resources/sql/migrations/list_db/V1__mifos-platform-shared-tenants.sql index eb7c9c24efa..41352561d00 100644 --- a/fineract-provider/src/main/resources/sql/migrations/list_db/V1__mifos-platform-shared-tenants.sql +++ b/fineract-provider/src/main/resources/sql/migrations/list_db/V1__mifos-platform-shared-tenants.sql @@ -67,6 +67,9 @@ CREATE TABLE `tenants` ( LOCK TABLES `tenants` WRITE; /*!40000 ALTER TABLE `tenants` DISABLE KEYS */; INSERT INTO `tenants` VALUES (1,'default','Default Demo Tenant','mifostenant-default','Asia/Kolkata',NULL,NULL,NULL,NULL,'localhost','3306','root','mysql',1); +-- -- Add additional tenants for testing interoperability. +--INSERT INTO `tenants` VALUES (2,'tn03','Rhino','tn03','Africa/Bujumbura',NULL,NULL,NULL,NULL,'localhost','3306','root','mysql',1); +--INSERT INTO `tenants` VALUES (3,'tn04','Elephant','tn04','Africa/Bujumbura',NULL,NULL,NULL,NULL,'localhost','3306','root','mysql',1); /*!40000 ALTER TABLE `tenants` ENABLE KEYS */; UNLOCK TABLES; diff --git a/fineract-provider/src/main/resources/sql/migrations/sample_data/interop_sample_data.sql b/fineract-provider/src/main/resources/sql/migrations/sample_data/interop_sample_data.sql new file mode 100644 index 00000000000..430b5151f57 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/sample_data/interop_sample_data.sql @@ -0,0 +1,200 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. +-- + +-- To use: Change saving_account_no before every run! +-- !both tn03, tn04 tenants + +-- saving product, account +SET @last_saving_prod_id = -1; +SELECT COALESCE(max(id), 1) into @last_saving_prod_id from m_savings_product; + +SET @saving_prod_name = concat('Saving Product', @last_saving_prod_id); + +INSERT INTO `m_savings_product` +(`name`, `short_name`, `description`, `deposit_type_enum`, `currency_code`, `currency_digits`, + `currency_multiplesof`, `nominal_annual_interest_rate`, `interest_compounding_period_enum`, + `interest_posting_period_enum`, `interest_calculation_type_enum`, `interest_calculation_days_in_year_type_enum`, + `min_required_opening_balance`, `accounting_type`, `withdrawal_fee_amount`, `withdrawal_fee_type_enum`, + `withdrawal_fee_for_transfer`, `allow_overdraft`, `min_required_balance`, `enforce_min_required_balance`, + `min_balance_for_interest_calculation`, `withhold_tax`, `tax_group_id`, `is_dormancy_tracking_active`) +VALUES (@saving_prod_name, concat('SP', @last_saving_prod_id), 'Saving Product', 100, 'TZS', 2, NULL, 0.000000, 1, + 4, 1, 360, NULL, 2, NULL, NULL, 0, 0, 0.000000, 1, NULL, 0, NULL, 0); + +SET @saving_prod_id = -1; +SELECT id INTO @saving_prod_id FROM m_savings_product WHERE name = @saving_prod_name; + +-- interop_identifier + +-- charge, mapping +-- gl_account, mappings +-- ASSET-1, LIABILITY-2, EQUITY-3, INCOME-4, EXPENSE-5 + +SET @payment_type_id = -1; +SELECT id INTO @payment_type_id FROM m_payment_type WHERE value = 'Money Transfer'; + +SET @saving_gl_name = 'Interoperation Saving'; +INSERT INTO `acc_gl_account` (`name`, `parent_id`, `hierarchy`, `gl_code`, `disabled`, `manual_journal_entries_allowed`, `account_usage`, `classification_enum`, `description`) +VALUES (@saving_gl_name, NULL, NULL, 'Interop_Saving', 0, 1, 1, 1, 'Interoperation Saving Asset'); -- account_usage: DETAIL, classification_enum: ASSET + +INSERT INTO `acc_product_mapping` (`gl_account_id`, `product_id`, `product_type`, `payment_type`, `charge_id`, `financial_account_type`) +VALUES ((SELECT id FROM acc_gl_account WHERE name = @saving_gl_name), @saving_prod_id, 2, @payment_type_id, NULL, 1); -- product_type: SAVING, financial_account_type: ASSET + +SET @nostro_gl_name = 'Interoperation NOSTRO'; +INSERT INTO `acc_gl_account` (`name`, `parent_id`, `hierarchy`, `gl_code`, `disabled`, `manual_journal_entries_allowed`, `account_usage`, `classification_enum`, `description`) +VALUES (@nostro_gl_name, NULL, NULL, 'Interop_Nostro', 0, 0, 1, 2, 'Interoperation NOSTRO Liability'); -- account_usage: DETAIL, classification_enum: LIABILITY + +INSERT INTO `acc_product_mapping` (`gl_account_id`, `product_id`, `product_type`, `payment_type`, `charge_id`, `financial_account_type`) +VALUES ((SELECT id FROM acc_gl_account WHERE name = @nostro_gl_name), @saving_prod_id, 2, NULL, NULL, 2); -- product_type: SAVING, financial_account_type: LIABILITY + +SET @fee_gl_name = 'Interoperation Fee'; +INSERT INTO `acc_gl_account` (`name`, `parent_id`, `hierarchy`, `gl_code`, `disabled`, `manual_journal_entries_allowed`, `account_usage`, `classification_enum`, `description`) +VALUES (@fee_gl_name, NULL, NULL, 'Interop_Fee', 0, 0, 1, 4, 'Interoperation Fee Income'); -- account_usage: DETAIL, classification_enum: INCOME + +SET @fee_gl_id = -1; +SELECT id INTO @fee_gl_id FROM acc_gl_account WHERE name = @fee_gl_name; + +INSERT INTO `acc_product_mapping` (`gl_account_id`, `product_id`, `product_type`, `payment_type`, `charge_id`, `financial_account_type`) +VALUES (@fee_gl_id, @saving_prod_id, 2, NULL, NULL, 4); -- product_type: SAVING, financial_account_type: INCOME + +SET @charge_name = 'Interoperation Withdraw Fee'; +INSERT INTO `m_charge` +(`name`,`currency_code`,`charge_applies_to_enum`,`charge_time_enum`,`charge_calculation_enum`,`charge_payment_mode_enum`, + `amount`,`fee_on_day`,`fee_interval`,`fee_on_month`,`is_penalty`,`is_active`,`is_deleted`,`min_cap`,`max_cap`,`fee_frequency`, + `income_or_liability_account_id`,`tax_group_id`) +VALUES (@charge_name, 'TZS', 2, 5, 1, NULL, 1.000000, NULL, NULL, NULL, 0, 0, 0, NULL, NULL, NULL, @fee_gl_id, NULL); + +-- loan product +/* +SET @last_ext_id = -1; +SELECT COALESCE(max(external_id), 1) INTO @last_ext_id FROM m_product_loan; + +INSERT INTO `m_product_loan` +VALUES +(CONCAT('IP', @last_product_id), 'EUR', 2, 1, 50000.000000, NULL, NULL, NULL, concat('Interoperation Customer Product', @last_product_id), + 'Demo Interoperation Product', NULL, b'0', b'0', 1.000000, 1.000000, NULL, NULL, 3, 1.000000, 0, 1, 1, 1, 2, 1200, NULL, + NULL, NULL, NULL, NULL, 1, 1, 3, @last_ext_id + 1, 0, 0,ADDDATE(curdate(),-100),ADDDATE(curdate(),100), 0, 0, NULL, NULL, + NULL, 1, 30, 0, 0, 0.00, 0, 1, 0, 0, 0); + +SET @product_id = -1; +SELECT id INTO @product_id FROM m_product_loan WHERE name = concat('Interoperation Customer Product', @last_product_id); + +-- charge, mapping +INSERT INTO `m_charge` VALUES ( + NULL, concat('Loan Withdraw Fee_', @product_id), 'TZS', 1, 2, + 1, 0, 1.000000, NULL, NULL, + NULL, 0, 1, 0, NULL, + NULL, NULL, NULL, NULL); + +INSERT INTO `m_product_loan_charge` VALUES + (@product_id, (SELECT id + FROM m_charge + WHERE name = concat('Loan Withdraw Fee_', @product_id))); + +-- gl_account, mappings +-- ASSET-1, LIABILITY-2, EQUITY-3, INCOME-4, EXPENSE-5 +SET @liab_acc_name = concat('Loan Payable Liability_', @product_id); +INSERT INTO `acc_gl_account` VALUES ( + NULL, @liab_acc_name, NULL, NULL, concat('0360009420', @product_id), + 0, 1, 1, 1, NULL, 'Loan Payable Liability'); + +INSERT INTO `acc_product_mapping` VALUES ( + NULL, + (SELECT id + FROM acc_gl_account + WHERE name = @liab_acc_name), + @product_id, + NULL, NULL, NULL, 2); + +SET @nostro_acc_name = concat('Loan NOSTRO_', @product_id); +INSERT INTO `acc_gl_account` VALUES ( + NULL, @nostro_acc_name, NULL, NULL, concat('0360009421', @product_id), + 0, 1, 1, 1, NULL, 'Loan NOSTRO'); + +INSERT INTO `acc_product_mapping` VALUES ( + NULL, + (SELECT id + FROM acc_gl_account + WHERE name = @nostro_acc_name), + @product_id, + NULL, NULL, NULL, 1); + +SET @cash_acc_name = concat('Loan Product Cash_', @product_id); +INSERT INTO `acc_gl_account` VALUES ( + NULL, @cash_acc_name, NULL, NULL, concat('0360009422', @product_id), + 0, 1, 1, 1, NULL, 'Loan Product Cash'); + +INSERT INTO `acc_product_mapping` VALUES ( + NULL, + (SELECT id + FROM acc_gl_account + WHERE name = @cash_acc_name), + @product_id, + NULL, NULL, NULL, 1); + +SET @expen_acc_name = concat('Loan Product Expenses_', @product_id); +INSERT INTO `acc_gl_account` VALUES ( + NULL, @expen_acc_name, NULL, NULL, concat('0360009423', @product_id), + 0, 1, 1, 1, NULL, 'Loan Product Expenses'); + +INSERT INTO `acc_product_mapping` VALUES ( + NULL, + (SELECT id + FROM acc_gl_account + WHERE name = @expen_acc_name), + @product_id, + NULL, NULL, NULL, 5); + +SET @accrue_acc_name = concat('Loan Product Accrue Liability_', @product_id); +INSERT INTO `acc_gl_account` VALUES ( + NULL, @accrue_acc_name, NULL, NULL, concat('0360009424', @product_id), + 0, 1, 1, 1, NULL, 'Loan Product Accrue Liability'); + +INSERT INTO `acc_product_mapping` VALUES ( + NULL, + (SELECT id + FROM acc_gl_account + WHERE name = @accrue_acc_name), + @product_id, + NULL, NULL, NULL, 2); + +SET @equ_acc_name = concat('Loan Product Equity_', @product_id); +INSERT INTO `acc_gl_account` VALUES ( + NULL, @equ_acc_name, NULL, NULL, concat('0360009425', @product_id), + 0, 1, 1, 1, NULL, 'Loan Product Equity'); + +INSERT INTO `acc_product_mapping` VALUES ( + NULL, + (SELECT id + FROM acc_gl_account + WHERE name = @equ_acc_name), + @product_id, + NULL, NULL, NULL, 3); + +SET @feer_acc_name = concat('Loan Product Fees Revenue_', @product_id); +INSERT INTO `acc_gl_account` VALUES ( + NULL, @feer_acc_name, NULL, NULL, concat('0360009426', @product_id), + 0, 1, 1, 1, NULL, 'Loan Product Fees Revenue'); + +INSERT INTO `acc_product_mapping` VALUES ( + NULL, + (SELECT id + FROM acc_gl_account + WHERE name = @feer_acc_name), + @product_id, + NULL, NULL, NULL, 4);*/ \ No newline at end of file diff --git a/fineract-provider/src/main/resources/sql/migrations/sample_data/tn03_interop_sample_data.sql b/fineract-provider/src/main/resources/sql/migrations/sample_data/tn03_interop_sample_data.sql new file mode 100644 index 00000000000..14f3fde3e6a --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/sample_data/tn03_interop_sample_data.sql @@ -0,0 +1,80 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. +-- + +-- To use: Change saving_account_no before every run! + +USE `tn03`; + +-- data initialization +-- user+roles + +-- client +SET @saving_account_no = '9062b90de19b43989005'; + +INSERT INTO `m_client` (`account_no`, `external_id`, `status_enum`, `sub_status`, `activation_date`, `office_joining_date`, + `office_id`, `transfer_to_office_id`, `staff_id`, `firstname`, `middlename`, `lastname`, `fullname`, + `display_name`, `mobile_no`, `gender_cv_id`, `date_of_birth`, `image_id`, `closure_reason_cv_id`, + `closedon_date`, `updated_by`, `updated_on`, `submittedon_date`, `submittedon_userid`, `activatedon_userid`, + `closedon_userid`, `default_savings_product`, `default_savings_account`, `client_type_cv_id`, `client_classification_cv_id`, + `reject_reason_cv_id`, `rejectedon_date`, `rejectedon_userid`, `withdraw_reason_cv_id`, `withdrawn_on_date`, + `withdraw_on_userid`, `reactivated_on_date`, `reactivated_on_userid`, `legal_form_enum`, `reopened_on_date`, + `reopened_by_userid`) +VALUES (@saving_account_no, NULL, 300, NULL, ADDDATE(curdate(), -100), NULL, 1, NULL, NULL, NULL, NULL, NULL, + 'InteropCustomer', 'InteropCustomer', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ADDDATE(curdate(), -100), + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,NULL, NULL); + +-- saving product, account +SET @saving_prod_name = concat('Saving Product', @last_saving_prod_id); +SET @saving_prod_id = -1; +SELECT id INTO @saving_prod_id FROM m_savings_product WHERE name = @saving_prod_name; + +SET @client_id = -1; +SELECT id INTO @client_id FROM m_client WHERE fullname = 'InteropCustomer'; + +INSERT INTO `m_savings_account` +(`account_no`, `external_id`, `client_id`, `group_id`, `product_id`, `field_officer_id`, `status_enum`, + `sub_status_enum`, `account_type_enum`, `deposit_type_enum`, `submittedon_date`, `submittedon_userid`, + `approvedon_date`, `approvedon_userid`, `activatedon_date`, `activatedon_userid`, + `currency_code`, `currency_digits`, `currency_multiplesof`, `nominal_annual_interest_rate`, + `interest_compounding_period_enum`, `interest_posting_period_enum`, `interest_calculation_type_enum`, + `interest_calculation_days_in_year_type_enum`, `min_required_opening_balance`, `withdrawal_fee_for_transfer`, + `allow_overdraft`, `account_balance_derived`, `min_required_balance`, `enforce_min_required_balance`, + `version`, `withhold_tax`) +VALUES (@saving_account_no, '9062b90de19b43989005d9', @client_id, NULL, @saving_prod_id, NULL, 300, 0, 1, 100, ADDDATE(curdate(), -100), + NULL, ADDDATE(curdate(), -100), NULL, ADDDATE(curdate(), -100), NULL, 'TZS', 2, NULL, 1.000000, 1, 4, 1, -- 29. - 4 + 360, NULL, 1, 1, 100000000.000000, 0.000000, 1, 1, 0); + +-- interop_identifier +SET @saving_acc_id = -1; +SELECT id INTO @saving_acc_id FROM m_savings_account WHERE account_no = @saving_account_no; + +INSERT INTO interop_identifier (id, account_id, type, a_value, sub_value_or_type, created_by, created_on, modified_by, modified_on) +VALUES (NULL, @saving_acc_id, 'IBAN', 'IC11in02tn039062b90de19b43989005d9', NULL, 'operator', CURDATE(), 'operator', + CURDATE()); +INSERT INTO interop_identifier (id, account_id, type, a_value, sub_value_or_type, created_by, created_on, modified_by, modified_on) +VALUES (NULL, @saving_acc_id, 'MSISDN', '27710203999', NULL, 'operator', CURDATE(), 'operator', CURDATE()); + +-- charge, mapping +-- gl_account, mappings +-- ASSET-1, LIABILITY-2, EQUITY-3, INCOME-4, EXPENSE-5 +SET @charge_name = 'Interoperation Withdraw Fee'; + +INSERT INTO `m_savings_account_charge` (`savings_account_id`, `charge_id`, `is_penalty`, `charge_time_enum`, `charge_calculation_enum`, + `amount`, `amount_outstanding_derived`,`is_paid_derived`, `waived`, `is_active`) +VALUES (@saving_acc_id, (SELECT id FROM m_charge WHERE name = @charge_name), 0, 5, 1, 1.000000, 0.000000, 0, 0, 1); diff --git a/fineract-provider/src/main/resources/sql/migrations/sample_data/tn04_interop_sample_data.sql b/fineract-provider/src/main/resources/sql/migrations/sample_data/tn04_interop_sample_data.sql new file mode 100644 index 00000000000..4135d701e18 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/sample_data/tn04_interop_sample_data.sql @@ -0,0 +1,80 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. +-- + +-- To use: Change saving_account_no before every run! + +USE `tn04`; + +-- data initialization +-- user+roles + +-- client +SET @saving_account_no = '9062b90de19b43989005'; + +INSERT INTO `m_client` (`account_no`, `external_id`, `status_enum`, `sub_status`, `activation_date`, `office_joining_date`, + `office_id`, `transfer_to_office_id`, `staff_id`, `firstname`, `middlename`, `lastname`, `fullname`, + `display_name`, `mobile_no`, `gender_cv_id`, `date_of_birth`, `image_id`, `closure_reason_cv_id`, + `closedon_date`, `updated_by`, `updated_on`, `submittedon_date`, `submittedon_userid`, `activatedon_userid`, + `closedon_userid`, `default_savings_product`, `default_savings_account`, `client_type_cv_id`, `client_classification_cv_id`, + `reject_reason_cv_id`, `rejectedon_date`, `rejectedon_userid`, `withdraw_reason_cv_id`, `withdrawn_on_date`, + `withdraw_on_userid`, `reactivated_on_date`, `reactivated_on_userid`, `legal_form_enum`, `reopened_on_date`, + `reopened_by_userid`) +VALUES (@saving_account_no, NULL, 300, NULL, ADDDATE(curdate(), -100), NULL, 1, NULL, NULL, NULL, NULL, NULL, + 'InteropMerchant', 'InteropMerchant', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ADDDATE(curdate(), -100), + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,NULL, NULL); + +-- saving product, account +SET @saving_prod_name = concat('Saving Product', @last_saving_prod_id); +SET @saving_prod_id = -1; +SELECT id INTO @saving_prod_id FROM m_savings_product WHERE name = @saving_prod_name; + +SET @client_id = -1; +SELECT id INTO @client_id FROM m_client WHERE fullname = 'InteropMerchant'; + +INSERT INTO `m_savings_account` +(`account_no`, `external_id`, `client_id`, `group_id`, `product_id`, `field_officer_id`, `status_enum`, + `sub_status_enum`, `account_type_enum`, `deposit_type_enum`, `submittedon_date`, `submittedon_userid`, + `approvedon_date`, `approvedon_userid`, `activatedon_date`, `activatedon_userid`, + `currency_code`, `currency_digits`, `currency_multiplesof`, `nominal_annual_interest_rate`, + `interest_compounding_period_enum`, `interest_posting_period_enum`, `interest_calculation_type_enum`, + `interest_calculation_days_in_year_type_enum`, `min_required_opening_balance`, `withdrawal_fee_for_transfer`, + `allow_overdraft`, `account_balance_derived`, `min_required_balance`, `enforce_min_required_balance`, + `version`, `withhold_tax`) +VALUES (@saving_account_no, '9062b90de19b43989005d9', @client_id, NULL, @saving_prod_id, NULL, 300, 0, 1, 100, ADDDATE(curdate(), -100), + NULL, ADDDATE(curdate(), -100), NULL, ADDDATE(curdate(), -100), NULL, 'TZS', 2, NULL, 1.000000, 1, 4, 1, -- 29. - 4 + 360, NULL, 1, 1, 100000000.000000, 0.000000, 1, 1, 0); + +-- interop_identifier +SET @saving_acc_id = -1; +SELECT id INTO @saving_acc_id FROM m_savings_account WHERE account_no = @saving_account_no; + +INSERT INTO interop_identifier (id, account_id, type, a_value, sub_value_or_type, created_by, created_on, modified_by, modified_on) +VALUES (NULL, @saving_acc_id, 'IBAN', 'IC11in02tn049062b90de19b43989005d9', NULL, 'operator', CURDATE(), 'operator', + CURDATE()); +INSERT INTO interop_identifier (id, account_id, type, a_value, sub_value_or_type, created_by, created_on, modified_by, modified_on) +VALUES (NULL, @saving_acc_id, 'MSISDN', '27710204999', NULL, 'operator', CURDATE(), 'operator', CURDATE()); + +-- charge, mapping +-- gl_account, mappings +-- ASSET-1, LIABILITY-2, EQUITY-3, INCOME-4, EXPENSE-5 +SET @charge_name = 'Interoperation Withdraw Fee'; + +INSERT INTO `m_savings_account_charge` (`savings_account_id`, `charge_id`, `is_penalty`, `charge_time_enum`, `charge_calculation_enum`, + `amount`, `amount_outstanding_derived`,`is_paid_derived`, `waived`, `is_active`) +VALUES (@saving_acc_id, (SELECT id FROM m_charge WHERE name = @charge_name), 0, 5, 1, 1.000000, 0.000000, 0, 0, 1);