Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions app/core/core_endpoints/endpoints_core.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from anyio import Path
from fastapi import APIRouter, Depends, Request
from fastapi.responses import FileResponse
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.core_endpoints import schemas_core
from app.core.mypayment.utils_mypayment import patch_payment_identity_in_text
from app.core.utils.config import Settings
from app.dependencies import (
get_db,
get_settings,
)
from app.types.module import CoreModule
Expand Down Expand Up @@ -78,13 +81,17 @@ async def read_terms_and_conditions(settings: Settings = Depends(get_settings)):
response_model=str,
status_code=200,
)
async def read_mypayment_tos(settings: Settings = Depends(get_settings)):
async def read_mypayment_tos(
settings: Settings = Depends(get_settings),
db: AsyncSession = Depends(get_db),
):
"""
Return MyPayment latest ToS
"""
return patch_identity_in_text(
return await patch_payment_identity_in_text(
await Path("assets/mypayment-terms-of-service.txt").read_text(encoding="utf-8"),
settings,
db,
)


Expand Down
7 changes: 4 additions & 3 deletions app/core/mypayment/endpoints_mypayment.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
apply_transaction,
call_mypayment_callback,
is_user_latest_tos_signed,
patch_payment_identity_in_text,
validate_transfer_callback,
verify_signature,
)
Expand Down Expand Up @@ -108,7 +109,6 @@
generate_pdf_from_template,
get_core_data,
get_file_from_data,
patch_identity_in_text,
set_core_data,
)

