Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 0 additions & 30 deletions .api-sync/unmodeled.json
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,6 @@
"reason": "CreatePayinInput is dead code (allowlist.json: create_evm() takes a bare payin_quote_id str, never this TypedDict), so its one field is unmodeled by construction.",
"owner": "eric@blindpay.com"
},
{
"kind": "property",
"schema": "CreatePayinOut",
"field": "billing_fee_amount",
"reason": "Not modeled on CreateEvmPayinResponse (Payin/GetPayinTrackResponse do model it).",
"owner": "eric@blindpay.com"
},
{
"kind": "property",
"schema": "CreatePayinOut",
Expand Down Expand Up @@ -279,20 +272,6 @@
"reason": "Not modeled on CreatePayinQuoteResponse.",
"owner": "eric@blindpay.com"
},
{
"kind": "property",
"schema": "CreateWalletIn",
"field": "external_id",
"reason": "Optional caller-supplied external reference; not modeled on CreateCustodialWalletInput.",
"owner": "eric@blindpay.com"
},
{
"kind": "property",
"schema": "CreateWalletIn",
"field": "name",
"reason": "REQUIRED by the API (CreateWalletIn.required includes name) but CreateCustodialWalletInput (customer_id, network only) never sends it -- likely a functional bug, not just a documentation gap; flagging for priority triage.",
"owner": "eric@blindpay.com"
},
{
"kind": "property",
"schema": "CustomerOut",
Expand Down Expand Up @@ -1017,15 +996,6 @@
"reason": "Modeled under a different name: CustodialWalletBalanceToken uses `token` where the wire's WalletTokenOut object uses `symbol`.",
"owner": "eric@blindpay.com"
},
{
"kind": "enum",
"enum": "BankAccountType",
"missing_values": [
"saving"
],
"reason": "Live defect, not just typing: the wire's real enum is [checking, saving] (singular); the SDK Literal is [checking, savings] (plural). Confirmed against packages/api-contract in blindpay-v2 and the API's own payout code, which branches on `account_type === 'saving'`. Do not auto-append \"saving\" as a 3rd member; this needs a coordinated rename PR (node and go already use the correct singular spelling).",
"owner": "eric@blindpay.com"
},
{
"kind": "enum",
"enum": "KycStatus",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import List, Optional

from typing_extensions import TypedDict
from typing_extensions import NotRequired, TypedDict

from ..._internal.api_client import InternalApiClient, InternalApiClientSync
from ...types import BlindpayApiResponse, Network
Expand Down Expand Up @@ -30,6 +30,8 @@ class CustodialWalletBalance(TypedDict):
class CreateCustodialWalletInput(TypedDict):
customer_id: str
network: Network
name: str
external_id: NotRequired[Optional[str]]


CreateCustodialWalletResponse = CustodialWallet
Expand Down
6 changes: 4 additions & 2 deletions src/blindpay/resources/payins/payins.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Optional, TypedDict
from typing import List, NotRequired, Optional, TypedDict
from urllib.parse import urlencode

from ..._internal.api_client import InternalApiClient, InternalApiClientSync
Expand Down Expand Up @@ -92,7 +92,7 @@ class Payin(TypedDict):
network: Network
blindpay_bank_details: BankDetails
is_otc: Optional[bool]
billing_fee_amount: Optional[str]
billing_fee_amount: Optional[float]
pse_document_type: Optional[str]
pse_full_name: Optional[str]
pse_payment_link: Optional[str]
Expand Down Expand Up @@ -170,6 +170,7 @@ class GetPayinTrackResponse(TypedDict):
network: Network
blindpay_bank_details: BankDetails
partner_fee_id: Optional[str]
billing_fee_amount: NotRequired[Optional[float]]


class ExportPayinsInput(TypedDict):
Expand All @@ -194,6 +195,7 @@ class CreateEvmPayinResponse(TypedDict):
blindpay_bank_details: BankDetails
customer_id: str
receiver_amount: float
billing_fee_amount: NotRequired[Optional[float]]


class PayinsResource:
Expand Down
8 changes: 4 additions & 4 deletions src/blindpay/types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Generic, Literal, TypeVar, Union
from typing import Generic, Literal, Optional, TypeVar, Union

from typing_extensions import TypedDict

Expand Down Expand Up @@ -45,7 +45,7 @@ class BlindpaySuccessResponse(TypedDict, Generic[T]):
"invoice", "purchase_order", "delivery_slip", "contract", "customs_declaration", "bill_of_lading", "others"
]

BankAccountType = Literal["checking", "savings"]
BankAccountType = Literal["checking", "saving"]

Currency = Literal["USDC", "USDT", "USDB", "BRL", "USD", "MXN", "COP", "ARS"]

