exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit e4fe366c992f436482f5d349bd5a7d7c35df8f25
parent dafc861b1c4e9df5ae5a280aed1d8d0ccf962973
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  9 Nov 2025 13:39:29 +0100

starting with typst templating

Diffstat:
Acontrib/typst/VQF_902_1_customers.typ | 449+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontrib/typst/pointing_finger.svg | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontrib/typst/vss_vqf_verein.png | 0
3 files changed, 558 insertions(+), 0 deletions(-)

diff --git a/contrib/typst/VQF_902_1_customers.typ b/contrib/typst/VQF_902_1_customers.typ @@ -0,0 +1,449 @@ +// VQF 902.1 Identification Form Template +// Pass JSON data as content dictionary + +#let form(data) = { + set page( + paper: "a4", + margin: (left: 2cm, right: 2cm, top: 2cm, bottom: 2.5cm), + footer: context [ + #grid( + columns: (1fr, 1fr), + align: (left, right), + text(size: 8pt)[ + VQF doc. Nr. 902.1#linebreak() + Version of 1 September 2021 + ], + text(size: 8pt)[ + Page #here().page() of #counter(page).final().first() + ] + ) + ] + ) + + set text(font: "Liberation Sans", size: 10pt) + set par(justify: false, leading: 0.65em) + + // Helper function to get value or empty string + let get(key, default: "") = { + data.at(key, default: default) + } + + // Helper function for checkbox + let checkbox(checked) = { + box( + width: 3mm, + height: 3mm, + stroke: 0.5pt + black, + inset: 0.3mm, + if checked == true or checked == "true" { + place(center + horizon, text(size: 8pt, sym.checkmark)) + } + ) + } + + // Header + align(center, text(size: 11pt, weight: "bold")[CONFIDENTIAL]) + + v(0.5em) + + + grid( + columns: (50%, 50%), + gutter: 1em, + image("vss_vqf_verein.png", width: 80%), + + align(right)[ + #table( + columns: (1fr, 1fr), + stroke: 0.5pt + black, + inset: 5pt, + align: (left, left), + [VQF member no.], [AMLA File No.], + [#get("VQF_MEMBER_NUMBER")], [#get("FILE_NUMBER")] + ) + ] + ) + + v(1em) + + align(left, text(size: 14pt, weight: "bold")[Identification Form]) + + v(-1em) + line(length:100%) + + grid( + columns: (auto, 1fr), + gutter: 0.5em, + align: (left, left), + image("pointing_finger.svg", height: 2em), + text(size: 9pt)[ + The customer has to be identified on entering into a permanent business relationship or on concluding a cash transaction, which meets the according threshold. + ] + ) + + v(1em) + + text(weight: "bold")[This form was completed by:] + + v(0.3em) + + table( + columns: (1fr, 1fr), + stroke: 0.5pt + black, + inset: 5pt, + align: (left, left), + [Full name], [#get("FILED_BY_NAME")], + [Date], [#get("FILING_DATE")], + ) + + v(1.5em) + + // Section 1: Information on customer + text(size: 11pt, weight: "bold")[1. Information on customer#footnote[The customer is the person with whom the member concludes the contract with regard to the financial service provided (civil law). Does the member act as director of a domiciliary company, this domiciliary company is the customer.]] + + v(0.5em) + + let is_natural = get("CUSTOMER_TYPE") == "NATURAL_PERSON" + let is_legal = get("CUSTOMER_TYPE") == "LEGAL_ENTITY" + + // Section 1a: Natural Person + grid( + columns: (auto, 1fr), + gutter: 0.5em, + checkbox(is_natural), + [a) The customer is a #underline([natural]) person:] + ) + + block(breakable: false)[ + #table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Full name\*], if is_natural { get("FULL_NAME") } else { "" }, + [Residential address\*], if is_natural { get("DOMICILE_ADDRESS") } else { "" }, + [Telephone], if is_natural { get("CONTACT_PHONE") } else { "" }, + [E-mail], if is_natural { get("CONTACT_EMAIL") } else { "" }, + [Date of birth\*], if is_natural { get("DATE_OF_BIRTH") } else { "" }, + [Nationality\*], if is_natural { get("NATIONALITY") } else { "" }, + [Identification document\*], [#checkbox(is_natural) *Copy enclosed in appendix*], + ) + #v(-1em) + #text(size: 8pt)[*\* mandatory*] + ] + v(1em) + + // Sole proprietorship section + text(weight: "bold")[For sole proprietorship (supplement to above):] + + let is_sole = is_natural and (get("CUSTOMER_IS_SOLE_PROPRIETOR") == true or get("CUSTOMER_IS_SOLE_PROPRIETOR") == "true") + + table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Company name], if is_sole { get("COMPANY_NAME") } else { "" }, + [Registered office], if is_sole { get("REGISTERED_OFFICE_ADDRESS") } else { "" }, + [Company identification document], [#checkbox(is_sole) *Copy enclosed in appendix*], + ) + + v(1em) + + // Section 1b: Legal Entity + grid( + columns: (auto, 1fr), + gutter: 0.5em, + checkbox(is_legal), + [*b) The customer is a legal entity:*] + ) + + block(breakable: false)[ + #table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Company name\*], if is_legal { get("COMPANY_NAME") } else { "" }, + [Domicile\*], if is_legal { get("DOMICILE_ADDRESS") } else { "" }, + [Contact person], if is_legal { get("CONTACT_PERSON_NAME") } else { "" }, + [Telephone], if is_legal { get("CONTACT_PHONE") } else { "" }, + [E-mail], if is_legal { get("CONTACT_EMAIL") } else { "" }, + [Identification document\*\ (not older than 12 months)], [#checkbox(is_legal) *Copy enclosed in appendix*], + ) + + #v(-1em) + #text(size: 8pt)[*\* mandatory*] + ] + v(0.5em) + + // Section 2: Natural persons establishing business relationship + text(size: 11pt, weight: "bold")[2. Information on the natural persons who establish the business relationship for legal entities and partnerships] + + v(0.5em) + + grid( + columns: (auto, 1fr), + gutter: 0.5em, + align: (left, left), + image("pointing_finger.svg", height: 2em), + text(size: 9pt)[ + For legal entities and partnerships the identity of the natural persons who establish the business relationship must be verified. + ] + ) + + v(1em) + + let establishers = get("ESTABLISHER_LIST", default: ()) + let has_establishers = is_legal and type(establishers) == array and establishers.len() > 0 + + // Show at least 1 table for establishers to match VQF form + let num_cols = if has_establishers { calc.max(1, establishers.len()) } else { 1 } + + for col_idx in range(num_cols) { + if col_idx > 0 { + h(2em) + } + } + + // Create a table for each establisher + range(num_cols).map(col_idx => { + let establisher = if has_establishers and col_idx < establishers.len() { + establishers.at(col_idx) + } else { + (:) + } + + let get_est(key) = { + if establisher != (:) { + establisher.at(key, default: "") + } else { + "" + } + } + + block(breakable: false)[ + #table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Full name\*], + [#get_est("FULL_NAME")], + [Residential address\*], + [#get_est("DOMICILE_ADDRESS")], + [Date of birth\*], + [#get_est("DATE_OF_BIRTH")], + [Nationality\*], + [#get_est("NATIONALITY")], + [Type of authorisation\ (signatory of representation)\*], + [#get_est("SIGNING_AUTHORITY_TYPE")], + [Identification document\*], + [#checkbox(establisher != (:)) *Copy enclosed in appendix*], + [*Power of attorney arrangements\**], + [#let evidence = get_est("SIGNING_AUTHORITY_EVIDENCE") + #grid( + columns: (auto, 1fr), + gutter: 0.5em, + row-gutter: 0.3em, + checkbox(evidence == "CR"), + [CR extract], + checkbox(evidence == "MANDATE"), + [Mandate], + checkbox(evidence == "OTHER"), + [Other: #get_est("SIGNING_AUTHORITY_EVIDENCE_OTHER")], + ) + ] + ) + #v(-1em) + #text(size: 8pt)[*\* mandatory*] + ] + }).join() + + + + v(2em) + + // Section 3: Acceptance of business relationship + text(size: 11pt, weight: "bold")[3. Acceptance of business relationship] + + v(0.5em) + + let acceptance = get("ACCEPTANCE_METHOD") + let lang = get("CORRESPONDENCE_LANGUAGE") + table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Date (conclusion of contract)], get("ACCEPTANCE_DATE"), + [Accepted by], + [#grid( + columns: (auto, 1fr), + gutter: 0.5em, + checkbox(acceptance == "FACE_TO_FACE"), + [Face-to-face meeting with customer], + checkbox(acceptance == "WAY_OF_CORRESPONDENCE"), + [Way of correspondence: + \ #v(-0.7em) #grid( + columns: (0.2em, auto), + gutter: 0.5em, + row-gutter: 0.3em, + [], + [#checkbox(acceptance == "AUTHENTICATED_COPY") + authenticated copy of identification document obtained], + [], + [#checkbox(acceptance == "RESIDENTIAL_ADDRESS_VALIDATED") + residential address validated] + ) + ] + )], + [Type of correspondence service], + [#grid( + columns: (auto), + gutter: 0.2em, + [#checkbox(true) to the customer], + [#checkbox(false) hold at bank], + [#checkbox(false) to the member], + [#checkbox(false) + to a third party (full name and address):], + )], + [Language], + [#grid( + columns: (auto), + gutter: 0.2em, + [#checkbox(lang == "de") German], + [#checkbox(lang == "en") English], + [#checkbox(lang == "fr") French], + [#checkbox(lang != "fr" and lang != "de" and lang != "en") + Other: #lang], + )], + [Further information], + [#get("ACCEPTANCE_FURTHER_INFO")] + ) + + // Section 4: Beneficial owner + text(size: 11pt, weight: "bold")[4. Information on the beneficial owner of the assets and/or controlling person] + + v(0.5em) + + let customer_type_vqf = get("CUSTOMER_TYPE_VQF") + grid( + columns: (35%,65%), + stroke: 0.5pt + black, + inset: 5pt, + [Establishment of the beneficial owner of the assets and/or controlling person.], + [The customer is: + #grid( + columns: (1.1em, auto), + inset: 5pt, +// gutter: 0.5em, +// row-gutter: 0.5em, + [#checkbox(customer_type_vqf == "NATURAL_PERSON")], + [a natural person and there are no doubts that this person is the sole beneficial owner of the assets], + [#checkbox(customer_type_vqf == "OPERATIONAL")], + [an operational legal entity or partnership #h(1fr) + \ $=>$ VQF doc. No. 902.11 (K)], + [#checkbox(customer_type_vqf == "FOUNDATION")], + [a foundation (or a similar construct; incl. underlying companies) + \ $=>$ VQF doc. No. 902.12 (S)], + [#checkbox(customer_type_vqf == "TRUST")], + [a trust (incl. underlying companies) + \ $=>$ VQF doc. No. 902.13 (T)], + [#checkbox(customer_type_vqf == "LIFE_INSURANCE")], + [a life insurance policy with separately managed accounts/ securities accounts (so-called insurance wrappers) + \ $=>$ VQF doc. No. 902.15 (I)], + [#checkbox(customer_type_vqf == "OTHER")], + [all other cases + $=>$ VQF doc. No. 902.9 (A)], + )] + ) + + v(2em) + + // Section 5: Embargo/terrorism evaluation + text(size: 11pt, weight: "bold")[5. Evaluation with regard to embargo procedures/terrorism lists on establishing the business relationship] + + v(0.5em) + + table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Verification whether the customer, beneficial owners of the assets, controlling persons, authorised representatives or other involved persons are listed on an embargo-/terrorism list (date of verification/result)#footnote[The evaluation must be made at the beginning of the business relationship and has to be repeated in the case of permanent business relationship every time the according lists are updated.]], + [Date: #get("EMBARGO_TERRORISM_CHECK_DATE") + \ Result: #get("EMBARGO_TERRORISM_CHECK_RESULT")], + ) + + v(2em) + + // Section 6: Cash transactions + text(size: 11pt, weight: "bold")[6. In the case of cash transactions/occasional customers: Information on type and purpose of business relationship] + + v(0.5em) + + grid( + columns: (auto, 1fr), + gutter: 0.5em, + align: (left, left), + image("pointing_finger.svg", height: 2em), + text(size: 9pt)[ + These details are only necessary for occasional customers, i.e. money exchange, money and asset transfer or other cash transactions provided that #underline([no]) customer profile (VQF doc. No. 902.5) is created + ] + ) + + v(1em) + + table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Type of business relationship], + [#grid( + columns: (auto), + gutter: 0.2em, + [#checkbox(false) Money exchange], + [#checkbox(false) Money and asset transfer], + [#checkbox(false) Other cash transaction, specify?], + )], + [Purpose of the business relationship\ (purpose of service requested)], [], + ) + + // Section 7: Enclosures + text(size: 11pt, weight: "bold")[7. Enclosures] + + v(0.5em) + + grid( + columns: (auto, 1fr), + stroke: 0.5pt + black, + inset: 5pt, + checkbox("" != get("CUSTOMER_ID_AMLA_FILE_REFERENCE_NO")), [Customer identification documents (or: reference#footnote[If the identification document is lists kept in another AMLA-File (in the case of Art. 15 para. 3 SRO Regulations) a reference to the according AMLA-File is sufficient.] to AMLA File No.: #underline([#get("CUSTOMER_ID_AMLA_FILE_REFERENCE_NO")]))], + checkbox("" != get("ESTABLISHER_ID_AMLA_FILE_REFERENCE_NO")), [Identification document of persons establishing the business relationship (or: reference to AMLA File No.: #underline([#get("ESTABLISHER_ID_AMLA_FILE_REFERENCE_NO")]))], + checkbox(get("HAVE_VQF_902_9") or get("HAVE_VQF_902_11") or get("HAVE_VQF_902_12") or get("HAVE_VQF_902_13") or get("HAVE_VQF_902_15")), [Establishing of the beneficial owner of the assets/controlling person (VQF Doc No. 902.15, 902.9, 902.11, 902.12 or 902.13)], + checkbox(get("HAVE_VQF_902_5")), [Customer profile (VQF doc. No. 902.5; only in the case of permanent business relationship and regular customers)], + checkbox(get("HAVE_VQF_902_4")), [Risk profile (VQF doc. No. 902.4)], + ) + + v(1em) + + text(size: 9pt, style: "italic")[⚠ *This form has to be updated when changes occur.*] + +} + +// Example usage: +#form(( + "VQF_MEMBER_NUMBER": "12345", + "FILE_NUMBER": "42", + "FILED_BY_NAME": "Manuela", + "AML_STAFF_NAME": "Manuela", + "CUSTOMER_ID_AMLA_FILE_REFERENCE_NO": "4242", // optional + "ESTABLISHER_ID_AMLA_FILE_REFERENCE_NO": "4243", // optional + "FILING_DATE": "1.4.2000", + "CUSTOMER_TYPE": "NATURAL_PERSON", + "FULL_NAME": "John Doe", + "DOMICILE_ADDRESS": "123 Main St, 8001 Zurich", + "HAVE_VQF_902_9": false, + "HAVE_VQF_902_11": false, + "HAVE_VQF_902_12": false, + "HAVE_VQF_902_13": false, + "HAVE_VQF_902_15": true, + "HAVE_VQF_902_4": true, + "HAVE_VQF_902_5": true, + // ... other fields + )) diff --git a/contrib/typst/pointing_finger.svg b/contrib/typst/pointing_finger.svg @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + id="svg2" + sodipodi:docname="pointerfinger.svg" + viewBox="0 0 300 200" + version="1.1" + inkscape:version="1.4 (e7c3feb100, 2024-10-09)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <defs + id="defs1" /> + <title + id="title4069">Pointer</title> + <sodipodi:namedview + id="base" + bordercolor="#666666" + inkscape:pageshadow="2" + inkscape:window-y="0" + pagecolor="#ffffff" + inkscape:window-height="1038" + inkscape:window-maximized="1" + inkscape:zoom="0.603125" + inkscape:window-x="3840" + showgrid="false" + borderopacity="1.0" + inkscape:current-layer="layer1" + inkscape:cx="-349.84456" + inkscape:cy="165.80311" + inkscape:window-width="1920" + inkscape:pageopacity="0.0" + inkscape:document-units="px" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> + <g + id="layer1" + inkscape:label="Ebene 1" + inkscape:groupmode="layer" + transform="translate(0 -852.36)"> + <g + id="g4055" + transform="matrix(-0.37425,0,0,-0.37734,294.99415,1056.3)"> + <path + id="path4057" + style="fill:currentColor" + inkscape:connector-curvature="0" + d="m 777,314 c 2,-15 2,-30 2,-46 0,-81 -19,-169 -27,-244 -32,15 -81,55 -124,86 -16,12 -22,14 -29,14 -1,0 -9,-1 -19,-1 h -5 c -14,-3 -24,-5 -33,-5 -13,0 -23,3 -35,8 -9,-4 -17,-5 -25,-5 -7,0 -14,1 -22,1 -28,0 -48,1 -86,11 -4,2 -15,7 -20,10 -21,16 -25,16 -32,25 -8,10 -11,28 -12,36 -17,16 -28,28 -33,42 -2,5 -2,10 -2,15 0,11 3,22 7,31 -23,17 -35,35 -35,55 0,8 2,17 6,26 h 4 c -5,12 -2,17 5,27 h -15 c -55,0 -100,9 -159,9 h -8 l -33,4 h -4 c -23,0 -43,22 -43,44 0,8 3,17 11,24 9,8 30,24 49,25 65,4 134,6 199,6 75,0 145,-3 195,-8 78,-18 150,-80 205,-118 37,-26 82,-41 118,-72 z M 466,158 c 3,-10 11,-9 6,-19 6,-1 11,-1 15,-1 6,0 11,1 20,4 8,-2 13,-6 21,-7 h 3 c 9,0 24,3 37,5 12,1 22,4 31,3 25,-4 113,-75 132,-88 8,73 28,142 28,214 0,14 0,27 -2,41 -36,27 -72,35 -110,62 -56,38 -107,90 -181,112 l -14,3 c -39,4 -87,6 -140,6 -77,0 -164,-3 -250,-7 -17,-1 -29,-7 -35,-13 -6,-6 -4,-17 -3,-24 3,-13 12,-24 53,-24 h 10 c 44,0 117,-9 157,-9 h 29 l 20,5 c 7,2 4,4 38,7 10,10 30,7 45,10 29,5 24,6 58,6 6,4 9,4 13,4 h 2 c 4,0 10,0 21,2 33,4 22,-2 4,-6 -28,-5 -27,-3 -38,-9 -33,0 -42,-5 -72,-9 -8,-2 -13,-1 -20,-3 -5,-2 -14,-3 -33,-11 h 16 c 28,0 53,-4 53,-18 0,-1 0,-2 -1,-4 -3,-13 -43,-30 -61,-32 -16,13 -34,34 -36,50 -6,-2 -10,-16 -10,-16 -8,-12 -1,-18 4,-25 15,-17 37,-21 65,-21 10,0 20,0 31,1 13,1 53,9 86,9 14,0 26,-2 34,-6 34,-15 90,-72 129,-88 -44,2 -95,45 -121,65 -7,6 -22,14 -28,14 10,-7 11,-18 1,-29 6,1 13,2 20,2 17,0 34,-5 35,-21 v -1 c 0,-30 -38,-51 -52,-57 17,-3 36,-6 41,-18 2,-2 4,-7 4,-12 0,-16 -13,-42 -55,-47 z M 322,406 c -8,-1 -13,0 -23,-2 2,-13 8,-15 23,-28 15,2 45,8 45,18 0,2 -1,4 -3,6 -5,4 -26,6 -42,6 z m 124,-67 c -32,-4 -61,-9 -94,-12 h -1 c -31,3 -69,12 -81,36 -4,-9 -6,-17 -6,-24 0,-15 10,-27 33,-41 25,-2 50,-4 77,-4 34,0 61,5 73,12 6,4 12,14 12,22 0,6 -3,11 -12,11 z m 31,-84 c 20,11 29,22 29,31 0,8 -8,13 -21,13 -7,0 -16,-2 -26,-6 -34,-15 -74,-16 -86,-16 -26,0 -46,3 -74,7 -4,-10 -12,-17 -7,-30 4,-12 15,-17 31,-33 6,-2 13,-3 20,-3 8,0 17,1 25,2 23,3 87,14 109,35 z m -15,-29 -65,-19 c -17,-4 -33,-7 -51,-7 -5,0 -11,0 -17,1 1,-5 2,-10 7,-17 7,-9 21,-25 43,-33 31,-8 50,-11 71,-11 h 9 c 6,7 -2,11 -4,17 -7,-2 -21,-1 -21,-1 -12,0 -28,0 -31,1 24,14 70,7 86,33 3,7 4,13 4,18 0,15 -13,18 -25,18 z" /> + </g> + </g> + <metadata + id="metadata1"> + <rdf:RDF> + <cc:Work> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <cc:license + rdf:resource="http://creativecommons.org/licenses/publicdomain/" /> + <dc:publisher> + <cc:Agent + rdf:about="http://openclipart.org/"> + <dc:title>Openclipart</dc:title> + </cc:Agent> + </dc:publisher> + <dc:title>pointer</dc:title> + <dc:date>2012-05-06T09:03:43</dc:date> + <dc:description>A pointing finger</dc:description> + <dc:source>https://openclipart.org/detail/169880/pointer-by-frankes</dc:source> + <dc:creator> + <cc:Agent> + <dc:title>frankes</dc:title> + </cc:Agent> + </dc:creator> + <dc:subject> + <rdf:Bag> + <rdf:li>Finger</rdf:li> + <rdf:li>Hand</rdf:li> + <rdf:li>Zeichen</rdf:li> + <rdf:li>Zeigefinger</rdf:li> + <rdf:li>auf</rdf:li> + <rdf:li>finger</rdf:li> + <rdf:li>hand</rdf:li> + <rdf:li>lineart</rdf:li> + <rdf:li>point</rdf:li> + <rdf:li>pointer</rdf:li> + <rdf:li>sign</rdf:li> + <rdf:li>zeigen</rdf:li> + </rdf:Bag> + </dc:subject> + </cc:Work> + <cc:License + rdf:about="http://creativecommons.org/licenses/publicdomain/"> + <cc:permits + rdf:resource="http://creativecommons.org/ns#Reproduction" /> + <cc:permits + rdf:resource="http://creativecommons.org/ns#Distribution" /> + <cc:permits + rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> + </cc:License> + </rdf:RDF> + </metadata> +</svg> diff --git a/contrib/typst/vss_vqf_verein.png b/contrib/typst/vss_vqf_verein.png Binary files differ.