Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the UltmtCdtr block for SEPA Direct Debit (Ultimate Creditor) + Customizable MsgId and InitgPty node #54

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
13 changes: 13 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Example:
import datetime, uuid

config = {
# "msg_id": "", # If your bank require a specific format for MsgId, you can optionnaly set it up here. Default will be randomly generated with timestamp followed by a random value.
"name": "Test von Testenstein",
"IBAN": "NL50BANK1234567890",
"BIC": "BANKNL2A",
Expand All @@ -57,6 +58,15 @@ Example:
"country_subdivision": None,
"lines": ["Line 1", "Line 2"],
},
# "initiating_party": "John Doe", # optional name of the initiator of the payment, required by some banks. default to ''name'
# "initiating_party_id": "DE26ZZZ00000000002", # optional, supplied by your bank or financial authority. default to 'creditor_id'
"ultimate_creditor": {
# The ultimate_creditor and all of its fields are optional but in some financial institution they are required
"name": "Real Creditor",
"BIC_or_BEI": "REALNL2A",
"id": "12345678900001", # can be a local official id or the creditor_id
"id_scheme_name": "SIRET", # proprietary scheme of the id provided (i.e. SEPA, SIRET...)
},
}
sepa = SepaDD(config, schema="pain.008.001.02", clean=True)

Expand Down Expand Up @@ -84,6 +94,8 @@ Example:
"country_subdivision": None,
"lines": ["Line 1", "Line 2"],
},
# "initiating_party": "John Doe", # optional name of the initiator of the payment, required by some banks. default to ''name'
# "initiating_party_id": "DE26ZZZ00000000002", # optional, supplied by your bank or financial authority. default to 'creditor_id'
}
sepa.add_payment(payment)

Expand All @@ -101,6 +113,7 @@ Example:
import datetime, uuid