Expand Down Expand Up @@ -334,8 +334,8 @@ class PaginationParams(TypedDict, total=False):

class PaginationMetadata(TypedDict):
has_more: bool
next_page: int
prev_page: int
next_page: Optional[str]
prev_page: Optional[str]


class TrackingTransaction(TypedDict):
Expand Down
69 changes: 67 additions & 2 deletions tests/resources/test_custodial_wallets.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ async def test_create_custodial_wallet(self):
{
"customer_id": "re_000000000000",
"network": "solana",
"name": "My Solana Wallet",
}
)

Expand All @@ -36,10 +37,44 @@ async def test_create_custodial_wallet(self):
assert response["data"]["id"] == "cw_000000000000"
assert response["data"]["network"] == "solana"
assert response["data"]["address"] == "So1ana1234567890"
# `name` is required on the wire (CreateWalletIn.required includes it);
# the create() body must actually carry it, not just the type.
mock_request.assert_called_once_with(
"POST",
"/instances/in_000000000000/customers/re_000000000000/wallets",
{"network": "solana"},
{"network": "solana", "name": "My Solana Wallet"},
)

@pytest.mark.asyncio
async def test_create_custodial_wallet_with_external_id(self):
"""external_id is optional on the wire; proves it flows through when
supplied and that name alone (without external_id) still type-checks."""
mocked_wallet = {
"id": "cw_000000000000",
"customer_id": "re_000000000000",
"instance_id": "in_000000000000",
"network": "solana",
"address": "So1ana1234567890",
"created_at": "2025-01-01T00:00:00Z",
}

with patch.object(self.blindpay._api, "_request") as mock_request:
mock_request.return_value = {"data": mocked_wallet, "error": None}

response = await self.blindpay.wallets.custodial.create(
{
"customer_id": "re_000000000000",
"network": "solana",
"name": "My Solana Wallet",
"external_id": "your-database-id",
}
)

assert response["error"] is None
mock_request.assert_called_once_with(
"POST",
"/instances/in_000000000000/customers/re_000000000000/wallets",
{"network": "solana", "name": "My Solana Wallet", "external_id": "your-database-id"},
)

@pytest.mark.asyncio
Expand Down Expand Up @@ -162,6 +197,7 @@ def test_create_custodial_wallet(self):
{
"customer_id": "re_000000000000",
"network": "solana",
"name": "My Solana Wallet",
}
)

Expand All @@ -171,7 +207,36 @@ def test_create_custodial_wallet(self):
mock_request.assert_called_once_with(
"POST",
"/instances/in_000000000000/customers/re_000000000000/wallets",
{"network": "solana"},
{"network": "solana", "name": "My Solana Wallet"},
)

def test_create_custodial_wallet_with_external_id(self):
mocked_wallet = {
"id": "cw_000000000000",
"customer_id": "re_000000000000",
"instance_id": "in_000000000000",
"network": "solana",
"address": "So1ana1234567890",
"created_at": "2025-01-01T00:00:00Z",
}

with patch.object(self.blindpay._api, "_request") as mock_request:
mock_request.return_value = {"data": mocked_wallet, "error": None}

response = self.blindpay.wallets.custodial.create(
{
"customer_id": "re_000000000000",
"network": "solana",
"name": "My Solana Wallet",
"external_id": "your-database-id",
}
)

assert response["error"] is None
mock_request.assert_called_once_with(
"POST",
"/instances/in_000000000000/customers/re_000000000000/wallets",
{"network": "solana", "name": "My Solana Wallet", "external_id": "your-database-id"},
)

def test_list_custodial_wallets(self):
Expand Down
8 changes: 4 additions & 4 deletions tests/resources/test_payins.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ async def test_list_payins(self):
],
"pagination": {
"has_more": True,
"next_page": 3,
"prev_page": 1,
"next_page": "pi_123",
"prev_page": "pi_123",
},
}

Expand Down Expand Up @@ -501,8 +501,8 @@ def test_list_payins(self):
],
"pagination": {
"has_more": True,
"next_page": 3,
"prev_page": 1,
"next_page": "pi_123",
"prev_page": "pi_123",
},
}

Expand Down
8 changes: 4 additions & 4 deletions tests/resources/test_payouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ async def test_list_payouts(self):
],
"pagination": {
"has_more": True,
"next_page": 3,
"prev_page": 1,
"next_page": "pi_123",
"prev_page": "pi_123",
},
}

Expand Down Expand Up @@ -694,8 +694,8 @@ def test_list_payouts(self):
],
"pagination": {
"has_more": True,
"next_page": 3,
"prev_page": 1,
"next_page": "pi_123",
"prev_page": "pi_123",
},
}

Expand Down
Loading
Loading