diff --git a/stdnum/pa/__init__.py b/stdnum/pa/__init__.py
new file mode 100644
index 00000000..787f7404
--- /dev/null
+++ b/stdnum/pa/__init__.py
@@ -0,0 +1,24 @@
+# __init__.py - collection of Panamanian numbers
+# coding: utf-8
+#
+# Copyright (C) 2023 Leandro Regueiro
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""Collection of Panamanian numbers."""
+
+# provide aliases
+from stdnum.pa import ruc as vat  # noqa: F401
diff --git a/stdnum/pa/ruc.py b/stdnum/pa/ruc.py
new file mode 100644
index 00000000..6e352ccd
--- /dev/null
+++ b/stdnum/pa/ruc.py
@@ -0,0 +1,165 @@
+# ruc.py - functions for handling Panama RUC numbers
+# coding: utf-8
+#
+# Copyright (C) 2023 Leandro Regueiro
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""RUC (Registro Único del Contribuyente, Panama tax number).
+
+The Registro Único del Contribuyente (RUC) is an identifier of legal entities
+for tax purposes.
+
+This number has different variants both for natural and legal persons, each
+with its own structure, but basically it consists on a number of digits and
+letters (only for natural persons) usually separated by hyphens (the number and
+position of these varies according to the variant), then followed by a check
+number (dígito verificador) consisting in up to two digits.
+
+More information:
+
+* https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Panama-TIN.pdf
+* https://studylib.es/doc/545131/algoritmo-para-el-calculo-del-digito-verificador-de-la-ru
+
+>>> validate('253-92-57027 DV 76')
+'00000002530092057027DV76'
+>>> validate('155587169-2-2014    D.V. 9')
+'01555871690002002014DV09'
+>>> validate('253-92-57027 DV 23')
+Traceback (most recent call last):
+    ...
+InvalidChecksum: ...
+>>> validate('12345678')
+Traceback (most recent call last):
+    ...
+InvalidLength: ...
+>>> format('253-92-57027 DV 76')
+'0000000253-0092-057027 DV76'
+"""  # noqa: E501
+
+from stdnum.exceptions import *
+from stdnum.util import clean, isdigits
+
+
+ARRVAL = {
+    '00': '00',
+    '10': '01',
+    '11': '02',
+    '12': '03',
+    '13': '04',
+    '14': '05',
+    '15': '06',
+    '16': '07',
+    '17': '08',
+    '18': '09',
+    '19': '01',
+    '20': '02',
+    '21': '03',
+    '22': '04',
+    '23': '07',
+    '24': '08',
+    '25': '09',
+    '26': '02',
+    '27': '03',
+    '28': '04',
+    '29': '05',
+    '30': '06',
+    '31': '07',
+    '32': '08',
+    '33': '09',
+    '34': '01',
+    '35': '02',
+    '36': '03',
+    '37': '04',
+    '38': '05',
+    '39': '06',
+    '40': '07',
+    '41': '08',
+    '42': '09',
+    '43': '01',
+    '44': '02',
+    '45': '03',
+    '46': '04',
+    '47': '05',
+    '48': '06',
+    '49': '07',
+}
+
+
+def compact(number):
+    """Convert the number to the minimal representation.
+
+    This strips the number of any valid separators and removes surrounding
+    whitespace.
+    """
+    parts = clean(number, ' .').strip().upper().split('-')
+
+    # We can currently only compact legal person's RUC numbers with check digit.
+    if len(parts) != 3 or len(parts[0]) in (1, 2) or 'DV' not in parts[2]:
+        return ''
+
+    parts[2], dv = parts[2].split('DV')
+
+    return ''.join([parts[0].zfill(10), parts[1].zfill(4),
+                    parts[2].strip().zfill(6), 'DV', dv.strip().zfill(2)])
+
+
+def calc_check_digit(number, is_old_legal_ruc):
+    """Calculate the check digit."""
+    if is_old_legal_ruc:
+        weights = list(range(2, 12)) + list(range(11, len(number) + 1))
+    else:
+        weights = list(range(2, len(number) + 2))
+    total = sum(int(n) * w for w, n in zip(weights, reversed(number)))
+    r = total % 11
+    return str(11 - r) if r > 1 else '0'
+
+
+def validate(number):
+    """Check if the number is a valid Panama RUC number.
+
+    This checks the length, formatting and check digits.
+    """
+    number = compact(number)
+    if len(number) != 24:
+        raise InvalidLength()
+    if not isdigits(number[:-4]) or not isdigits(number[-2:]):
+        raise InvalidComponent()
+    is_old_legal_ruc = number[3:6] in ('000', '001', '002', '003', '004')
+    if is_old_legal_ruc and number[5:7] in ARRVAL:
+        number = number[:5] + ARRVAL[number[5:7]] + number[7:]
+    dv1 = calc_check_digit(number[:-4], is_old_legal_ruc)
+    if number[-2] != dv1:
+        raise InvalidChecksum()
+    dv2 = calc_check_digit(number[:-4] + dv1, is_old_legal_ruc)
+    if number[-1] != dv2:
+        raise InvalidChecksum()
+    return number
+
+
+def is_valid(number):
+    """Check if the number is a valid Panama RUC number."""
+    try:
+        return bool(validate(number))
+    except ValidationError:
+        return False
+
+
+def format(number):
+    """Reformat the number to the standard presentation format."""
+    number = compact(number)
+    return ''.join([number[:10], '-', number[10:14], '-', number[14:-4], ' ',
+                    number[-4:]])
diff --git a/tests/test_pa_ruc.doctest b/tests/test_pa_ruc.doctest
new file mode 100644
index 00000000..635b90ed
--- /dev/null
+++ b/tests/test_pa_ruc.doctest
@@ -0,0 +1,256 @@
+test_pa_ruc.doctest - more detailed doctests for stdnum.pa.ruc module
+
+Copyright (C) 2022 Leandro Regueiro
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA
+
+
+This file contains more detailed doctests for the stdnum.pa.ruc module. It
+tries to test more corner cases and detailed functionality that is not really
+useful as module documentation.
+
+>>> from stdnum.pa import ruc
+
+
+Tests for some corner cases.
+
+>>> ruc.validate('253-92-57027 DV 76')
+'00000002530092057027DV76'
+>>> ruc.validate('155587169-2-2014    D.V. 9')
+'01555871690002002014DV09'
+>>> ruc.validate('32812-2-249262      D.V. 63')
+'00000088120002249262DV63'
+>>> ruc.validate('155625946-2-2016 DV00')
+'01556259460002002016DV00'
+>>> ruc.validate('12345')
+Traceback (most recent call last):
+    ...
+InvalidLength: ...
+>>> ruc.validate('253-92-57027')
+Traceback (most recent call last):
+    ...
+InvalidLength: ...
+>>> ruc.validate('253-92-57027 DV')
+Traceback (most recent call last):
+    ...
+InvalidChecksum: ...
+>>> ruc.validate('XXX-92-57027 DV 76')
+Traceback (most recent call last):
+    ...
+InvalidComponent: ...
+>>> ruc.validate('253-92-57027 DV XX')
+Traceback (most recent call last):
+    ...
+InvalidComponent: ...
+>>> ruc.validate('253-92-57027 DV 26')
+Traceback (most recent call last):
+    ...
+InvalidChecksum: ...
+>>> ruc.validate('253-92-57027 DV 73')
+Traceback (most recent call last):
+    ...
+InvalidChecksum: ...
+>>> ruc.format('253-92-57027 DV 76')
+'0000000253-0092-057027 DV76'
+>>> ruc.format('155587169-2-2014    D.V. 9')
+'0155587169-0002-002014 DV09'
+
+
+These have been found online and should all be valid numbers.
+
+>>> numbers = '''
+...
+... 130-377-34706 DV 2
+... 58223-2-855 DV 04
+... 356-146-78600 DV 90
+... 1870951-1-1751 DV 18
+... 30746-002-240130 DV 72
+... 2117539-1-759645 DV 39
+... 46004-187-302083 DV 96
+... 994855-1-534959 DV 48
+... 410-0018-001604 DV 26
+... 1002259-1-536336 DV 18
+... 247342-1-402582 DV 38
+... 1644095-1-674293 DV 97
+... 85253-1-376213 DV 18
+... 2110333-1-758424 DV 92
+... 36101-76-262338 DV 11
+... 2251567-0001-781630 DV 00
+... 15419-167-148643 DV 42
+... 702-437-15272 DV 87
+... 2196897-1-37015 DV 80
+... 55462-2-333607 DV 81
+... 16292-152-155203 DV 65
+... 1384907-1-624944 DV 04
+... 2612285-1-835546 DV 17
+... 2456294-1-812932 DV 90
+... 4265-60-58061 DV 00
+... 1897412-1-722027 DV 1
+... 280-134-61098 DV 2
+... 15430-249-148718 DV 09
+... 2492633-1-817803 DV 36
+... 2092063-1-755310 DV 00
+... 2092695-1-755448 DV 00
+... 1407887-1-30747 DV 18
+... 1647770-1-675008 DV 52
+... 2506296-1-819700 DV 19
+... 3724-0103-053659 DV 43
+... 699126-1-468330 DV 17
+... 756-374-135990 DV 70
+... 1200810-1-582064 DV 42
+... 126634-1-382152 DV 92
+... 54920-21-332178 DV 07
+... 6763-10-76640 DV 08
+... 315710-1-412268 DV 59
+... 854254-1-506006 DV 45
+... 1410729-1-629832 DV 92
+... 757127-1-481594 DV 5
+... 1724590-1-691090 DV93
+... 1129184-1-566509 DV26
+... 43085-133-290460 DV20
+... 59838-19-345377 DV 5
+... 59838-2-345376 DV12
+... 1038743-1-544512 DV99
+... 395593-1-423610 DV 4
+... 1423328-1-632401 DV69
+... 1173387-1-576582 DV94
+... 1837876-1-1727 DV62
+... 48192-119-320004 DV42
+... 1620775-1-670190 DV40
+... 56014-2-334942 DV57
+... 48854-14-312652 DV53
+... 19573-102-178376 DV78
+... 2605013-1-834447 DV16
+... 860-116-100257 DV 6
+... 2486668-1-817006 DV77
+... 280-319-61818 DV53
+... 1026161-1-541697 DV41
+... 42860-39-289557 DV42
+... 673-563-133317 DV43
+... 1529223-1-653120 DV85
+... 649-529-117695 DV 1
+... 196-8-47222 DV21
+... 2978-2-46909 DV 9
+... 4360-163-58819 DV75
+... 256211-1-1035 DV89
+... 190229-1-393739 DV80
+... 2486790-1-817030 DV91
+... 9944-73-101955 DV 9
+... 37405-45-267330 DV75
+... 637-262-109021 DV 5
+... 472-520-105024 DV39
+... 358157-1-418546 DV 7
+... 2278610-1-2148 DV20
+... 186-505-46878 DV80
+... 410-468-90986 DV47
+... 210550-1-396820 DV58
+... 644-570-114659 DV12
+... 2451381-1-812308 DV80
+... 515924-1-437913 DV75
+... 2182158-1-770482 DV 9
+... 990445-1-534073 DV 0
+... 2636962-1-838889 DV45
+... 2327456-1-793996 DV96
+... 511078-1-437311 DV94
+... 2424000-1-808259 DV 0
+... 63942-78-356454 DV47
+... 2301555-1-789993 DV19
+... 396-569-88934 DV99
+... 30170-22-237573 DV19
+... 1623537-1-670664 DV72
+... 835607-1-502581 DV22
+... 155630598-2-2016 DV 30
+... 155628291-2-2016 DV32
+... 155650394-2-2017 DV14
+... 155587102-2-2014 DV75
+... 155639020-2-2016 DV49
+... 155589402-2-2014 DV16
+... 155589772-2-2014 DV 8
+... 155668678-2-2018 DV84
+... 155658360-2-2017 DV13
+... 155698228-2-2020 DV68
+... 155671731-2-2018 DV 5
+... 155615059-2-2015 DV62
+... 155660186-2-2018 DV 4
+... 155636329-2-2016 DV96
+... 155614464-2-2015 DV18
+... 155618602-2-2015 DV47
+... 155660844-2-2018    D.V.3
+... 155587169-2-2014    D.V. 9
+... 155660896-2-2018    D.V.6
+... 155604680-2-2015    D.V.0
+... 155593964-2-2015    D.V.09
+... 155599780-2-2015    D.V. 43
+... 155630598-2-2016 DV30
+... 297-154-64691 DV 1
+... 652-446-131292 DV 9
+... 50871-26-319483 DV54
+... 627-549-108161 DV 4
+... 2182171-1-770489 DV 7
+... 342-240-75526 DV50
+... 21094-39-189991 DV48
+... 276745-1-406809 DV 6
+... 39160-2-274372 DV03
+... 1486616-1-1504 DV39
+... 1647259-1-674887 DV 1
+... 12561-16-124551 DV78
+... 853111-1-505797 DV30
+... 1281233-1-600583 DV93
+... 203-438-49939 DV84
+... 38682-54-272473 DV 0
+... 1442235-1-636269 DV20
+... 2122574-1-1987 DV70
+... 1910501-1-724391 DV83
+... 1351606-1-617448 DV76
+... 963942-1-528502 DV19
+... 780179-1-487966 DV40
+... 1570395-1-660585    D.V. 97
+... 3152-26-11756       D.V.60
+... 2413309-1-806698    D.V.89
+... 260141-1-404438     D.V.33
+... 2596686-1-833270    D.V.74
+... 43059-0023-290396   D.V.70
+... 27957-10-229660     D.V.5
+... 42819-60-289401     D.V.81
+... 8075-10-85775       D.V.49
+... 35751-78-261168     D.V.84
+... 811457-1-498110     D.V.88
+... 34932-2-258267      D.V.17
+... 545-496-119585      D.V. 22
+... 128877-1-382443     D.V.62
+... 1610553-1-668171    D.V.22
+... 1066306-1-551009    D.V. 48
+... 356-19-77860        D.V. 12
+... 1848547-1-713252    D.V. 8
+... 521347-1-438253     D.V.70
+... 652-212-129962      D.V. 35
+... 12561-16-124551     D.V.78
+... 32812-2-249262      D.V. 63
+... 4360-163-58819      D.V.75
+... 417-139-92433       D.V. 72
+... 2064-63-38048       D.V. 0
+... 20435-57-185073     D.V. 60
+... 85253 -1-376213     D.V. 18
+... 49511-149-315269   D.V.75
+... 572-395-119290      D.V. 30
+... 105765-1-379278     D.V. 60
+... 822226-1-500338     D.V.30
+... 946321-1-524832     D.V. 04
+... 533-555-116945      D.V. 40
+...
+... '''
+>>> [x for x in numbers.splitlines() if x and not ruc.is_valid(x)]
+[]