Skip to content

Commit

Permalink
Pay: Adds Datatrans payment provider
Browse files Browse the repository at this point in the history
TYPE: Feature
LINK: OGC-2007
  • Loading branch information
Daverball authored Jan 30, 2025
1 parent 41b2f79 commit cdf4acb
Show file tree
Hide file tree
Showing 32 changed files with 1,263 additions and 158 deletions.
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ install_requires =
qrbill
qrcode
py
pycountry
pydantic
pydantic-extra-types
pyotp
pysaml2
python-dateutil
Expand Down
2 changes: 1 addition & 1 deletion src/onegov/activity/models/invoice_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,5 @@ def amount(cls) -> ColumnElement[Decimal | None]:

@validates('source')
def validate_source(self, key: str, value: str | None) -> str | None:
assert value in (None, 'xml', 'stripe_connect')
assert value in (None, 'xml', 'datatrans', 'stripe_connect')
return value
25 changes: 25 additions & 0 deletions src/onegov/core/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,18 @@ def theme_link(self) -> str:

return self.link(self.app.modules.theme.ThemeFile(filename))

def get_session_nonce(self) -> str:
""" Returns a nonce that can be passed as a POST parameter
to restore a session in a context where the session cookie
is unavailable, due to `SameSite=Lax`.
"""
nonce = random_token()
self.app.cache.set(
nonce,
self.app.sign(self.browser_session._token),
)
return nonce