Expand Down Expand Up @@ -1418,9 +1418,10 @@ async def get_user_tos(
return schemas_mypayment.TOSSignatureResponse(
accepted_tos_version=existing_user_payment.accepted_tos_version,
latest_tos_version=LATEST_TOS,
tos_content=patch_identity_in_text(
tos_content=await patch_payment_identity_in_text(
await Path("assets/mypayment-terms-of-service.txt").read_text(),
settings=settings,
settings,
db,
),
max_wallet_balance=settings.MYPAYMENT_MAXIMUM_WALLET_BALANCE,
)
Expand Down
2 changes: 1 addition & 1 deletion app/core/mypayment/types_mypayment.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from enum import StrEnum

LATEST_TOS = 2
LATEST_TOS = 3
QRCODE_EXPIRATION = 5 # minutes
REQUEST_EXPIRATION = 8 # minutes
RETENTION_DURATION = 10 * 365 # 10 years in days
Expand Down
35 changes: 35 additions & 0 deletions app/core/mypayment/utils_mypayment.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from app.core.checkout.types_checkout import HelloAssoConfigName
from app.core.checkout.utils_checkout import CHECKOUT_EXPIRATION
from app.core.mypayment import cruds_mypayment, models_mypayment, schemas_mypayment
from app.core.mypayment.coredata_mypayment import MyPaymentBankAccountHolder
from app.core.mypayment.exceptions_mypayment import (
InvalidCheckoutToolError,
InvalidRequestTypeError,
Expand Down Expand Up @@ -42,7 +43,9 @@
from app.core.users import schemas_users
from app.core.utils.config import Settings
from app.module import all_modules
from app.types.exceptions import CoreDataNotFoundError
from app.utils.communication.notifications import NotificationTool
from app.utils.tools import get_core_data, patch_identity_in_text

hyperion_security_logger = logging.getLogger("hyperion.security")
hyperion_mypayment_logger = logging.getLogger("hyperion.mypayment")
Expand Down Expand Up @@ -440,3 +443,35 @@ async def ensure_user_can_manage_events(
403,
detail="User is not authorized to manage store's events",
)


async def patch_payment_identity_in_text(
text: str,
settings: Settings,
db: AsyncSession,
) -> str:
"""
Replace placeholders in the text with the corresponding values from the settings.
"""
try:
bank_account_holder = await get_core_data(
MyPaymentBankAccountHolder,
db=db,
)
structure = await cruds_mypayment.get_structure_by_id(
db=db,
structure_id=bank_account_holder.holder_structure_id,
)
except CoreDataNotFoundError:
structure = None
text = patch_identity_in_text(
text,
settings=settings,
)
return text.replace(
"{mypayment_max_balance}",
"%.2f" % (settings.MYPAYMENT_MAXIMUM_WALLET_BALANCE / 100.0),
).replace(
"{bank_account_holder}",
structure.name if structure else "N/A",
)
69 changes: 51 additions & 18 deletions assets/mypayment-terms-of-service.txt
Original file line number Diff line number Diff line change
@@ -1,30 +1,63 @@
Version 2
Version 3

# CGU {payment_name}

## Objet des CGU
Les présentes Conditions Générales d’Utilisation (CGU) ont pour objet de définir les modalités et conditions d’utilisation de la solution de paiement dématérialisée {payment_name}, éditée par {entity_name}, destinée aux élèves et étudiants de {school_name}.
## 1. Objet des CGU

## Présentation de {payment_name}
{payment_name} est une solution de paiement virtuel et dématérialisé utilisable au sein de {school_name} exclusivement, avec les associations participantes. Elle repose sur une monnaie virtuelle appelée <nom de la devise>, qui n’a aucune valeur légale et ne constitue pas une monnaie électronique au sens du Code monétaire et financier. Un (1) euro correspond à un (1) <nom de la devise>.
Les présentes Conditions Générales d’Utilisation (CGU) ont pour objet de définir les modalités et conditions d’utilisation de la solution de paiement dématérialisée {payment_name}, éditée par {entity_name}, exclusivement destinée aux élèves et étudiants de {school_name}.

## Fonctionnement du portefeuille virtuel
Lors de son inscription, l’utilisateur se voit attribuer un portefeuille numérique personnel plafonné. Il s’engage à ne pas tenter de dépasser ce plafond par quelque moyen que ce soit. En validant son inscription, l’utilisateur reconnaît renoncer expressément à son droit de rétractation, conformément à l’article L221-28 du Code de la Consommation.
La monnaie virtuelle ne peut pas être convertie en euros ou en toute autre devise à cours légal. Elle est non remboursable, non réversible, et ne peut pas être cédée ou transférée à d’autres utilisateurs. Un portefeuille ne peut jamais être en solde négatif.
## 2. Présentation du service et Cadre Réglementaire

{payment_name} est un instrument de paiement reposant sur une valeur monétaire stockée sous forme électronique (monnaie électronique), appelée MyCoin (ʍ). Un (1) euro correspond à un (1) MyCoin.

Conformément aux articles L. 521-3 et L. 525-5 du Code monétaire et financier, ce service est fourni sous le régime de l’exemption de réseau limité. L'utilisation de MyCoin est strictement limitée à l’enceinte de {school_name} et aux événements organisés par les associations partenaires ayant conclu un mandat d’encaissement avec {bank_account_holder}.

## 3. Fonctionnement du portefeuille virtuel et Plafonds

Lors de son inscription, l’utilisateur se voit attribuer un portefeuille numérique personnel. Pour respecter les seuils de vigilance relatifs à la lutte contre le blanchiment et le financement du terrorisme (LCB-FT), le portefeuille est soumis aux limites suivantes :

- Le solde du compte ne peut excéder {mypayment_max_balance} ʍ.
- Un portefeuille ne peut jamais être en solde négatif.
- La monnaie virtuelle ne peut pas être cédée ou transférée à d’autres utilisateurs.

## 4. Recharge du portefeuille via HelloAsso

L’utilisateur peut recharger son portefeuille par carte bancaire via la plateforme HelloAsso. Les fonds seront versés directement sur le compte bancaire de l’association {bank_account_holder}, et un équivalent en est crédité sur le portefeuille numérique de l’utilisateur.

## Recharge du portefeuille via HelloAsso
L’utilisateur peut recharger son portefeuille par carte bancaire via la plateforme HelloAsso. Les fonds seront versés directement sur le compte bancaire de l’association {entity_name}, et un équivalent en est crédité sur le portefeuille numérique de l’utilisateur.
HelloAsso est une plateforme française de paiement pour les associations, fonctionnant sur un modèle solidaire, garantissant que 100 % de votre paiement sera versé à l’association choisie.
Elle se finance uniquement par des 'contribution volontaire' et par défaut, HelloAsso proposera à l’utilisateur de faire une 'contribution volontaire', qui peut librement refuser sans que cela empêche la transaction. Si vous choisissez de faire cette contribution, celle-ci soutiendra l’aide qu’HelloAsso apporte aux associations et seul HelloAsso en bénéficiera.

## Utilisation des fonds et paiements
Le paiement avec {payment_name} est uniquement possible auprès des associations partenaires. L’utilisateur doit préalablement activer son appareil par un lien unique reçu par email. Chaque paiement est validé par une vérification biométrique effectuée localement par l’appareil de l’utilisateur.
Conformément à l'article L221-28 du Code de la consommation, les paiements en ligne pour des prestations de services de billetterie ou de consommation immédiate sont fermes et définitifs. Toutefois, le stockage de la valeur sur le portefeuille {payment_name} reste remboursable selon les conditions de l’Article 5.

## 5. Droit au remboursement

Conformément à la réglementation applicable à la monnaie électronique, l’utilisateur dispose d’un droit au remboursement de son solde à tout moment, à sa valeur nominale.

L'utilisateur peut à tout moment demander le remboursement de son solde via l'application. Le remboursement sera effectué par virement bancaire sur le compte bancaire de l’utilisateur, dans un délai maximum de 30 jours.

Les frais bancaires éventuels liés au virement seront à la charge de l’utilisateur.
Cette demande de remboursement équivaut à la fermeture définitive du compte {payment_name} de l'utilisateur.

## 6. Utilisation des fonds et Interdictions

Le paiement avec {payment_name} est uniquement possible auprès des associations partenaires pour l’acquisition de biens et services liés à la vie du campus.

- Interdiction de transfert (P2P) : La cession ou le transfert de MyCoins entre utilisateurs est strictement interdit afin de garantir l'étanchéité du circuit de paiement.
- Interdiction de retrait : La conversion du crédit MyCoin en espèces (cash-out) est interdite, hors procédure de remboursement exceptionnel définie à l'Article 5.

## 7. Sécurité, responsabilité et données personnelles

L’utilisateur est responsable de la sécurité de son accès. Chaque paiement est validé par une vérification biométrique locale effectuée par l’appareil de l’utilisateur. Aucune donnée biométrique n'est collectée, transmise ou stockée par {payment_name} ou {entity_name}.
{payment_name} respecte le RGPD. Les données collectées (email, historique de transactions, identifiant d'appareil) sont strictement nécessaires au service.

Dans le cadre du remboursement, l'IBAN fourni lors de la demande est utilisé exclusivement pour l'exécution du virement et n'est pas conservé au-delà du traitement comptable.

Aucune autre donnée bancaire n’est conservée par l’association {bank_account_holder}, cette gestion étant déléguée aux prestataires de services de paiement agréés de HelloAsso.

{payment_name} fait partie de la plateforme {application_name}. En utilisant le service, l’utilisateur reconnait et accepte les conditions générales d’utilisations et la politique de confidentialité de {application_name}

## 8. Modifications des CGU

## Sécurité, responsabilité et données personnelles
L’utilisateur est responsable de la sécurité de son appareil et de son accès à {payment_name}. Toute utilisation frauduleuse, perte ou vol doit être signalée sans délai. Le compte pourra alors être suspendu temporairement. Il est interdit de prêter ou partager son compte.
{payment_name} respecte le Règlement Général sur la Protection des Données (RGPD). Les données collectées sont strictement nécessaires au fonctionnement du service : identifiants, email, historique de transactions, identifiant de l’appareil. Aucune donnée bancaire n’est collectée ni conservée par {payment_name}.
{payment_name} fait partie de la plateforme {application_name}, en utilisant le service, l’utilisateur reconnait et accepte les conditions générales d’utilisations et la politique de confidentialité de {application_name}
{entity_name} se réserve le droit de modifier les présentes CGU à tout moment. L’utilisateur sera notifié de toute modification majeure.

## Modifications des CGU
{entity_name} se réserve le droit de modifier les présentes CGU à tout moment. L’utilisation du service après notification est conditionné par l’acceptation des nouvelles CGU.
Pour toute question, demande d’assistance ou réclamation, l’utilisateur peut contacter : {entity_email}.
12 changes: 12 additions & 0 deletions tests/core/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,53 @@ def test_get_privacy(client: TestClient) -> None:
"/privacy",
)
assert response.status_code == 200
assert "{" not in response.text # Ensure no placeholders are present
assert "}" not in response.text


def test_get_terms_and_conditions(client: TestClient) -> None:
response = client.get(
"/terms-and-conditions",
)
assert response.status_code == 200
assert "{" not in response.text # Ensure no placeholders are present
assert "}" not in response.text


def test_get_mypayment_tos(client: TestClient) -> None:
response = client.get(
"/mypayment-terms-of-service",
)
assert response.status_code == 200
assert "{" not in response.text # Ensure no placeholders are present
assert "}" not in response.text


def test_get_support(client: TestClient) -> None:
response = client.get(
"/support",
)
assert response.status_code == 200
assert "{" not in response.text # Ensure no placeholders are present
assert "}" not in response.text


def test_get_security_txt(client: TestClient) -> None:
response = client.get(
"/security.txt",
)
assert response.status_code == 200
assert "{" not in response.text # Ensure no placeholders are present
assert "}" not in response.text


def test_get_wellknown_security_txt(client: TestClient) -> None:
response = client.get(
"/.well-known/security.txt",
)
assert response.status_code == 200
assert "{" not in response.text # Ensure no placeholders are present
assert "}" not in response.text


def test_get_favicon(client: TestClient) -> None:
Expand Down
3 changes: 3 additions & 0 deletions tests/core/test_mypayment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2012,6 +2012,9 @@ async def test_get_user_tos(client: TestClient):
assert response.status_code == 200
assert response.json()["latest_tos_version"] == LATEST_TOS
assert response.json()["accepted_tos_version"] == LATEST_TOS
# Check that the TOS content does not contain any placeholders
assert "{" not in response.json()["tos_content"]
assert "}" not in response.json()["tos_content"]


async def test_register_new_user(client: TestClient):
Expand Down
Loading