Skip to content

Commit

Permalink
feat: ebics client (backport #103) (#129)
Browse files Browse the repository at this point in the history
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
  • Loading branch information
mergify[bot] and barredterra authored Nov 14, 2024
1 parent c568076 commit 718e7e3
Show file tree
Hide file tree
Showing 26 changed files with 2,926 additions and 57 deletions.
36 changes: 13 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,34 +20,14 @@ Check out the [Banking Wiki](https://github.com/alyf-de/banking/wiki) for a step

## Country and Bank Coverage

Currently, we [support more than 15.000 banks from the following countries](https://portal.openbanking.klarna.com/bank-matrix).
<img src="ready_for_ebics.jpg" height="70px">

We use the EBICS protocol which is widely supported by banks in the following countries:

- 🇦🇹 Austria
- 🇧🇪 Belgium
- 🇭🇷 Croatia
- 🇨🇿 Czech Republic
- 🇩🇰 Denmark
- 🇪🇪 Estonia
- 🇫🇮 Finland
- 🇫🇷 France
- 🇩🇪 Germany
- 🇭🇺 Hungary
- 🇮🇪 Ireland
- 🇮🇹 Italy
- 🇱🇻 Latvia
- 🇱🇹 Lithuania
- 🇱🇺 Luxembourg
- 🇲🇹 Malta
- 🇳🇱 Netherlands
- 🇳🇴 Norway
- 🇵🇱 Poland
- 🇵🇹 Portugal
- 🇷🇴 Romania
- 🇸🇰 Slovakia
- 🇪🇸 Spain
- 🇸🇪 Sweden
- 🇨🇭 Switzerland
- 🇬🇧 United Kingdom

## Installation

Expand All @@ -57,3 +37,13 @@ Install [via Frappe Cloud](https://frappecloud.com/marketplace/apps/banking) or
bench get-app https://github.com/alyf-de/banking.git
bench --site <sitename> install-app banking
```

If you want to use ebics on Apple Silicon, the runtime library must be signed manually:

```bash
# python3.11
sudo codesign --force --deep --sign - env/lib/python3.11/site-packages/fintech/runtime/darwin/aarch64/pyarmor_runtime.so

# python3.10
sudo codesign --force --deep --sign - env/lib/python3.10/site-packages/fintech/pytransform/platforms/darwin/aarch64/_pytransform.dylib
```
26 changes: 26 additions & 0 deletions banking/connectors/admin_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,29 @@ def fetch_subscription(self):
def get_customer_portal(self):
method = "banking_admin.api.get_customer_portal"
return requests.get(url=self.url + method)

def get_fintech_license(self):
method = "banking_admin.ebics_api.get_fintech_license"
return requests.post(
url=self.url + method, headers=self.headers, json=self.data.copy()
)

def register_ebics_user(self, host_id: str, partner_id: str, user_id: str):
data = self.data
data.update({"host_id": host_id, "partner_id": partner_id, "user_id": user_id})
method = "banking_admin.ebics_api.register_ebics_user"
return requests.post(
url=self.url + method,
headers=self.headers,
json=data,
)

def remove_ebics_user(self, host_id: str, partner_id: str, user_id: str):
data = self.data
data.update({"host_id": host_id, "partner_id": partner_id, "user_id": user_id})
method = "banking_admin.ebics_api.remove_ebics_user"
return requests.post(
url=self.url + method,
headers=self.headers,
json=data,
)
Empty file added banking/ebics/__init__.py
Empty file.
Empty file.
Empty file.
205 changes: 205 additions & 0 deletions banking/ebics/doctype/ebics_user/ebics_user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Copyright (c) 2024, ALYF GmbH and contributors
// For license information, please see license.txt

frappe.ui.form.on("EBICS User", {
refresh(frm) {
if (frm.doc.initialized && !frm.doc.bank_keys_activated) {
frm.dashboard.set_headline(
__("Please print the attached INI letter, send it to your bank and wait for confirmation. Then verify the bank keys.")
);
}

if (!frm.doc.initialized || frappe.boot.developer_mode) {
frm.add_custom_button(
__("Initialize"),
() => {
frappe.prompt(
[
{
fieldname: "passphrase",
label: __("Passphrase"),
fieldtype: "Password",
description: __("Set a new password for downloading bank statements from your bank.")
},
{
fieldname: "store_passphrase",
label: __("Store Passphrase"),
fieldtype: "Check",
default: 1,
description: __("Store the passphrase in the ERPNext database to enable automated, regular download of bank statements.")
},
{
fieldname: "signature_passphrase",
label: __("Signature Passphrase"),
fieldtype: "Password",
description: __("Set a new password for uploading transactions to your bank.")
},
{
fieldname: "info",
fieldtype: "HTML",
options: __(
"Note: When you lose these passwords, you will have to go through the initialization process with your bank again."
)
}
],
(values) => {
frappe.call({
method: "banking.ebics.doctype.ebics_user.ebics_user.initialize",
args: { ebics_user: frm.doc.name, ...values },
freeze: true,
freeze_message: __("Initializing..."),
callback: () => frm.reload_doc(),
});
},
__("Initialize EBICS User"),
__("Initialize")
);
},
frm.doc.initialized ? __("Actions") : null
);
}

if (frm.doc.initialized && (!frm.doc.bank_keys_activated || frappe.boot.developer_mode)) {
frm.add_custom_button(
__("Verify Bank Keys"),
async () => {
let passphrase = null;
if (!frm.doc.passphrase) {
passphrase = await ask_for_passphrase();
}

const bank_keys = await get_bank_keys(frm.doc.name, passphrase);
if (!bank_keys) {
return;
}

const message = __(
"Please confirm that the following keys are identical to the ones mentioned on your bank's letter:"
);
frappe.confirm(
`<p>${message}</p>
<pre>${bank_keys}</pre>`,
async () => {
await confirm_bank_keys(frm.doc.name, passphrase);
frm.reload_doc();
}
);
},
frm.doc.bank_keys_activated ? __("Actions") : null
);
}

if (frm.doc.initialized && frm.doc.bank_keys_activated) {
frm.add_custom_button(__("Download Bank Statements"), () => {
download_bank_statements(frm.doc.name, !frm.doc.passphrase);
});
}
},
});


function ask_for_passphrase() {
return new Promise((resolve) => {
frappe.prompt(
[
{
fieldname: "passphrase",
label: __("Passphrase"),
fieldtype: "Password",
reqd: true,
},
],
(values) => {
resolve(values.passphrase);
},
__("Enter Passphrase"),
__("Continue")
);
});
}

async function get_bank_keys(ebics_user, passphrase) {
try {
return await frappe.xcall(
"banking.ebics.doctype.ebics_user.ebics_user.download_bank_keys",
{ ebics_user: ebics_user, passphrase: passphrase }
);
} catch (e) {
frappe.show_alert({
message: e || __("An error occurred"),
indicator: "red",
});
}
}

async function confirm_bank_keys(ebics_user, passphrase) {
try {
await frappe.xcall(
"banking.ebics.doctype.ebics_user.ebics_user.confirm_bank_keys",
{ ebics_user: ebics_user, passphrase: passphrase }
);
frappe.show_alert({
message: __("Bank keys confirmed"),
indicator: "green",
});
} catch (e) {
frappe.show_alert({
message: e || __("An error occurred"),
indicator: "red",
});
}
}

function download_bank_statements(ebics_user, needs_passphrase) {
const fields = [
{
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.datetime.now_date(),
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.now_date(),
},
];

if (needs_passphrase) {
fields.push({
fieldname: "passphrase",
label: __("Passphrase"),
fieldtype: "Password",
reqd: true
});
}

frappe.prompt(
fields,
async (values) => {
try {
await frappe.xcall(
"banking.ebics.doctype.ebics_user.ebics_user.download_bank_statements",
{
ebics_user: ebics_user,
from_date: values.from_date,
to_date: values.to_date,
passphrase: values.passphrase,
}
);
frappe.show_alert({
message: __("Bank statements are being downloaded in the background."),
indicator: "blue",
});
} catch (e) {
frappe.show_alert({
message: e || __("An error occurred"),
indicator: "red",
});
}
},
__("Download Bank Statements"),
__("Download")
);
}
Loading

0 comments on commit 718e7e3

Please sign in to comment.