@cached_property
def browser_session(self) -> BrowserSession:
""" Returns a browser_session bound to the request. Works via cookies,
Expand Down Expand Up @@ -377,6 +389,19 @@ def browser_session(self) -> BrowserSession:
# infinite CSRF errors
del self.cookies['session_id']
session_id = random_token()

elif isinstance(nonce := self.POST.get('session_nonce'), str):
# restore the session in a non SameSite context
signed_session_id = self.app.cache.get(nonce)
if signed_session_id:
# make sure this nonce can't be reused
self.app.cache.delete(nonce)
session_id = self.app.unsign(signed_session_id)
else:
session_id = None

if session_id is None:
session_id = random_token()
else:
session_id = random_token()

Expand Down
6 changes: 3 additions & 3 deletions src/onegov/feriennet/views/invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ def payment_button(title: str, price: Price | None) -> str | None:
title=title,
price=price,
email=user.username,
locale=request.locale
complete_url=request.link(self),
request=request,
)

def user_select_link(user: User) -> str:
Expand Down Expand Up @@ -197,8 +198,7 @@ def handle_payment(

provider = request.app.default_payment_provider
assert provider is not None
token = request.params.get('payment_token')
assert token is None or isinstance(token, str)
token = provider.get_token(request)
# FIXME: Can period actually be omitted, i.e. are there
# cases where we only get a single Invoice when we
# omit the period?
Expand Down
33 changes: 28 additions & 5 deletions src/onegov/feriennet/views/payment_overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from onegov.activity import InvoiceCollection, InvoiceItem
from onegov.core.security import Private
from onegov.feriennet import FeriennetApp
from onegov.pay import Payment, PaymentProviderCollection
from onegov.pay import PaymentProviderCollection
from onegov.pay.models.payment_providers.datatrans import DatatransPayment
from onegov.pay.models.payment_providers.stripe import StripePayment
from onegov.org.views.payment_provider import sync_payments
from onegov.org.views.payment import refund
from onegov.org.views.payment import refund_datatrans, refund_stripe


from typing import TYPE_CHECKING
Expand Down Expand Up @@ -40,13 +42,34 @@ def sync_payments_and_reconcile(


@FeriennetApp.view(
model=Payment,
model=StripePayment,
name='refund',
request_method='POST',
permission=Private)
def refund_and_reconcile(self: Payment, request: FeriennetRequest) -> None:
result = refund(self, request)
def refund_and_reconcile_stripe(
self: StripePayment,
request: FeriennetRequest
) -> None:

result = refund_stripe(self, request)
for link in self.links:
if isinstance(link, InvoiceItem):
link.paid = self.state == 'paid'

return result


@FeriennetApp.view(
model=DatatransPayment,
name='refund',
request_method='POST',
permission=Private)
def refund_and_reconcile_datatrans(
self: DatatransPayment,
request: FeriennetRequest
) -> None:

result = refund_datatrans(self, request)
for link in self.links:
if isinstance(link, InvoiceItem):
link.paid = self.state == 'paid'
Expand Down
11 changes: 6 additions & 5 deletions src/onegov/org/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,8 @@ def checkout_button(
title: str,
price: Price | None,
email: str,
locale: str
complete_url: str,
request: OrgRequest
) -> str | None:
provider = self.default_payment_provider

Expand All @@ -377,10 +378,8 @@ def checkout_button(
email=email,
name=self.org.name,
description=title,
locale=locale.split('_')[0],
# FIXME: This seems Stripe specific, so it should probably be
# built into that payment provider
allowRememberMe='false',
complete_url=complete_url,
request=request,
**extra
)

Expand Down Expand Up @@ -485,6 +484,8 @@ def org_content_security_policy() -> ContentSecurityPolicy:
policy.child_src.add('https://*.vimeo.com')
policy.child_src.add('https://*.infomaniak.com')
policy.child_src.add('https://checkout.stripe.com')
policy.child_src.add('https://pay.datatrans.com')
policy.child_src.add('https://pay.sandbox.datatrans.com')

policy.connect_src.add(SELF)
policy.connect_src.add('https://checkout.stripe.com')
Expand Down
8 changes: 8 additions & 0 deletions src/onegov/org/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -3120,6 +3120,14 @@ def editbar_links(self) -> list[Link | LinkGroup] | None:
LinkGroup(
title=_('Add'),
links=(
Link(
text=_('Datatrans'),
url=self.request.class_link(
PaymentProviderCollection,
name='new-datatrans'
),
attrs={'class': 'new-datatrans'}
),
Link(
text=_('Stripe Connect'),
url=self.request.class_link(
Expand Down
102 changes: 70 additions & 32 deletions src/onegov/org/locale/de_CH/LC_MESSAGES/onegov.org.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2025-01-28 08:31+0100\n"
"POT-Creation-Date: 2025-01-30 15:51+0100\n"
"PO-Revision-Date: 2022-03-15 10:21+0100\n"
"Last-Translator: Marc Sommerhalder <[email protected]>\n"
"Language-Team: German\n"
Expand Down Expand Up @@ -1922,6 +1922,16 @@ msgstr ""
" - Unterthema 3.1\n"
"```"

msgid "Notify on newsletter unsubscription"
msgstr "Benachrichtigung bei Abmeldung vom Newsletter"

msgid ""
"Send an email notification to the following users when a recipient "
"unsubscribes from the newsletter"
msgstr ""
"Sende eine E-Mail-Benachrichtigung an die folgenden Benutzer, wenn sich ein "
"Empfänger vom Newsletter abmeldet"

msgid "Invalid YAML format. Please refer to the example."
msgstr "Ungültiges YAML Format. Bitte beachten Sie das Beispiel."

Expand Down Expand Up @@ -2600,6 +2610,9 @@ msgstr "Exporte"
msgid "Payment Providers"
msgstr "Zahlungsanbieter"

msgid "Datatrans"
msgstr "Datatrans"

msgid "Synchronise"
msgstr "Synchronisieren"

Expand Down Expand Up @@ -2806,9 +2819,6 @@ msgstr ""
msgid "Delete content"
msgstr "Inhalt löschen"

msgid "Photo album. Will be shown at the end of content."
msgstr "Fotoalbum. Wird am Ende des Inhalts angezeigt."

msgid "Photo album"
msgstr "Fotoalbum"

Expand Down Expand Up @@ -2987,8 +2997,8 @@ msgstr "Die Absage von ${title} kann nicht rückgängig gemacht werden."
msgid "Reject reservation"
msgstr "Reservation absagen"

#. Used in sentence: "${event} published."
#.
#. Used in sentence: "${event} published."
msgid "Event"
msgstr "Veranstaltung"

Expand Down Expand Up @@ -3855,20 +3865,6 @@ msgstr "Heute keine Reservationen."
msgid "Have a great day!"
msgstr "Wir wünschen Ihnen einen schönen Tag!"

msgid "Notify on newsletter unsubscription"
msgstr "Benachrichtigung bei Abmeldung vom Newsletter"

msgid "Send an email notification to the following users when a recipient "
"unsubscribes from the newsletter"
msgstr "Sende eine E-Mail-Benachrichtigung an die folgenden Benutzer, wenn "
"sich ein Empfänger vom Newsletter abmeldet"

msgid "Unsubscription from newsletter"
msgstr "Abmeldung vom Newsletter"

msgid "The user with the address ${address} just unsubscribed from the newsletter subscriber list."
msgstr "Der Benutzer mit der Adresse ${address} hat sich soeben von der Newsletter-Abonnentenliste abgemeldet."

msgid ""
"This is the daily reservation overview for ${organisation}. If you no longer "
"want to receive this e-mail please contact an administrator so they can "
Expand Down Expand Up @@ -4063,6 +4059,13 @@ msgstr "Klicken Sie hier, um die Web-Version anzuzeigen."
msgid "You no longer wish to receive the newsletter?"
msgstr "Sie möchten den Newsletter nicht mehr erhalten?"

msgid ""
"The user with the address ${address} just unsubscribed from the newsletter "
"subscriber list."
msgstr ""
"Der Benutzer mit der Adresse ${address} hat sich soeben von der Newsletter-"
"Abonnentenliste abgemeldet."

msgid "Click the following link to set a new password:"
msgstr "Klicken Sie auf den folgenden Link, um ein neues Passwort zu setzen:"

Expand Down Expand Up @@ -4472,6 +4475,15 @@ msgstr "Dateien zur Prüfung hierhin ziehen"
msgid "No subscribers yet"
msgstr "Noch keine Abonnenten"

msgid ""
"This recipient has delivery failures, including hard bounces, invalid email "
"addresses, spam complaints, manual deactivations, or being blocked. We "
"recommend unsubscribing it from the list."
msgstr ""
"Dieser Empfänger hat Zustellungsfehler, einschließlich Hard Bounces, "
"ungültige E-Mail-Adresse, Spam-Beschwerde, manuelle Deaktivierung oder wird "
"blockiert. Wir empfehlen, ihn von der Liste abzumelden."

msgid "submitted"
msgstr "gemeldet"

Expand Down Expand Up @@ -5456,15 +5468,15 @@ msgstr ""
"Der Totalbetrag für Ihre Eingaben beläuft sich auf ${total} allerdings ist "
"der Minimalbetrag ${minimum}. Bitte ändern Sie ihre Eingaben."

msgid "Registrations are no longer possible"
msgstr "Anmeldungen sind nicht mehr möglich"

msgid "Pay Online and Complete"
msgstr "Online zahlen und abschliessen"

msgid "Your payment could not be processed"
msgstr "Ihre Zahlung konnte nicht verarbeitet werden"

msgid "Registrations are no longer possible"
msgstr "Anmeldungen sind nicht mehr möglich"

msgid "Your registration has been confirmed"
msgstr "Ihre Anmeldung wurde bestätigt"

Expand Down Expand Up @@ -5513,18 +5525,17 @@ msgid "Welcome to the ${org} Newsletter"
msgstr "Willkommen beim ${org} Newsletter"

#, python-format
msgid ""
"Success! We have added ${address} to the list of recipients."
msgstr ""
"Erfolg! Wir haben ${address} zur Liste der Empfänger hinzugefügt."

msgid ""
"Success! We have added ${address} to the list of recipients. Subscribed "
"categories are ${subscribed}."
msgstr ""
"Erfolg! Wir haben ${address} zur Liste der Empfänger hinzugefügt. Abonnierte "
"Kategorien sind ${subscribed}."

#, python-format
msgid "Success! We have added ${address} to the list of recipients."
msgstr "Erfolg! Wir haben ${address} zur Liste der Empfänger hinzugefügt."

#, python-format
msgid ""
"Success! We have sent a confirmation link to ${address}, if we didn't send "
Expand Down Expand Up @@ -5653,6 +5664,12 @@ msgstr "Die Zahlung wurde eingenommen"
msgid "The payment was refunded"
msgstr "Die Zahlung wurde rückerstattet"

msgid "The payment is already captured but is still processing"
msgstr "Die Zahlung wurde bereits eingenommen, aber wurde noch nicht fertig verarbeitet"

msgid "Could not refund the payment"
msgstr "Die Zahlung konnte nicht rückerstattet werden"

msgid "As default"
msgstr "Als Standard"

Expand Down Expand Up @@ -5701,9 +5718,30 @@ msgstr "Der Zahlungsanbieter wurde gelöscht."
msgid "Successfully synchronised payments"
msgstr "Synchronisierung erfolgreich"

msgid "Merchant Name"
msgstr "Händlername"

msgid "UPP Username"
msgstr "UPP Benutername"

msgid "UPP Password"
msgstr "UPP Passwort"

msgid "Webhook Signing Key"
msgstr "Webhook Signier-Schlüssel"

msgid "Charge fees to customer"
msgstr "Kreditkarten-Gebühren dem Kunden verrechnen"

msgid "Use sandbox environment (for testing)"
msgstr "Sandbox Umbegung verwenden (nur für Testing)"

msgid "Datatrans has been added"
msgstr "Datatrans wurde hinzugefügt"

msgid "Add Datatrans"
msgstr "Datatrans hinzufügen"

msgid "Added a new person"
msgstr "Eine neue Person wurde hinzugefügt"

Expand Down Expand Up @@ -5975,6 +6013,9 @@ msgid "You have successfully unsubscribed from the newsletter at ${address}"
msgstr ""
"Sie haben die Mail-Adresse ${address} erfolgreich vom Newsletter abgemeldet"

msgid "Unsubscription from newsletter"
msgstr "Abmeldung vom Newsletter"

msgid "A survey with this name already exists"
msgstr "Eine Umfrage mit diesem Namen existiert bereits"

Expand Down Expand Up @@ -6256,15 +6297,12 @@ msgstr "Ein Konto wurde für Sie erstellt"
msgid "The user was created successfully"
msgstr "Der Benutzer wurde erfolgreich erstellt"

msgid "This recipient has delivery failures, including hard bounces, invalid email addresses, spam complaints, manual deactivations, or being blocked. We recommend unsubscribing it from the list."
msgstr "Dieser Empfänger hat Zustellungsfehler, einschließlich Hard Bounces, ungültige E-Mail-Adresse, Spam-Beschwerde, manuelle Deaktivierung oder wird blockiert. Wir empfehlen, ihn von der Liste abzumelden."
#~ msgid "Photo album. Will be shown at the end of content."
#~ msgstr "Fotoalbum. Wird am Ende des Inhalts angezeigt."

#~ msgid "Describes in detail how this form is to be used"
#~ msgstr "Beschreibt detailliert wie dieses Formular benutzt werden soll"

#~ msgid "Photo album. Will be shown at the end of content."
#~ msgstr "Fotoalbum. Wird am Ende des Inhalts angezeigt."

#~ msgid "Include logo in newsletter"
#~ msgstr "Logo im Newsletter anzeigen"

Expand Down
Loading

0 comments on commit cdf4acb

Please sign in to comment.