config = {
# "msg_id": "", # If your bank require a specific message_id format, you can set it up here. Default will be randomly generated with timestamp followed by a random value.
"name": "Test von Testenstein",
"IBAN": "NL50BANK1234567890",
"BIC": "BANKNL2A",
Expand Down
57 changes: 56 additions & 1 deletion sepaxml/debit.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def add_payment(self, payment):

TX_nodes['MndtIdNode'].text = payment['mandate_id']
TX_nodes['DtOfSgntrNode'].text = payment['mandate_date']
TX_nodes['AmdmntIndNode'].text = 'false'
if bic:
TX_nodes['BIC_DbtrAgt_Node'].text = payment['BIC']
else:
Expand All @@ -135,7 +136,7 @@ def add_payment(self, payment):
TX_nodes['UstrdNode'].text = payment['description']
if not payment.get('endtoend_id', ''):
payment['endtoend_id'] = make_id(self._config['name'])
TX_nodes['EndToEndIdNode'].text = payment['endtoend_id']
TX_nodes['EndToEndIdNode'].text = payment['endtoend_id'][:35]

if self._config['batch']:
self._add_batch(TX_nodes, payment)
Expand Down Expand Up @@ -167,7 +168,11 @@ def _create_header(self):
MsgId_node.text = self.msg_id
CreDtTm_node.text = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
Nm_node.text = self._config['name']
if 'initiating_party' in self._config and self._config['initiating_party']:
Nm_node.text = self._config['initiating_party']
Id_node.text = self._config['creditor_id']
if 'initiating_party_id' in self._config and self._config['initiating_party_id']:
Id_node.text = self._config['initiating_party_id']

# Append the nodes
Othr_node.append(Id_node)
Expand Down Expand Up @@ -215,6 +220,20 @@ def _create_PmtInf_node(self):
else:
ED['Othr_CdtrAgt_Node'] = ET.Element("Othr")
ED['Id_CdtrAgt_Node'] = ET.Element("Id")
if 'ultimate_creditor' in self._config:
ED['UltmtCdtrNode'] = ET.Element("UltmtCdtr")
if 'name' in self._config['ultimate_creditor']:
ED['Nm_UltmtCdtr_Node'] = ET.Element("Nm")
ED['Id_UltmtCdtr_Node'] = ET.Element("Id")
ED['OrgId_Id_UltmtCdtr_Node'] = ET.Element("OrgId")
if 'BIC_or_BEI' in self._config['ultimate_creditor']:
ED['BICOrBEI_OrgId_Id_UltmtCdtr_Node'] = ET.Element("BICOrBEI")
if 'id' in self._config['ultimate_creditor']:
ED['Othr_OrgId_Id_UltmtCdtr_Node'] = ET.Element("Othr")
ED['Id_Othr_OrgId_Id_UltmtCdtr_Node'] = ET.Element("Id")
if 'id_scheme_name' in self._config['ultimate_creditor']:
ED['SchmeNm_Othr_OrgId_Id_UltmtCdtr_Node'] = ET.Element("SchmeNm")
ED['Prtry_SchmeNm_Othr_OrgId_Id_UltmtCdtr_Node'] = ET.Element("Prtry")
ED['ChrgBrNode'] = ET.Element("ChrgBr")
ED['CdtrSchmeIdNode'] = ET.Element("CdtrSchmeId")
ED['Id_CdtrSchmeId_Node'] = ET.Element("Id")
Expand All @@ -239,6 +258,7 @@ def _create_TX_node(self, bic=True):
ED['MndtRltdInfNode'] = ET.Element("MndtRltdInf")
ED['MndtIdNode'] = ET.Element("MndtId")
ED['DtOfSgntrNode'] = ET.Element("DtOfSgntr")
ED['AmdmntIndNode'] = ET.Element("AmdmntInd")
ED['DbtrAgtNode'] = ET.Element("DbtrAgt")
ED['FinInstnId_DbtrAgt_Node'] = ET.Element("FinInstnId")
if bic:
Expand Down Expand Up @@ -315,6 +335,7 @@ def _add_non_batch(self, TX_nodes, PmtInf_nodes):

TX_nodes['MndtRltdInfNode'].append(TX_nodes['MndtIdNode'])
TX_nodes['MndtRltdInfNode'].append(TX_nodes['DtOfSgntrNode'])
TX_nodes['MndtRltdInfNode'].append(TX_nodes['AmdmntIndNode'])
TX_nodes['DrctDbtTxNode'].append(TX_nodes['MndtRltdInfNode'])
TX_nodes['DrctDbtTxInfNode'].append(TX_nodes['DrctDbtTxNode'])

Expand Down Expand Up @@ -356,6 +377,7 @@ def _add_batch(self, TX_nodes, payment):

TX_nodes['MndtRltdInfNode'].append(TX_nodes['MndtIdNode'])
TX_nodes['MndtRltdInfNode'].append(TX_nodes['DtOfSgntrNode'])
TX_nodes['MndtRltdInfNode'].append(TX_nodes['AmdmntIndNode'])
TX_nodes['DrctDbtTxNode'].append(TX_nodes['MndtRltdInfNode'])
TX_nodes['DrctDbtTxInfNode'].append(TX_nodes['DrctDbtTxNode'])

Expand Down Expand Up @@ -445,6 +467,16 @@ def _finalize_batch(self):
PmtInf_nodes['NbOfTxsNode'].text = str(len(batch_nodes))
PmtInf_nodes['CtrlSumNode'].text = int_to_decimal_str(self._batch_totals[batch_meta])

if 'ultimate_creditor' in self._config:
if 'name' in self._config['ultimate_creditor']:
PmtInf_nodes['Nm_UltmtCdtr_Node'].text = self._config['ultimate_creditor']['name']
if 'BIC_or_BEI' in self._config['ultimate_creditor']:
PmtInf_nodes['BICOrBEI_OrgId_Id_UltmtCdtr_Node'].text = self._config['ultimate_creditor']['BIC_or_BEI']
if 'id' in self._config['ultimate_creditor']:
PmtInf_nodes['Id_Othr_OrgId_Id_UltmtCdtr_Node'].text = self._config['ultimate_creditor']['id']
if 'id_scheme_name' in self._config['ultimate_creditor']:
PmtInf_nodes['Prtry_SchmeNm_Othr_OrgId_Id_UltmtCdtr_Node'].text = self._config['ultimate_creditor']['id_scheme_name']

PmtInf_nodes['PmtInfNode'].append(PmtInf_nodes['PmtInfIdNode'])
PmtInf_nodes['PmtInfNode'].append(PmtInf_nodes['PmtMtdNode'])
PmtInf_nodes['PmtInfNode'].append(PmtInf_nodes['BtchBookgNode'])
Expand Down Expand Up @@ -484,6 +516,29 @@ def _finalize_batch(self):
PmtInf_nodes['FinInstnId_CdtrAgt_Node'])
PmtInf_nodes['PmtInfNode'].append(PmtInf_nodes['CdtrAgtNode'])

