diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b447266 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# NIP24 Client for Python + +This is the official repository for NIP24 Client for Python: https://nip24.pl + +This library contains validators for common Polish tax numbers like NIP, REGON and KRS. Validators for +EU VAT ID and IBAN are also included. After registration at NIP24 Portal this library could be used for various +on-line validations of Polish and EU companies. Please, visit our web page for more details. + +# Documentation + +The documentation and samples are available at https://nip24.pl/dokumentacja/ + +# License + +This project is delivered under Apache License, Version 2.0: + +- [![License (Apache 2.0)](https://img.shields.io/badge/license-Apache%20version%202.0-blue.svg?style=flat-square)](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/example.py b/example.py new file mode 100644 index 0000000..9d37abf --- /dev/null +++ b/example.py @@ -0,0 +1,105 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + +from nip24 import * +from pprint import pprint + +# Utworzenie obiektu klienta usługi serwisu produkcyjnego +# id – ciąg znaków reprezentujący identyfikator klucza +# key – ciąg znaków reprezentujący klucz +# nip24 = NIP24Client('id', 'key') + +# Utworzenie obiektu klienta usługi serwisu testowego +nip24 = NIP24Client() + +nip = '7171642051' +nip_eu = 'PL' + nip +account_number = '49154000046458439719826658' + +# Sprawdzenie stanu konta +account = nip24.getAccountStatus() + +if account: + pprint(vars(account)) +else: + print u'Błąd: ' + nip24.getLastError() + +# Sprawdzenie statusu fimy +active = nip24.isActiveExt(Number.NIP, nip) + +if active: + print u'Firma prowadzi aktywną działalność' +else: + if not nip24.getLastError(): + print u'Firma zawiesiła lub zakończyła działalność' + else: + print u'Błąd: ' + nip24.getLastError() + +# Sprawdzenie statusu firmy w rejestrze VAT +vat = nip24.getVATStatusExt(Number.NIP, nip, True) + +if vat: + print u'NIP: ' + vat.nip + print u'REGON: ' + vat.regon + print u'Nazwa firmy: ' + vat.name + print u'Status: ' + str(vat.status) + print u'Wynik: ' + vat.result + print u'Data sprawdzenia: ' + vat.date.strftime('%Y-%m-%d') + print u'Źródło: ' + vat.source +else: + print u'Błąd: ' + nip24.getLastError() + +# Wywołanie metody zwracającej dane do faktury +invoice = nip24.getInvoiceDataExt(Number.NIP, nip, False) + +if invoice: + print u'Nazwa: ' + invoice.name + print u'Imię i nazwisko: ' + invoice.firstname + ' ' + invoice.lastname + print u'Adres: ' + invoice.postCode + ' ' + invoice.postCity + ' ' + invoice.street \ + + ' ' + invoice.streetNumber + print u'NIP: ' + invoice.nip +else: + print u'Błąd: ' + nip24.getLastError() + +# Wywołanie metody zwracającej szczegółowe dane firmy +all = nip24.getAllDataExt(Number.NIP, nip, False) + +if all: + pprint(vars(all)) +else: + print u'Błąd: ' + nip24.getLastError() + +# Wywołanie metody zwracającej dane z systemu VIES +vies = nip24.getVIESData(nip_eu) + +if vies: + pprint(vars(vies)) +else: + print u'Błąd: ' + nip24.getLastError() + +# Wywołanie metody zwracającej informacje o rachunku bankowym +iban = nip24.getIBANStatusExt(Number.NIP, nip, account_number) + +if iban: + pprint(vars(iban)) +else: + print u'Błąd: ' + nip24.getLastError() diff --git a/nip24/__init__.py b/nip24/__init__.py new file mode 100644 index 0000000..09eb2fa --- /dev/null +++ b/nip24/__init__.py @@ -0,0 +1,38 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + +from number import * +from pkd import * +from invoicedata import * +from alldata import * +from viesdata import * +from vatstatus import * +from ibanstatus import * +from accountstatus import * +from nip import * +from regon import * +from krs import * +from euvat import * +from iban import * +from nip24client import * + +__version__ = '1.3.3' diff --git a/nip24/accountstatus.py b/nip24/accountstatus.py new file mode 100644 index 0000000..12bbc92 --- /dev/null +++ b/nip24/accountstatus.py @@ -0,0 +1,62 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + + +class AccountStatus: + """ + Account status + """ + + def __init__(self): + self.uid = None + self.billingPlanName = None + self.subscriptionPrice = None + self.itemPrice = None + self.itemPriceStatus = None + self.itemPriceInvoice = None + self.itemPriceAll = None + self.itemPriceIBAN = None + self.limit = None + self.requestDelay = None + self.domainLimit = None + self.overPlanAllowed = None + self.terytCodes = None + self.excelAddIn = None + self.JPKVAT = None + self.stats = None + self.nipMonitor = None + self.searchByNIP = None + self.searchByREGON = None + self.searchByKRS = None + self.funcIsActive = None + self.funcGetInvoiceData = None + self.funcGetAllData = None + self.funcGetVIESData = None + self.funcGetVATStatus = None + self.funcGetIBANStatus = None + self.invoiceDataCount = None + self.allDataCount = None + self.firmStatusCount = None + self.vatStatusCount = None + self.viesStatusCount = None + self.ibanStatusCount = None + self.totalCount = None diff --git a/nip24/alldata.py b/nip24/alldata.py new file mode 100644 index 0000000..cc463a1 --- /dev/null +++ b/nip24/alldata.py @@ -0,0 +1,75 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + + +class AllData: + """ + All firm data + """ + + def __init__(self): + self.uid = None + self.type = None + self.nip = None + self.regon = None + self.name = None + self.shortname = None + self.firstname = None + self.secondname = None + self.lastname = None + self.street = None + self.streetCode = None + self.streetNumber = None + self.houseNumber = None + self.city = None + self.cityCode = None + self.community = None + self.communityCode = None + self.county = None + self.countyCode = None + self.state = None + self.stateCode = None + self.postCode = None + self.postCity = None + self.phone = None + self.email = None + self.www = None + self.creationDate = None + self.startDate = None + self.registrationDate = None + self.holdDate = None + self.renevalDate = None + self.lastUpdateDate = None + self.endDate = None + self.registryEntityCode = None + self.registryEntityName = None + self.registryCode = None + self.registryName = None + self.recordCreationDate = None + self.recordNumber = None + self.basicLegalFormCode = None + self.basicLegalFormName = None + self.specificLegalFormCode = None + self.specificLegalFormName = None + self.ownershipFormCode = None + self.ownershipFormName = None + self.pkd = [] diff --git a/nip24/euvat.py b/nip24/euvat.py new file mode 100644 index 0000000..ab67761 --- /dev/null +++ b/nip24/euvat.py @@ -0,0 +1,113 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + +import re + +from nip24 import NIP + + +class EUVAT: + """ + EU VAT number verificator + """ + + @staticmethod + def normalize(nip): + """ + Normalizes form of the VAT number + + :param nip: input string + :type nip: str + :returns: normalized string or False + :rtype: str or False + """ + + if not nip: + return False + + nip = nip.strip().translate(None, '-').translate(None, ' ').upper() + + if not re.match('[A-Z]{2}[A-Z0-9]{2,12}', nip): + return False + + return nip + + @staticmethod + def isValid(nip): + """ + Checks if specified NIP is valid + + :param nip: input string + :type nip: str + :returns: True if NIP is valid + :rtype: bool + """ + + nip = EUVAT.normalize(nip) + + if not nip: + return False + + map = { + 'AT': 'ATU\\d{8}', + 'BE': 'BE0\\d{9}', + 'BG': 'BG\\d{9,10}', + 'CY': 'CY\\d{8}[A-Z]{1}', + 'CZ': 'CZ\\d{8,10}', + 'DE': 'DE\\d{9}', + 'DK': 'DK\\d{8}', + 'EE': 'EE\\d{9}', + 'EL': 'EL\\d{9}', + 'ES': 'ES[A-Z0-9]{9}', + 'FI': 'FI\\d{8}', + 'FR': 'FR[A-Z0-9]{2}\\d{9}', + 'GB': 'GB[A-Z0-9]{5,12}', + 'HR': 'HR\\d{11}', + 'HU': 'HU\\d{8}', + 'IE': 'IE[A-Z0-9]{8,9}', + 'IT': 'IT\\d{11}', + 'LT': 'LT\\d{9,12}', + 'LU': 'LU\\d{8}', + 'LV': 'LV\\d{11}', + 'MT': 'MT\\d{8}', + 'NL': 'NL\\d{9}B\\d{2}', + 'PL': 'PL\\d{10}', + 'PT': 'PT\\d{9}', + 'RO': 'RO\\d{2,10}', + 'SE': 'SE\\d{12}', + 'SI': 'SI\\d{8}', + 'SK': 'SK\\d{10}' + } + + cc = nip[0:2].upper() + num = nip[2:].upper() + + if cc not in map: + return False + + if not re.match(map[cc], nip): + return False + + if cc == 'PL': + return NIP.isValid(num) + + return True diff --git a/nip24/iban.py b/nip24/iban.py new file mode 100644 index 0000000..d080116 --- /dev/null +++ b/nip24/iban.py @@ -0,0 +1,159 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + +import re +import string + + +class IBAN: + """ + IBAN verificator + """ + + @staticmethod + def normalize(iban): + """ + Normalizes form of the IBAN number + + :param iban: input string + :type iban: str + :returns: normalized string or False + :rtype: str or False + """ + + if not iban: + return False + + iban = iban.strip().translate(None, '-').translate(None, ' ').upper() + + if not re.match('[A-Z]{2}[0-9A-Z]{13,30}', iban): + return False + + return iban + + @staticmethod + def isValid(iban): + """ + Checks if specified IBAN is valid + + :param iban: input string + :type iban: str + :returns: True if IBAN is valid + :rtype: bool + """ + + iban = IBAN.normalize(iban) + + if not iban: + return False + + map = { + 'AD': 'AD\\d{10}[A-Z0-9]{12}', + 'AE': 'AE\\d{21}', + 'AL': 'AL\\d{10}[A-Z0-9]{16}', + 'AT': 'AT\\d{18}', + 'AZ': 'AZ\\d{2}[A-Z]{4}[A-Z0-9]{20}', + 'BA': 'BA\\d{18}', + 'BE': 'BE\\d{14}', + 'BG': 'BG\\d{2}[A-Z]{4}\\d{6}[A-Z0-9]{8}', + 'BH': 'BH\\d{2}[A-Z]{4}[A-Z0-9]{14}', + 'BR': 'BR\\d{25}[A-Z]{1}[A-Z0-9]{1}', + 'BY': 'BY\\d{2}[A-Z0-9]{4}\\d{4}[A-Z0-9]{16}', + 'CH': 'CH\\d{7}[A-Z0-9]{12}', + 'CR': 'CR\\d{20}', + 'CY': 'CY\\d{10}[A-Z0-9]{16}', + 'CZ': 'CZ\\d{22}', + 'DE': 'DE\\d{20}', + 'DK': 'DK\\d{16}', + 'DO': 'DO\\d{2}[A-Z0-9]{4}\\d{20}', + 'EE': 'EE\\d{18}', + 'ES': 'ES\\d{22}', + 'FI': 'FI\\d{16}', + 'FO': 'FO\\d{16}', + 'FR': 'FR\\d{12}[A-Z0-9]{11}\\d{2}', + 'GB': 'GB\\d{2}[A-Z]{4}\\d{14}', + 'GE': 'GE\\d{2}[A-Z]{2}\\d{16}', + 'GI': 'GI\\d{2}[A-Z]{4}[A-Z0-9]{15}', + 'GL': 'GL\\d{16}', + 'GR': 'GR\\d{9}[A-Z0-9]{16}', + 'GT': 'GT\\d{2}[A-Z0-9]{24}', + 'HR': 'HR\\d{19}', + 'HU': 'HU\\d{26}', + 'IE': 'IE\\d{2}[A-Z]{4}\\d{14}', + 'IL': 'IL\\d{21}', + 'IQ': 'IQ\\d{2}[A-Z]{4}\\d{15}', + 'IS': 'IS\\d{24}', + 'IT': 'IT\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}', + 'JO': 'JO\\d{2}[A-Z]{4}\\d{4}[A-Z0-9]{18}', + 'KW': 'KW\\d{2}[A-Z]{4}[A-Z0-9]{22}', + 'KZ': 'KZ\\d{5}[A-Z0-9]{13}', + 'LB': 'LB\\d{6}[A-Z0-9]{20}', + 'LC': 'LC\\d{2}[A-Z]{4}[A-Z0-9]{24}', + 'LI': 'LI\\d{7}[A-Z0-9]{12}', + 'LT': 'LT\\d{18}', + 'LU': 'LU\\d{5}[A-Z0-9]{13}', + 'LV': 'LV\\d{2}[A-Z]{4}[A-Z0-9]{13}', + 'MC': 'MC\\d{12}[A-Z0-9]{11}\\d{2}', + 'MD': 'MD\\d{2}[A-Z0-9]{20}', + 'ME': 'ME\\d{20}', + 'MK': 'MK\\d{5}[A-Z0-9]{10}\\d{2}', + 'MR': 'MR\\d{25}', + 'MT': 'MT\\d{2}[A-Z]{4}\\d{5}[A-Z0-9]{18}', + 'MU': 'MU\\d{2}[A-Z]{4}\\d{19}[A-Z]{3}', + 'NL': 'NL\\d{2}[A-Z]{4}\\d{10}', + 'NO': 'NO\\d{13}', + 'PK': 'PK\\d{2}[A-Z]{4}[A-Z0-9]{16}', + 'PL': 'PL\\d{26}', + 'PS': 'PS\\d{2}[A-Z]{4}[A-Z0-9]{21}', + 'PT': 'PT\\d{23}', + 'QA': 'QA\\d{2}[A-Z]{4}[A-Z0-9]{21}', + 'RO': 'RO\\d{2}[A-Z]{4}[A-Z0-9]{16}', + 'RS': 'RS\\d{20}', + 'SA': 'SA\\d{4}[A-Z0-9]{18}', + 'SC': 'SC\\d{2}[A-Z]{4}\\d{20}[A-Z]{3}', + 'SE': 'SE\\d{22}', + 'SI': 'SI\\d{17}', + 'SK': 'SK\\d{22}', + 'SM': 'SM\\d{2}[A-Z]{1}\\d{10}[A-Z0-9]{12}', + 'ST': 'ST\\d{23}', + 'SV': 'SV\\d{2}[A-Z]{4}\\d{20}', + 'TL': 'TL\\d{21}', + 'TN': 'TN\\d{22}', + 'TR': 'TR\\d{8}[A-Z0-9]{16}', + 'UA': 'UA\\d{8}[A-Z0-9]{19}', + 'VG': 'VG\\d{2}[A-Z]{4}\\d{16}', + 'XK': 'XK\\d{18}' + } + + cc = iban[0:2].upper() + + if cc not in map: + return False + + if not re.match(map[cc], iban): + return False + + chars = string.digits + string.ascii_uppercase + iban = iban[4:] + iban[:4] + num = int(''.join(str(chars.index(c)) for c in iban)) + + return num % 97 == 1 diff --git a/nip24/ibanstatus.py b/nip24/ibanstatus.py new file mode 100644 index 0000000..4c6403a --- /dev/null +++ b/nip24/ibanstatus.py @@ -0,0 +1,37 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + + +class IBANStatus: + """ + IBAN status info + """ + + def __init__(self): + self.uid = None + self.nip = None + self.regon = None + self.iban = None + self.valid = None + self.id = None + self.date = None + self.source = None diff --git a/nip24/invoicedata.py b/nip24/invoicedata.py new file mode 100644 index 0000000..bbfc3ee --- /dev/null +++ b/nip24/invoicedata.py @@ -0,0 +1,43 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + + +class InvoiceData: + """ + Invoice data + """ + + def __init__(self): + self.uid = None + self.nip = None + self.name = None + self.firstname = None + self.lastname = None + self.street = None + self.streetNumber = None + self.houseNumber = None + self.city = None + self.postCode = None + self.postCity = None + self.phone = None + self.email = None + self.www = None diff --git a/nip24/krs.py b/nip24/krs.py new file mode 100644 index 0000000..a356c18 --- /dev/null +++ b/nip24/krs.py @@ -0,0 +1,67 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + +import re + +class KRS: + """ + KRS number validator + """ + + @staticmethod + def normalize(krs): + """ + Normalizes form of the KRS number + + :param krs: input string + :type krs: str + :returns: normalized string or False + :rtype: str or False + """ + + if not krs: + return False + + krs = krs.strip().zfill(10) + + if not re.match('[0-9]{10}', krs): + return False + + return krs + + @staticmethod + def isValid(krs): + """ + Checks if specified KRS is valid + + :param nip: input string + :type nip: str + :returns: True if NIP is valid + :rtype: bool + """ + + krs = KRS.normalize(krs) + + if not krs: + return False + + return True diff --git a/nip24/nip.py b/nip24/nip.py new file mode 100644 index 0000000..59005fc --- /dev/null +++ b/nip24/nip.py @@ -0,0 +1,79 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + +import re + + +class NIP: + """ + NIP number validator + """ + + @staticmethod + def normalize(nip): + """ + Normalizes form of the NIP number + + :param nip: input string + :type nip: str + :returns: normalized string or False + :rtype: str or False + """ + + if not nip: + return False + + nip = nip.strip().translate(None, '-') + + if not re.match('[0-9]{10}', nip): + return False + + return nip + + @staticmethod + def isValid(nip): + """ + Checks if specified NIP is valid + + :param nip: input string + :type nip: str + :returns: True if NIP is valid + :rtype: bool + """ + + nip = NIP.normalize(nip) + + if not nip: + return False + + w = [ 6, 5, 7, 2, 3, 4, 5, 6, 7 ] + sum = 0 + + for i in range(0, len(w)): + sum += int(nip[i]) * w[i] + + sum %= 11 + + if sum != int(nip[9]): + return False + + return True diff --git a/nip24/nip24client.py b/nip24/nip24client.py new file mode 100644 index 0000000..96237e1 --- /dev/null +++ b/nip24/nip24client.py @@ -0,0 +1,874 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + +import base64 +import datetime +import hashlib +import hmac +import os +import sys +import time +import urllib2 +import urlparse + +from nip24 import Number, NIP, REGON, KRS, EUVAT, PKD, IBAN, AllData, InvoiceData, VIESData, VATStatus, IBANStatus, AccountStatus +from io import BytesIO +from lxml import etree +from dateutil.parser import parse + +class NIP24Client: + """ + NIP24 service client + """ + + VERSION = '1.3.3' + + PRODUCTION_URL = 'https://www.nip24.pl/api' + TEST_URL = 'https://www.nip24.pl/api-test' + + TEST_ID = 'test_id' + TEST_KEY = 'test_key' + + HMAC_ALG = hashlib.sha256 + + def __init__(self, id = None, key = None): + """ + Construct new service client object + + :param id: NIP24 key identifier + :type id: str + :param key: NIP24 key + :type key: str + """ + self.__url__ = self.TEST_URL + self.__id__ = self.TEST_ID + self.__key__ = self.TEST_KEY + + if id is not None and key is not None: + self.__url__ = self.PRODUCTION_URL + self.__id__ = id + self.__key__ = key + + self.__err__ = u'' + + def setURL(self, url): + """ + Set non default service URL + + :param url: service URL + :type url: str + """ + + self.__url__ = url + + def isActive(self, nip_): + """ + Check firm activity + + :param nip_: NIP number + :type nip_: str + :returns: True is firm is active + :rtype: bool + """ + + return self.isActiveExt(Number.NIP, nip_) + + def isActiveExt(self, type, number): + """ + Check firm activity + + :param type: search number type as Number.xxx value + :type type: Number + :param number: search number value + :type number: str + :returns: True is firm is active + :rtype: bool + """ + + # clear error + self.__err__ = u'' + + # validate number and construct path + suffix = self.__getPathSuffix(type, number) + + if not suffix: + return False + + # prepare url + url = self.__url__ + '/check/firm/' + suffix + + # send request + res = self.__get(url) + + if not res: + self.__err__ = u'Nie udało się nawiązać połączenia z serwisem NIP24' + return False + + # parse response + doc = etree.parse(BytesIO(res)) + + if not doc: + self.__err__ = u'Odpowiedź serwisu NIP24 ma nieprawidłowy format' + return False + + err = self.__get_text(doc, '/result/error/code/text()') + + if len(err) > 0: + if int(err) == 9: + # not active + self.__err__ = u'' + return False + else: + self.__err__ = self.__get_text(doc, '/result/error/description/text()') + return False + + # ok + return True + + def getInvoiceData(self, nip, force=True): + """ + Get invoice data for specified NIP number + + :param nip: NIP number + :type nip: str + :param force: ignored, left for backward compatibility + :type force: bool + :return: InvoiceData object or False + :rtype: InvoiceData or False + """ + + return self.getInvoiceDataExt(Number.NIP, nip) + + def getInvoiceDataExt(self, type, number, force=True): + """ + Get invoice data for specified number type + + :param type: search number type as Number.xxx value + :type type: Number + :param number: search number value + :type number: str + :param force: ignored, left for backward compatibility + :type force: bool + :return: InvoiceData object or False + :rtype: InvoiceData or False + """ + + # clear error + self.__err__ = u'' + + # validate number and construct path + suffix = self.__getPathSuffix(type, number) + + if not suffix: + return False + + # prepare url + url = self.__url__ + '/get/invoice/' + suffix + + # send request + res = self.__get(url) + + if not res: + self.__err__ = u'Nie udało się nawiązać połączenia z serwisem NIP24' + return False + + # parse response + doc = etree.parse(BytesIO(res)) + + if not doc: + self.__err__ = u'Odpowiedź serwisu NIP24 ma nieprawidłowy format' + return False + + err = self.__get_text(doc, '/result/error/code/text()') + + if len(err) > 0: + self.__err__ = self.__get_text(doc, '/result/error/description/text()') + return False + + invoice = InvoiceData() + + invoice.uid = self.__get_text(doc, '/result/firm/uid/text()') + + invoice.nip = self.__get_text(doc, '/result/firm/nip/text()') + + invoice.name = self.__get_text(doc, '/result/firm/name/text()') + invoice.firstname = self.__get_text(doc, '/result/firm/firstname/text()') + invoice.lastname = self.__get_text(doc, '/result/firm/lastname/text()') + + invoice.street = self.__get_text(doc, '/result/firm/street/text()') + invoice.streetNumber = self.__get_text(doc, '/result/firm/streetNumber/text()') + invoice.houseNumber = self.__get_text(doc, '/result/firm/houseNumber/text()') + invoice.city = self.__get_text(doc, '/result/firm/city/text()') + invoice.postCode = self.__get_text(doc, '/result/firm/postCode/text()') + invoice.postCity = self.__get_text(doc, '/result/firm/postCity/text()') + + invoice.phone = self.__get_text(doc, '/result/firm/phone/text()') + invoice.email = self.__get_text(doc, '/result/firm/email/text()') + invoice.www = self.__get_text(doc, '/result/firm/www/text()') + + return invoice + + def getAllData(self, nip, force=True): + """ + Get all firm data for specified NIP number + + :param nip: NIP number + :type nip: str + :param force: ignored, left for backward compatibility + :type force: bool + :return: AllData object or False + :rtype: AllData or False + """ + + return self.getAllDataExt(Number.NIP, nip) + + def getAllDataExt(self, type, number, force=True): + """ + Get all data for specified number type + + :param type: search number type as Number.xxx value + :type type: Number + :param number: search number value + :type number: str + :param force: ignored, left for backward compatibility + :type force: bool + :return: AllData object or False + :rtype: AllData or False + """ + + # clear error + self.__err__ = u'' + + # validate number and construct path + suffix = self.__getPathSuffix(type, number) + + if not suffix: + return False + + # prepare url + url = self.__url__ + '/get/all/' + suffix + + # send request + res = self.__get(url) + + if not res: + self.__err__ = u'Nie udało się nawiązać połączenia z serwisem NIP24' + return False + + # parse response + doc = etree.parse(BytesIO(res)) + + if not doc: + self.__err__ = u'Odpowiedź serwisu NIP24 ma nieprawidłowy format' + return False + + err = self.__get_text(doc, '/result/error/code/text()') + + if len(err) > 0: + self.__err__ = self.__get_text(doc, '/result/error/description/text()') + return False + + all = AllData() + + all.uid = self.__get_text(doc, '/result/firm/uid/text()') + + all.type = self.__get_text(doc, '/result/firm/type/text()') + all.nip = self.__get_text(doc, '/result/firm/nip/text()') + all.regon = self.__get_text(doc, '/result/firm/regon/text()') + + all.name = self.__get_text(doc, '/result/firm/name/text()') + all.shortname = self.__get_text(doc, '/result/firm/shortname/text()') + all.firstname = self.__get_text(doc, '/result/firm/firstname/text()') + all.secondname = self.__get_text(doc, '/result/firm/secondname/text()') + all.lastname = self.__get_text(doc, '/result/firm/lastname/text()') + + all.street = self.__get_text(doc, '/result/firm/street/text()') + all.streetCode = self.__get_text(doc, '/result/firm/streetCode/text()') + all.streetNumber = self.__get_text(doc, '/result/firm/streetNumber/text()') + all.houseNumber = self.__get_text(doc, '/result/firm/houseNumber/text()') + all.city = self.__get_text(doc, '/result/firm/city/text()') + all.cityCode = self.__get_text(doc, '/result/firm/cityCode/text()') + all.community = self.__get_text(doc, '/result/firm/community/text()') + all.communityCode = self.__get_text(doc, '/result/firm/communityCode/text()') + all.county = self.__get_text(doc, '/result/firm/county/text()') + all.countyCode = self.__get_text(doc, '/result/firm/countyCode/text()') + all.state = self.__get_text(doc, '/result/firm/state/text()') + all.stateCode = self.__get_text(doc, '/result/firm/stateCode/text()') + all.postCode = self.__get_text(doc, '/result/firm/postCode/text()') + all.postCity = self.__get_text(doc, '/result/firm/postCity/text()') + + all.phone = self.__get_text(doc, '/result/firm/phone/text()') + all.email = self.__get_text(doc, '/result/firm/email/text()') + all.www = self.__get_text(doc, '/result/firm/www/text()') + + all.creationDate = self.__get_date_time(doc, '/result/firm/creationDate/text()') + all.startDate = self.__get_date_time(doc, '/result/firm/startDate/text()') + all.registrationDate = self.__get_date_time(doc, '/result/firm/registrationDate/text()') + all.holdDate = self.__get_date_time(doc, '/result/firm/holdDate/text()') + all.renevalDate = self.__get_date_time(doc, '/result/firm/renevalDate/text()') + all.lastUpdateDate = self.__get_date_time(doc, '/result/firm/lastUpdateDate/text()') + all.endDate = self.__get_date_time(doc, '/result/firm/endDate/text()') + + all.registryEntityCode = self.__get_text(doc, '/result/firm/registryEntity/code/text()') + all.registryEntityName = self.__get_text(doc, '/result/firm/registryEntity/name/text()') + + all.registryCode = self.__get_text(doc, '/result/firm/registry/code/text()') + all.registryName = self.__get_text(doc, '/result/firm/registry/name/text()') + + all.recordCreationDate = self.__get_date_time(doc, '/result/firm/record/created/text()') + all.recordNumber = self.__get_text(doc, '/result/firm/record/number/text()') + + all.basicLegalFormCode = self.__get_text(doc, '/result/firm/basicLegalForm/code/text()') + all.basicLegalFormName = self.__get_text(doc, '/result/firm/basicLegalForm/name/text()') + + all.specificLegalFormCode = self.__get_text(doc, '/result/firm/specificLegalForm/code/text()') + all.specificLegalFormName = self.__get_text(doc, '/result/firm/specificLegalForm/name/text()') + + all.ownershipFormCode = self.__get_text(doc, '/result/firm/ownershipForm/code/text()') + all.ownershipFormName = self.__get_text(doc, '/result/firm/ownershipForm/name/text()') + + all.pkd = [] + + i = 1 + while True: + code = self.__get_text(doc, '/result/firm/PKDs/PKD[' + str(i) + ']/code/text()') + + if len(code) == 0: + break + + descr = self.__get_text(doc, '/result/firm/PKDs/PKD[' + str(i) + ']/description/text()') + pri = self.__get_text(doc, '/result/firm/PKDs/PKD[' + str(i) + ']/primary/text()') + + pkd = PKD() + + pkd.code = code + pkd.description = descr + pkd.primary = True if pri == "true" else False + + all.pkd.append(pkd) + i += 1 + + return all + + def getVIESData(self, euvat): + """ + Get VIES data for specified number + + :param euvat: EU VAT number with 2-letter country prefix + :type euvat: str + :return: VIESData object or False + :rtype: VIESData or False + """ + + # clear error + self.__err__ = u'' + + # validate number and construct path + suffix = self.__getPathSuffix(Number.EUVAT, euvat) + + if not suffix: + return False + + # prepare url + url = self.__url__ + '/get/vies/' + suffix + + # send request + res = self.__get(url) + + if not res: + self.__err__ = u'Nie udało się nawiązać połączenia z serwisem NIP24' + return False + + # parse response + doc = etree.parse(BytesIO(res)) + + if not doc: + self.__err__ = u'Odpowiedź serwisu NIP24 ma nieprawidłowy format' + return False + + err = self.__get_text(doc, '/result/error/code/text()') + + if len(err) > 0: + self.__err__ = self.__get_text(doc, '/result/error/description/text()') + return False + + vies = VIESData() + + vies.uid = self.__get_text(doc, '/result/vies/uid/text()') + + vies.countryCode = self.__get_text(doc, '/result/vies/countryCode/text()') + vies.vatNumber = self.__get_text(doc, '/result/vies/vatNumber/text()') + + vies.valid = True if self.__get_text(doc, '/result/vies/valid/text()') == 'true' else False + + vies.traderName = self.__get_text(doc, '/result/vies/traderName/text()') + vies.traderCompanyType = self.__get_text(doc, '/result/vies/traderCompanyType/text()') + vies.traderAddress = self.__get_text(doc, '/result/vies/traderAddress/text()') + + vies.id = self.__get_text(doc, '/result/vies/id/text()') + vies.date = self.__get_date(doc, '/result/vies/date/text()') + vies.source = self.__get_text(doc, '/result/vies/source/text()') + + return vies + + def getVATStatus(self, nip, direct=True): + """ + Check if frim is an active VAT payer + + :param nip: NIP number + :type nip: str + :param direct: ignored, left for backward compatibility + :type direct: bool + :return: VATStatus object or False + :rtype: VATStatus or False + """ + + return self.getVATStatusExt(Number.NIP, nip) + + def getVATStatusExt(self, type, number, direct=True): + """ + Check if firm is an active VAT payer + + :param type: search number type as Number.xxx value + :type type: Number + :param number: search number value + :type number: str + :param direct: ignored, left for backward compatibility + :type direct: bool + :return: VATStatus object or False + :rtype: VATStatus or False + """ + + # clear error + self.__err__ = u'' + + # validate number and construct path + suffix = self.__getPathSuffix(type, number) + + if not suffix: + return False + + # prepare url + url = self.__url__ + '/check/vat/direct/' + suffix + + # send request + res = self.__get(url) + + if not res: + self.__err__ = u'Nie udało się nawiązać połączenia z serwisem NIP24' + return False + + # parse response + doc = etree.parse(BytesIO(res)) + + if not doc: + self.__err__ = u'Odpowiedź serwisu NIP24 ma nieprawidłowy format' + return False + + err = self.__get_text(doc, '/result/error/code/text()') + + if len(err) > 0: + self.__err__ = self.__get_text(doc, '/result/error/description/text()') + return False + + vat = VATStatus() + + vat.uid = self.__get_text(doc, '/result/vat/uid/text()') + + vat.nip = self.__get_text(doc, '/result/vat/nip/text()') + vat.regon = self.__get_text(doc, '/result/vat/regon/text()') + vat.name = self.__get_text(doc, '/result/vat/name/text()') + + vat.status = int(self.__get_text(doc, '/result/vat/status/text()')) + vat.result = self.__get_text(doc, '/result/vat/result/text()') + + vat.date = self.__get_date(doc, '/result/vat/date/text()') + vat.source = self.__get_text(doc, '/result/vat/source/text()') + + return vat + + def getIBANStatus(self, nip, iban, date=None): + """ + Check if firm owns bank account number + + :param nip: NIP number + :type nip: str + :param iban: bank account IBAN (for polish numbers PL prefix may be omitted) + :type iban: str + :param date: date in format 'yyyy-mm-dd' (null - current day) + :type date: str + :return: IBANStatus object or False + :rtype: IBANStatus or False + """ + + return self.getIBANStatusExt(Number.NIP, nip, iban, date) + + def getIBANStatusExt(self, type, number, iban, date=None): + """ + Check if firm owns bank account number + + :param type: search number type as Number.xxx value + :type type: Number + :param number: search number value + :type number: str + :param iban: bank account IBAN (for polish numbers PL prefix may be omitted) + :type iban: str + :param date: date in format 'yyyy-mm-dd' (None - current day) + :type date: str + :return: IBANStatus object or False + :rtype: IBANStatus or False + """ + + # clear error + self.__err__ = u'' + + # validate number and construct path + suffix = self.__getPathSuffix(type, number) + + if not suffix: + return False + + if not IBAN.isValid(iban): + iban = 'PL' + iban + + if not IBAN.isValid(iban): + self.__err__ = u'Numer IBAN jest nieprawidłowy' + return False + + if not date: + date = datetime.date.today().strftime('%Y-%m-%d') + + # prepare url + url = self.__url__ + '/check/iban/' + suffix + '/' + IBAN.normalize(iban) + '/' + parse(date).strftime('%Y-%m-%d') + + # send request + res = self.__get(url) + + if not res: + self.__err__ = u'Nie udało się nawiązać połączenia z serwisem NIP24' + return False + + # parse response + doc = etree.parse(BytesIO(res)) + + if not doc: + self.__err__ = u'Odpowiedź serwisu NIP24 ma nieprawidłowy format' + return False + + err = self.__get_text(doc, '/result/error/code/text()') + + if len(err) > 0: + self.__err__ = self.__get_text(doc, '/result/error/description/text()') + return False + + ibs = IBANStatus() + + ibs.uid = self.__get_text(doc, '/result/iban/uid/text()') + + ibs.nip = self.__get_text(doc, '/result/iban/nip/text()') + ibs.regon = self.__get_text(doc, '/result/iban/regon/text()') + ibs.iban = self.__get_text(doc, '/result/iban/iban/text()') + + ibs.valid = True if self.__get_text(doc, '/result/iban/valid/text()') == 'true' else False + + ibs.id = self.__get_text(doc, '/result/iban/id/text()') + ibs.date = self.__get_date(doc, '/result/iban/date/text()') + ibs.source = self.__get_text(doc, '/result/iban/source/text()') + + return ibs + + def getAccountStatus(self): + """ + Get user account's status + + :return: AccountStatus object or False + :rtype: AccountStatus or False + """ + + # clear error + self.__err__ = u'' + + # prepare url + url = self.__url__ + '/check/account/status' + + # send request + res = self.__get(url) + + if not res: + self.__err__ = u'Nie udało się nawiązać połączenia z serwisem NIP24' + return False + + # parse response + doc = etree.parse(BytesIO(res)) + + if not doc: + self.__err__ = u'Odpowiedź serwisu NIP24 ma nieprawidłowy format' + return False + + err = self.__get_text(doc, '/result/error/code/text()') + + if len(err) > 0: + self.__err__ = self.__get_text(doc, '/result/error/description/text()') + return False + + status = AccountStatus() + + status.uid = self.__get_text(doc, '/result/vat/uid/text()') + status.billingPlanName = self.__get_text(doc, '/result/account/billingPlan/name/text()') + + status.subscriptionPrice = float('0' + self.__get_text(doc, '/result/account/billingPlan/subscriptionPrice/text()')) + status.itemPrice = float('0' + self.__get_text(doc, '/result/account/billingPlan/itemPrice/text()')) + status.itemPriceStatus = float('0' + self.__get_text(doc, '/result/account/billingPlan/itemPriceCheckStatus/text()')) + status.itemPriceInvoice = float('0' + self.__get_text(doc, '/result/account/billingPlan/itemPriceInvoiceData/text()')) + status.itemPriceAll = float('0' + self.__get_text(doc, '/result/account/billingPlan/itemPriceAllData/text()')) + status.itemPriceIBAN = float('0' + self.__get_text(doc, '/result/account/billingPlan/itemPriceIBANStatus/text()')) + + status.limit = int(self.__get_text(doc, '/result/account/billingPlan/limit/text()')) + status.requestDelay = int(self.__get_text(doc, '/result/account/billingPlan/requestDelay/text()')) + status.domainLimit = int(self.__get_text(doc, '/result/account/billingPlan/domainLimit/text()')) + + status.overPlanAllowed = True if self.__get_text(doc, '/result/account/billingPlan/overplanAllowed/text()') == 'true' else False + status.terytCodes = True if self.__get_text(doc, '/result/account/billingPlan/terytCodes/text()') == 'true' else False + status.excelAddIn = True if self.__get_text(doc, '/result/account/billingPlan/excelAddin/text()') == 'true' else False + status.JPKVAT = True if self.__get_text(doc, '/result/account/billingPlan/jpkVat/text()') == 'true' else False + status.stats = True if self.__get_text(doc, '/result/account/billingPlan/stats/text()') == 'true' else False + status.nipMonitor = True if self.__get_text(doc, '/result/account/billingPlan/nipMonitor/text()') == 'true' else False + + status.searchByNIP = True if self.__get_text(doc, '/result/account/billingPlan/searchByNip/text()') == 'true' else False + status.searchByREGON = True if self.__get_text(doc, '/result/account/billingPlan/searchByRegon/text()') == 'true' else False + status.searchByKRS = True if self.__get_text(doc, '/result/account/billingPlan/searchByKrs/text()') == 'true' else False + + status.funcIsActive = True if self.__get_text(doc, '/result/account/billingPlan/funcIsActive/text()') == 'true' else False + status.funcGetInvoiceData = True if self.__get_text(doc, '/result/account/billingPlan/funcGetInvoiceData/text()') == 'true' else False + status.funcGetAllData = True if self.__get_text(doc, '/result/account/billingPlan/funcGetAllData/text()') == 'true' else False + status.funcGetVIESData = True if self.__get_text(doc, '/result/account/billingPlan/funcGetVIESData/text()') == 'true' else False + status.funcGetVATStatus = True if self.__get_text(doc, '/result/account/billingPlan/funcGetVATStatus/text()') == 'true' else False + status.funcGetIBANStatus = True if self.__get_text(doc, '/result/account/billingPlan/funcGetIBANStatus/text()') == 'true' else False + + status.invoiceDataCount = int(self.__get_text(doc, '/result/account/requests/invoiceData/text()')) + status.allDataCount = int(self.__get_text(doc, '/result/account/requests/allData/text()')) + status.firmStatusCount = int(self.__get_text(doc, '/result/account/requests/firmStatus/text()')) + status.vatStatusCount = int(self.__get_text(doc, '/result/account/requests/vatStatus/text()')) + status.viesStatusCount = int(self.__get_text(doc, '/result/account/requests/viesStatus/text()')) + status.ibanStatusCount = int(self.__get_text(doc, '/result/account/requests/ibanStatus/text()')) + status.totalCount = int(self.__get_text(doc, '/result/account/requests/total/text()')) + + return status + + def getLastError(self): + """ + Get last error message + + :return: unicode string + :rtype: str + """ + + return self.__err__ + + def __auth(self, method, url): + """ + Prepare authorization header content + + :param method: HTTP method + :type method: str + :param url: target URL + :type url: str + :returns: authorization header content or False + :rtype: str or False + """ + + # parse url + u = urlparse.urlparse(url) + ls = u.netloc.split(':') + + host = ls[0] + port = 443 if u.scheme == 'https' else 80 + + if len(ls) > 1: + port = ls[1] + + # prepare auth header value + nonce = os.urandom(4).encode('hex') + ts = int(time.time()) + + s = '' + str(ts) + '\n' \ + + nonce + '\n' \ + + method + '\n' \ + + u.path + '\n' \ + + host + '\n' \ + + str(port) + '\n' \ + + '\n' + + mac = base64.b64encode(hmac.new(self.__key__, s, self.HMAC_ALG).digest()) + + return 'MAC id="' + self.__id__ + '", ts="' + str(ts) + '", nonce="' + nonce + '", mac="' + mac + '"' + + def __user_agent(self): + """ + Prepare user agent information header content + + :return: user agent header content + :rtype: str + """ + + return 'NIP24Client/' + self.VERSION + ' Python/' + str(sys.version_info[0]) + '.' + str(sys.version_info[1]) \ + + '.' + str(sys.version_info[2]) + + def __get(self, url): + """ + Get result of HTTP GET request + + :param url: target URL + :type url: str + :returns: result content or False + :rtype: str or False + """ + + # auth + auth = self.__auth('GET', url) + + if not auth: + return False + + # send request + try : + req = urllib2.Request(url) + req.add_header('User-Agent', self.__user_agent()) + req.add_header('Authorization', auth) + + res = urllib2.urlopen(req) + content = res.read() + except urllib2.URLError, ue: + #print ue.code + return False + + return content + + def __get_text(self, doc, xpath_): + """ + Get XML element as text + + :param doc: etree document + :type doc: tree + :param xpath_: xpath string + :type xpath_: string + :return: string + :rtype: str + """ + + s = doc.xpath(xpath_) + + if not s: + return u'' + + if len(s) != 1: + return u'' + + return unicode(s[0].strip()) + + def __get_date_time(self, doc, xpath_): + """ + Get XML element as date time object + + :param doc: etree document + :type doc: tree + :param xpath_: xpath string + :type xpath_: string + :return: datetime + :rtype: datetime or None + """ + + s = self.__get_text(doc, xpath_) + + if len(s) == 0: + return None + + return parse(s) + + def __get_date(self, doc, xpath_): + """ + Get XML element as date object + + :param doc: etree document + :type doc: tree + :param xpath_: xpath string + :type xpath_: string + :return: datetime + :rtype: datetime or None + """ + + s = self.__get_text(doc, xpath_) + + sl = len(s) + + if sl == 0: + return None + elif sl == 11: + # dateutil does not support xsd:date type in form YYYY-MM-DDZ + s = s[0:10] + 'T00:00:00Z' + elif sl == 16: + # dateutil does not support xsd:date type in form YYYY-MM-DD+00:00 + s = s[0:10] + 'T00:00:00' + s[10:] + + return parse(s) + + def __getPathSuffix(self, type, number): + """ + Get path suffix + + :param type: search number type as Number.xxx value + :type type: Number + :param number: search number value + :type number: str + :return: path suffix + :rtype: string or False + """ + + if type == Number.NIP: + if not NIP.isValid(number): + self.__err__ = u'Numer NIP jest nieprawidłowy' + return False + + path = 'nip/' + NIP.normalize(number) + elif type == Number.REGON: + if not REGON.isValid(number): + self.__err__ = u'Numer REGON jest nieprawidłowy' + return False + + path = 'regon/' + REGON.normalize(number) + elif type == Number.KRS: + if not KRS.isValid(number): + self.__err__ = u'Numer KRS jest nieprawidłowy' + return False + + path = 'krs/' + KRS.normalize(number) + elif type == Number.EUVAT: + if not EUVAT.isValid(number): + self.__err__ = u'Numer EU VAT ID jest nieprawidłowy' + return False + + path = 'euvat/' + EUVAT.normalize(number) + else: + self.__err__ = u'Nieprawidłowy typ numeru' + return False + + return path diff --git a/nip24/number.py b/nip24/number.py new file mode 100644 index 0000000..8960f76 --- /dev/null +++ b/nip24/number.py @@ -0,0 +1,31 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + +class Number: + """ + Number types + """ + + NIP = 1 + REGON = 2 + KRS = 3 + EUVAT = 4 diff --git a/nip24/pkd.py b/nip24/pkd.py new file mode 100644 index 0000000..4bb0b44 --- /dev/null +++ b/nip24/pkd.py @@ -0,0 +1,32 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + + +class PKD: + """ + PKD data + """ + + def __init__(self): + self.code = None + self.description = None + self.primary = None diff --git a/nip24/regon.py b/nip24/regon.py new file mode 100644 index 0000000..9c5536c --- /dev/null +++ b/nip24/regon.py @@ -0,0 +1,130 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + +import re + +class REGON: + """ + REGON number validator + """ + + @staticmethod + def normalize(regon): + """ + Normalizes form of the REGON number + + :param regon: input string + :type regon: str + :returns: normalized string or False + :rtype: str or False + """ + + if not regon: + return False + + regon = regon.strip() + + if not re.match('[0-9]{9,14}', regon): + return False + + if len(regon) != 9 and len(regon) != 14: + return False + + return regon + + @staticmethod + def isValid(regon): + """ + Checks if specified REGON is valid + + :param regon: input string + :type regon: str + :returns: True if NIP is valid + :rtype: bool + """ + + regon = REGON.normalize(regon) + + if not regon: + return False + + if len(regon) == 9: + return REGON.__isValidR9(regon) + else: + if not REGON.__isValidR9(regon[0:9]): + return False + + return REGON.__isValidR14(regon) + + @staticmethod + def __isValidR9(regon): + """ + Check 9-digit REGON number + + :param regon: input string + :type regon: str + :returns: True if NIP is valid + :rtype: bool + """ + + w = [ 8, 9, 2, 3, 4, 5, 6, 7 ] + sum = 0 + + for i in range(0, len(w)): + sum += int(regon[i]) * w[i] + + sum %= 11 + + if (sum == 10): + sum = 0 + + if sum != int(regon[8]): + return False + + return True + + @staticmethod + def __isValidR14(regon): + """ + Check 14-digit REGON number + + :param regon: input string + :type regon: str + :returns: True if NIP is valid + :rtype: bool + """ + + w = [ 2, 4, 8, 5, 0, 9, 7, 3, 6, 1, 2, 4, 8 ] + sum = 0 + + for i in range(0, len(w)): + sum += int(regon[i]) * w[i] + + sum %= 11 + + if (sum == 10): + sum = 0 + + if sum != int(regon[13]): + return False + + return True diff --git a/nip24/vatstatus.py b/nip24/vatstatus.py new file mode 100644 index 0000000..ce43951 --- /dev/null +++ b/nip24/vatstatus.py @@ -0,0 +1,41 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + + +class VATStatus: + """ + VAT status info + """ + + NOT_REGISTERED = 1 + ACTIVE = 2 + EXEMPTED = 3 + + def __init__(self): + self.uid = None + self.nip = None + self.regon = None + self.name = None + self.status = None + self.result = None + self.date = None + self.source = None diff --git a/nip24/viesdata.py b/nip24/viesdata.py new file mode 100644 index 0000000..24f2053 --- /dev/null +++ b/nip24/viesdata.py @@ -0,0 +1,39 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + + +class VIESData: + """ + VIES data + """ + + def __init__(self): + self.uid = None + self.countryCode = None + self.vatNumber = None + self.valid = None + self.traderName = None + self.traderCompanyType = None + self.traderAddress = None + self.id = None + self.date = None + self.source = None diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..36ac74e --- /dev/null +++ b/setup.py @@ -0,0 +1,34 @@ +# +# -*- coding: utf-8 -*- +# +# Copyright 2015-2019 NETCAT (www.netcat.pl) +# +# Licensed 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. +# +# @author NETCAT +# @copyright 2015-2019 NETCAT (www.netcat.pl) +# @license http://www.apache.org/licenses/LICENSE-2.0 +# + +from setuptools import setup + +setup(name='nip24', + version='1.3.3', + description='NIP24 Service Client', + url='https://www.nip24.pl', + author='nip24.pl', + author_email='kontakt@nip24.pl', + license='nip24.pl', + packages=['nip24'], + zip_safe=False, + install_requires=['lxml', 'python-dateutil']) \ No newline at end of file