fix: find customer by email
This commit is contained in:
@@ -363,17 +363,70 @@ class RevolutWallet(FiatProvider):
|
||||
# "Revolut subscriptions require customer_id or customer_email.",
|
||||
# )
|
||||
|
||||
customer = await self.get_customer_by_email(payment_options.customer_email)
|
||||
customer_id = customer.get("id") if customer else None
|
||||
if customer_id:
|
||||
return customer_id, None
|
||||
|
||||
customer = await self.create_customer(payment_options.customer_email)
|
||||
customer_id = customer.get("id")
|
||||
if not customer_id:
|
||||
return None, "Server error: missing customer id"
|
||||
return customer_id, None
|
||||
|
||||
async def get_customer_by_email(self, email: str) -> dict[str, Any] | None:
|
||||
page_token = None
|
||||
while True:
|
||||
customer_page = await self.list_customers(page_token=page_token)
|
||||
customer = self._find_customer_by_email(customer_page["customers"], email)
|
||||
if customer:
|
||||
return customer
|
||||
|
||||
page_token = customer_page.get("next_page_token")
|
||||
if not page_token:
|
||||
return None
|
||||
|
||||
async def list_customers(self, page_token: str | None = None) -> dict[str, Any]:
|
||||
params: dict[str, Any] = {"limit": 500}
|
||||
if page_token:
|
||||
params["page_token"] = page_token
|
||||
r = await self.client.get("/api/customers", params=params)
|
||||
r.raise_for_status()
|
||||
return self._extract_customer_page(r.json())
|
||||
|
||||
async def create_customer(self, email: str) -> dict[str, Any]:
|
||||
r = await self.client.post("/api/customers", json={"email": email})
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
@classmethod
|
||||
def _extract_customer_page(cls, data: Any) -> dict[str, Any]:
|
||||
if isinstance(data, list):
|
||||
return {"customers": cls._filter_customer_list(data)}
|
||||
if isinstance(data, dict):
|
||||
for field in ["customers", "data", "items"]:
|
||||
customers = data.get(field)
|
||||
if isinstance(customers, list):
|
||||
return {
|
||||
"customers": cls._filter_customer_list(customers),
|
||||
"next_page_token": data.get("next_page_token"),
|
||||
}
|
||||
return {"customers": []}
|
||||
|
||||
@classmethod
|
||||
def _filter_customer_list(cls, customers: list[Any]) -> list[dict[str, Any]]:
|
||||
return [customer for customer in customers if isinstance(customer, dict)]
|
||||
|
||||
@classmethod
|
||||
def _find_customer_by_email(
|
||||
cls, customers: list[dict[str, Any]], email: str
|
||||
) -> dict[str, Any] | None:
|
||||
normalized_email = email.casefold()
|
||||
for customer in customers:
|
||||
if str(customer.get("email") or "").casefold() == normalized_email:
|
||||
return customer
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def create_webhook(
|
||||
cls,
|
||||
|
||||
@@ -926,6 +926,130 @@ async def test_revolut_wallet_create_subscription(settings: Settings):
|
||||
assert client.calls[1][0] == "/api/orders/ORDER123"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_revolut_wallet_create_subscription_uses_customer_email(
|
||||
settings: Settings,
|
||||
):
|
||||
settings.revolut_api_endpoint = "https://sandbox-merchant.revolut.com"
|
||||
settings.revolut_api_secret_key = "revolut-secret"
|
||||
settings.revolut_api_version = "2026-04-20"
|
||||
|
||||
wallet = RevolutWallet()
|
||||
client = MockHTTPClient(
|
||||
[
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
"customers": [
|
||||
{
|
||||
"id": "CUSTOMER123",
|
||||
"email": "customer@example.com",
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
"id": "SUBSCRIPTION123",
|
||||
"setup_order_id": "ORDER123",
|
||||
}
|
||||
),
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
"id": "ORDER123",
|
||||
"checkout_url": "https://checkout.revolut.com/payment-link/sub_123",
|
||||
}
|
||||
),
|
||||
]
|
||||
)
|
||||
wallet.client = client # type: ignore[assignment]
|
||||
|
||||
payment_options = FiatSubscriptionPaymentOptions(
|
||||
wallet_id="wallet_1",
|
||||
customer_email="customer@example.com",
|
||||
)
|
||||
|
||||
response = await wallet.create_subscription(
|
||||
"PLAN_VARIATION_123", 1, payment_options
|
||||
)
|
||||
|
||||
assert response.ok is True
|
||||
assert client.calls[0][0] == "/api/customers"
|
||||
assert client.calls[0][1]["params"] == {"limit": 500}
|
||||
assert client.calls[1][0] == "/api/subscriptions"
|
||||
assert client.calls[1][1]["json"]["customer_id"] == "CUSTOMER123"
|
||||
assert client.calls[2][0] == "/api/orders/ORDER123"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_revolut_wallet_create_subscription_uses_paginated_customer_email(
|
||||
settings: Settings,
|
||||
):
|
||||
settings.revolut_api_endpoint = "https://sandbox-merchant.revolut.com"
|
||||
settings.revolut_api_secret_key = "revolut-secret"
|
||||
settings.revolut_api_version = "2026-04-20"
|
||||
|
||||
wallet = RevolutWallet()
|
||||
client = MockHTTPClient(
|
||||
[
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
"next_page_token": "PAGE2",
|
||||
"customers": [
|
||||
{
|
||||
"id": "OTHER_CUSTOMER",
|
||||
"email": "other@example.com",
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
"customers": [
|
||||
{
|
||||
"id": "CUSTOMER123",
|
||||
"email": "customer@example.com",
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
"id": "SUBSCRIPTION123",
|
||||
"setup_order_id": "ORDER123",
|
||||
}
|
||||
),
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
"id": "ORDER123",
|
||||
"checkout_url": "https://checkout.revolut.com/payment-link/sub_123",
|
||||
}
|
||||
),
|
||||
]
|
||||
)
|
||||
wallet.client = client # type: ignore[assignment]
|
||||
|
||||
payment_options = FiatSubscriptionPaymentOptions(
|
||||
wallet_id="wallet_1",
|
||||
customer_email="customer@example.com",
|
||||
)
|
||||
|
||||
response = await wallet.create_subscription(
|
||||
"PLAN_VARIATION_123", 1, payment_options
|
||||
)
|
||||
|
||||
assert response.ok is True
|
||||
assert client.calls[0][0] == "/api/customers"
|
||||
assert client.calls[0][1]["params"] == {"limit": 500}
|
||||
assert client.calls[1][0] == "/api/customers"
|
||||
assert client.calls[1][1]["params"] == {
|
||||
"limit": 500,
|
||||
"page_token": "PAGE2",
|
||||
}
|
||||
assert client.calls[2][0] == "/api/subscriptions"
|
||||
assert client.calls[2][1]["json"]["customer_id"] == "CUSTOMER123"
|
||||
assert client.calls[3][0] == "/api/orders/ORDER123"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_revolut_wallet_create_subscription_creates_customer(settings: Settings):
|
||||
settings.revolut_api_endpoint = "https://sandbox-merchant.revolut.com"
|
||||
@@ -935,6 +1059,18 @@ async def test_revolut_wallet_create_subscription_creates_customer(settings: Set
|
||||
wallet = RevolutWallet()
|
||||
client = MockHTTPClient(
|
||||
[
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
"next_page_token": "PAGE2",
|
||||
"customers": [
|
||||
{
|
||||
"id": "OTHER_CUSTOMER",
|
||||
"email": "other@example.com",
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
MockHTTPResponse(json_data={"customers": []}),
|
||||
MockHTTPResponse(json_data={"id": "CUSTOMER123"}),
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
@@ -963,20 +1099,46 @@ async def test_revolut_wallet_create_subscription_creates_customer(settings: Set
|
||||
|
||||
assert response.ok is True
|
||||
assert client.calls[0][0] == "/api/customers"
|
||||
assert client.calls[0][1]["json"] == {"email": "customer@example.com"}
|
||||
assert client.calls[1][0] == "/api/subscriptions"
|
||||
assert client.calls[1][1]["json"]["customer_id"] == "CUSTOMER123"
|
||||
assert client.calls[2][0] == "/api/orders/ORDER123"
|
||||
assert client.calls[0][1]["params"] == {"limit": 500}
|
||||
assert client.calls[1][0] == "/api/customers"
|
||||
assert client.calls[1][1]["params"] == {
|
||||
"limit": 500,
|
||||
"page_token": "PAGE2",
|
||||
}
|
||||
assert client.calls[2][0] == "/api/customers"
|
||||
assert client.calls[2][1]["json"] == {"email": "customer@example.com"}
|
||||
assert client.calls[3][0] == "/api/subscriptions"
|
||||
assert client.calls[3][1]["json"]["customer_id"] == "CUSTOMER123"
|
||||
assert client.calls[4][0] == "/api/orders/ORDER123"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_revolut_wallet_create_subscription_requires_customer(settings: Settings):
|
||||
async def test_revolut_wallet_create_subscription_uses_default_email(
|
||||
settings: Settings,
|
||||
):
|
||||
settings.revolut_api_endpoint = "https://sandbox-merchant.revolut.com"
|
||||
settings.revolut_api_secret_key = "revolut-secret"
|
||||
settings.revolut_api_version = "2026-04-20"
|
||||
|
||||
wallet = RevolutWallet()
|
||||
client = MockHTTPClient([])
|
||||
client = MockHTTPClient(
|
||||
[
|
||||
MockHTTPResponse(json_data={"customers": []}),
|
||||
MockHTTPResponse(json_data={"id": "CUSTOMER123"}),
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
"id": "SUBSCRIPTION123",
|
||||
"setup_order_id": "ORDER123",
|
||||
}
|
||||
),
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
"id": "ORDER123",
|
||||
"checkout_url": "https://checkout.revolut.com/payment-link/sub_123",
|
||||
}
|
||||
),
|
||||
]
|
||||
)
|
||||
wallet.client = client # type: ignore[assignment]
|
||||
|
||||
payment_options = FiatSubscriptionPaymentOptions(wallet_id="wallet_1")
|
||||
@@ -985,12 +1147,12 @@ async def test_revolut_wallet_create_subscription_requires_customer(settings: Se
|
||||
"PLAN_VARIATION_123", 1, payment_options
|
||||
)
|
||||
|
||||
assert response.ok is False
|
||||
assert (
|
||||
response.error_message
|
||||
== "Revolut subscriptions require customer_id or customer_email."
|
||||
)
|
||||
assert client.calls == []
|
||||
assert response.ok is True
|
||||
assert client.calls[0][0] == "/api/customers"
|
||||
assert client.calls[0][1]["params"] == {"limit": 500}
|
||||
assert client.calls[1][0] == "/api/customers"
|
||||
assert client.calls[1][1]["json"] == {"email": "test01@lnbits.com"}
|
||||
assert client.calls[2][1]["json"]["customer_id"] == "CUSTOMER123"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
|
||||
Reference in New Issue
Block a user