Python SDK for Finanfut Billing External API
Project description
Finanfut Billing Python SDK
Client oficial sincrònic per consumir la Finanfut Billing External API (/external/v1) amb models compatibles amb Pydantic v2, ara preparat per treballar amb Business Units.
Instal·lació
- Pydantic 2.x:
pip install finanfut-billing-sdk>=2.0 - Pydantic 1.x:
pip install finanfut-billing-sdk<2.0 - Des del repositori local:
pip install -e backend/sdk
Dependències principals:
pydantic>=2.0,<3.0requests>=2.31
Configuració bàsica i Business Units
from finanfut_billing_sdk import FinanfutBillingClient
client = FinanfutBillingClient(
base_url="https://api.finanfut-billing.com",
api_key="sk_live_xxx",
business_unit_id="bu_default", # opcional: aplicada a serveis/factures/liquidacions per defecte
timeout=10,
max_retries=2,
)
Com funciona business_unit_id
- Global al client: passa
business_unit_idal constructor i s'aplicarà automàticament a les operacions compatibles. - Per operació: pots sobreescriure-la en cada mètode (
business_unit_id="bu_alt"). - Endpoints sense BU: tax rates i partner payment methods són d'abast de companyia i ignoren la BU (l'SDK emet un avís si n'hi ha una definida).
- Liquidacions (settlements): la BU és opcional però s'envia quan està disponible per enrutar payouts.
- External API: serveis i factures externes accepten BU però actualment poden ignorar-la; l'SDK ja no la tracta com a obligatòria.
Exemples d'ús
Crear producte/servei amb BU
from decimal import Decimal
from finanfut_billing_sdk.models import ExternalServiceUpsertRequest
payload = ExternalServiceUpsertRequest(
external_reference="service_abc",
type="service",
name="Monthly subscription",
description="Access to premium content",
price=Decimal("29.90"),
vat_rate_code="vat_21",
)
service = client.upsert_service(payload) # usa la BU global
Crear factura amb BU (sobre-escrivint la BU global)
from finanfut_billing_sdk.models import ExternalInvoiceCreateRequest, ExternalInvoiceLine
invoice = client.create_invoice(
ExternalInvoiceCreateRequest(
client_external_reference="client_123",
currency="EUR",
lines=[
ExternalInvoiceLine(
service_external_reference="service_abc",
description="Premium plan",
qty=1,
price=29.90,
vat_rate_id="tax_rate_uuid",
),
],
),
business_unit_id="bu_sales", # prioritat respecte la BU global
)
Operacions sense BU (àmbit de companyia)
# Els tax rates i partner payment methods ignoren la BU.
client.list_tax_rates()
client.partner_payment_methods.list_partner_payment_methods()
Idempotència en liquidacions
settlement = client.settlements.create_settlement(
payload,
idempotency_key="settlement-create-2024-12-31",
)
Enviar factura i registrar pagament
from finanfut_billing_sdk.models import ExternalInvoiceEmailRequest, ExternalPaymentCreateRequest
email = client.send_invoice_email(
invoice.invoice_id,
ExternalInvoiceEmailRequest(subject="La teva factura", body="Adjunt trobaràs el PDF"),
)
payment = client.register_payment(
invoice.invoice_id,
ExternalPaymentCreateRequest(amount=29.90, method="stripe"),
)
Checkout i onboarding de Stripe Connect
from finanfut_billing_sdk.models import ExternalCheckoutCreateRequest, ExternalConnectOnboardRequest
checkout = client.payments.create_checkout(
"stripe",
ExternalCheckoutCreateRequest(
amount=29.90,
currency="EUR",
business_unit_id="bu_sales",
provider_payload={"payment_method_types": ["card"]},
),
)
connect = client.payments.connect_onboard(
"stripe",
ExternalConnectOnboardRequest(
provider_id="provider_uuid",
return_url="https://app.example.com/connect/return",
refresh_url="https://app.example.com/connect/refresh",
),
)
Checkout sessions de Stripe (external)
from finanfut_billing_sdk.models import ExternalCheckoutSessionCreateRequest
session = client.payments.create_checkout_session(
ExternalCheckoutSessionCreateRequest(
amount=49.90,
currency="EUR",
success_url="https://app.example.com/ok",
cancel_url="https://app.example.com/cancel",
description="Pagament",
),
idempotency_key="checkout-session-2024-12-01",
)
Validació de targeta sense cobrar
from finanfut_billing_sdk.models import PaymentMethodSetupStartRequest, SubscriptionPayer
setup = client.payment_method_setups.start(
PaymentMethodSetupStartRequest(
request_id="membership-enrollment-123-card-setup",
business_unit_id="bu_sports",
subject_type="membership_enrollment",
subject_id="enrollment_123",
billing_client_id="client_uuid",
payer=SubscriptionPayer(email="payer@example.com", name="Payer One"),
success_url="https://sports.example.com/memberships/card-ok",
cancel_url="https://sports.example.com/memberships/card-cancel",
metadata={"origin": "finanfut-sports"},
),
idempotency_key="membership-enrollment-123-card-setup",
)
print(setup.checkout_url, setup.payment_method_status)
Aquest flux crea un Stripe Checkout mode=setup i no cobra cap import. Per altes
pendents d'aprovació, no useu subscriptions.start_subscription() només per validar
targeta; espereu el webhook payment_method.saved i creeu/activeu els cobraments
posteriors amb el customer i payment method guardats.
Subscripcions BU
from finanfut_billing_sdk.models import SubscriptionPricingSnapshot, SubscriptionStartRequest
payload = SubscriptionStartRequest(
request_id="sports-pro-2025-01",
business_unit_id="bu_sales",
subject_type="team",
subject_id="team_123",
billing_client_id="client_uuid",
bu_plan_ref="pro_v3",
pricing_snapshot=SubscriptionPricingSnapshot(
amount=29.9,
currency="EUR",
interval="month",
),
success_url="https://app.example.com/billing/success",
cancel_url="https://app.example.com/billing/cancel",
)
response = client.subscriptions.start_subscription(payload)
Contractes canònics Sports ↔ Billing
Sports ha d'integrar-se amb Billing a través de finanfut-billing-sdk>=2.1.22.
L'endpoint HTTP és el transport intern i la font OpenAPI, però el contracte
d'aplicació és client.contracts. Sports no ha de construir JSON manual.
La guia canònica completa és
docs/integrations/sports_billing_contracts_v1.md.
Aquest README només en resumeix la superfície pública.
Per quotes finites noves, Sports ha d'enviar scheduled_charges ja resolts.
No s'ha d'enviar billing_mode, installments, fixed_installments ni
custom_schedule a través d'aquest camí.
from datetime import datetime
from zoneinfo import ZoneInfo
from finanfut_billing_sdk.models import ExternalContractCharge, ExternalContractStartRequest
payload = ExternalContractStartRequest(
request_id="sports_membership_enrollment_123_v1",
source_system="sports",
contract_type="scheduled_charges",
business_unit_id="bu_sales",
source_entity_type="membership_enrollment",
source_entity_id="membership_enrollment_123",
billing_client_id="client_uuid",
bu_plan_ref="membership_plan:plan_123",
bu_price_ref="membership_plan_price:price_123",
currency="EUR",
total_amount_minor=12000,
charges=[
ExternalContractCharge(
sequence=1,
amount_minor=4000,
due_at=datetime(2026, 7, 15, tzinfo=ZoneInfo("Europe/Madrid")),
external_ref="membership_enrollment_123_charge_1",
)
],
payment_method_required=True,
success_url="https://sports.example.test/billing/success",
cancel_url="https://sports.example.test/billing/cancel",
sports_original_snapshot={"payment_type": "installments", "schedule_mode": "relative"},
economic_classification={
"economic_flow_type": "club_membership",
"recognition_role": "THIRD_PARTY_FUNDS",
"seller_role": "MARKETPLACE_OPERATOR",
"funds_ownership": "THIRD_PARTY_FUNDS",
"tax_ownership": "MERCHANT_TAX",
"ownership_confidence": "explicit",
},
economic_owner="club",
merchant="billing",
seller="club",
)
response = client.contracts.start_contract(payload, idempotency_key=payload.request_id)
Billing és propietari de targetes, setup checkout, payment checkout, retries,
webhooks, invoices, ledger i settlements. Sports pot enviar un
payment_method_setup_id creat per Billing, però no ha d'enviar cap
stripe_payment_method_id cru. Billing només accepta setups ready; un setup
pendent retorna un 409 funcional perquè Sports pugui reintentar després de
payment_method.saved.
Per quotes recurrents, Sports ha d'usar el mateix client amb
contract_type="recurring":
from datetime import date
from finanfut_billing_sdk.models import ExternalContractStartRequest
payload = ExternalContractStartRequest(
request_id="sports_membership_enrollment_123_recurring_v1",
source_system="sports",
contract_type="recurring",
business_unit_id="bu_sales",
source_entity_type="membership_enrollment",
source_entity_id="membership_enrollment_123",
billing_client_id="client_uuid",
bu_plan_ref="membership_plan:plan_123",
bu_price_ref="membership_plan_price:monthly",
currency="EUR",
amount_minor=3000,
frequency="monthly",
start_date=date(2026, 9, 1),
end_date=date(2027, 6, 30),
billing_day_of_month=1,
billing_months=[9, 10, 11, 12, 1, 2, 3, 4, 5, 6],
success_url="https://sports.example.test/success",
cancel_url="https://sports.example.test/cancel",
sports_original_snapshot={"payment_type": "recurring"},
economic_classification={
"economic_flow_type": "club_membership",
"recognition_role": "THIRD_PARTY_FUNDS",
"seller_role": "MARKETPLACE_OPERATOR",
"funds_ownership": "THIRD_PARTY_FUNDS",
"tax_ownership": "MERCHANT_TAX",
"ownership_confidence": "explicit",
},
economic_owner="club",
merchant="billing",
seller="club",
)
response = client.contracts.start_contract(payload, idempotency_key=payload.request_id)
Sports ha d'enviar start_date com a data efectiva d'alta/activació. Billing
no ha de cobrar períodes anteriors a aquesta data; si start_date és futura,
el checkout/setup pot passar ara i el primer període facturable comença a
start_date.
La resposta és un ExternalContractStartResponse:
assert response.contract_id
assert response.ledger_id
if response.contract_type == "scheduled_charges":
assert response.schedule_id
assert response.next_charge_at
assert response.payment_method_status
Si response.checkout_url és present, Sports ha de redirigir l'usuari. Si no
hi ha checkout i payment_method_status=="ready", Billing ja té una targeta
operativa.
Operacions de consulta i cancel·lació:
contract = client.contracts.get_contract(response.contract_id)
canceled = client.contracts.cancel_contract(
response.contract_id,
reason="sports_contract_replaced",
idempotency_key=f"{response.contract_id}:cancel:v1",
)
La cancel·lació conserva ítems pagats i només cancel·la ítems pendents.
Sports ha de tractar subscription.canceled com la confirmació canònica.
Webhooks que Sports ha de consumir:
subscription.invoice_paidsubscription.payment_failedsubscription.completedsubscription.canceled
Els payloads de contracte inclouen external_contract_id, contract_type,
ledger_id, schedule_id, schedule_sequence, external_ref,
billing_transaction_id, amount_total, amount_fee i amount_net.
settlement_lines_suggested només apareix quan Billing té
commission_breakdown. Sports no ha de calcular nets ni fees a partir del
contracte inicial.
Errors habituals:
pydantic.ValidationError: el payload no compleix els models del SDK.FinanfutBillingValidationError: Billing rebutja valors o forma del payload.FinanfutBillingServiceErrorambstatus_code == 409: mateix(source_system, request_id)amb payload diferent.FinanfutBillingAuthError: API key absent, incorrecta o sense scope.
Errors
from finanfut_billing_sdk.errors import (
FinanfutBillingAuthError,
FinanfutBillingServiceError,
FinanfutBillingValidationError,
)
try:
client.list_tax_rates()
except FinanfutBillingAuthError:
print("API key incorrecta o sense permisos")
except FinanfutBillingValidationError as e:
print("Error de validació:", e.payload)
except FinanfutBillingServiceError as e:
print(f"Error de servei ({e.request_id}): {e.error}")
if e.retry_after is not None:
print(f"Reintenta després de {e.retry_after:.0f}s")
Els errors del backend inclouen sempre error, message i request_id. El client reintenta automàticament errors transitoris (429, 500, 502, 503, 504) en lectures i en mutacions només quan la petició porta Idempotency-Key.
Publicació a PyPI
El paquet està preparat per publicar-se a PyPI quan es creen tags v* al repositori. El workflow publish-sdk.yml valida la versió (__version__) i fa l'upload amb Twine.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file finanfut_billing_sdk-2.1.23.tar.gz.
File metadata
- Download URL: finanfut_billing_sdk-2.1.23.tar.gz
- Upload date:
- Size: 24.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
64d8e2f1e9469ac07afb989e8113d83edf18069b2e6ba469de11bb5bce26debe
|
|
| MD5 |
2fa4e0b853836bcb9af7b6532d8d9288
|
|
| BLAKE2b-256 |
12012ceb884d54a153b58c8f92d16662bbf404160d798be9763539b222002592
|
File details
Details for the file finanfut_billing_sdk-2.1.23-py3-none-any.whl.
File metadata
- Download URL: finanfut_billing_sdk-2.1.23-py3-none-any.whl
- Upload date:
- Size: 21.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91ff0611bb21e514b8aadb926056b8cc180ed7248dbac278d865db7b7457fc46
|
|
| MD5 |
2b9111ea27c63c0363eb05142b2d8dc9
|
|
| BLAKE2b-256 |
f753cb2ab91978c9540adbc8b3ec2c2e6b00c48835fff6908e2f04473afb7476
|