if 'ultimate_creditor' in self._config:
if 'BIC_or_BEI' in self._config['ultimate_creditor']:
PmtInf_nodes['OrgId_Id_UltmtCdtr_Node'].append(
PmtInf_nodes['BICOrBEI_OrgId_Id_UltmtCdtr_Node'])
PmtInf_nodes['Id_UltmtCdtr_Node'].append(
PmtInf_nodes['OrgId_Id_UltmtCdtr_Node'])
if 'id' in self._config['ultimate_creditor']:
PmtInf_nodes['Othr_OrgId_Id_UltmtCdtr_Node'].append(
PmtInf_nodes['Id_Othr_OrgId_Id_UltmtCdtr_Node'])
if 'id_scheme_name' in self._config['ultimate_creditor']:
PmtInf_nodes['SchmeNm_Othr_OrgId_Id_UltmtCdtr_Node'].append(
PmtInf_nodes['Prtry_SchmeNm_Othr_OrgId_Id_UltmtCdtr_Node'])
PmtInf_nodes['Othr_OrgId_Id_UltmtCdtr_Node'].append(
PmtInf_nodes['SchmeNm_Othr_OrgId_Id_UltmtCdtr_Node'])
PmtInf_nodes['OrgId_Id_UltmtCdtr_Node'].append(
PmtInf_nodes['Othr_OrgId_Id_UltmtCdtr_Node'])
if 'name' in self._config['ultimate_creditor']:
PmtInf_nodes['UltmtCdtrNode'].append(
PmtInf_nodes['Nm_UltmtCdtr_Node'])
PmtInf_nodes['UltmtCdtrNode'].append(
PmtInf_nodes['Id_UltmtCdtr_Node'])
PmtInf_nodes['PmtInfNode'].append(PmtInf_nodes['UltmtCdtrNode'])

PmtInf_nodes['PmtInfNode'].append(PmtInf_nodes['ChrgBrNode'])

PmtInf_nodes['OthrNode'].append(PmtInf_nodes['Id_Othr_Node'])
Expand Down
5 changes: 4 additions & 1 deletion sepaxml/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ def __init__(self, config, schema, clean=True):

self._config['name'] = unidecode(self._config['name'])[:70]

if self._config['msg_id']:
self.msg_id = self._config['msg_id'][:35]

self._prepare_document()
self._create_header()

Expand Down Expand Up @@ -93,7 +96,7 @@ def export(self, validate=True, pretty_print=False):
if pretty_print:
from xml.dom import minidom
out_minidom = minidom.parseString(out)
out = out_minidom.toprettyxml(encoding="utf-8")
out = out_minidom.toprettyxml(encoding="UTF-8")

if validate:
try_valid_xml(out, self.schema)
Expand Down
13 changes: 13 additions & 0 deletions sepaxml/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,27 @@ def _create_header(self):
CtrlSum_node = ET.Element("CtrlSum")
InitgPty_node = ET.Element("InitgPty")
Nm_node = ET.Element("Nm")
SupId_node = ET.Element("Id")
OrgId_node = ET.Element("OrgId")
Othr_node = ET.Element("Othr")
Id_node = ET.Element("Id")

# Add data to some header nodes.
MsgId_node.text = self.msg_id
CreDtTm_node.text = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
Nm_node.text = self._config['name']
if 'initiating_party' in self._config and self._config['initiating_party']:
Nm_node.text = self._config['initiating_party']
if 'initiating_party_id' in self._config and self._config['initiating_party_id']:
Id_node.text = self._config['initiating_party_id']

# Append the nodes
Othr_node.append(Id_node)
OrgId_node.append(Othr_node)
SupId_node.append(OrgId_node)
InitgPty_node.append(Nm_node)
if 'initiating_party_id' in self._config:
InitgPty_node.append(SupId_node)
GrpHdr_node.append(MsgId_node)
GrpHdr_node.append(CreDtTm_node)
GrpHdr_node.append(NbOfTxs_node)
Expand Down