generated from coulomb/repo-seed
331 lines
12 KiB
Python
331 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from .provider_publication import (
|
|
ProviderMappedArtifact,
|
|
ProviderPublicationPackage,
|
|
PublicationBundle,
|
|
PublishableCommitment,
|
|
PublishableConfiguration,
|
|
PublishableMeter,
|
|
PublishablePrice,
|
|
)
|
|
|
|
|
|
def _lookup_key(*parts: str) -> str:
|
|
return "--".join(
|
|
part.replace(":", "-").replace("_", "-")
|
|
for part in parts
|
|
if part
|
|
)
|
|
|
|
|
|
def _stripe_interval(value: str | None) -> str | None:
|
|
if value is None:
|
|
return None
|
|
normalized = value.lower()
|
|
if normalized in {"monthly", "month"}:
|
|
return "month"
|
|
if normalized in {"yearly", "annual", "year"}:
|
|
return "year"
|
|
return normalized
|
|
|
|
|
|
def _stripe_hints(bundle: PublicationBundle) -> dict[str, Any]:
|
|
return dict(bundle.provider_hints.get("stripe", {}))
|
|
|
|
|
|
def _product_provider_id(bundle: PublicationBundle) -> str:
|
|
hints = _stripe_hints(bundle)
|
|
return hints.get("product_lookup_key") or _lookup_key("product", bundle.product.product_id)
|
|
|
|
|
|
def _meter_provider_id(bundle: PublicationBundle, meter: PublishableMeter) -> str:
|
|
hints = _stripe_hints(bundle)
|
|
if hints.get("meter_name") and len(bundle.meters) == 1:
|
|
return str(hints["meter_name"])
|
|
return _lookup_key("meter", bundle.model_id, meter.meter_id)
|
|
|
|
|
|
def _product_artifact(bundle: PublicationBundle) -> ProviderMappedArtifact:
|
|
hints = _stripe_hints(bundle)
|
|
return ProviderMappedArtifact(
|
|
provider="stripe",
|
|
source_key=bundle.product.key,
|
|
source_kind="product",
|
|
provider_id=_product_provider_id(bundle),
|
|
provider_object_type="product",
|
|
mapping_status="exact",
|
|
payload={
|
|
"lookup_key": _product_provider_id(bundle),
|
|
"name": bundle.product.name,
|
|
"description": bundle.product.description,
|
|
"active": bundle.product.active,
|
|
"metadata": {
|
|
**bundle.product.metadata,
|
|
"collection_method": hints.get("collection_method", "charge_automatically"),
|
|
"source_of_truth": "adaptive-pricing",
|
|
},
|
|
},
|
|
metadata={"model_id": bundle.model_id},
|
|
notes=("Stripe product mapping is direct for catalog identity and metadata.",),
|
|
)
|
|
|
|
|
|
def _meter_artifact(bundle: PublicationBundle, meter: PublishableMeter) -> ProviderMappedArtifact:
|
|
provider_id = _meter_provider_id(bundle, meter)
|
|
return ProviderMappedArtifact(
|
|
provider="stripe",
|
|
source_key=meter.key,
|
|
source_kind="meter",
|
|
provider_id=provider_id,
|
|
provider_object_type="billing_meter",
|
|
mapping_status="exact",
|
|
payload={
|
|
"lookup_key": provider_id,
|
|
"display_name": meter.name,
|
|
"event_name": meter.event_name,
|
|
"default_aggregation": {"formula": meter.aggregation},
|
|
"unit_label": meter.unit,
|
|
"metadata": {
|
|
**meter.metadata,
|
|
"source_of_truth": "adaptive-pricing",
|
|
},
|
|
},
|
|
metadata={"model_id": bundle.model_id},
|
|
notes=("Stripe meter mapping is direct for metered usage identifiers.",),
|
|
)
|
|
|
|
|
|
def _fixed_price_payload(bundle: PublicationBundle, price: PublishablePrice) -> dict[str, Any]:
|
|
payload = {
|
|
"lookup_key": _lookup_key("price", bundle.model_id, price.component_id),
|
|
"product": _product_provider_id(bundle),
|
|
"currency": price.currency.lower(),
|
|
"nickname": price.label,
|
|
"unit_amount_decimal": str(price.amount or "0"),
|
|
"metadata": {
|
|
**price.metadata,
|
|
"source_of_truth": "adaptive-pricing",
|
|
},
|
|
}
|
|
if price.billing_treatment != "one_time" and price.cadence:
|
|
payload["recurring"] = {"interval": _stripe_interval(price.cadence)}
|
|
return payload
|
|
|
|
|
|
def _discount_artifact(bundle: PublicationBundle, price: PublishablePrice) -> ProviderMappedArtifact:
|
|
provider_id = _lookup_key("coupon", bundle.model_id, price.component_id)
|
|
return ProviderMappedArtifact(
|
|
provider="stripe",
|
|
source_key=price.key,
|
|
source_kind="price",
|
|
provider_id=provider_id,
|
|
provider_object_type="coupon",
|
|
mapping_status="approximate",
|
|
payload={
|
|
"lookup_key": provider_id,
|
|
"amount_off_decimal": str(abs(price.amount or 0)),
|
|
"currency": price.currency.lower(),
|
|
"name": price.label,
|
|
"metadata": {
|
|
**price.metadata,
|
|
"source_of_truth": "adaptive-pricing",
|
|
},
|
|
},
|
|
metadata={"model_id": bundle.model_id},
|
|
notes=(
|
|
"Stripe coupons approximate discount components because attachment to subscriptions and eligibility rules lives outside the price object.",
|
|
),
|
|
)
|
|
|
|
|
|
def _usage_price_artifact(
|
|
bundle: PublicationBundle,
|
|
price: PublishablePrice,
|
|
) -> ProviderMappedArtifact:
|
|
provider_id = _lookup_key("price", bundle.model_id, price.component_id)
|
|
if price.meter_key is None or price.unit_price is None:
|
|
return ProviderMappedArtifact(
|
|
provider="stripe",
|
|
source_key=price.key,
|
|
source_kind="price",
|
|
provider_id=provider_id,
|
|
provider_object_type="price",
|
|
mapping_status="unsupported",
|
|
payload={
|
|
"lookup_key": provider_id,
|
|
"reason": "usage component lacks a billable per-unit price or mapped meter",
|
|
},
|
|
metadata={"model_id": bundle.model_id},
|
|
notes=(
|
|
"Stripe publication cannot create a metered price without both a meter and a per-unit charge.",
|
|
),
|
|
)
|
|
|
|
meter_provider_id = _lookup_key("meter", bundle.model_id, price.component_id)
|
|
if len(bundle.meters) == 1:
|
|
meter_provider_id = _meter_provider_id(bundle, bundle.meters[0])
|
|
status = "approximate" if price.included_units not in (None, 0) else "exact"
|
|
notes = [
|
|
"Stripe metered price mapping is direct for per-unit overage billing.",
|
|
]
|
|
if status == "approximate":
|
|
notes.append(
|
|
"Included usage allowance requires supplemental credits, invoice adjustments, or custom entitlement logic outside the Stripe price object."
|
|
)
|
|
return ProviderMappedArtifact(
|
|
provider="stripe",
|
|
source_key=price.key,
|
|
source_kind="price",
|
|
provider_id=provider_id,
|
|
provider_object_type="price",
|
|
mapping_status=status,
|
|
payload={
|
|
"lookup_key": provider_id,
|
|
"product": _product_provider_id(bundle),
|
|
"currency": price.currency.lower(),
|
|
"nickname": price.label,
|
|
"billing_scheme": "per_unit",
|
|
"unit_amount_decimal": str(price.unit_price),
|
|
"recurring": {
|
|
"interval": _stripe_interval(price.cadence) or "month",
|
|
"usage_type": "metered",
|
|
},
|
|
"meter": meter_provider_id,
|
|
"metadata": {
|
|
**price.metadata,
|
|
"included_units": str(price.included_units) if price.included_units is not None else None,
|
|
"source_of_truth": "adaptive-pricing",
|
|
},
|
|
},
|
|
metadata={"model_id": bundle.model_id},
|
|
notes=tuple(notes),
|
|
)
|
|
|
|
|
|
def _price_artifact(bundle: PublicationBundle, price: PublishablePrice) -> ProviderMappedArtifact:
|
|
provider_id = _lookup_key("price", bundle.model_id, price.component_id)
|
|
if price.component_kind == "usage":
|
|
return _usage_price_artifact(bundle, price)
|
|
if price.component_kind == "discount":
|
|
return _discount_artifact(bundle, price)
|
|
|
|
return ProviderMappedArtifact(
|
|
provider="stripe",
|
|
source_key=price.key,
|
|
source_kind="price",
|
|
provider_id=provider_id,
|
|
provider_object_type="price",
|
|
mapping_status="exact",
|
|
payload=_fixed_price_payload(bundle, price),
|
|
metadata={"model_id": bundle.model_id},
|
|
notes=("Stripe price mapping is direct for fixed recurring or one-time charges.",),
|
|
)
|
|
|
|
|
|
def _commitment_artifact(
|
|
bundle: PublicationBundle,
|
|
commitment: PublishableCommitment,
|
|
) -> ProviderMappedArtifact:
|
|
provider_id = _lookup_key("commitment", bundle.model_id, commitment.commitment_id)
|
|
if commitment.kind == "contract_duration":
|
|
return ProviderMappedArtifact(
|
|
provider="stripe",
|
|
source_key=commitment.key,
|
|
source_kind="commitment",
|
|
provider_id=provider_id,
|
|
provider_object_type="metadata_binding",
|
|
mapping_status="approximate",
|
|
payload={
|
|
"lookup_key": provider_id,
|
|
"metadata": {
|
|
**commitment.metadata,
|
|
"contract_duration_value": commitment.value,
|
|
"contract_duration_unit": commitment.unit,
|
|
},
|
|
},
|
|
metadata={"model_id": bundle.model_id},
|
|
notes=(
|
|
"Stripe can store contract duration metadata, but enforcement still relies on subscription schedule policy or external contract workflow.",
|
|
),
|
|
)
|
|
|
|
return ProviderMappedArtifact(
|
|
provider="stripe",
|
|
source_key=commitment.key,
|
|
source_kind="commitment",
|
|
provider_id=provider_id,
|
|
provider_object_type="metadata_binding",
|
|
mapping_status="unsupported",
|
|
payload={
|
|
"lookup_key": provider_id,
|
|
"kind": commitment.kind,
|
|
"value": commitment.value,
|
|
"unit": commitment.unit,
|
|
},
|
|
metadata={"model_id": bundle.model_id},
|
|
notes=(
|
|
"Stripe metadata alone cannot enforce this commitment semantics; it remains an internal pricing and contract artifact.",
|
|
),
|
|
)
|
|
|
|
|
|
def _configuration_artifact(
|
|
bundle: PublicationBundle,
|
|
configuration: PublishableConfiguration,
|
|
) -> ProviderMappedArtifact:
|
|
provider_id = _lookup_key("configuration", configuration.configuration_id)
|
|
return ProviderMappedArtifact(
|
|
provider="stripe",
|
|
source_key=configuration.key,
|
|
source_kind="configuration",
|
|
provider_id=provider_id,
|
|
provider_object_type="metadata_binding",
|
|
mapping_status="approximate",
|
|
payload={
|
|
"lookup_key": provider_id,
|
|
"metadata": {
|
|
**configuration.metadata,
|
|
"configuration_id": configuration.configuration_id,
|
|
"segment": configuration.segment,
|
|
"price_keys": list(configuration.price_keys),
|
|
"commitment_keys": list(configuration.commitment_keys),
|
|
},
|
|
},
|
|
metadata={"model_id": bundle.model_id},
|
|
notes=(
|
|
"Customer or default pricing configurations can be recorded in Stripe metadata, but they are not first-class Stripe catalog objects.",
|
|
),
|
|
)
|
|
|
|
|
|
def map_bundle_to_stripe(bundle: PublicationBundle) -> ProviderPublicationPackage:
|
|
artifacts: list[ProviderMappedArtifact] = [_product_artifact(bundle)]
|
|
artifacts.extend(_meter_artifact(bundle, meter) for meter in bundle.meters)
|
|
artifacts.extend(_price_artifact(bundle, price) for price in bundle.prices)
|
|
artifacts.extend(_commitment_artifact(bundle, commitment) for commitment in bundle.commitments)
|
|
artifacts.extend(
|
|
_configuration_artifact(bundle, configuration)
|
|
for configuration in bundle.configurations
|
|
)
|
|
|
|
notes = [
|
|
"Stripe remains an execution backend; adaptive-pricing stays the source of truth.",
|
|
"Exact mappings create publishable Stripe shadow artifacts; approximate mappings require supplemental operational logic.",
|
|
]
|
|
if _stripe_hints(bundle).get("metered_usage_strategy") == "future_adapter":
|
|
notes.append(
|
|
"The pricing model declares a future metered-usage strategy hint, so Stripe publication keeps the usage allowance semantics descriptive rather than executable."
|
|
)
|
|
|
|
return ProviderPublicationPackage(
|
|
provider="stripe",
|
|
bundle_id=bundle.bundle_id,
|
|
model_id=bundle.model_id,
|
|
model_name=bundle.model_name,
|
|
artifacts=tuple(artifacts),
|
|
notes=tuple(notes),
|
|
)
|