feat: first subscription
This commit is contained in:
@@ -12,6 +12,8 @@ from lnbits.core.views.callback_api import (
|
||||
handle_square_event,
|
||||
handle_stripe_event,
|
||||
)
|
||||
from lnbits.fiat.square import SquareWallet
|
||||
from lnbits.settings import Settings
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@@ -140,7 +142,9 @@ async def test_callback_api_handles_square_paid_events(mocker):
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_callback_api_handles_subscription_flows_and_validation(mocker):
|
||||
async def test_callback_api_handles_subscription_flows_and_validation(
|
||||
mocker, settings: Settings
|
||||
):
|
||||
user = await create_user_account(
|
||||
Account(
|
||||
id=uuid4().hex,
|
||||
@@ -208,6 +212,98 @@ async def test_callback_api_handles_subscription_flows_and_validation(mocker):
|
||||
)
|
||||
assert create_fiat_invoice_mock.await_count == 2
|
||||
|
||||
await handle_square_event(
|
||||
{
|
||||
"event_id": "evt_square_subscription",
|
||||
"type": "payment.updated",
|
||||
"data": {
|
||||
"object": {
|
||||
"payment": {
|
||||
"id": "PAYMENT_SUB_1",
|
||||
"order_id": "ORDER_SUB_1",
|
||||
"status": "COMPLETED",
|
||||
"amount_money": {"amount": 925, "currency": "USD"},
|
||||
"note": json.dumps(
|
||||
[
|
||||
wallet.id,
|
||||
"members",
|
||||
"subscription_square_1",
|
||||
"link-1",
|
||||
"Square Members",
|
||||
]
|
||||
),
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
assert create_fiat_invoice_mock.await_count == 3
|
||||
square_call = create_fiat_invoice_mock.await_args.kwargs
|
||||
assert square_call["wallet_id"] == wallet.id
|
||||
square_invoice = square_call["invoice_data"]
|
||||
assert square_invoice.fiat_provider == "square"
|
||||
assert square_invoice.amount == 9.25
|
||||
assert square_invoice.memo == "Square Members"
|
||||
assert square_invoice.extra["fiat_method"] == "subscription"
|
||||
assert square_invoice.extra["tag"] == "members"
|
||||
assert (
|
||||
square_invoice.extra["subscription"]["checking_id"] == "payment_PAYMENT_SUB_1"
|
||||
)
|
||||
|
||||
payment.extra = {
|
||||
"subscription_request_id": "subscription_square_1",
|
||||
"tag": "members",
|
||||
"square_subscription_id": "SUBSCRIPTION_1",
|
||||
"link": "link-1",
|
||||
}
|
||||
payment.memo = "Square Members"
|
||||
settings.square_api_endpoint = "https://connect.squareupsandbox.com"
|
||||
settings.square_access_token = "square-token"
|
||||
settings.square_location_id = "LOC123"
|
||||
settings.square_api_version = "2026-01-22"
|
||||
square_provider = SquareWallet()
|
||||
mocker.patch.object(
|
||||
square_provider,
|
||||
"get_payment_for_order",
|
||||
return_value={
|
||||
"id": "PAYMENT_SUB_2",
|
||||
"status": "COMPLETED",
|
||||
"amount_money": {"amount": 925, "currency": "USD"},
|
||||
},
|
||||
)
|
||||
mocker.patch(
|
||||
"lnbits.core.views.callback_api.get_fiat_provider",
|
||||
mocker.AsyncMock(return_value=square_provider),
|
||||
)
|
||||
mocker.patch(
|
||||
"lnbits.core.views.callback_api.get_latest_payment_by_extra_key_value",
|
||||
mocker.AsyncMock(return_value=payment),
|
||||
)
|
||||
|
||||
await handle_square_event(
|
||||
{
|
||||
"event_id": "evt_square_invoice",
|
||||
"type": "invoice.payment_made",
|
||||
"data": {
|
||||
"object": {
|
||||
"invoice": {
|
||||
"order_id": "ORDER_SUB_2",
|
||||
"subscription_id": "SUBSCRIPTION_1",
|
||||
"public_url": "https://square.example/invoice",
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
assert create_fiat_invoice_mock.await_count == 4
|
||||
square_invoice_call = create_fiat_invoice_mock.await_args.kwargs
|
||||
square_invoice = square_invoice_call["invoice_data"]
|
||||
assert square_invoice.extra["square_subscription_id"] == "SUBSCRIPTION_1"
|
||||
assert (
|
||||
square_invoice.extra["subscription"]["payment_request"]
|
||||
== "https://square.example/invoice"
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
ValueError, match="PayPal subscription event missing custom metadata."
|
||||
):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import time
|
||||
from base64 import b64encode
|
||||
from unittest.mock import AsyncMock
|
||||
@@ -25,7 +26,12 @@ from lnbits.core.services.fiat_providers import (
|
||||
test_connection as fiat_provider_connection,
|
||||
)
|
||||
from lnbits.core.services.users import create_user_account
|
||||
from lnbits.fiat.base import FiatInvoiceResponse, FiatPaymentStatus, FiatStatusResponse
|
||||
from lnbits.fiat.base import (
|
||||
FiatInvoiceResponse,
|
||||
FiatPaymentStatus,
|
||||
FiatStatusResponse,
|
||||
FiatSubscriptionPaymentOptions,
|
||||
)
|
||||
from lnbits.fiat.square import SquareWallet
|
||||
from lnbits.settings import Settings
|
||||
from tests.helpers import get_random_string
|
||||
@@ -401,6 +407,125 @@ async def test_square_wallet_create_invoice(settings: Settings):
|
||||
assert payload["order"]["line_items"][0]["base_price_money"]["amount"] == 123
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_square_wallet_create_subscription(settings: Settings):
|
||||
settings.square_api_endpoint = "https://connect.squareupsandbox.com"
|
||||
settings.square_access_token = "square-token"
|
||||
settings.square_location_id = "LOC123"
|
||||
settings.square_api_version = "2026-01-22"
|
||||
settings.square_payment_success_url = "https://lnbits.example/success"
|
||||
|
||||
wallet = SquareWallet()
|
||||
client = MockHTTPClient(
|
||||
[
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
"object": {
|
||||
"type": "SUBSCRIPTION_PLAN_VARIATION",
|
||||
"subscription_plan_variation_data": {
|
||||
"phases": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"pricing": {
|
||||
"type": "STATIC",
|
||||
"price_money": {
|
||||
"amount": 1500,
|
||||
"currency": "USD",
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
),
|
||||
MockHTTPResponse(
|
||||
json_data={
|
||||
"payment_link": {
|
||||
"id": "plink_123",
|
||||
"url": "https://square.link/u/sub_123",
|
||||
}
|
||||
}
|
||||
),
|
||||
]
|
||||
)
|
||||
wallet.client = client # type: ignore[assignment]
|
||||
|
||||
payment_options = FiatSubscriptionPaymentOptions(
|
||||
wallet_id="wallet_1",
|
||||
memo="Monthly Gold",
|
||||
tag="gold",
|
||||
extra={"link": "link-1"},
|
||||
success_url="https://lnbits.example/subscription-success",
|
||||
)
|
||||
|
||||
response = await wallet.create_subscription(
|
||||
"PLAN_VARIATION_123", 1, payment_options
|
||||
)
|
||||
|
||||
assert response.ok is True
|
||||
assert response.checkout_session_url == "https://square.link/u/sub_123"
|
||||
assert response.subscription_request_id is not None
|
||||
assert client.calls[0][0] == "/v2/catalog/object/PLAN_VARIATION_123"
|
||||
assert client.calls[1][0] == "/v2/online-checkout/payment-links"
|
||||
payload = client.calls[1][1]["json"]
|
||||
assert payload["idempotency_key"] == response.subscription_request_id
|
||||
assert payload["quick_pay"]["location_id"] == "LOC123"
|
||||
assert payload["quick_pay"]["price_money"] == {"amount": 1500, "currency": "USD"}
|
||||
assert payload["checkout_options"] == {
|
||||
"redirect_url": "https://lnbits.example/subscription-success",
|
||||
"subscription_plan_id": "PLAN_VARIATION_123",
|
||||
}
|
||||
metadata = json.loads(payload["payment_note"])
|
||||
assert metadata[:3] == ["wallet_1", "gold", response.subscription_request_id]
|
||||
assert metadata[3:] == ["link-1", "Monthly Gold"]
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_square_wallet_create_subscription_invoice(settings: Settings):
|
||||
settings.square_api_endpoint = "https://connect.squareupsandbox.com"
|
||||
settings.square_access_token = "square-token"
|
||||
settings.square_location_id = "LOC123"
|
||||
settings.square_api_version = "2026-01-22"
|
||||
|
||||
wallet = SquareWallet()
|
||||
|
||||
response = await wallet.create_invoice(
|
||||
amount=15,
|
||||
payment_hash="hash123",
|
||||
currency="USD",
|
||||
memo="Square subscription payment",
|
||||
extra={
|
||||
"fiat_method": "subscription",
|
||||
"subscription": {
|
||||
"checking_id": "payment_PAYMENT123",
|
||||
"payment_request": "https://square.example/invoice",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.ok is True
|
||||
assert response.checking_id == "payment_PAYMENT123"
|
||||
assert response.payment_request == "https://square.example/invoice"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_square_wallet_cancel_subscription(settings: Settings):
|
||||
settings.square_api_endpoint = "https://connect.squareupsandbox.com"
|
||||
settings.square_access_token = "square-token"
|
||||
settings.square_location_id = "LOC123"
|
||||
settings.square_api_version = "2026-01-22"
|
||||
|
||||
wallet = SquareWallet()
|
||||
client = MockHTTPClient([MockHTTPResponse(json_data={"subscription": {}})])
|
||||
wallet.client = client # type: ignore[assignment]
|
||||
|
||||
response = await wallet.cancel_subscription("SUBSCRIPTION123", "wallet_1")
|
||||
|
||||
assert response.ok is True
|
||||
assert client.calls[0][0] == "/v2/subscriptions/SUBSCRIPTION123/cancel"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_square_wallet_get_invoice_status(settings: Settings):
|
||||
settings.square_api_endpoint = "https://connect.squareupsandbox.com"
|
||||
|
||||
Reference in New Issue
Block a user