### Configure Webserver Path Prefix Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Define a path prefix for web routes, for example, '/api'. An empty string means no prefix. ```bash WEBSERVER_PATH="/api" ``` -------------------------------- ### Get Checkout Session State Source: https://context7.com/citrineos/citrineos-payment/llms.txt Retrieves the current state of a checkout session, including transaction details, energy consumed, and pricing breakdown. The frontend polls this endpoint to track charging progress and display final costs. ```bash curl -X GET http://localhost:9010/api/checkouts/42 ``` ```json { "id": 42, "payment_intent_id": "pi_3ExamplePaymentIntent", "connector_id": 1, "tariff_id": 1, "remote_request_status": "Accepted", "remote_request_transaction_id": "txn-uuid-001", "transaction_start_time": "2024-06-01T10:00:00Z", "transaction_end_time": null, "transaction_kwh": 7.4, "power_active_import": 11.0, "transaction_soc": 62.5, "pricing": { "currency": "EUR", "tax_rate": 19, "payment_fee": 150, "energy_consumption_kwh": 7.4, "energy_costs": 289, "time_consumption_min": null, "time_costs": null, "session_consumption": 1, "session_costs": 100, "total_costs_net": 389, "tax_costs": 74, "total_costs_gross": 463, "payment_costs_gross": 6, "payment_costs_net": 5 } } ``` ```json { "detail": "charging.error.sessionnotfound" } ``` -------------------------------- ### Handle Stripe Webhook Events Source: https://context7.com/citrineos/citrineos-payment/llms.txt Receives and validates Stripe webhook events, handling `checkout.session.completed` to trigger remote starts for web portal or scan-and-charge flows. Verifies signatures using `STRIPE_ENDPOINT_SECRET_CONNECT` for Connect-type events. ```APIDOC ## POST /api/webhooks/stripe — Handle Stripe Webhook Events ### Description Receives and validates Stripe webhook events. Handles `checkout.session.completed` to trigger either a **web portal** remote start (via CitrineOS `requestStartTransaction`) or a **scan-and-charge** remote start tied to an existing OCPP transaction. Verifies signature using `STRIPE_ENDPOINT_SECRET_CONNECT` for Connect-type events. ### Method POST ### Endpoint /api/webhooks/stripe ### Request Body (Stripe webhook event payload - structure varies by event type, see examples) ### Request Example (checkout.session.completed - web portal flow) ```json { "type": "checkout.session.completed", "data": { "object": { "payment_intent": "pi_3ExamplePaymentIntent", "amount_total": 3000, "metadata": { "checkoutId": "42" } } } } ``` ### Request Example (checkout.session.completed - scan-and-charge flow) ```json { "type": "checkout.session.completed", "data": { "object": { "payment_intent": "pi_3ExamplePaymentIntent", "amount_total": 3000, "metadata": { "checkoutId": "42", "stationId": "STATION-001", "transactionId": "TXN-UUID-001" } } } } ``` ### Response #### Success Response (200) An empty JSON object `{}` indicates successful processing. #### Error Response (400) Returned on signature verification failure (`stripe.error.SignatureVerificationError`). #### Error Response (404) Returned if no checkout is found for the given payment intent. Example: `{ "detail": "No checkout found for payment intent" }` ``` -------------------------------- ### Get Checkout Session State Source: https://context7.com/citrineos/citrineos-payment/llms.txt Retrieves the current state of a checkout session, including transaction details, energy consumed, and pricing breakdown. This endpoint is used by the frontend to poll for updates on the charging session's progress. ```APIDOC ## GET /api/checkouts/{id} ### Description Retrieves the current state of a checkout session, including transaction timing, energy consumed (kWh), active power draw, state-of-charge, and the calculated pricing breakdown. Used by the frontend to poll session progress. ### Method GET ### Endpoint /api/checkouts/{id} ### Parameters #### Path Parameters - **id** (integer) - Required - The internal ID of the checkout session. ### Response #### Success Response (200) — active session - **id** (integer) - The internal ID of the checkout session. - **payment_intent_id** (string) - The Stripe Payment Intent ID, if applicable. - **connector_id** (integer) - The ID of the connector used for the session. - **tariff_id** (integer) - The ID of the tariff applied to the session. - **remote_request_status** (string) - The status of the remote request to start charging (e.g., "Accepted"). - **remote_request_transaction_id** (string) - The transaction ID generated by the charging station. - **transaction_start_time** (string) - The timestamp when the transaction started (ISO 8601 format). - **transaction_end_time** (string) - The timestamp when the transaction ended, or null if ongoing. - **transaction_kwh** (number) - The total energy consumed in kWh. - **power_active_import** (number) - The active power import in kW. - **transaction_soc** (number) - The state of charge of the vehicle battery when the transaction ended. - **pricing** (object) - Detailed pricing breakdown. - **currency** (string) - The currency used for pricing. - **tax_rate** (number) - The tax rate applied. - **payment_fee** (number) - The payment processing fee. - **energy_consumption_kwh** (number) - The amount of energy consumed in kWh. - **energy_costs** (number) - The cost of the energy consumed. - **time_consumption_min** (number) - The duration of the charging session in minutes, or null. - **time_costs** (number) - The cost based on charging time, or null. - **session_consumption** (number) - A fixed session fee, or 1 if applicable. - **session_costs** (number) - The cost of the fixed session fee. - **total_costs_net** (number) - The total costs before tax. - **tax_costs** (number) - The amount of tax. - **total_costs_gross** (number) - The total costs including tax. - **payment_costs_gross** (number) - The gross payment processing fee. - **payment_costs_net** (number) - The net payment processing fee. #### Error Response (404 Not Found) - **detail** (string) - "charging.error.sessionnotfound" ### Request Example ```bash curl -X GET http://localhost:9010/api/checkouts/42 ``` ### Response Example #### Success Response (200) — active session ```json { "id": 42, "payment_intent_id": "pi_3ExamplePaymentIntent", "connector_id": 1, "tariff_id": 1, "remote_request_status": "Accepted", "remote_request_transaction_id": "txn-uuid-001", "transaction_start_time": "2024-06-01T10:00:00Z", "transaction_end_time": null, "transaction_kwh": 7.4, "power_active_import": 11.0, "transaction_soc": 62.5, "pricing": { "currency": "EUR", "tax_rate": 19, "payment_fee": 150, "energy_consumption_kwh": 7.4, "energy_costs": 289, "time_consumption_min": null, "time_costs": null, "session_consumption": 1, "session_costs": 100, "total_costs_net": 389, "tax_costs": 74, "total_costs_gross": 463, "payment_costs_gross": 6, "payment_costs_net": 5 } } ``` #### Error Response (404 Not Found) ```json { "detail": "charging.error.sessionnotfound" } ``` ``` -------------------------------- ### Deploy Local Environment Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Run this script from the root directory to set up the local development environment. ```bash ./deploy_local.sh ``` -------------------------------- ### Run Unit Tests Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Execute the project's unit tests by running this command from the root directory. ```bash python -m unittest ``` -------------------------------- ### Configure Webserver Host Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set the host address for the web server. '0.0.0.0' makes it accessible externally. This is a required setting. ```bash WEBSERVER_HOST="0.0.0.0" ``` -------------------------------- ### Minimum Viable Local Dev Configuration (.env) Source: https://context7.com/citrineos/citrineos-payment/llms.txt This .env file provides the essential configuration for a local development environment. Ensure all required fields are populated for CitrineOS integration, web server, database, message broker, and payment processing. ```bash # .env (minimum viable local dev configuration) # CitrineOS integration CITRINEOS_MESSAGE_API_URL="http://localhost:8080/ocpp" CITRINEOS_DATA_API_URL="http://localhost:8080/data" CITRINEOS_SCAN_AND_CHARGE="true" CITRINEOS_DIRECTUS_URL="http://localhost:8055" CITRINEOS_DIRECTUS_LOGIN_EMAIL="admin@CitrineOS.com" CITRINEOS_DIRECTUS_LOGIN_PASSWORD="CitrineOS!" CITRINEOS_DIRECTUS_QR_CODE_FOLDER="" # Web server WEBSERVER_HOST="0.0.0.0" WEBSERVER_PORT=9010 WEBSERVER_PATH="/api" CLIENT_URL="http://localhost:9010" # PostgreSQL DB_HOST="127.0.0.1" DB_PORT=5432 DB_DATABASE="citrine" DB_USER="citrine" DB_PASSWORD="citrine" DB_TABLE_PREFIX="payment_" # RabbitMQ MESSAGE_BROKER_SSL_ACTIVE=False MESSAGE_BROKER_HOST="127.0.0.1" MESSAGE_BROKER_PORT=5672 MESSAGE_BROKER_USER="guest" MESSAGE_BROKER_PASSWORD="guest" MESSAGE_BROKER_VHOST="/" MESSAGE_BROKER_EXCHANGE_TYPE="headers" MESSAGE_BROKER_EXCHANGE_NAME="citrineos" MESSAGE_BROKER_EVENT_CONSUMER_QUEUE_NAME="paymentService" # Stripe STRIPE_API_KEY="sk_test_..." STRIPE_ENDPOINT_SECRET_ACCOUNT="whsec_..." STRIPE_ENDPOINT_SECRET_CONNECT="whsec_..." # Pricing AMPAY_DEFAULT_FEE=1.5 AMPAY_COUNTRY_CODE_FOR_ADDING_TAX="DE" AMPAY_ADDING_TAX_RATE=19 OCPP_REMOTESTART_IDTAG_PREFIX="PAYMENT-" AMPAY_RECEIPT_BASE_URL="http://localhost:9010/receipt" ``` -------------------------------- ### Configure Database Host Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set the hostname or IP address for the database server. This is a required setting. ```bash DB_HOST="127.0.0.1" ``` -------------------------------- ### Configure Database Name Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set the name of the database to connect to. This is a required setting. ```bash DB_DATABASE="citrine" ``` -------------------------------- ### Enable CitrineOS Scan and Charge Feature Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set this environment variable to 'true' to enable the CitrineOS Scan and Charge feature. ```bash CITRINEOS_SCAN_AND_CHARGE="true" ``` -------------------------------- ### Configure Database User Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set the username for connecting to the database. This is a required setting. ```bash DB_USER="citrine" ``` -------------------------------- ### Configure Client URL Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set the URL that the frontend application will use to interact with the backend. ```bash CLIENT_URL="http://localhost:9010" ``` -------------------------------- ### Configure CitrineOS Directus URL Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Provide the URL for the CitrineOS Directus instance. This is required for the Scan and Charge feature. ```bash CITRINEOS_DIRECTUS_URL="http://localhost:8055" ``` -------------------------------- ### Configure Stripe API Key Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Enter your Stripe API key. This is a required setting. ```bash STRIPE_API_KEY="sk_test_some-stripe-api-key" ``` -------------------------------- ### Configure Database Password Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set the password for connecting to the database. This is a required setting. ```bash DB_PASSWORD="citrine" ``` -------------------------------- ### Configure Webserver Port Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Specify the port number for the web server. This is a required setting. ```bash WEBSERVER_PORT=9010 ``` -------------------------------- ### Format Code with Ruff Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Use Ruff to automatically format the codebase according to defined style guidelines. ```bash ruff format ``` -------------------------------- ### Configure Message Broker SSL Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set to 'True' or 'False' to indicate if SSL should be used for the Message Broker connection. This is a required setting. ```bash MESSAGE_BROKER_SSL_ACTIVE=False ``` -------------------------------- ### generate_pricing(checkout_id) Source: https://context7.com/citrineos/citrineos-payment/llms.txt Computes session pricing by loading checkout and tariff data, building a `TransactionSummary`, and returning a `Pricing` object. All monetary values are in currency sub-units (e.g., cents). ```APIDOC ## generate_pricing(checkout_id) — Compute Session Pricing ### Description Loads the checkout and associated tariff from the database, builds a `TransactionSummary`, and returns a fully computed `Pricing` object with net/gross costs, tax, and payment fee. All monetary values are in currency sub-units (e.g., cents). Called both at checkout retrieval and at payment capture time. ### Parameters #### Path Parameters - **checkout_id** (integer) - Required - The ID of the checkout to generate pricing for. ### Response #### Success Response Returns a `Pricing` object containing detailed cost information. ### Response Example ```python # Assuming pricing is the returned Pricing object: pricing.currency → "EUR" pricing.energy_costs → 289 (cents) pricing.session_costs → 100 (cents) pricing.total_costs_net → 389 (cents) pricing.tax_costs → 74 (cents) pricing.total_costs_gross → 463 (cents) pricing.payment_costs_gross → 6 (cents) ``` ### Usage Example ```python from utils.utils import generate_pricing pricing = generate_pricing(checkout_id=42) # Used in payment capture: # stripe.PaymentIntent.capture( # intent=db_checkout.payment_intent_id, # stripe_account=operator.stripe_account_id, # amount_to_capture=pricing.total_costs_gross, # ) ``` ``` -------------------------------- ### CitrineOSIntegration.receive_events Source: https://context7.com/citrineos/citrineos-payment/llms.txt Async event loop that connects to RabbitMQ and dispatches incoming OCPP messages. ```APIDOC ## CitrineOSIntegration.receive_events() — RabbitMQ OCPP Event Consumer ### Description Async event loop that connects to RabbitMQ, binds to the CitrineOS exchange with headers filters for `TransactionEvent` and `StatusNotification`, and dispatches incoming messages to the appropriate handlers. Runs as a background task on FastAPI startup. ### Method Asynchronous Function ### Endpoint N/A (Listens to RabbitMQ) ### Parameters None ### Request Example ```python # Registered automatically in main.py on startup: @app.on_event("startup") async def startup_event(): loop = get_event_loop() loop.create_task(coro=ocpp_integration.receive_events()) ``` ### Response N/A (Consumes messages from RabbitMQ) ### Incoming Message Format (RabbitMQ JSON body): #### TransactionEvent: ```json { "action": "TransactionEvent", "payload": { "eventType": "Started", # or "Updated" / "Ended" "triggerReason": "RemoteStart", "timestamp": "2024-06-01T10:00:00Z", "transactionInfo": { "transactionId": "TXN-UUID-001", "remoteStartId": 42 }, "meterValue": [ { "sampledValue": [ { "value": 7400, "measurand": "Energy.Active.Import.Register", "unitOfMeasure": {"unit": "Wh"} } ] } ] } } ``` #### StatusNotification: ```json { "action": "StatusNotification", "payload": { "evseId": 1, "connectorId": 1, "connectorStatus": "Available", "timestamp": "2024-06-01T09:55:00Z" } } ``` ### Headers Required for Filtering: - **action**: `TransactionEvent` or `StatusNotification` - **state**: `1` ``` -------------------------------- ### Configure CitrineOS Directus Login Email Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set the login email for the CitrineOS Directus instance. This is required for the Scan and Charge feature. ```bash CITRINEOS_DIRECTUS_LOGIN_EMAIL="admin@CitrineOS.com" ``` -------------------------------- ### Configure Message Broker User Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set the username for authenticating with the Message Broker. This is a required setting. ```bash MESSAGE_BROKER_USER="guest" ``` -------------------------------- ### Configure Database Table Prefix Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Specify a prefix to be used for all database tables managed by this service. ```bash DB_TABLE_PREFIX="payment_" ``` -------------------------------- ### Configure Message Broker Password Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set the password for authenticating with the Message Broker. This is a required setting. ```bash MESSAGE_BROKER_PASSWORD="guest" ``` -------------------------------- ### Create a Stripe Checkout Session Source: https://context7.com/citrineos/citrineos-payment/llms.txt Initiates a Stripe Checkout session for a given EVSE. This endpoint validates necessary details, creates a local checkout record, sets up a Stripe session for manual payment capture, and returns a URL for the customer to complete payment. ```APIDOC ## POST /api/checkouts/ ### Description Initiates a Stripe Checkout Session for a given EVSE. Validates the EVSE, tariff, and location, persists a local checkout record, creates a Stripe session with manual capture, and returns the Stripe-hosted payment URL. The driver is redirected to `success_url/{checkout_id}` on payment completion. ### Method POST ### Endpoint /api/checkouts/ ### Parameters #### Request Body - **evse_id** (string) - Required - The unique identifier of the EVSE for which to create a checkout session. - **success_url** (string) - Required - The URL to redirect the user to upon successful payment. - **cancel_url** (string) - Required - The URL to redirect the user to if they cancel the payment. ### Response #### Success Response (200) - **id** (integer) - The internal ID of the created checkout session. - **url** (string) - The URL for the Stripe Checkout page. #### Error Response (404 Not Found) - **detail** (string) - "EVSE not found" (or similar if tariff/location is missing). ### Request Example ```bash curl -X POST http://localhost:9010/api/checkouts/ \ -H "Content-Type: application/json" \ -d '{ "evse_id": "EVSE-001", "success_url": "https://myapp.example.com/charging/EVSE-001", "cancel_url": "https://myapp.example.com/checkout/EVSE-001" }' ``` ### Response Example #### Success Response (200) ```json { "id": 42, "url": "https://checkout.stripe.com/pay/cs_test_a1b2c3..." } ``` #### Error Response (404 Not Found) ```json { "detail": "EVSE not found" } ``` ``` -------------------------------- ### Configure Message Broker Host Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Specify the hostname or IP address of the Message Broker. This is a required setting. ```bash MESSAGE_BROKER_HOST="127.0.0.1" ``` -------------------------------- ### Configure Message Broker Vhost Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Specify the virtual host to use on the Message Broker. This is a required setting. ```bash MESSAGE_BROKER_VHOST="/" ``` -------------------------------- ### Configure Database Port Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Specify the port number for the database connection. This is a required setting. ```bash DB_PORT=5432 ``` -------------------------------- ### Retrieve Tariff by ID Source: https://context7.com/citrineos/citrineos-payment/llms.txt Fetches the pricing details for a specific tariff, including rates per kWh, per minute, per session, currency, tax rate, and Stripe authorization amount. This is essential for calculating charging costs. ```bash curl -X GET http://localhost:9010/api/tariffs/1 ``` ```json { "id": 1, "price_kwh": 0.39, "price_minute": null, "price_session": 1.00, "currency": "EUR", "tax_rate": 19.0, "authorization_amount": 30.0 } ``` ```json { "detail": "Tariff not found" } ``` -------------------------------- ### Configure CitrineOS Directus QR Code Folder Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Specify the folder ID within CitrineOS Directus for QR codes. This is required for the Scan and Charge feature. ```bash CITRINEOS_DIRECTUS_QR_CODE_FOLDER="put folder id here" ``` -------------------------------- ### TransactionSummary Source: https://context7.com/citrineos/citrineos-payment/llms.txt A model for calculating session cost components from raw session data, using `py-moneyed` for accurate monetary arithmetic. ```APIDOC ## TransactionSummary — Session Cost Calculation Model ### Description Pure-Python model that calculates all cost components from raw session data (kWh, start/end time, tariff rates). Uses `py-moneyed` for monetary arithmetic. All cost properties are computed lazily and return `None` if the underlying rate is not configured. ### Parameters #### Initialization Parameters - **kwh** (float) - Required - Energy consumed in kilowatt-hours. - **start_time** (datetime) - Required - The start time of the session in UTC. - **end_time** (datetime) - Required - The end time of the session in UTC. - **currency** (str) - Required - The currency code (e.g., "EUR"). - **tax_rate** (float) - Required - The tax rate percentage. - **payment_fee** (float) - Required - The fixed payment fee. - **price_kwh** (float) - Optional - The price per kilowatt-hour. - **price_minute** (float) - Optional - The price per minute (for time-based billing). - **price_session** (float) - Optional - A fixed session price. ### Response #### Computed Properties - **energy_costs** (Money) - The calculated costs for energy consumption. - **time_consumption_min** (Decimal) - The total time consumed in minutes. - **time_costs** (Money) - The calculated costs based on time consumption (if `price_minute` is set). - **session_costs** (Money) - The fixed session costs (if `price_session` is set). - **total_costs_net** (Money) - The total costs before tax. - **tax_costs** (Money) - The calculated tax amount. - **total_costs_gross** (Money) - The total costs including tax. - **payment_costs_gross** (Money) - The gross payment processing costs. ### Usage Example ```python from model.transaction_summary import TransactionSummary from datetime import datetime, timezone summary = TransactionSummary( kwh=7.4, start_time=datetime(2024, 6, 1, 10, 0, 0, tzinfo=timezone.utc), end_time=datetime(2024, 6, 1, 10, 45, 0, tzinfo=timezone.utc), currency="EUR", tax_rate=19.0, payment_fee=1.5, price_kwh=0.39, price_minute=None, # time-based billing disabled price_session=1.00, ) # Accessing computed properties: # summary.energy_costs → Money("2.886", "EUR") # summary.time_consumption_min → Decimal("45.0") # summary.time_costs → None (price_minute is None) # summary.session_costs → Money("1.00", "EUR") # summary.total_costs_net → Money("3.886", "EUR") # summary.tax_costs → Money("0.738...", "EUR") # summary.total_costs_gross → Money("4.624...", "EUR") # summary.payment_costs_gross → Money("0.069...", "EUR") ``` ``` -------------------------------- ### Create Stripe Checkout Session Source: https://context7.com/citrineos/citrineos-payment/llms.txt Initiates a Stripe Checkout Session for a given EVSE. This endpoint validates the EVSE, tariff, and location, creates a local checkout record, and returns a Stripe payment URL. Ensure `success_url` and `cancel_url` are provided. ```bash curl -X POST http://localhost:9010/api/checkouts/ \ -H "Content-Type: application/json" \ -d '{ "evse_id": "EVSE-001", "success_url": "https://myapp.example.com/charging/EVSE-001", "cancel_url": "https://myapp.example.com/checkout/EVSE-001" }' ``` ```json { "id": 42, "url": "https://checkout.stripe.com/pay/cs_test_a1b2c3..." } ``` ```json { "detail": "EVSE not found" } ``` -------------------------------- ### Generate Session Pricing Source: https://context7.com/citrineos/citrineos-payment/llms.txt Computes session pricing by loading checkout and tariff data from the database. Returns a fully computed Pricing object. All monetary values are in currency sub-units (e.g., cents). Used for both checkout retrieval and payment capture. ```python from utils.utils import generate_pricing pricing = generate_pricing(checkout_id=42) # Returns Pricing object: # pricing.currency → "EUR" # pricing.energy_costs → 289 (cents) # pricing.session_costs → 100 (cents) # pricing.total_costs_net → 389 (cents) # pricing.tax_costs → 74 (cents) # pricing.total_costs_gross → 463 (cents) # pricing.payment_costs_gross → 6 (cents) # pricing.total_costs_gross is used directly for Stripe capture amount # Used in payment capture: stripe.PaymentIntent.capture( intent=db_checkout.payment_intent_id, stripe_account=operator.stripe_account_id, amount_to_capture=pricing.total_costs_gross, ) ``` -------------------------------- ### Set CitrineOS Data API URL Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Configure the base URL for the CitrineOS Data API. Ensure no trailing slash is included. ```bash CITRINEOS_DATA_API_URL="http://localhost:8080/data" ``` -------------------------------- ### Configure CitrineOS Directus Login Password Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set the login password for the CitrineOS Directus instance. This is required for the Scan and Charge feature. ```bash CITRINEOS_DIRECTUS_LOGIN_PASSWORD="CitrineOS!" ``` -------------------------------- ### Lint Code with Ruff Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Run Ruff to check the code for style violations and potential errors. ```bash ruff check ``` -------------------------------- ### DirectusIntegration.upload_file Source: https://context7.com/citrineos/citrineos-payment/llms.txt Uploads a QR code PNG image to Directus and returns a public asset URL. ```APIDOC ## DirectusIntegration.upload_file(...) — Upload QR Code to Directus CMS ### Description Uploads a QR code PNG image (as a `BytesIO` buffer) to a configured Directus folder and returns a public asset URL. Used during the scan-and-charge flow to make the payment link QR code available for display on the charger screen via `setDisplayMessage`. ### Method POST ### Endpoint /files ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **file** (BytesIO) - Required - The QR code image as a byte buffer. - **mime_type** (string) - Required - The MIME type of the file (e.g., `image/png`). - **filename** (string) - Required - The desired filename for the uploaded file. - **filetitle** (string) - Required - The title for the uploaded file. ### Request Example ```python from integrations.directus.directus import DirectusIntegration from io import BytesIO import qrcode directus = DirectusIntegration( url="http://localhost:8055", email="admin@CitrineOS.com", password="CitrineOS!", ) payment_link_url = "https://buy.stripe.com/test_abc123" qr_img = qrcode.make(payment_link_url) buffer = BytesIO() qr_img.save(buffer) buffer.seek(0) asset_url = directus.upload_file( file=buffer, mime_type="image/png", filename="qrcode_STATION-001_TXN-UUID-001.png", filetitle="QRCode_STATION-001_TXN-UUID-001", ) ``` ### Response #### Success Response (200) - **asset_url** (string) - The public URL of the uploaded asset. #### Response Example ```json { "asset_url": "http://localhost:8055/assets/some-uuid-from-directus" } ``` ``` -------------------------------- ### Configure Message Broker Exchange Type Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Define the exchange type for the Message Broker. The default is 'topic'. ```bash MESSAGE_BROKER_EXCHANGE_TYPE="headers" ``` -------------------------------- ### Set CitrineOS Message API URL Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Configure the base URL for the CitrineOS Message API. Ensure no trailing slash is included. ```bash CITRINEOS_MESSAGE_API_URL="http://localhost:8080/ocpp" ``` -------------------------------- ### Configure Message Broker Port Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Specify the port number for the Message Broker connection. This is a required setting. ```bash MESSAGE_BROKER_PORT=5672 ``` -------------------------------- ### Register OCPP Authorization with CitrineOS Source: https://context7.com/citrineos/citrineos-payment/llms.txt Creates an OCPP authorization token in CitrineOS via its Data API. Associates additional info like PaymentIntentId or TransactionId. Returns the authorization payload on success or None on failure. ```python from integrations.citrineos.citrineos import CitrineOSIntegration from integrations.directus.directus import DirectusIntegration file_integration = DirectusIntegration( url="http://localhost:8055", email="admin@CitrineOS.com", password="CitrineOS!", ) ocpp = CitrineOSIntegration(file_integration) # Web portal flow: link checkout ID to payment intent authorization = await ocpp.create_authorization( idToken="PAYMENT-PREFIX-42", # OCPP_REMOTESTART_IDTAG_PREFIX + checkout_id idTokenType="Central", additionalInfo=[ ("pi_3ExamplePaymentIntent", "PaymentIntentId"), ], ) # Returns: # { # "idToken": { # "idToken": "PAYMENT-PREFIX-42", # "type": "Central", # "additionalInfo": [{"additionalIdToken": "pi_3Example...", "type": "PaymentIntentId"}] # }, # "idTokenInfo": {"status": "Accepted"} # } ``` -------------------------------- ### Configure Message Broker Exchange Name Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Specify the name of the exchange to be used on the Message Broker. This is a required setting. ```bash MESSAGE_BROKER_EXCHANGE_NAME="citrineos" ``` -------------------------------- ### Basic HTML Structure and Styling Source: https://github.com/citrineos/citrineos-payment/blob/main/frontend/public/index.html This CSS snippet ensures that the main content areas (html and body) take up at least the full height of the viewport. This is a common practice for full-page applications. ```css html {min-height: 100%}; body {min-height: 100%} ``` -------------------------------- ### Configure Message Broker Event Consumer Queue Name Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Set the name of the queue where the service will listen for events, such as TransactionEvents. This is a required setting. ```bash MESSAGE_BROKER_EVENT_CONSUMER_QUEUE_NAME="paymentService" ``` -------------------------------- ### Retrieve Tariff by ID Source: https://context7.com/citrineos/citrineos-payment/llms.txt Fetches the pricing details for a specific tariff, including rates per kWh, per minute, per session, currency, tax rate, and Stripe authorization amount. This is used to calculate costs for charging sessions. ```APIDOC ## GET /api/tariffs/{id} ### Description Returns the pricing structure for a connector: per-kWh, per-minute, per-session rates, currency, tax rate, Stripe authorization amount, and payment fee percentage. ### Method GET ### Endpoint /api/tariffs/{id} ### Parameters #### Path Parameters - **id** (integer) - Required - The unique internal ID of the tariff. ### Response #### Success Response (200) - **id** (integer) - The internal ID of the tariff. - **price_kwh** (number) - The price per kilowatt-hour, or null if not applicable. - **price_minute** (number) - The price per minute, or null if not applicable. - **price_session** (number) - A fixed price per charging session, or null if not applicable. - **currency** (string) - The currency of the prices (e.g., "EUR"). - **tax_rate** (number) - The applicable tax rate as a percentage. - **authorization_amount** (number) - The amount to authorize on the customer's card upfront. #### Error Response (404 Not Found) - **detail** (string) - "Tariff not found" ### Request Example ```bash curl -X GET http://localhost:9010/api/tariffs/1 ``` ### Response Example #### Success Response (200) ```json { "id": 1, "price_kwh": 0.39, "price_minute": null, "price_session": 1.00, "currency": "EUR", "tax_rate": 19.0, "authorization_amount": 30.0 } ``` #### Error Response (404 Not Found) ```json { "detail": "Tariff not found" } ``` ``` -------------------------------- ### Set Client API URL Environment Variable Source: https://github.com/citrineos/citrineos-payment/blob/main/frontend/public/index.html This line sets the global JavaScript variable CLIENT_API_URL using a templated value from the environment. Ensure this variable is correctly configured in your build process. ```javascript window.CLIENT_API_URL = "{{ CLIENT_API_URL }}"; ``` -------------------------------- ### Upload QR Code to Directus CMS Source: https://context7.com/citrineos/citrineos-payment/llms.txt Uploads a QR code image to Directus and returns a public URL. This is used in the scan-and-charge flow to display the QR code on the charger screen. ```python from integrations.directus.directus import DirectusIntegration from io import BytesIO import qrcode directus = DirectusIntegration( url="http://localhost:8055", email="admin@CitrineOS.com", password="CitrineOS!", ) payment_link_url = "https://buy.stripe.com/test_abc123" qr_img = qrcode.make(payment_link_url) buffer = BytesIO() qr_img.save(buffer) buffer.seek(0) asset_url = directus.upload_file( file=buffer, mime_type="image/png", filename="qrcode_STATION-001_TXN-UUID-001.png", filetitle="QRCode_STATION-001_TXN-UUID-001", ) # Returns: "http://localhost:8055/assets/some-uuid-from-directus" # This URL is passed to CitrineOS setDisplayMessage as the QR code URI ``` -------------------------------- ### CitrineOSIntegration.create_authorization(...) Source: https://context7.com/citrineos/citrineos-payment/llms.txt Registers an OCPP authorization token in CitrineOS via its Data API, associating additional information like `PaymentIntentId` or `TransactionId`. ```APIDOC ## CitrineOSIntegration.create_authorization(...) — Register OCPP Authorization ### Description Creates an OCPP authorization token in CitrineOS via its Data API (`PUT /evdriver/authorization`). Associates additional info such as `PaymentIntentId` or `TransactionId` to the token. Returns the authorization payload on success or `None` on failure. ### Parameters #### Path Parameters - **idToken** (string) - Required - The token ID to create, typically prefixed (e.g., `OCPP_REMOTESTART_IDTAG_PREFIX + checkout_id`). - **idTokenType** (string) - Required - The type of the token (e.g., `Central`). - **additionalInfo** (list of tuples) - Optional - A list of tuples, where each tuple contains `(value, type)` for additional information (e.g., `("pi_3ExamplePaymentIntent", "PaymentIntentId")`). ### Response #### Success Response Returns the authorization payload on success. ### Response Example ```json { "idToken": { "idToken": "PAYMENT-PREFIX-42", "type": "Central", "additionalInfo": [{"additionalIdToken": "pi_3Example...", "type": "PaymentIntentId"}] }, "idTokenInfo": {"status": "Accepted"} } ``` ### Usage Example ```python from integrations.citrineos.citrineos import CitrineOSIntegration from integrations.directus.directus import DirectusIntegration file_integration = DirectusIntegration( url="http://localhost:8055", email="admin@CitrineOS.com", password="CitrineOS!", ) ocpp = CitrineOSIntegration(file_integration) # Web portal flow: link checkout ID to payment intent authorization = await ocpp.create_authorization( idToken="PAYMENT-PREFIX-42", # OCPP_REMOTESTART_IDTAG_PREFIX + checkout_id idTokenType="Central", additionalInfo=[ ("pi_3ExamplePaymentIntent", "PaymentIntentId"), ], ) ``` ``` -------------------------------- ### Configure Stripe Endpoint Secret for Connect Webhooks Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Provide the secret for verifying Stripe webhooks of endpoint-type 'Connect'. This secret must be configured in Stripe. ```bash STRIPE_ENDPOINT_SECRET_CONNECT="whsec_some-stripe-signing-secret" ``` -------------------------------- ### RabbitMQ OCPP Event Consumer Source: https://context7.com/citrineos/citrineos-payment/llms.txt This async event loop consumes OCPP events from RabbitMQ. It's registered automatically on FastAPI startup and filters for TransactionEvent and StatusNotification. ```python # Registered automatically in main.py on startup: @app.on_event("startup") async def startup_event(): loop = get_event_loop() loop.create_task(coro=ocpp_integration.receive_events()) ``` ```json # Incoming RabbitMQ message format (JSON body): # TransactionEvent: { "action": "TransactionEvent", "payload": { "eventType": "Started", # or "Updated" / "Ended" "triggerReason": "RemoteStart", "timestamp": "2024-06-01T10:00:00Z", "transactionInfo": { "transactionId": "TXN-UUID-001", "remoteStartId": 42 }, "meterValue": [ { "sampledValue": [ { "value": 7400, "measurand": "Energy.Active.Import.Register", "unitOfMeasure": {"unit": "Wh"} } ] } ] } } ``` ```json # StatusNotification: { "action": "StatusNotification", "payload": { "evseId": 1, "connectorId": 1, "connectorStatus": "Available", "timestamp": "2024-06-01T09:55:00Z" } } # Headers must include: { "action": "TransactionEvent"/"StatusNotification", "state": "1" } ``` -------------------------------- ### Configure Stripe Endpoint Secret for Account Webhooks Source: https://github.com/citrineos/citrineos-payment/blob/main/README.md Provide the secret for verifying Stripe webhooks of endpoint-type 'Account'. This secret must be configured in Stripe. ```bash STRIPE_ENDPOINT_SECRET_ACCOUNT="whsec_some-stripe-signing-secret" ``` -------------------------------- ### Send OCPP Command via CitrineOS Message API Source: https://context7.com/citrineos/citrineos-payment/llms.txt Use this to trigger OCPP actions for a specific station and tenant. Ensure the URL path and JSON payload are correctly formatted for the desired action. ```python # Trigger remote start for a charging session response = ocpp.send_citrineos_message( station_id="STATION-001", tenant_id="T01", url_path="evdriver/requestStartTransaction", json_payload={ "remoteStartId": 42, "idToken": { "idToken": "PAYMENT-PREFIX-42", "type": "Central", "additionalInfo": [] }, "evseId": 1, }, ) # response.status_code == 200 and response.json().get("success") == True → Accepted # response.status_code != 200 → Rejected ``` ```python # Set QR code display message on charger screen (scan-and-charge flow) ocpp.send_citrineos_message( station_id="STATION-001", tenant_id="T01", url_path="configuration/setDisplayMessage", json_payload={ "message": { "id": 5, "priority": "AlwaysFront", "transactionId": "TXN-UUID-001", "message": { "format": "URI", "content": "http://directus.example.com/assets/qrcode-uuid.png" } } }, ) ``` -------------------------------- ### Transaction Summary Cost Calculation Source: https://context7.com/citrineos/citrineos-payment/llms.txt A pure-Python model for calculating session cost components from raw data. Uses py-moneyed for monetary arithmetic. Cost properties are computed lazily and return None if the underlying rate is not configured. ```python from model.transaction_summary import TransactionSummary from datetime import datetime, timezone summary = TransactionSummary( kwh=7.4, start_time=datetime(2024, 6, 1, 10, 0, 0, tzinfo=timezone.utc), end_time=datetime(2024, 6, 1, 10, 45, 0, tzinfo=timezone.utc), currency="EUR", tax_rate=19.0, payment_fee=1.5, price_kwh=0.39, price_minute=None, # time-based billing disabled price_session=1.00, ) summary.energy_costs # Money("2.886", "EUR") summary.time_consumption_min # Decimal("45.0") summary.time_costs # None (price_minute is None) summary.session_costs # Money("1.00", "EUR") summary.total_costs_net # Money("3.886", "EUR") summary.tax_costs # Money("0.738...", "EUR") summary.total_costs_gross # Money("4.624...", "EUR") summary.payment_costs_gross # Money("0.069...", "EUR") ``` -------------------------------- ### Retrieve EVSE by ID Source: https://context7.com/citrineos/citrineos-payment/llms.txt Fetches details about a specific Electric Vehicle Supply Equipment (EVSE), including its status, connector information, and associated tariff. Use this to check charger availability before initiating a payment. ```bash curl -X GET http://localhost:9010/api/evses/EVSE-001 ``` ```json { "id": 1, "evse_id": "EVSE-001", "ocpp_evse_id": 1, "status": "Available", "location_id": 1, "connectors": [ { "id": 1, "connector_id": "CONNECTOR-001", "power_type": "AC_3_PHASE", "max_voltage": 400, "max_amperage": 32, "tariff_id": 1 } ] } ``` ```json { "detail": "EVSE not found" } ``` -------------------------------- ### Handle Stripe Webhook Events Source: https://context7.com/citrineos/citrineos-payment/llms.txt Receives and validates Stripe webhook events, specifically 'checkout.session.completed'. Handles both web portal and scan-and-charge flows. Verifies signature using STRIPE_ENDPOINT_SECRET_CONNECT for Connect-type events. ```bash # Stripe sends this automatically; simulate locally with Stripe CLI: stripe listen --forward-to http://localhost:9010/api/webhooks/stripe ``` ```json # Example event body (checkout.session.completed — web portal flow): { "type": "checkout.session.completed", "data": { "object": { "payment_intent": "pi_3ExamplePaymentIntent", "amount_total": 3000, "metadata": { "checkoutId": "42" } } } } ``` ```json # Example event body (scan-and-charge flow — includes stationId and transactionId): { "type": "checkout.session.completed", "data": { "object": { "payment_intent": "pi_3ExamplePaymentIntent", "amount_total": 3000, "metadata": { "checkoutId": "42", "stationId": "STATION-001", "transactionId": "TXN-UUID-001" } } } } ``` ```text # On success: returns {} with HTTP 200 # On signature failure: raises stripe.error.SignatureVerificationError (HTTP 400) # On missing checkout: HTTP 404 { "detail": "No checkout found for payment intent" } ``` -------------------------------- ### Public URL Image Placeholder Source: https://github.com/citrineos/citrineos-payment/blob/main/frontend/public/index.html This is an HTML image tag that uses a public URL placeholder. It's typically used for displaying logos or other static assets included in the public directory of a web application. ```html ```