Skip to content

Commit

Permalink
Adding support for the new Usage Billing APIs (#1397)
Browse files Browse the repository at this point in the history
  • Loading branch information
prathmesh-stripe authored Sep 27, 2024
1 parent 791fc11 commit 8fc7af7
Show file tree
Hide file tree
Showing 62 changed files with 2,640 additions and 224 deletions.
11 changes: 11 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Running an example

From the examples folder, run:
`PYTHONPATH=../ python your_example.py`

## Adding a new example

1. Clone new_example.py
2. Implement your example
3. Run it (as per above)
4. 👍
44 changes: 44 additions & 0 deletions examples/meter_event_stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from datetime import datetime, timezone
import stripe

# Global variable for the meter event session
meter_event_session = None


def refresh_meter_event_session(api_key):
global meter_event_session

# Check if the session is None or expired
if meter_event_session is None or datetime.fromisoformat(
meter_event_session["expires_at"]
) <= datetime.now(timezone.utc):
# Create a new meter event session if the existing session has expired
client = stripe.StripeClient(api_key)
meter_event_session = client.v2.billing.meter_event_session.create()


def send_meter_event(meter_event, api_key):
# Refresh the meter event session if necessary
refresh_meter_event_session(api_key)
if not meter_event_session:
raise RuntimeError("Unable to refresh meter event session")

# Create a meter event with the current session's authentication token
client = stripe.StripeClient(meter_event_session["authentication_token"])
client.v2.billing.meter_event_stream.create(
params={"events": [meter_event]}
)


# Set your API key here
api_key = "{{API_KEY}}"
customer_id = "{{CUSTOMER_ID}}"

# Send meter event
send_meter_event(
{
"event_name": "alpaca_ai_tokens",
"payload": {"stripe_customer_id": customer_id, "value": "25"},
},
api_key,
)
8 changes: 8 additions & 0 deletions examples/new_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import stripe

# Set your API key here
api_key = "{{API_KEY}}"

print("Hello world")
# client = stripe.StripeClient(api_key)
# client.v2....
40 changes: 40 additions & 0 deletions examples/stripe_webhook_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import os
from stripe import StripeClient
from stripe.events import V1BillingMeterErrorReportTriggeredEvent

from flask import Flask, request, jsonify

app = Flask(__name__)
api_key = os.environ.get("STRIPE_API_KEY")
webhook_secret = os.environ.get("WEBHOOK_SECRET")

client = StripeClient(api_key)


@app.route("/webhook", methods=["POST"])
def webhook():
webhook_body = request.data
sig_header = request.headers.get("Stripe-Signature")

try:
thin_event = client.parse_thin_event(
webhook_body, sig_header, webhook_secret
)

# Fetch the event data to understand the failure
event = client.v2.core.events.retrieve(thin_event.id)
if isinstance(event, V1BillingMeterErrorReportTriggeredEvent):
# CHECK: fetch_object is present and callable, returning a strongly-typed object (without casting)
meter = event.fetch_related_object()
meter_id = meter.id

# Record the failures and alert your team
# Add your logic here

return jsonify(success=True), 200
except Exception as e:
return jsonify(error=str(e)), 400


if __name__ == "__main__":
app.run(port=4242)
54 changes: 51 additions & 3 deletions stripe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Optional
import sys as _sys
import os
import warnings

# Stripe Python bindings
# API docs at http://stripe.com/docs/api
Expand All @@ -25,29 +26,70 @@
DEFAULT_API_BASE: str = "https://api.stripe.com"
DEFAULT_CONNECT_API_BASE: str = "https://connect.stripe.com"
DEFAULT_UPLOAD_API_BASE: str = "https://files.stripe.com"
DEFAULT_METER_EVENTS_API_BASE: str = "https://meter-events.stripe.com"


api_key: Optional[str] = None
client_id: Optional[str] = None
api_base: str = DEFAULT_API_BASE
connect_api_base: str = DEFAULT_CONNECT_API_BASE
upload_api_base: str = DEFAULT_UPLOAD_API_BASE
meter_events_api_base: str = DEFAULT_METER_EVENTS_API_BASE
api_version: str = _ApiVersion.CURRENT
verify_ssl_certs: bool = True
proxy: Optional[str] = None
default_http_client: Optional["HTTPClient"] = None
app_info: Optional[AppInfo] = None
enable_telemetry: bool = True
max_network_retries: int = 0
max_network_retries: int = 2
ca_bundle_path: str = os.path.join(
os.path.dirname(__file__), "data", "ca-certificates.crt"
)

# Lazily initialized stripe.default_http_client
default_http_client = None
_default_proxy = None


def ensure_default_http_client():
if default_http_client:
_warn_if_mismatched_proxy()
return
_init_default_http_client()


def _init_default_http_client():
global _default_proxy
global default_http_client

# If the stripe.default_http_client has not been set by the user
# yet, we'll set it here. This way, we aren't creating a new
# HttpClient for every request.
default_http_client = new_default_http_client(
verify_ssl_certs=verify_ssl_certs, proxy=proxy
)
_default_proxy = proxy


def _warn_if_mismatched_proxy():
global _default_proxy
from stripe import proxy

if proxy != _default_proxy:
warnings.warn(
"stripe.proxy was updated after sending a "
"request - this is a no-op. To use a different proxy, "
"set stripe.default_http_client to a new client "
"configured with the proxy."
)


# Set to either 'debug' or 'info', controls console logging
log: Optional[Literal["debug", "info"]] = None

# OAuth
from stripe._oauth import OAuth as OAuth
from stripe._oauth_service import OAuthService as OAuthService

# Webhooks
from stripe._webhook import (
Expand All @@ -58,6 +100,8 @@
# StripeClient
from stripe._stripe_client import StripeClient as StripeClient # noqa

from stripe.v2._event import ThinEvent as ThinEvent # noqa


# Sets some basic information about the running application that's sent along
# with API requests. Useful for plugin authors to identify their plugin when
Expand Down Expand Up @@ -180,8 +224,6 @@ def set_app_info(
from stripe import _request_metrics as request_metrics
from stripe._file import File as FileUpload

import warnings

# Python 3.7+ supports module level __getattr__ that allows us to lazy load deprecated modules
# this matters because if we pre-load all modules from api_resources while suppressing warning
# users will never see those warnings
Expand Down Expand Up @@ -218,6 +260,7 @@ def __getattr__(name):
checkout as checkout,
climate as climate,
entitlements as entitlements,
events as events,
financial_connections as financial_connections,
forwarding as forwarding,
identity as identity,
Expand All @@ -229,6 +272,7 @@ def __getattr__(name):
terminal as terminal,
test_helpers as test_helpers,
treasury as treasury,
v2 as v2,
)
from stripe._account import Account as Account
from stripe._account_capability_service import (
Expand Down Expand Up @@ -355,6 +399,9 @@ def __getattr__(name):
from stripe._ephemeral_key_service import (
EphemeralKeyService as EphemeralKeyService,
)
from stripe._error import (
TemporarySessionExpiredError as TemporarySessionExpiredError,
)
from stripe._event import Event as Event
from stripe._event_service import EventService as EventService
from stripe._exchange_rate import ExchangeRate as ExchangeRate
Expand Down Expand Up @@ -530,6 +577,7 @@ def __getattr__(name):
from stripe._usage_record_summary import (
UsageRecordSummary as UsageRecordSummary,
)
from stripe._v2_services import V2Services as V2Services
from stripe._webhook_endpoint import WebhookEndpoint as WebhookEndpoint
from stripe._webhook_endpoint_service import (
WebhookEndpointService as WebhookEndpointService,
Expand Down
2 changes: 1 addition & 1 deletion stripe/_api_mode.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing_extensions import Literal


ApiMode = Literal["V1"]
ApiMode = Literal["V1", "V2"]
Loading

0 comments on commit 8fc7af7

Please sign in to comment.