From e651c141f4f85f0e5e3b26685a0f2a18036179b3 Mon Sep 17 00:00:00 2001 From: linq-sdks-bot Date: Mon, 27 Jul 2026 17:51:53 +0000 Subject: [PATCH 1/3] feat: add action field to message content for app experiences --- README.md | 70 ++---------- src/linq/resources/chats/chats.py | 10 ++ src/linq/resources/chats/messages.py | 10 ++ src/linq/resources/messages.py | 10 ++ src/linq/types/chat_create_params.py | 5 + src/linq/types/chats/message_send_params.py | 5 + src/linq/types/message_content_param.py | 26 +++-- src/linq/types/message_create_params.py | 5 + tests/api_resources/chats/test_messages.py | 92 +++------------ tests/api_resources/test_chats.py | 74 +++--------- tests/api_resources/test_messages.py | 74 +++--------- tests/test_client.py | 118 ++------------------ uv.lock | 2 +- 13 files changed, 130 insertions(+), 371 deletions(-) diff --git a/README.md b/README.md index 353c698..1a21748 100644 --- a/README.md +++ b/README.md @@ -43,14 +43,7 @@ client = LinqAPIV3( chat = client.chats.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) print(chat.chat) @@ -78,14 +71,7 @@ client = AsyncLinqAPIV3( async def main() -> None: chat = await client.chats.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) print(chat.chat) @@ -123,14 +109,7 @@ async def main() -> None: ) as client: chat = await client.chats.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) print(chat.chat) @@ -253,14 +232,7 @@ client = LinqAPIV3() try: client.chats.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) except linq.APIConnectionError as e: @@ -307,14 +279,7 @@ client = LinqAPIV3( # Or, configure per-request: client.with_options(max_retries=5).chats.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) ``` @@ -341,14 +306,7 @@ client = LinqAPIV3( # Override per-request: client.with_options(timeout=5.0).chats.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) ``` @@ -393,12 +351,7 @@ from linq import LinqAPIV3 client = LinqAPIV3() response = client.chats.with_raw_response.create( from_="+12052535597", - message={ - "parts": [{ - "type": "text", - "value": "Hello! How can I help you today?", - }] - }, + message={}, to=["+12052532136"], ) print(response.headers.get('X-My-Header')) @@ -420,14 +373,7 @@ To stream the response body, use `.with_streaming_response` instead, which requi ```python with client.chats.with_streaming_response.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) as response: print(response.headers.get("X-My-Header")) diff --git a/src/linq/resources/chats/chats.py b/src/linq/resources/chats/chats.py index 536da39..883eced 100644 --- a/src/linq/resources/chats/chats.py +++ b/src/linq/resources/chats/chats.py @@ -326,6 +326,11 @@ def create( separating the "what" (message content) from the "where" (routing fields like from/to). + A message carries EITHER `parts` — text and attachments, which compose into one + bubble — or a single `action`, which invokes an experience inside Linq's + iMessage app. Never both: an app card is the whole message (Apple's `MSMessage` + cannot coexist with text), so copy and a card are two sends, not one. + to: Array of recipient handles (phone numbers in E.164 format or email addresses). For individual chats, provide one recipient. For group chats, provide multiple. @@ -965,6 +970,11 @@ async def create( separating the "what" (message content) from the "where" (routing fields like from/to). + A message carries EITHER `parts` — text and attachments, which compose into one + bubble — or a single `action`, which invokes an experience inside Linq's + iMessage app. Never both: an app card is the whole message (Apple's `MSMessage` + cannot coexist with text), so copy and a card are two sends, not one. + to: Array of recipient handles (phone numbers in E.164 format or email addresses). For individual chats, provide one recipient. For group chats, provide multiple. diff --git a/src/linq/resources/chats/messages.py b/src/linq/resources/chats/messages.py index f993eb1..68ebc24 100644 --- a/src/linq/resources/chats/messages.py +++ b/src/linq/resources/chats/messages.py @@ -216,6 +216,11 @@ def send( separating the "what" (message content) from the "where" (routing fields like from/to). + A message carries EITHER `parts` — text and attachments, which compose into one + bubble — or a single `action`, which invokes an experience inside Linq's + iMessage app. Never both: an app card is the whole message (Apple's `MSMessage` + cannot coexist with text), so copy and a card are two sends, not one. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -428,6 +433,11 @@ async def send( separating the "what" (message content) from the "where" (routing fields like from/to). + A message carries EITHER `parts` — text and attachments, which compose into one + bubble — or a single `action`, which invokes an experience inside Linq's + iMessage app. Never both: an app card is the whole message (Apple's `MSMessage` + cannot coexist with text), so copy and a card are two sends, not one. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request diff --git a/src/linq/resources/messages.py b/src/linq/resources/messages.py index 5078412..eb07537 100644 --- a/src/linq/resources/messages.py +++ b/src/linq/resources/messages.py @@ -178,6 +178,11 @@ def create( separating the "what" (message content) from the "where" (routing fields like from/to). + A message carries EITHER `parts` — text and attachments, which compose into one + bubble — or a single `action`, which invokes an experience inside Linq's + iMessage app. Never both: an app card is the whole message (Apple's `MSMessage` + cannot coexist with text), so copy and a card are two sends, not one. + to: Recipient handles (E.164 phone numbers or email addresses). One handle is a direct chat; multiple handles a group chat. Order-independent — the set identifies the chat. @@ -706,6 +711,11 @@ async def create( separating the "what" (message content) from the "where" (routing fields like from/to). + A message carries EITHER `parts` — text and attachments, which compose into one + bubble — or a single `action`, which invokes an experience inside Linq's + iMessage app. Never both: an app card is the whole message (Apple's `MSMessage` + cannot coexist with text), so copy and a card are two sends, not one. + to: Recipient handles (E.164 phone numbers or email addresses). One handle is a direct chat; multiple handles a group chat. Order-independent — the set identifies the chat. diff --git a/src/linq/types/chat_create_params.py b/src/linq/types/chat_create_params.py index a8c4156..a549e03 100644 --- a/src/linq/types/chat_create_params.py +++ b/src/linq/types/chat_create_params.py @@ -24,6 +24,11 @@ class ChatCreateParams(TypedDict, total=False): Groups all message-related fields together, separating the "what" (message content) from the "where" (routing fields like from/to). + + A message carries EITHER `parts` — text and attachments, which compose into one + bubble — or a single `action`, which invokes an experience inside Linq's + iMessage app. Never both: an app card is the whole message (Apple's `MSMessage` + cannot coexist with text), so copy and a card are two sends, not one. """ to: Required[SequenceNotStr[str]] diff --git a/src/linq/types/chats/message_send_params.py b/src/linq/types/chats/message_send_params.py index f469cc6..25f3fac 100644 --- a/src/linq/types/chats/message_send_params.py +++ b/src/linq/types/chats/message_send_params.py @@ -15,4 +15,9 @@ class MessageSendParams(TypedDict, total=False): Groups all message-related fields together, separating the "what" (message content) from the "where" (routing fields like from/to). + + A message carries EITHER `parts` — text and attachments, which compose into one + bubble — or a single `action`, which invokes an experience inside Linq's + iMessage app. Never both: an app card is the whole message (Apple's `MSMessage` + cannot coexist with text), so copy and a card are two sends, not one. """ diff --git a/src/linq/types/message_content_param.py b/src/linq/types/message_content_param.py index db5405f..450800c 100644 --- a/src/linq/types/message_content_param.py +++ b/src/linq/types/message_content_param.py @@ -154,9 +154,24 @@ class MessageContentParam(TypedDict, total=False): Groups all message-related fields together, separating the "what" (message content) from the "where" (routing fields like from/to). + + A message carries EITHER `parts` — text and attachments, which compose + into one bubble — or a single `action`, which invokes an experience + inside Linq's iMessage app. Never both: an app card is the whole message + (Apple's `MSMessage` cannot coexist with text), so copy and a card are + two sends, not one. + """ + + effect: MessageEffectParam + """iMessage effect to apply to this message (screen or bubble effect)""" + + idempotency_key: str + """ + Optional idempotency key for this message. Use this to prevent duplicate sends + of the same message. """ - parts: Required[Iterable[Part]] + parts: Iterable[Part] """Array of message parts. Each part can be text, media, or link. Parts are displayed in order. Text and @@ -198,15 +213,6 @@ class MessageContentParam(TypedDict, total=False): `POST /v3/attachments` and reference by `attachment_id` or `download_url`. """ - effect: MessageEffectParam - """iMessage effect to apply to this message (screen or bubble effect)""" - - idempotency_key: str - """ - Optional idempotency key for this message. Use this to prevent duplicate sends - of the same message. - """ - preferred_service: ServiceType """Messaging service type""" diff --git a/src/linq/types/message_create_params.py b/src/linq/types/message_create_params.py index f4ff991..ba7947c 100644 --- a/src/linq/types/message_create_params.py +++ b/src/linq/types/message_create_params.py @@ -17,6 +17,11 @@ class MessageCreateParams(TypedDict, total=False): Groups all message-related fields together, separating the "what" (message content) from the "where" (routing fields like from/to). + + A message carries EITHER `parts` — text and attachments, which compose into one + bubble — or a single `action`, which invokes an experience inside Linq's + iMessage app. Never both: an app card is the whole message (Apple's `MSMessage` + cannot coexist with text), so copy and a card are two sends, not one. """ to: Required[SequenceNotStr[str]] diff --git a/tests/api_resources/chats/test_messages.py b/tests/api_resources/chats/test_messages.py index 16786d6..7ce6718 100644 --- a/tests/api_resources/chats/test_messages.py +++ b/tests/api_resources/chats/test_messages.py @@ -76,14 +76,7 @@ def test_path_params_list(self, client: LinqAPIV3) -> None: def test_method_send(self, client: LinqAPIV3) -> None: message = client.chats.messages.send( chat_id="550e8400-e29b-41d4-a716-446655440000", - message={ - "parts": [ - { - "type": "text", - "value": "Hello, world!", - } - ] - }, + message={}, ) assert_matches_type(MessageSendResponse, message, path=["response"]) @@ -93,6 +86,11 @@ def test_method_send_with_all_params(self, client: LinqAPIV3) -> None: message = client.chats.messages.send( chat_id="550e8400-e29b-41d4-a716-446655440000", message={ + "effect": { + "name": "confetti", + "type": "screen", + }, + "idempotency_key": "msg-abc123xyz", "parts": [ { "type": "text", @@ -111,11 +109,6 @@ def test_method_send_with_all_params(self, client: LinqAPIV3) -> None: ], } ], - "effect": { - "name": "confetti", - "type": "screen", - }, - "idempotency_key": "msg-abc123xyz", "preferred_service": "iMessage", "reply_to": { "message_id": "550e8400-e29b-41d4-a716-446655440000", @@ -130,14 +123,7 @@ def test_method_send_with_all_params(self, client: LinqAPIV3) -> None: def test_raw_response_send(self, client: LinqAPIV3) -> None: response = client.chats.messages.with_raw_response.send( chat_id="550e8400-e29b-41d4-a716-446655440000", - message={ - "parts": [ - { - "type": "text", - "value": "Hello, world!", - } - ] - }, + message={}, ) assert response.is_closed is True @@ -150,14 +136,7 @@ def test_raw_response_send(self, client: LinqAPIV3) -> None: def test_streaming_response_send(self, client: LinqAPIV3) -> None: with client.chats.messages.with_streaming_response.send( chat_id="550e8400-e29b-41d4-a716-446655440000", - message={ - "parts": [ - { - "type": "text", - "value": "Hello, world!", - } - ] - }, + message={}, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -173,14 +152,7 @@ def test_path_params_send(self, client: LinqAPIV3) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `chat_id` but received ''"): client.chats.messages.with_raw_response.send( chat_id="", - message={ - "parts": [ - { - "type": "text", - "value": "Hello, world!", - } - ] - }, + message={}, ) @@ -246,14 +218,7 @@ async def test_path_params_list(self, async_client: AsyncLinqAPIV3) -> None: async def test_method_send(self, async_client: AsyncLinqAPIV3) -> None: message = await async_client.chats.messages.send( chat_id="550e8400-e29b-41d4-a716-446655440000", - message={ - "parts": [ - { - "type": "text", - "value": "Hello, world!", - } - ] - }, + message={}, ) assert_matches_type(MessageSendResponse, message, path=["response"]) @@ -263,6 +228,11 @@ async def test_method_send_with_all_params(self, async_client: AsyncLinqAPIV3) - message = await async_client.chats.messages.send( chat_id="550e8400-e29b-41d4-a716-446655440000", message={ + "effect": { + "name": "confetti", + "type": "screen", + }, + "idempotency_key": "msg-abc123xyz", "parts": [ { "type": "text", @@ -281,11 +251,6 @@ async def test_method_send_with_all_params(self, async_client: AsyncLinqAPIV3) - ], } ], - "effect": { - "name": "confetti", - "type": "screen", - }, - "idempotency_key": "msg-abc123xyz", "preferred_service": "iMessage", "reply_to": { "message_id": "550e8400-e29b-41d4-a716-446655440000", @@ -300,14 +265,7 @@ async def test_method_send_with_all_params(self, async_client: AsyncLinqAPIV3) - async def test_raw_response_send(self, async_client: AsyncLinqAPIV3) -> None: response = await async_client.chats.messages.with_raw_response.send( chat_id="550e8400-e29b-41d4-a716-446655440000", - message={ - "parts": [ - { - "type": "text", - "value": "Hello, world!", - } - ] - }, + message={}, ) assert response.is_closed is True @@ -320,14 +278,7 @@ async def test_raw_response_send(self, async_client: AsyncLinqAPIV3) -> None: async def test_streaming_response_send(self, async_client: AsyncLinqAPIV3) -> None: async with async_client.chats.messages.with_streaming_response.send( chat_id="550e8400-e29b-41d4-a716-446655440000", - message={ - "parts": [ - { - "type": "text", - "value": "Hello, world!", - } - ] - }, + message={}, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -343,12 +294,5 @@ async def test_path_params_send(self, async_client: AsyncLinqAPIV3) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `chat_id` but received ''"): await async_client.chats.messages.with_raw_response.send( chat_id="", - message={ - "parts": [ - { - "type": "text", - "value": "Hello, world!", - } - ] - }, + message={}, ) diff --git a/tests/api_resources/test_chats.py b/tests/api_resources/test_chats.py index fbdad2e..58046ba 100644 --- a/tests/api_resources/test_chats.py +++ b/tests/api_resources/test_chats.py @@ -29,14 +29,7 @@ class TestChats: def test_method_create(self, client: LinqAPIV3) -> None: chat = client.chats.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) assert_matches_type(ChatCreateResponse, chat, path=["response"]) @@ -47,6 +40,11 @@ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None: chat = client.chats.create( from_="+12052535597", message={ + "effect": { + "name": "confetti", + "type": "screen", + }, + "idempotency_key": "msg-abc123xyz", "parts": [ { "type": "text", @@ -65,11 +63,6 @@ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None: ], } ], - "effect": { - "name": "confetti", - "type": "screen", - }, - "idempotency_key": "msg-abc123xyz", "preferred_service": "iMessage", "reply_to": { "message_id": "550e8400-e29b-41d4-a716-446655440000", @@ -85,14 +78,7 @@ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None: def test_raw_response_create(self, client: LinqAPIV3) -> None: response = client.chats.with_raw_response.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) @@ -106,14 +92,7 @@ def test_raw_response_create(self, client: LinqAPIV3) -> None: def test_streaming_response_create(self, client: LinqAPIV3) -> None: with client.chats.with_streaming_response.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) as response: assert not response.is_closed @@ -446,14 +425,7 @@ class TestAsyncChats: async def test_method_create(self, async_client: AsyncLinqAPIV3) -> None: chat = await async_client.chats.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) assert_matches_type(ChatCreateResponse, chat, path=["response"]) @@ -464,6 +436,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3) chat = await async_client.chats.create( from_="+12052535597", message={ + "effect": { + "name": "confetti", + "type": "screen", + }, + "idempotency_key": "msg-abc123xyz", "parts": [ { "type": "text", @@ -482,11 +459,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3) ], } ], - "effect": { - "name": "confetti", - "type": "screen", - }, - "idempotency_key": "msg-abc123xyz", "preferred_service": "iMessage", "reply_to": { "message_id": "550e8400-e29b-41d4-a716-446655440000", @@ -502,14 +474,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3) async def test_raw_response_create(self, async_client: AsyncLinqAPIV3) -> None: response = await async_client.chats.with_raw_response.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) @@ -523,14 +488,7 @@ async def test_raw_response_create(self, async_client: AsyncLinqAPIV3) -> None: async def test_streaming_response_create(self, async_client: AsyncLinqAPIV3) -> None: async with async_client.chats.with_streaming_response.create( from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, + message={}, to=["+12052532136"], ) as response: assert not response.is_closed diff --git a/tests/api_resources/test_messages.py b/tests/api_resources/test_messages.py index 9ceb69b..d23a7de 100644 --- a/tests/api_resources/test_messages.py +++ b/tests/api_resources/test_messages.py @@ -27,14 +27,7 @@ class TestMessages: @parametrize def test_method_create(self, client: LinqAPIV3) -> None: message = client.messages.create( - message={ - "parts": [ - { - "type": "text", - "value": "Hi! Thanks for reaching out — how can we help?", - } - ] - }, + message={}, to=["+14155559876"], ) assert_matches_type(MessageCreateResponse, message, path=["response"]) @@ -44,6 +37,11 @@ def test_method_create(self, client: LinqAPIV3) -> None: def test_method_create_with_all_params(self, client: LinqAPIV3) -> None: message = client.messages.create( message={ + "effect": { + "name": "confetti", + "type": "screen", + }, + "idempotency_key": "msg-abc123xyz", "parts": [ { "type": "text", @@ -62,11 +60,6 @@ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None: ], } ], - "effect": { - "name": "confetti", - "type": "screen", - }, - "idempotency_key": "msg-abc123xyz", "preferred_service": "iMessage", "reply_to": { "message_id": "550e8400-e29b-41d4-a716-446655440000", @@ -83,14 +76,7 @@ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None: @parametrize def test_raw_response_create(self, client: LinqAPIV3) -> None: response = client.messages.with_raw_response.create( - message={ - "parts": [ - { - "type": "text", - "value": "Hi! Thanks for reaching out — how can we help?", - } - ] - }, + message={}, to=["+14155559876"], ) @@ -103,14 +89,7 @@ def test_raw_response_create(self, client: LinqAPIV3) -> None: @parametrize def test_streaming_response_create(self, client: LinqAPIV3) -> None: with client.messages.with_streaming_response.create( - message={ - "parts": [ - { - "type": "text", - "value": "Hi! Thanks for reaching out — how can we help?", - } - ] - }, + message={}, to=["+14155559876"], ) as response: assert not response.is_closed @@ -452,14 +431,7 @@ class TestAsyncMessages: @parametrize async def test_method_create(self, async_client: AsyncLinqAPIV3) -> None: message = await async_client.messages.create( - message={ - "parts": [ - { - "type": "text", - "value": "Hi! Thanks for reaching out — how can we help?", - } - ] - }, + message={}, to=["+14155559876"], ) assert_matches_type(MessageCreateResponse, message, path=["response"]) @@ -469,6 +441,11 @@ async def test_method_create(self, async_client: AsyncLinqAPIV3) -> None: async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3) -> None: message = await async_client.messages.create( message={ + "effect": { + "name": "confetti", + "type": "screen", + }, + "idempotency_key": "msg-abc123xyz", "parts": [ { "type": "text", @@ -487,11 +464,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3) ], } ], - "effect": { - "name": "confetti", - "type": "screen", - }, - "idempotency_key": "msg-abc123xyz", "preferred_service": "iMessage", "reply_to": { "message_id": "550e8400-e29b-41d4-a716-446655440000", @@ -508,14 +480,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3) @parametrize async def test_raw_response_create(self, async_client: AsyncLinqAPIV3) -> None: response = await async_client.messages.with_raw_response.create( - message={ - "parts": [ - { - "type": "text", - "value": "Hi! Thanks for reaching out — how can we help?", - } - ] - }, + message={}, to=["+14155559876"], ) @@ -528,14 +493,7 @@ async def test_raw_response_create(self, async_client: AsyncLinqAPIV3) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncLinqAPIV3) -> None: async with async_client.messages.with_streaming_response.create( - message={ - "parts": [ - { - "type": "text", - "value": "Hi! Thanks for reaching out — how can we help?", - } - ] - }, + message={}, to=["+14155559876"], ) as response: assert not response.is_closed diff --git a/tests/test_client.py b/tests/test_client.py index 751abe0..430c066 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -879,16 +879,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, clien with pytest.raises(APITimeoutError): client.chats.with_streaming_response.create( - from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, - to=["+12052532136"], + from_="+12052535597", message={}, to=["+12052532136"] ).__enter__() assert _get_open_connections(client) == 0 @@ -900,16 +891,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client with pytest.raises(APIStatusError): client.chats.with_streaming_response.create( - from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, - to=["+12052532136"], + from_="+12052535597", message={}, to=["+12052532136"] ).__enter__() assert _get_open_connections(client) == 0 @@ -939,18 +921,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v3/chats").mock(side_effect=retry_handler) - response = client.chats.with_raw_response.create( - from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, - to=["+12052532136"], - ) + response = client.chats.with_raw_response.create(from_="+12052535597", message={}, to=["+12052532136"]) assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -975,17 +946,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v3/chats").mock(side_effect=retry_handler) response = client.chats.with_raw_response.create( - from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, - to=["+12052532136"], - extra_headers={"x-stainless-retry-count": Omit()}, + from_="+12052535597", message={}, to=["+12052532136"], extra_headers={"x-stainless-retry-count": Omit()} ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1010,17 +971,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v3/chats").mock(side_effect=retry_handler) response = client.chats.with_raw_response.create( - from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, - to=["+12052532136"], - extra_headers={"x-stainless-retry-count": "42"}, + from_="+12052535597", message={}, to=["+12052532136"], extra_headers={"x-stainless-retry-count": "42"} ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -1868,16 +1819,7 @@ async def test_retrying_timeout_errors_doesnt_leak( with pytest.raises(APITimeoutError): await async_client.chats.with_streaming_response.create( - from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, - to=["+12052532136"], + from_="+12052535597", message={}, to=["+12052532136"] ).__aenter__() assert _get_open_connections(async_client) == 0 @@ -1891,16 +1833,7 @@ async def test_retrying_status_errors_doesnt_leak( with pytest.raises(APIStatusError): await async_client.chats.with_streaming_response.create( - from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, - to=["+12052532136"], + from_="+12052535597", message={}, to=["+12052532136"] ).__aenter__() assert _get_open_connections(async_client) == 0 @@ -1930,18 +1863,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v3/chats").mock(side_effect=retry_handler) - response = await client.chats.with_raw_response.create( - from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, - to=["+12052532136"], - ) + response = await client.chats.with_raw_response.create(from_="+12052535597", message={}, to=["+12052532136"]) assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1966,17 +1888,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v3/chats").mock(side_effect=retry_handler) response = await client.chats.with_raw_response.create( - from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, - to=["+12052532136"], - extra_headers={"x-stainless-retry-count": Omit()}, + from_="+12052535597", message={}, to=["+12052532136"], extra_headers={"x-stainless-retry-count": Omit()} ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -2001,17 +1913,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v3/chats").mock(side_effect=retry_handler) response = await client.chats.with_raw_response.create( - from_="+12052535597", - message={ - "parts": [ - { - "type": "text", - "value": "Hello! How can I help you today?", - } - ] - }, - to=["+12052532136"], - extra_headers={"x-stainless-retry-count": "42"}, + from_="+12052535597", message={}, to=["+12052532136"], extra_headers={"x-stainless-retry-count": "42"} ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" diff --git a/uv.lock b/uv.lock index a5cdde6..a662c66 100644 --- a/uv.lock +++ b/uv.lock @@ -530,7 +530,7 @@ wheels = [ [[package]] name = "linq-python" -version = "0.17.0" +version = "0.18.0" source = { editable = "." } dependencies = [ { name = "anyio" }, From e589db270708747444492759a550da6e21edfd96 Mon Sep 17 00:00:00 2001 From: linq-sdks-bot Date: Wed, 29 Jul 2026 16:45:08 +0000 Subject: [PATCH 2/3] fix: clarify contact card creation and update behavior --- src/linq/resources/contact_card.py | 42 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/linq/resources/contact_card.py b/src/linq/resources/contact_card.py index c1e2e6e..1de54ac 100644 --- a/src/linq/resources/contact_card.py +++ b/src/linq/resources/contact_card.py @@ -71,12 +71,12 @@ def create( This endpoint is intended for initial, one-time setup only. - The contact card is stored in an inactive state first. Once it's applied - successfully, it is activated and `is_active` is returned as `true`. On failure, - `is_active` is `false`. + If setup does not complete, the response is `500` (`2022`) — call this endpoint + again. - **Note:** To update an existing contact card after setup, use - `PATCH /v3/contact_card` instead. + **Note:** once a card is active, this endpoint returns `409` (`2014`) so an + existing card is never overwritten by accident. Use `PATCH /v3/contact_card` to + change it. Args: first_name: First name for the contact card. Required. @@ -168,13 +168,14 @@ def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SetContactCard: """ - Partially updates an existing active contact card for a phone number. + Partially updates the contact card for a phone number. - Fetches the current active contact card and merges the provided fields. Only - fields present in the request body are updated; omitted fields retain their - existing values. + Fetches the current contact card and merges the provided fields. Only fields + present in the request body are updated; omitted fields retain their existing + values. - Requires an active contact card to exist for the phone number. + If the update does not complete, the response is `500` (`2022`) — call this + endpoint again. Args: phone_number: E.164 phone number of the contact card to update @@ -265,12 +266,12 @@ async def create( This endpoint is intended for initial, one-time setup only. - The contact card is stored in an inactive state first. Once it's applied - successfully, it is activated and `is_active` is returned as `true`. On failure, - `is_active` is `false`. + If setup does not complete, the response is `500` (`2022`) — call this endpoint + again. - **Note:** To update an existing contact card after setup, use - `PATCH /v3/contact_card` instead. + **Note:** once a card is active, this endpoint returns `409` (`2014`) so an + existing card is never overwritten by accident. Use `PATCH /v3/contact_card` to + change it. Args: first_name: First name for the contact card. Required. @@ -362,13 +363,14 @@ async def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SetContactCard: """ - Partially updates an existing active contact card for a phone number. + Partially updates the contact card for a phone number. - Fetches the current active contact card and merges the provided fields. Only - fields present in the request body are updated; omitted fields retain their - existing values. + Fetches the current contact card and merges the provided fields. Only fields + present in the request body are updated; omitted fields retain their existing + values. - Requires an active contact card to exist for the phone number. + If the update does not complete, the response is `500` (`2022`) — call this + endpoint again. Args: phone_number: E.164 phone number of the contact card to update From 448768eeb5f3e7d70abcba2060ac1e6619597459 Mon Sep 17 00:00:00 2001 From: linq-sdks-bot Date: Wed, 29 Jul 2026 20:23:00 +0000 Subject: [PATCH 3/3] feat: add agentcard payment provider api --- .stats.yml | 2 +- api.md | 56 +++ src/linq/_client.py | 296 ++++++++++- src/linq/resources/__init__.py | 56 +++ src/linq/resources/experiences.py | 235 +++++++++ src/linq/resources/payment_handles.py | 459 ++++++++++++++++++ src/linq/resources/payment_providers.py | 274 +++++++++++ src/linq/resources/payment_requests.py | 8 +- src/linq/resources/payments.py | 458 +++++++++++++++++ src/linq/types/__init__.py | 10 + src/linq/types/chat.py | 8 + src/linq/types/chat_create_response.py | 8 + src/linq/types/chat_created_webhook_event.py | 8 + src/linq/types/experience_list_response.py | 45 ++ .../types/experience_retrieve_response.py | 41 ++ src/linq/types/message_content_param.py | 49 +- .../types/message_edited_webhook_event.py | 8 + src/linq/types/message_event_v2.py | 8 + src/linq/types/payment.py | 40 ++ src/linq/types/payment_create_params.py | 29 ++ .../types/payment_credentials_response.py | 25 + src/linq/types/payment_handle_connection.py | 21 + .../types/payment_handle_verify_params.py | 15 + src/linq/types/payment_provider.py | 14 + .../types/payment_provider_connect_params.py | 12 + .../payment_provider_connect_response.py | 16 + tests/api_resources/chats/test_messages.py | 10 + tests/api_resources/test_chats.py | 10 + tests/api_resources/test_experiences.py | 164 +++++++ tests/api_resources/test_messages.py | 10 + tests/api_resources/test_payment_handles.py | 376 ++++++++++++++ tests/api_resources/test_payment_providers.py | 200 ++++++++ tests/api_resources/test_payments.py | 388 +++++++++++++++ 33 files changed, 3340 insertions(+), 19 deletions(-) create mode 100644 src/linq/resources/experiences.py create mode 100644 src/linq/resources/payment_handles.py create mode 100644 src/linq/resources/payment_providers.py create mode 100644 src/linq/resources/payments.py create mode 100644 src/linq/types/experience_list_response.py create mode 100644 src/linq/types/experience_retrieve_response.py create mode 100644 src/linq/types/payment.py create mode 100644 src/linq/types/payment_create_params.py create mode 100644 src/linq/types/payment_credentials_response.py create mode 100644 src/linq/types/payment_handle_connection.py create mode 100644 src/linq/types/payment_handle_verify_params.py create mode 100644 src/linq/types/payment_provider.py create mode 100644 src/linq/types/payment_provider_connect_params.py create mode 100644 src/linq/types/payment_provider_connect_response.py create mode 100644 tests/api_resources/test_experiences.py create mode 100644 tests/api_resources/test_payment_handles.py create mode 100644 tests/api_resources/test_payment_providers.py create mode 100644 tests/api_resources/test_payments.py diff --git a/.stats.yml b/.stats.yml index c7c071b..03b0268 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 45 +configured_endpoints: 57 diff --git a/api.md b/api.md index d408acf..18a3ba5 100644 --- a/api.md +++ b/api.md @@ -179,6 +179,62 @@ Methods: - client.payment_requests.list(\*\*params) -> PaymentRequestListResponse - client.payment_requests.cancel(payment_request_id) -> PaymentRequest +# PaymentProviders + +Types: + +```python +from linq.types import PaymentProvider, PaymentProviderConnectResponse +``` + +Methods: + +- client.payment_providers.retrieve(provider) -> PaymentProvider +- client.payment_providers.connect(provider, \*\*params) -> PaymentProviderConnectResponse + +# PaymentHandles + +Types: + +```python +from linq.types import PaymentHandleConnection +``` + +Methods: + +- client.payment_handles.connect(handle) -> PaymentHandleConnection +- client.payment_handles.connection(handle) -> PaymentHandleConnection +- client.payment_handles.revoke(handle) -> PaymentHandleConnection +- client.payment_handles.verify(handle, \*\*params) -> PaymentHandleConnection + +# Payments + +Types: + +```python +from linq.types import Payment, PaymentCredentialsResponse +``` + +Methods: + +- client.payments.create(\*\*params) -> Payment +- client.payments.retrieve(payment_id) -> Payment +- client.payments.cancel(payment_id) -> Payment +- client.payments.credentials(payment_id) -> PaymentCredentialsResponse + +# Experiences + +Types: + +```python +from linq.types import ExperienceRetrieveResponse, ExperienceListResponse +``` + +Methods: + +- client.experiences.retrieve(experience) -> ExperienceRetrieveResponse +- client.experiences.list() -> ExperienceListResponse + # WebhookEvents Types: diff --git a/src/linq/_client.py b/src/linq/_client.py index 05ed097..68bc120 100644 --- a/src/linq/_client.py +++ b/src/linq/_client.py @@ -39,27 +39,35 @@ from .resources import ( chats, messages, + payments, capability, attachments, + experiences, contact_card, phonenumbers, phone_numbers, webhook_events, + payment_handles, available_number, payment_requests, + payment_providers, webhook_subscriptions, ) from .resources.messages import MessagesResource, AsyncMessagesResource + from .resources.payments import PaymentsResource, AsyncPaymentsResource from .resources.webhooks import WebhooksResource, AsyncWebhooksResource from .resources.capability import CapabilityResource, AsyncCapabilityResource from .resources.attachments import AttachmentsResource, AsyncAttachmentsResource from .resources.chats.chats import ChatsResource, AsyncChatsResource + from .resources.experiences import ExperiencesResource, AsyncExperiencesResource from .resources.contact_card import ContactCardResource, AsyncContactCardResource from .resources.phonenumbers import PhonenumbersResource, AsyncPhonenumbersResource from .resources.phone_numbers import PhoneNumbersResource, AsyncPhoneNumbersResource from .resources.webhook_events import WebhookEventsResource, AsyncWebhookEventsResource + from .resources.payment_handles import PaymentHandlesResource, AsyncPaymentHandlesResource from .resources.available_number import AvailableNumberResource, AsyncAvailableNumberResource from .resources.payment_requests import PaymentRequestsResource, AsyncPaymentRequestsResource + from .resources.payment_providers import PaymentProvidersResource, AsyncPaymentProvidersResource from .resources.webhook_subscriptions import WebhookSubscriptionsResource, AsyncWebhookSubscriptionsResource __all__ = [ @@ -467,7 +475,7 @@ def payment_requests(self) -> PaymentRequestsResource: ## Connected accounts (Stripe Standard, direct charges) - Agent Pay runs on **Stripe Connect Standard accounts** using **direct + Payments run on **Stripe Connect Standard accounts** using **direct charges**: the charge is created on *your* connected account and **you are the merchant of record**. That means the money, the payout schedule, the customer relationship, and the compliance surface are all yours — Linq @@ -550,7 +558,7 @@ def payment_requests(self) -> PaymentRequestsResource: no-install checkout sheet. Everywhere else (Android, desktop, iPhones without the App Clip yet) the same URL opens the web checkout, so the link always works. The App Clip experience for your payment links is registered - automatically by Linq and refreshed whenever you update your Agent Pay + automatically by Linq and refreshed whenever you update your payments branding; a newly registered experience can take up to ~24 hours to activate on Apple's side, during which links open the web checkout. @@ -565,6 +573,50 @@ def payment_requests(self) -> PaymentRequestsResource: return PaymentRequestsResource(self) + @cached_property + def payment_providers(self) -> PaymentProvidersResource: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payment_providers import PaymentProvidersResource + + return PaymentProvidersResource(self) + + @cached_property + def payment_handles(self) -> PaymentHandlesResource: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payment_handles import PaymentHandlesResource + + return PaymentHandlesResource(self) + + @cached_property + def payments(self) -> PaymentsResource: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payments import PaymentsResource + + return PaymentsResource(self) + + @cached_property + def experiences(self) -> ExperiencesResource: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.experiences import ExperiencesResource + + return ExperiencesResource(self) + @cached_property def webhook_events(self) -> WebhookEventsResource: """ @@ -1376,7 +1428,7 @@ def payment_requests(self) -> AsyncPaymentRequestsResource: ## Connected accounts (Stripe Standard, direct charges) - Agent Pay runs on **Stripe Connect Standard accounts** using **direct + Payments run on **Stripe Connect Standard accounts** using **direct charges**: the charge is created on *your* connected account and **you are the merchant of record**. That means the money, the payout schedule, the customer relationship, and the compliance surface are all yours — Linq @@ -1459,7 +1511,7 @@ def payment_requests(self) -> AsyncPaymentRequestsResource: no-install checkout sheet. Everywhere else (Android, desktop, iPhones without the App Clip yet) the same URL opens the web checkout, so the link always works. The App Clip experience for your payment links is registered - automatically by Linq and refreshed whenever you update your Agent Pay + automatically by Linq and refreshed whenever you update your payments branding; a newly registered experience can take up to ~24 hours to activate on Apple's side, during which links open the web checkout. @@ -1474,6 +1526,50 @@ def payment_requests(self) -> AsyncPaymentRequestsResource: return AsyncPaymentRequestsResource(self) + @cached_property + def payment_providers(self) -> AsyncPaymentProvidersResource: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payment_providers import AsyncPaymentProvidersResource + + return AsyncPaymentProvidersResource(self) + + @cached_property + def payment_handles(self) -> AsyncPaymentHandlesResource: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payment_handles import AsyncPaymentHandlesResource + + return AsyncPaymentHandlesResource(self) + + @cached_property + def payments(self) -> AsyncPaymentsResource: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payments import AsyncPaymentsResource + + return AsyncPaymentsResource(self) + + @cached_property + def experiences(self) -> AsyncExperiencesResource: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.experiences import AsyncExperiencesResource + + return AsyncExperiencesResource(self) + @cached_property def webhook_events(self) -> AsyncWebhookEventsResource: """ @@ -2219,7 +2315,7 @@ def payment_requests(self) -> payment_requests.PaymentRequestsResourceWithRawRes ## Connected accounts (Stripe Standard, direct charges) - Agent Pay runs on **Stripe Connect Standard accounts** using **direct + Payments run on **Stripe Connect Standard accounts** using **direct charges**: the charge is created on *your* connected account and **you are the merchant of record**. That means the money, the payout schedule, the customer relationship, and the compliance surface are all yours — Linq @@ -2302,7 +2398,7 @@ def payment_requests(self) -> payment_requests.PaymentRequestsResourceWithRawRes no-install checkout sheet. Everywhere else (Android, desktop, iPhones without the App Clip yet) the same URL opens the web checkout, so the link always works. The App Clip experience for your payment links is registered - automatically by Linq and refreshed whenever you update your Agent Pay + automatically by Linq and refreshed whenever you update your payments branding; a newly registered experience can take up to ~24 hours to activate on Apple's side, during which links open the web checkout. @@ -2317,6 +2413,50 @@ def payment_requests(self) -> payment_requests.PaymentRequestsResourceWithRawRes return PaymentRequestsResourceWithRawResponse(self._client.payment_requests) + @cached_property + def payment_providers(self) -> payment_providers.PaymentProvidersResourceWithRawResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payment_providers import PaymentProvidersResourceWithRawResponse + + return PaymentProvidersResourceWithRawResponse(self._client.payment_providers) + + @cached_property + def payment_handles(self) -> payment_handles.PaymentHandlesResourceWithRawResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payment_handles import PaymentHandlesResourceWithRawResponse + + return PaymentHandlesResourceWithRawResponse(self._client.payment_handles) + + @cached_property + def payments(self) -> payments.PaymentsResourceWithRawResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payments import PaymentsResourceWithRawResponse + + return PaymentsResourceWithRawResponse(self._client.payments) + + @cached_property + def experiences(self) -> experiences.ExperiencesResourceWithRawResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.experiences import ExperiencesResourceWithRawResponse + + return ExperiencesResourceWithRawResponse(self._client.experiences) + @cached_property def webhook_events(self) -> webhook_events.WebhookEventsResourceWithRawResponse: """ @@ -2935,7 +3075,7 @@ def payment_requests(self) -> payment_requests.AsyncPaymentRequestsResourceWithR ## Connected accounts (Stripe Standard, direct charges) - Agent Pay runs on **Stripe Connect Standard accounts** using **direct + Payments run on **Stripe Connect Standard accounts** using **direct charges**: the charge is created on *your* connected account and **you are the merchant of record**. That means the money, the payout schedule, the customer relationship, and the compliance surface are all yours — Linq @@ -3018,7 +3158,7 @@ def payment_requests(self) -> payment_requests.AsyncPaymentRequestsResourceWithR no-install checkout sheet. Everywhere else (Android, desktop, iPhones without the App Clip yet) the same URL opens the web checkout, so the link always works. The App Clip experience for your payment links is registered - automatically by Linq and refreshed whenever you update your Agent Pay + automatically by Linq and refreshed whenever you update your payments branding; a newly registered experience can take up to ~24 hours to activate on Apple's side, during which links open the web checkout. @@ -3033,6 +3173,50 @@ def payment_requests(self) -> payment_requests.AsyncPaymentRequestsResourceWithR return AsyncPaymentRequestsResourceWithRawResponse(self._client.payment_requests) + @cached_property + def payment_providers(self) -> payment_providers.AsyncPaymentProvidersResourceWithRawResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payment_providers import AsyncPaymentProvidersResourceWithRawResponse + + return AsyncPaymentProvidersResourceWithRawResponse(self._client.payment_providers) + + @cached_property + def payment_handles(self) -> payment_handles.AsyncPaymentHandlesResourceWithRawResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payment_handles import AsyncPaymentHandlesResourceWithRawResponse + + return AsyncPaymentHandlesResourceWithRawResponse(self._client.payment_handles) + + @cached_property + def payments(self) -> payments.AsyncPaymentsResourceWithRawResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payments import AsyncPaymentsResourceWithRawResponse + + return AsyncPaymentsResourceWithRawResponse(self._client.payments) + + @cached_property + def experiences(self) -> experiences.AsyncExperiencesResourceWithRawResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.experiences import AsyncExperiencesResourceWithRawResponse + + return AsyncExperiencesResourceWithRawResponse(self._client.experiences) + @cached_property def webhook_events(self) -> webhook_events.AsyncWebhookEventsResourceWithRawResponse: """ @@ -3651,7 +3835,7 @@ def payment_requests(self) -> payment_requests.PaymentRequestsResourceWithStream ## Connected accounts (Stripe Standard, direct charges) - Agent Pay runs on **Stripe Connect Standard accounts** using **direct + Payments run on **Stripe Connect Standard accounts** using **direct charges**: the charge is created on *your* connected account and **you are the merchant of record**. That means the money, the payout schedule, the customer relationship, and the compliance surface are all yours — Linq @@ -3734,7 +3918,7 @@ def payment_requests(self) -> payment_requests.PaymentRequestsResourceWithStream no-install checkout sheet. Everywhere else (Android, desktop, iPhones without the App Clip yet) the same URL opens the web checkout, so the link always works. The App Clip experience for your payment links is registered - automatically by Linq and refreshed whenever you update your Agent Pay + automatically by Linq and refreshed whenever you update your payments branding; a newly registered experience can take up to ~24 hours to activate on Apple's side, during which links open the web checkout. @@ -3749,6 +3933,50 @@ def payment_requests(self) -> payment_requests.PaymentRequestsResourceWithStream return PaymentRequestsResourceWithStreamingResponse(self._client.payment_requests) + @cached_property + def payment_providers(self) -> payment_providers.PaymentProvidersResourceWithStreamingResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payment_providers import PaymentProvidersResourceWithStreamingResponse + + return PaymentProvidersResourceWithStreamingResponse(self._client.payment_providers) + + @cached_property + def payment_handles(self) -> payment_handles.PaymentHandlesResourceWithStreamingResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payment_handles import PaymentHandlesResourceWithStreamingResponse + + return PaymentHandlesResourceWithStreamingResponse(self._client.payment_handles) + + @cached_property + def payments(self) -> payments.PaymentsResourceWithStreamingResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payments import PaymentsResourceWithStreamingResponse + + return PaymentsResourceWithStreamingResponse(self._client.payments) + + @cached_property + def experiences(self) -> experiences.ExperiencesResourceWithStreamingResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.experiences import ExperiencesResourceWithStreamingResponse + + return ExperiencesResourceWithStreamingResponse(self._client.experiences) + @cached_property def webhook_events(self) -> webhook_events.WebhookEventsResourceWithStreamingResponse: """ @@ -4367,7 +4595,7 @@ def payment_requests(self) -> payment_requests.AsyncPaymentRequestsResourceWithS ## Connected accounts (Stripe Standard, direct charges) - Agent Pay runs on **Stripe Connect Standard accounts** using **direct + Payments run on **Stripe Connect Standard accounts** using **direct charges**: the charge is created on *your* connected account and **you are the merchant of record**. That means the money, the payout schedule, the customer relationship, and the compliance surface are all yours — Linq @@ -4450,7 +4678,7 @@ def payment_requests(self) -> payment_requests.AsyncPaymentRequestsResourceWithS no-install checkout sheet. Everywhere else (Android, desktop, iPhones without the App Clip yet) the same URL opens the web checkout, so the link always works. The App Clip experience for your payment links is registered - automatically by Linq and refreshed whenever you update your Agent Pay + automatically by Linq and refreshed whenever you update your payments branding; a newly registered experience can take up to ~24 hours to activate on Apple's side, during which links open the web checkout. @@ -4465,6 +4693,50 @@ def payment_requests(self) -> payment_requests.AsyncPaymentRequestsResourceWithS return AsyncPaymentRequestsResourceWithStreamingResponse(self._client.payment_requests) + @cached_property + def payment_providers(self) -> payment_providers.AsyncPaymentProvidersResourceWithStreamingResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payment_providers import AsyncPaymentProvidersResourceWithStreamingResponse + + return AsyncPaymentProvidersResourceWithStreamingResponse(self._client.payment_providers) + + @cached_property + def payment_handles(self) -> payment_handles.AsyncPaymentHandlesResourceWithStreamingResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payment_handles import AsyncPaymentHandlesResourceWithStreamingResponse + + return AsyncPaymentHandlesResourceWithStreamingResponse(self._client.payment_handles) + + @cached_property + def payments(self) -> payments.AsyncPaymentsResourceWithStreamingResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.payments import AsyncPaymentsResourceWithStreamingResponse + + return AsyncPaymentsResourceWithStreamingResponse(self._client.payments) + + @cached_property + def experiences(self) -> experiences.AsyncExperiencesResourceWithStreamingResponse: + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + from .resources.experiences import AsyncExperiencesResourceWithStreamingResponse + + return AsyncExperiencesResourceWithStreamingResponse(self._client.experiences) + @cached_property def webhook_events(self) -> webhook_events.AsyncWebhookEventsResourceWithStreamingResponse: """ diff --git a/src/linq/resources/__init__.py b/src/linq/resources/__init__.py index b0ae102..018caad 100644 --- a/src/linq/resources/__init__.py +++ b/src/linq/resources/__init__.py @@ -16,6 +16,14 @@ MessagesResourceWithStreamingResponse, AsyncMessagesResourceWithStreamingResponse, ) +from .payments import ( + PaymentsResource, + AsyncPaymentsResource, + PaymentsResourceWithRawResponse, + AsyncPaymentsResourceWithRawResponse, + PaymentsResourceWithStreamingResponse, + AsyncPaymentsResourceWithStreamingResponse, +) from .webhooks import WebhooksResource, AsyncWebhooksResource from .capability import ( CapabilityResource, @@ -33,6 +41,14 @@ AttachmentsResourceWithStreamingResponse, AsyncAttachmentsResourceWithStreamingResponse, ) +from .experiences import ( + ExperiencesResource, + AsyncExperiencesResource, + ExperiencesResourceWithRawResponse, + AsyncExperiencesResourceWithRawResponse, + ExperiencesResourceWithStreamingResponse, + AsyncExperiencesResourceWithStreamingResponse, +) from .contact_card import ( ContactCardResource, AsyncContactCardResource, @@ -65,6 +81,14 @@ WebhookEventsResourceWithStreamingResponse, AsyncWebhookEventsResourceWithStreamingResponse, ) +from .payment_handles import ( + PaymentHandlesResource, + AsyncPaymentHandlesResource, + PaymentHandlesResourceWithRawResponse, + AsyncPaymentHandlesResourceWithRawResponse, + PaymentHandlesResourceWithStreamingResponse, + AsyncPaymentHandlesResourceWithStreamingResponse, +) from .available_number import ( AvailableNumberResource, AsyncAvailableNumberResource, @@ -81,6 +105,14 @@ PaymentRequestsResourceWithStreamingResponse, AsyncPaymentRequestsResourceWithStreamingResponse, ) +from .payment_providers import ( + PaymentProvidersResource, + AsyncPaymentProvidersResource, + PaymentProvidersResourceWithRawResponse, + AsyncPaymentProvidersResourceWithRawResponse, + PaymentProvidersResourceWithStreamingResponse, + AsyncPaymentProvidersResourceWithStreamingResponse, +) from .webhook_subscriptions import ( WebhookSubscriptionsResource, AsyncWebhookSubscriptionsResource, @@ -133,6 +165,30 @@ "AsyncPaymentRequestsResourceWithRawResponse", "PaymentRequestsResourceWithStreamingResponse", "AsyncPaymentRequestsResourceWithStreamingResponse", + "PaymentProvidersResource", + "AsyncPaymentProvidersResource", + "PaymentProvidersResourceWithRawResponse", + "AsyncPaymentProvidersResourceWithRawResponse", + "PaymentProvidersResourceWithStreamingResponse", + "AsyncPaymentProvidersResourceWithStreamingResponse", + "PaymentHandlesResource", + "AsyncPaymentHandlesResource", + "PaymentHandlesResourceWithRawResponse", + "AsyncPaymentHandlesResourceWithRawResponse", + "PaymentHandlesResourceWithStreamingResponse", + "AsyncPaymentHandlesResourceWithStreamingResponse", + "PaymentsResource", + "AsyncPaymentsResource", + "PaymentsResourceWithRawResponse", + "AsyncPaymentsResourceWithRawResponse", + "PaymentsResourceWithStreamingResponse", + "AsyncPaymentsResourceWithStreamingResponse", + "ExperiencesResource", + "AsyncExperiencesResource", + "ExperiencesResourceWithRawResponse", + "AsyncExperiencesResourceWithRawResponse", + "ExperiencesResourceWithStreamingResponse", + "AsyncExperiencesResourceWithStreamingResponse", "WebhookEventsResource", "AsyncWebhookEventsResource", "WebhookEventsResourceWithRawResponse", diff --git a/src/linq/resources/experiences.py b/src/linq/resources/experiences.py new file mode 100644 index 0000000..6a5d87e --- /dev/null +++ b/src/linq/resources/experiences.py @@ -0,0 +1,235 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.experience_list_response import ExperienceListResponse +from ..types.experience_retrieve_response import ExperienceRetrieveResponse + +__all__ = ["ExperiencesResource", "AsyncExperiencesResource"] + + +class ExperiencesResource(SyncAPIResource): + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + + @cached_property + def with_raw_response(self) -> ExperiencesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/linq-team/linq-python#accessing-raw-response-data-eg-headers + """ + return ExperiencesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ExperiencesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/linq-team/linq-python#with_streaming_response + """ + return ExperiencesResourceWithStreamingResponse(self) + + def retrieve( + self, + experience: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExperienceRetrieveResponse: + """ + Get one experience + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not experience: + raise ValueError(f"Expected a non-empty value for `experience` but received {experience!r}") + return self._get( + path_template("/v3/experiences/{experience}", experience=experience), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExperienceRetrieveResponse, + ) + + def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExperienceListResponse: + """ + The experiences enabled for your account, with the actions you may invoke on + each and the fields each action accepts. This is the authoritative list — an + action missing here cannot be sent. + """ + return self._get( + "/v3/experiences", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExperienceListResponse, + ) + + +class AsyncExperiencesResource(AsyncAPIResource): + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + + @cached_property + def with_raw_response(self) -> AsyncExperiencesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/linq-team/linq-python#accessing-raw-response-data-eg-headers + """ + return AsyncExperiencesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncExperiencesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/linq-team/linq-python#with_streaming_response + """ + return AsyncExperiencesResourceWithStreamingResponse(self) + + async def retrieve( + self, + experience: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExperienceRetrieveResponse: + """ + Get one experience + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not experience: + raise ValueError(f"Expected a non-empty value for `experience` but received {experience!r}") + return await self._get( + path_template("/v3/experiences/{experience}", experience=experience), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExperienceRetrieveResponse, + ) + + async def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExperienceListResponse: + """ + The experiences enabled for your account, with the actions you may invoke on + each and the fields each action accepts. This is the authoritative list — an + action missing here cannot be sent. + """ + return await self._get( + "/v3/experiences", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExperienceListResponse, + ) + + +class ExperiencesResourceWithRawResponse: + def __init__(self, experiences: ExperiencesResource) -> None: + self._experiences = experiences + + self.retrieve = to_raw_response_wrapper( + experiences.retrieve, + ) + self.list = to_raw_response_wrapper( + experiences.list, + ) + + +class AsyncExperiencesResourceWithRawResponse: + def __init__(self, experiences: AsyncExperiencesResource) -> None: + self._experiences = experiences + + self.retrieve = async_to_raw_response_wrapper( + experiences.retrieve, + ) + self.list = async_to_raw_response_wrapper( + experiences.list, + ) + + +class ExperiencesResourceWithStreamingResponse: + def __init__(self, experiences: ExperiencesResource) -> None: + self._experiences = experiences + + self.retrieve = to_streamed_response_wrapper( + experiences.retrieve, + ) + self.list = to_streamed_response_wrapper( + experiences.list, + ) + + +class AsyncExperiencesResourceWithStreamingResponse: + def __init__(self, experiences: AsyncExperiencesResource) -> None: + self._experiences = experiences + + self.retrieve = async_to_streamed_response_wrapper( + experiences.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + experiences.list, + ) diff --git a/src/linq/resources/payment_handles.py b/src/linq/resources/payment_handles.py new file mode 100644 index 0000000..269bda2 --- /dev/null +++ b/src/linq/resources/payment_handles.py @@ -0,0 +1,459 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..types import payment_handle_verify_params +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.payment_handle_connection import PaymentHandleConnection + +__all__ = ["PaymentHandlesResource", "AsyncPaymentHandlesResource"] + + +class PaymentHandlesResource(SyncAPIResource): + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + + @cached_property + def with_raw_response(self) -> PaymentHandlesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/linq-team/linq-python#accessing-raw-response-data-eg-headers + """ + return PaymentHandlesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> PaymentHandlesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/linq-team/linq-python#with_streaming_response + """ + return PaymentHandlesResourceWithStreamingResponse(self) + + def connect( + self, + handle: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentHandleConnection: + """ + Starts connecting a customer (by phone/email) so an agent can pay on their + behalf. Linq drives the OTP + consent ceremony through the messaging channel; + this returns `pending` and a `connection.created` webhook fires once the + customer completes it. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not handle: + raise ValueError(f"Expected a non-empty value for `handle` but received {handle!r}") + return self._post( + path_template("/v3/payments/handles/{handle}/connect", handle=handle), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentHandleConnection, + ) + + def connection( + self, + handle: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentHandleConnection: + """ + Get a handle's connection status + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not handle: + raise ValueError(f"Expected a non-empty value for `handle` but received {handle!r}") + return self._get( + path_template("/v3/payments/handles/{handle}/connection", handle=handle), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentHandleConnection, + ) + + def revoke( + self, + handle: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentHandleConnection: + """Revokes this partner's grant for the customer. + + Only your grant is removed; the + customer's wallet at the provider is untouched. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not handle: + raise ValueError(f"Expected a non-empty value for `handle` but received {handle!r}") + return self._delete( + path_template("/v3/payments/handles/{handle}/connection", handle=handle), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentHandleConnection, + ) + + def verify( + self, + handle: str, + *, + code: str, + connect_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentHandleConnection: + """ + Completes the ceremony `connect` started: verifies the code, records the + customer's consent, and stores the connection. Returns `connected` on success, + after which payments for this handle no longer need the customer present. + + The code reaches you however your channel works — typically the customer replies + with it in the thread. Codes are single-use and short-lived; if one has expired, + call `connect` again for a fresh `connect_id`. + + Args: + code: The one-time code the customer received. + + connect_id: The id returned by `connect`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not handle: + raise ValueError(f"Expected a non-empty value for `handle` but received {handle!r}") + return self._post( + path_template("/v3/payments/handles/{handle}/verify", handle=handle), + body=maybe_transform( + { + "code": code, + "connect_id": connect_id, + }, + payment_handle_verify_params.PaymentHandleVerifyParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentHandleConnection, + ) + + +class AsyncPaymentHandlesResource(AsyncAPIResource): + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + + @cached_property + def with_raw_response(self) -> AsyncPaymentHandlesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/linq-team/linq-python#accessing-raw-response-data-eg-headers + """ + return AsyncPaymentHandlesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncPaymentHandlesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/linq-team/linq-python#with_streaming_response + """ + return AsyncPaymentHandlesResourceWithStreamingResponse(self) + + async def connect( + self, + handle: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentHandleConnection: + """ + Starts connecting a customer (by phone/email) so an agent can pay on their + behalf. Linq drives the OTP + consent ceremony through the messaging channel; + this returns `pending` and a `connection.created` webhook fires once the + customer completes it. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not handle: + raise ValueError(f"Expected a non-empty value for `handle` but received {handle!r}") + return await self._post( + path_template("/v3/payments/handles/{handle}/connect", handle=handle), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentHandleConnection, + ) + + async def connection( + self, + handle: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentHandleConnection: + """ + Get a handle's connection status + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not handle: + raise ValueError(f"Expected a non-empty value for `handle` but received {handle!r}") + return await self._get( + path_template("/v3/payments/handles/{handle}/connection", handle=handle), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentHandleConnection, + ) + + async def revoke( + self, + handle: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentHandleConnection: + """Revokes this partner's grant for the customer. + + Only your grant is removed; the + customer's wallet at the provider is untouched. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not handle: + raise ValueError(f"Expected a non-empty value for `handle` but received {handle!r}") + return await self._delete( + path_template("/v3/payments/handles/{handle}/connection", handle=handle), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentHandleConnection, + ) + + async def verify( + self, + handle: str, + *, + code: str, + connect_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentHandleConnection: + """ + Completes the ceremony `connect` started: verifies the code, records the + customer's consent, and stores the connection. Returns `connected` on success, + after which payments for this handle no longer need the customer present. + + The code reaches you however your channel works — typically the customer replies + with it in the thread. Codes are single-use and short-lived; if one has expired, + call `connect` again for a fresh `connect_id`. + + Args: + code: The one-time code the customer received. + + connect_id: The id returned by `connect`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not handle: + raise ValueError(f"Expected a non-empty value for `handle` but received {handle!r}") + return await self._post( + path_template("/v3/payments/handles/{handle}/verify", handle=handle), + body=await async_maybe_transform( + { + "code": code, + "connect_id": connect_id, + }, + payment_handle_verify_params.PaymentHandleVerifyParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentHandleConnection, + ) + + +class PaymentHandlesResourceWithRawResponse: + def __init__(self, payment_handles: PaymentHandlesResource) -> None: + self._payment_handles = payment_handles + + self.connect = to_raw_response_wrapper( + payment_handles.connect, + ) + self.connection = to_raw_response_wrapper( + payment_handles.connection, + ) + self.revoke = to_raw_response_wrapper( + payment_handles.revoke, + ) + self.verify = to_raw_response_wrapper( + payment_handles.verify, + ) + + +class AsyncPaymentHandlesResourceWithRawResponse: + def __init__(self, payment_handles: AsyncPaymentHandlesResource) -> None: + self._payment_handles = payment_handles + + self.connect = async_to_raw_response_wrapper( + payment_handles.connect, + ) + self.connection = async_to_raw_response_wrapper( + payment_handles.connection, + ) + self.revoke = async_to_raw_response_wrapper( + payment_handles.revoke, + ) + self.verify = async_to_raw_response_wrapper( + payment_handles.verify, + ) + + +class PaymentHandlesResourceWithStreamingResponse: + def __init__(self, payment_handles: PaymentHandlesResource) -> None: + self._payment_handles = payment_handles + + self.connect = to_streamed_response_wrapper( + payment_handles.connect, + ) + self.connection = to_streamed_response_wrapper( + payment_handles.connection, + ) + self.revoke = to_streamed_response_wrapper( + payment_handles.revoke, + ) + self.verify = to_streamed_response_wrapper( + payment_handles.verify, + ) + + +class AsyncPaymentHandlesResourceWithStreamingResponse: + def __init__(self, payment_handles: AsyncPaymentHandlesResource) -> None: + self._payment_handles = payment_handles + + self.connect = async_to_streamed_response_wrapper( + payment_handles.connect, + ) + self.connection = async_to_streamed_response_wrapper( + payment_handles.connection, + ) + self.revoke = async_to_streamed_response_wrapper( + payment_handles.revoke, + ) + self.verify = async_to_streamed_response_wrapper( + payment_handles.verify, + ) diff --git a/src/linq/resources/payment_providers.py b/src/linq/resources/payment_providers.py new file mode 100644 index 0000000..29c8132 --- /dev/null +++ b/src/linq/resources/payment_providers.py @@ -0,0 +1,274 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..types import payment_provider_connect_params +from .._types import Body, Query, Headers, NotGiven, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.payment_provider import PaymentProvider +from ..types.payment_provider_connect_response import PaymentProviderConnectResponse + +__all__ = ["PaymentProvidersResource", "AsyncPaymentProvidersResource"] + + +class PaymentProvidersResource(SyncAPIResource): + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + + @cached_property + def with_raw_response(self) -> PaymentProvidersResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/linq-team/linq-python#accessing-raw-response-data-eg-headers + """ + return PaymentProvidersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> PaymentProvidersResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/linq-team/linq-python#with_streaming_response + """ + return PaymentProvidersResourceWithStreamingResponse(self) + + def retrieve( + self, + provider: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentProvider: + """ + Returns your organization's onboarding status for a payment provider. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not provider: + raise ValueError(f"Expected a non-empty value for `provider` but received {provider!r}") + return self._get( + path_template("/v3/payments/providers/{provider}", provider=provider), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentProvider, + ) + + def connect( + self, + provider: str, + *, + return_url: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentProviderConnectResponse: + """Begins connecting your organization to a payment provider (e.g. + + `agentcard`). + Returns a hosted URL where an admin authorizes the connection; on completion the + provider redirects back and Linq stores your connected credentials. + + Args: + return_url: Where to send the admin after they authorize the connection. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not provider: + raise ValueError(f"Expected a non-empty value for `provider` but received {provider!r}") + return self._post( + path_template("/v3/payments/providers/{provider}/connect", provider=provider), + body=maybe_transform( + {"return_url": return_url}, payment_provider_connect_params.PaymentProviderConnectParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentProviderConnectResponse, + ) + + +class AsyncPaymentProvidersResource(AsyncAPIResource): + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + + @cached_property + def with_raw_response(self) -> AsyncPaymentProvidersResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/linq-team/linq-python#accessing-raw-response-data-eg-headers + """ + return AsyncPaymentProvidersResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncPaymentProvidersResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/linq-team/linq-python#with_streaming_response + """ + return AsyncPaymentProvidersResourceWithStreamingResponse(self) + + async def retrieve( + self, + provider: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentProvider: + """ + Returns your organization's onboarding status for a payment provider. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not provider: + raise ValueError(f"Expected a non-empty value for `provider` but received {provider!r}") + return await self._get( + path_template("/v3/payments/providers/{provider}", provider=provider), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentProvider, + ) + + async def connect( + self, + provider: str, + *, + return_url: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentProviderConnectResponse: + """Begins connecting your organization to a payment provider (e.g. + + `agentcard`). + Returns a hosted URL where an admin authorizes the connection; on completion the + provider redirects back and Linq stores your connected credentials. + + Args: + return_url: Where to send the admin after they authorize the connection. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not provider: + raise ValueError(f"Expected a non-empty value for `provider` but received {provider!r}") + return await self._post( + path_template("/v3/payments/providers/{provider}/connect", provider=provider), + body=await async_maybe_transform( + {"return_url": return_url}, payment_provider_connect_params.PaymentProviderConnectParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentProviderConnectResponse, + ) + + +class PaymentProvidersResourceWithRawResponse: + def __init__(self, payment_providers: PaymentProvidersResource) -> None: + self._payment_providers = payment_providers + + self.retrieve = to_raw_response_wrapper( + payment_providers.retrieve, + ) + self.connect = to_raw_response_wrapper( + payment_providers.connect, + ) + + +class AsyncPaymentProvidersResourceWithRawResponse: + def __init__(self, payment_providers: AsyncPaymentProvidersResource) -> None: + self._payment_providers = payment_providers + + self.retrieve = async_to_raw_response_wrapper( + payment_providers.retrieve, + ) + self.connect = async_to_raw_response_wrapper( + payment_providers.connect, + ) + + +class PaymentProvidersResourceWithStreamingResponse: + def __init__(self, payment_providers: PaymentProvidersResource) -> None: + self._payment_providers = payment_providers + + self.retrieve = to_streamed_response_wrapper( + payment_providers.retrieve, + ) + self.connect = to_streamed_response_wrapper( + payment_providers.connect, + ) + + +class AsyncPaymentProvidersResourceWithStreamingResponse: + def __init__(self, payment_providers: AsyncPaymentProvidersResource) -> None: + self._payment_providers = payment_providers + + self.retrieve = async_to_streamed_response_wrapper( + payment_providers.retrieve, + ) + self.connect = async_to_streamed_response_wrapper( + payment_providers.connect, + ) diff --git a/src/linq/resources/payment_requests.py b/src/linq/resources/payment_requests.py index 31a20e4..75758f6 100644 --- a/src/linq/resources/payment_requests.py +++ b/src/linq/resources/payment_requests.py @@ -47,7 +47,7 @@ class PaymentRequestsResource(SyncAPIResource): ## Connected accounts (Stripe Standard, direct charges) - Agent Pay runs on **Stripe Connect Standard accounts** using **direct + Payments run on **Stripe Connect Standard accounts** using **direct charges**: the charge is created on *your* connected account and **you are the merchant of record**. That means the money, the payout schedule, the customer relationship, and the compliance surface are all yours — Linq @@ -130,7 +130,7 @@ class PaymentRequestsResource(SyncAPIResource): no-install checkout sheet. Everywhere else (Android, desktop, iPhones without the App Clip yet) the same URL opens the web checkout, so the link always works. The App Clip experience for your payment links is registered - automatically by Linq and refreshed whenever you update your Agent Pay + automatically by Linq and refreshed whenever you update your payments branding; a newly registered experience can take up to ~24 hours to activate on Apple's side, during which links open the web checkout. @@ -433,7 +433,7 @@ class AsyncPaymentRequestsResource(AsyncAPIResource): ## Connected accounts (Stripe Standard, direct charges) - Agent Pay runs on **Stripe Connect Standard accounts** using **direct + Payments run on **Stripe Connect Standard accounts** using **direct charges**: the charge is created on *your* connected account and **you are the merchant of record**. That means the money, the payout schedule, the customer relationship, and the compliance surface are all yours — Linq @@ -516,7 +516,7 @@ class AsyncPaymentRequestsResource(AsyncAPIResource): no-install checkout sheet. Everywhere else (Android, desktop, iPhones without the App Clip yet) the same URL opens the web checkout, so the link always works. The App Clip experience for your payment links is registered - automatically by Linq and refreshed whenever you update your Agent Pay + automatically by Linq and refreshed whenever you update your payments branding; a newly registered experience can take up to ~24 hours to activate on Apple's side, during which links open the web checkout. diff --git a/src/linq/resources/payments.py b/src/linq/resources/payments.py new file mode 100644 index 0000000..b018d4e --- /dev/null +++ b/src/linq/resources/payments.py @@ -0,0 +1,458 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict + +import httpx + +from ..types import payment_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.payment import Payment +from ..types.payment_credentials_response import PaymentCredentialsResponse + +__all__ = ["PaymentsResource", "AsyncPaymentsResource"] + + +class PaymentsResource(SyncAPIResource): + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + + @cached_property + def with_raw_response(self) -> PaymentsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/linq-team/linq-python#accessing-raw-response-data-eg-headers + """ + return PaymentsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> PaymentsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/linq-team/linq-python#with_streaming_response + """ + return PaymentsResourceWithStreamingResponse(self) + + def create( + self, + *, + amount_cents: int, + currency: str, + handle: str, + description: str | Omit = omit, + merchant: payment_create_params.Merchant | Omit = omit, + metadata: Dict[str, str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Payment: + """ + Advances the pay flow for a connected customer handle and returns a `status` + describing where it is (`needs_connection`, `awaiting_user_action`, `ready`, + ...). A payment `id` appears once a card is minted. Idempotent on the + `Idempotency-Key` header. + + Args: + handle: Customer phone (E.164) or email. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/v3/payments", + body=maybe_transform( + { + "amount_cents": amount_cents, + "currency": currency, + "handle": handle, + "description": description, + "merchant": merchant, + "metadata": metadata, + }, + payment_create_params.PaymentCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Payment, + ) + + def retrieve( + self, + payment_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Payment: + """ + Get a payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not payment_id: + raise ValueError(f"Expected a non-empty value for `payment_id` but received {payment_id!r}") + return self._get( + path_template("/v3/payments/{payment_id}", payment_id=payment_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Payment, + ) + + def cancel( + self, + payment_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Payment: + """ + Closes the virtual card and cancels the payment. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not payment_id: + raise ValueError(f"Expected a non-empty value for `payment_id` but received {payment_id!r}") + return self._post( + path_template("/v3/payments/{payment_id}/cancel", payment_id=payment_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Payment, + ) + + def credentials( + self, + payment_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentCredentialsResponse: + """Returns a short-lived handoff for a `ready` payment. + + Fetch the card credentials + **directly from the provider** with the returned `user_token` at `fetch_url` — + the card number never passes through Linq. Do not persist PAN/CVC. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not payment_id: + raise ValueError(f"Expected a non-empty value for `payment_id` but received {payment_id!r}") + return self._get( + path_template("/v3/payments/{payment_id}/credentials", payment_id=payment_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentCredentialsResponse, + ) + + +class AsyncPaymentsResource(AsyncAPIResource): + """ + Let an agent pay on a customer's behalf with a single-use virtual card. + Connect a customer once, then create a payment — a virtual card is minted + scoped to that purchase and the card details are handed back for checkout. + """ + + @cached_property + def with_raw_response(self) -> AsyncPaymentsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/linq-team/linq-python#accessing-raw-response-data-eg-headers + """ + return AsyncPaymentsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncPaymentsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/linq-team/linq-python#with_streaming_response + """ + return AsyncPaymentsResourceWithStreamingResponse(self) + + async def create( + self, + *, + amount_cents: int, + currency: str, + handle: str, + description: str | Omit = omit, + merchant: payment_create_params.Merchant | Omit = omit, + metadata: Dict[str, str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Payment: + """ + Advances the pay flow for a connected customer handle and returns a `status` + describing where it is (`needs_connection`, `awaiting_user_action`, `ready`, + ...). A payment `id` appears once a card is minted. Idempotent on the + `Idempotency-Key` header. + + Args: + handle: Customer phone (E.164) or email. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/v3/payments", + body=await async_maybe_transform( + { + "amount_cents": amount_cents, + "currency": currency, + "handle": handle, + "description": description, + "merchant": merchant, + "metadata": metadata, + }, + payment_create_params.PaymentCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Payment, + ) + + async def retrieve( + self, + payment_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Payment: + """ + Get a payment + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not payment_id: + raise ValueError(f"Expected a non-empty value for `payment_id` but received {payment_id!r}") + return await self._get( + path_template("/v3/payments/{payment_id}", payment_id=payment_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Payment, + ) + + async def cancel( + self, + payment_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> Payment: + """ + Closes the virtual card and cancels the payment. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not payment_id: + raise ValueError(f"Expected a non-empty value for `payment_id` but received {payment_id!r}") + return await self._post( + path_template("/v3/payments/{payment_id}/cancel", payment_id=payment_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Payment, + ) + + async def credentials( + self, + payment_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> PaymentCredentialsResponse: + """Returns a short-lived handoff for a `ready` payment. + + Fetch the card credentials + **directly from the provider** with the returned `user_token` at `fetch_url` — + the card number never passes through Linq. Do not persist PAN/CVC. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not payment_id: + raise ValueError(f"Expected a non-empty value for `payment_id` but received {payment_id!r}") + return await self._get( + path_template("/v3/payments/{payment_id}/credentials", payment_id=payment_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=PaymentCredentialsResponse, + ) + + +class PaymentsResourceWithRawResponse: + def __init__(self, payments: PaymentsResource) -> None: + self._payments = payments + + self.create = to_raw_response_wrapper( + payments.create, + ) + self.retrieve = to_raw_response_wrapper( + payments.retrieve, + ) + self.cancel = to_raw_response_wrapper( + payments.cancel, + ) + self.credentials = to_raw_response_wrapper( + payments.credentials, + ) + + +class AsyncPaymentsResourceWithRawResponse: + def __init__(self, payments: AsyncPaymentsResource) -> None: + self._payments = payments + + self.create = async_to_raw_response_wrapper( + payments.create, + ) + self.retrieve = async_to_raw_response_wrapper( + payments.retrieve, + ) + self.cancel = async_to_raw_response_wrapper( + payments.cancel, + ) + self.credentials = async_to_raw_response_wrapper( + payments.credentials, + ) + + +class PaymentsResourceWithStreamingResponse: + def __init__(self, payments: PaymentsResource) -> None: + self._payments = payments + + self.create = to_streamed_response_wrapper( + payments.create, + ) + self.retrieve = to_streamed_response_wrapper( + payments.retrieve, + ) + self.cancel = to_streamed_response_wrapper( + payments.cancel, + ) + self.credentials = to_streamed_response_wrapper( + payments.credentials, + ) + + +class AsyncPaymentsResourceWithStreamingResponse: + def __init__(self, payments: AsyncPaymentsResource) -> None: + self._payments = payments + + self.create = async_to_streamed_response_wrapper( + payments.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + payments.retrieve, + ) + self.cancel = async_to_streamed_response_wrapper( + payments.cancel, + ) + self.credentials = async_to_streamed_response_wrapper( + payments.credentials, + ) diff --git a/src/linq/types/__init__.py b/src/linq/types/__init__.py index ecfd46b..e5b78f2 100644 --- a/src/linq/types/__init__.py +++ b/src/linq/types/__init__.py @@ -14,6 +14,7 @@ MediaPartResponse as MediaPartResponse, ) from .message import Message as Message +from .payment import Payment as Payment from .reply_to import ReplyTo as ReplyTo from .message_effect import MessageEffect as MessageEffect from .reply_to_param import ReplyToParam as ReplyToParam @@ -22,6 +23,7 @@ from .text_part_param import TextPartParam as TextPartParam from .media_part_param import MediaPartParam as MediaPartParam from .message_event_v2 import MessageEventV2 as MessageEventV2 +from .payment_provider import PaymentProvider as PaymentProvider from .set_contact_card import SetContactCard as SetContactCard from .chat_create_params import ChatCreateParams as ChatCreateParams from .chat_update_params import ChatUpdateParams as ChatUpdateParams @@ -36,12 +38,15 @@ from .message_content_param import MessageContentParam as MessageContentParam from .message_create_params import MessageCreateParams as MessageCreateParams from .message_update_params import MessageUpdateParams as MessageUpdateParams +from .payment_create_params import PaymentCreateParams as PaymentCreateParams from .chat_list_chats_params import ChatListChatsParams as ChatListChatsParams from .schemas_message_effect import SchemasMessageEffect as SchemasMessageEffect from .supported_content_type import SupportedContentType as SupportedContentType from .message_create_response import MessageCreateResponse as MessageCreateResponse from .attachment_create_params import AttachmentCreateParams as AttachmentCreateParams from .chat_leave_chat_response import ChatLeaveChatResponse as ChatLeaveChatResponse +from .experience_list_response import ExperienceListResponse as ExperienceListResponse +from .payment_handle_connection import PaymentHandleConnection as PaymentHandleConnection from .phonenumber_list_response import PhonenumberListResponse as PhonenumberListResponse from .attachment_create_response import AttachmentCreateResponse as AttachmentCreateResponse from .chat_created_webhook_event import ChatCreatedWebhookEvent as ChatCreatedWebhookEvent @@ -61,8 +66,11 @@ from .attachment_retrieve_response import AttachmentRetrieveResponse as AttachmentRetrieveResponse from .chat_send_voicememo_response import ChatSendVoicememoResponse as ChatSendVoicememoResponse from .contact_card_retrieve_params import ContactCardRetrieveParams as ContactCardRetrieveParams +from .experience_retrieve_response import ExperienceRetrieveResponse as ExperienceRetrieveResponse from .message_edited_webhook_event import MessageEditedWebhookEvent as MessageEditedWebhookEvent from .message_failed_webhook_event import MessageFailedWebhookEvent as MessageFailedWebhookEvent +from .payment_credentials_response import PaymentCredentialsResponse as PaymentCredentialsResponse +from .payment_handle_verify_params import PaymentHandleVerifyParams as PaymentHandleVerifyParams from .phone_number_update_response import PhoneNumberUpdateResponse as PhoneNumberUpdateResponse from .reaction_added_webhook_event import ReactionAddedWebhookEvent as ReactionAddedWebhookEvent from .message_add_reaction_response import MessageAddReactionResponse as MessageAddReactionResponse @@ -74,10 +82,12 @@ from .reaction_removed_webhook_event import ReactionRemovedWebhookEvent as ReactionRemovedWebhookEvent from .message_delivered_webhook_event import MessageDeliveredWebhookEvent as MessageDeliveredWebhookEvent from .participant_added_webhook_event import ParticipantAddedWebhookEvent as ParticipantAddedWebhookEvent +from .payment_provider_connect_params import PaymentProviderConnectParams as PaymentProviderConnectParams from .available_number_retrieve_params import AvailableNumberRetrieveParams as AvailableNumberRetrieveParams from .message_update_app_card_response import MessageUpdateAppCardResponse as MessageUpdateAppCardResponse from .capability_check_i_message_params import CapabilityCheckIMessageParams as CapabilityCheckIMessageParams from .participant_removed_webhook_event import ParticipantRemovedWebhookEvent as ParticipantRemovedWebhookEvent +from .payment_provider_connect_response import PaymentProviderConnectResponse as PaymentProviderConnectResponse from .available_number_retrieve_response import AvailableNumberRetrieveResponse as AvailableNumberRetrieveResponse from .webhook_subscription_create_params import WebhookSubscriptionCreateParams as WebhookSubscriptionCreateParams from .webhook_subscription_list_response import WebhookSubscriptionListResponse as WebhookSubscriptionListResponse diff --git a/src/linq/types/chat.py b/src/linq/types/chat.py index d78b2ee..1340a9f 100644 --- a/src/linq/types/chat.py +++ b/src/linq/types/chat.py @@ -29,6 +29,14 @@ class HealthStatus(BaseModel): See the [Chat Health guide](/guides/chats/chat-health) for what each value means and how to react. `doc_url` deep-links to the relevant section. + + `OPTED_OUT` is terminal — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, + `CANCEL`, `END`, or `QUIT`, and you should send nothing further on this chat. + Matching is exact and case-sensitive against the whole trimmed message. It + clears if they later send `START`, `OPTIN`, or `UNSTOP`, or if they keep + replying on the chat — sustained two-way conversation is treated as a sign the + stop keyword was a false positive. Suppressing sends to opted-out recipients is + your responsibility — Linq surfaces the status but does not block the send. """ updated_at: datetime diff --git a/src/linq/types/chat_create_response.py b/src/linq/types/chat_create_response.py index b2ef111..beaaee7 100644 --- a/src/linq/types/chat_create_response.py +++ b/src/linq/types/chat_create_response.py @@ -30,6 +30,14 @@ class ChatHealthStatus(BaseModel): See the [Chat Health guide](/guides/chats/chat-health) for what each value means and how to react. `doc_url` deep-links to the relevant section. + + `OPTED_OUT` is terminal — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, + `CANCEL`, `END`, or `QUIT`, and you should send nothing further on this chat. + Matching is exact and case-sensitive against the whole trimmed message. It + clears if they later send `START`, `OPTIN`, or `UNSTOP`, or if they keep + replying on the chat — sustained two-way conversation is treated as a sign the + stop keyword was a false positive. Suppressing sends to opted-out recipients is + your responsibility — Linq surfaces the status but does not block the send. """ updated_at: datetime diff --git a/src/linq/types/chat_created_webhook_event.py b/src/linq/types/chat_created_webhook_event.py index ae410f7..0731c17 100644 --- a/src/linq/types/chat_created_webhook_event.py +++ b/src/linq/types/chat_created_webhook_event.py @@ -30,6 +30,14 @@ class DataHealthStatus(BaseModel): See the [Chat Health guide](/guides/chats/chat-health) for what each value means and how to react. `doc_url` deep-links to the relevant section. + + `OPTED_OUT` is terminal — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, + `CANCEL`, `END`, or `QUIT`, and you should send nothing further on this chat. + Matching is exact and case-sensitive against the whole trimmed message. It + clears if they later send `START`, `OPTIN`, or `UNSTOP`, or if they keep + replying on the chat — sustained two-way conversation is treated as a sign the + stop keyword was a false positive. Suppressing sends to opted-out recipients is + your responsibility — Linq surfaces the status but does not block the send. """ updated_at: datetime diff --git a/src/linq/types/experience_list_response.py b/src/linq/types/experience_list_response.py new file mode 100644 index 0000000..71d4137 --- /dev/null +++ b/src/linq/types/experience_list_response.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ExperienceListResponse", "Experience", "ExperienceAction", "ExperienceActionFields"] + + +class ExperienceActionFields(BaseModel): + max: Optional[int] = None + """Maximum length, for strings.""" + + required: Optional[bool] = None + + type: Optional[Literal["string", "cents", "int"]] = None + + +class ExperienceAction(BaseModel): + fields: Optional[Dict[str, ExperienceActionFields]] = None + """Fields you may send in `params`, keyed by the exact name to use.""" + + name: Optional[str] = None + + summary: Optional[str] = None + + +class Experience(BaseModel): + """What an experience offers you. + + Deliberately a projection: where its + templates live and how they are built is not yours to depend on, so it + is not here. + """ + + actions: Optional[List[ExperienceAction]] = None + + display_name: Optional[str] = None + + experience: Optional[str] = None + + +class ExperienceListResponse(BaseModel): + experiences: Optional[List[Experience]] = None diff --git a/src/linq/types/experience_retrieve_response.py b/src/linq/types/experience_retrieve_response.py new file mode 100644 index 0000000..a5287a1 --- /dev/null +++ b/src/linq/types/experience_retrieve_response.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ExperienceRetrieveResponse", "Action", "ActionFields"] + + +class ActionFields(BaseModel): + max: Optional[int] = None + """Maximum length, for strings.""" + + required: Optional[bool] = None + + type: Optional[Literal["string", "cents", "int"]] = None + + +class Action(BaseModel): + fields: Optional[Dict[str, ActionFields]] = None + """Fields you may send in `params`, keyed by the exact name to use.""" + + name: Optional[str] = None + + summary: Optional[str] = None + + +class ExperienceRetrieveResponse(BaseModel): + """What an experience offers you. + + Deliberately a projection: where its + templates live and how they are built is not yours to depend on, so it + is not here. + """ + + actions: Optional[List[Action]] = None + + display_name: Optional[str] = None + + experience: Optional[str] = None diff --git a/src/linq/types/message_content_param.py b/src/linq/types/message_content_param.py index 450800c..0f6863b 100644 --- a/src/linq/types/message_content_param.py +++ b/src/linq/types/message_content_param.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Union, Iterable +from typing import Dict, Union, Iterable from typing_extensions import Literal, Required, TypeAlias, TypedDict from .reply_to_param import ReplyToParam @@ -12,7 +12,42 @@ from .shared.service_type import ServiceType from .message_effect_param import MessageEffectParam -__all__ = ["MessageContentParam", "Part", "PartIMessageAppPart", "PartIMessageAppPartApp", "PartIMessageAppPartLayout"] +__all__ = [ + "MessageContentParam", + "Action", + "Part", + "PartIMessageAppPart", + "PartIMessageAppPartApp", + "PartIMessageAppPartLayout", +] + + +class Action(TypedDict, total=False): + """ + Invokes an action on an experience — a third party that renders inside + Linq's iMessage app. Linq resolves the recipient's connection, mints any + session the action needs, composes the card and sends it; none of that + is visible to you. + + Call `GET /v3/experiences/{experience}` for the actions you may invoke + and the fields each accepts. + """ + + action: Required[str] + """Which of its actions, e.g. `attach_card`.""" + + experience: Required[str] + """The experience to invoke, e.g. `agentcard`.""" + + params: Dict[str, object] + """Values for the fields this action exposes. + + Keys are exactly the field names listed for the action — no mapping, no nesting. + + Display copy only. Params can never change where a button goes or what a secure + field loads; those are fixed by the experience and validated before it is + registered. + """ class PartIMessageAppPartApp(TypedDict, total=False): @@ -162,6 +197,16 @@ class MessageContentParam(TypedDict, total=False): two sends, not one. """ + action: Action + """ + Invokes an action on an experience — a third party that renders inside Linq's + iMessage app. Linq resolves the recipient's connection, mints any session the + action needs, composes the card and sends it; none of that is visible to you. + + Call `GET /v3/experiences/{experience}` for the actions you may invoke and the + fields each accepts. + """ + effect: MessageEffectParam """iMessage effect to apply to this message (screen or bubble effect)""" diff --git a/src/linq/types/message_edited_webhook_event.py b/src/linq/types/message_edited_webhook_event.py index c7e098a..fb03c12 100644 --- a/src/linq/types/message_edited_webhook_event.py +++ b/src/linq/types/message_edited_webhook_event.py @@ -28,6 +28,14 @@ class DataChatHealthStatus(BaseModel): See the [Chat Health guide](/guides/chats/chat-health) for what each value means and how to react. `doc_url` deep-links to the relevant section. + + `OPTED_OUT` is terminal — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, + `CANCEL`, `END`, or `QUIT`, and you should send nothing further on this chat. + Matching is exact and case-sensitive against the whole trimmed message. It + clears if they later send `START`, `OPTIN`, or `UNSTOP`, or if they keep + replying on the chat — sustained two-way conversation is treated as a sign the + stop keyword was a false positive. Suppressing sends to opted-out recipients is + your responsibility — Linq surfaces the status but does not block the send. """ updated_at: datetime diff --git a/src/linq/types/message_event_v2.py b/src/linq/types/message_event_v2.py index 779a882..acc4a29 100644 --- a/src/linq/types/message_event_v2.py +++ b/src/linq/types/message_event_v2.py @@ -43,6 +43,14 @@ class ChatHealthStatus(BaseModel): See the [Chat Health guide](/guides/chats/chat-health) for what each value means and how to react. `doc_url` deep-links to the relevant section. + + `OPTED_OUT` is terminal — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, + `CANCEL`, `END`, or `QUIT`, and you should send nothing further on this chat. + Matching is exact and case-sensitive against the whole trimmed message. It + clears if they later send `START`, `OPTIN`, or `UNSTOP`, or if they keep + replying on the chat — sustained two-way conversation is treated as a sign the + stop keyword was a false positive. Suppressing sends to opted-out recipients is + your responsibility — Linq surfaces the status but does not block the send. """ updated_at: datetime diff --git a/src/linq/types/payment.py b/src/linq/types/payment.py new file mode 100644 index 0000000..abee0e9 --- /dev/null +++ b/src/linq/types/payment.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["Payment"] + + +class Payment(BaseModel): + id: Optional[str] = None + + amount_cents: Optional[int] = None + + approval_url: Optional[str] = None + """Present when the customer must approve with a passkey.""" + + attach_url: Optional[str] = None + """Present when the customer must attach a card.""" + + currency: Optional[str] = None + + description: Optional[str] = None + + handle: Optional[str] = None + + status: Optional[ + Literal[ + "needs_connection", + "connecting", + "awaiting_user_action", + "ready", + "authorized", + "succeeded", + "declined", + "canceled", + "expired", + ] + ] = None diff --git a/src/linq/types/payment_create_params.py b/src/linq/types/payment_create_params.py new file mode 100644 index 0000000..d36af28 --- /dev/null +++ b/src/linq/types/payment_create_params.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict +from typing_extensions import Required, TypedDict + +__all__ = ["PaymentCreateParams", "Merchant"] + + +class PaymentCreateParams(TypedDict, total=False): + amount_cents: Required[int] + + currency: Required[str] + + handle: Required[str] + """Customer phone (E.164) or email.""" + + description: str + + merchant: Merchant + + metadata: Dict[str, str] + + +class Merchant(TypedDict, total=False): + name: str + + url: str diff --git a/src/linq/types/payment_credentials_response.py b/src/linq/types/payment_credentials_response.py new file mode 100644 index 0000000..b808160 --- /dev/null +++ b/src/linq/types/payment_credentials_response.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["PaymentCredentialsResponse", "Handoff"] + + +class Handoff(BaseModel): + """Fetch the card directly from the provider with these — never through Linq.""" + + card_ref: Optional[str] = None + + fetch_url: Optional[str] = None + + provider: Optional[str] = None + + user_token: Optional[str] = None + """Short-lived bearer to fetch the card from the provider.""" + + +class PaymentCredentialsResponse(BaseModel): + handoff: Optional[Handoff] = None + """Fetch the card directly from the provider with these — never through Linq.""" diff --git a/src/linq/types/payment_handle_connection.py b/src/linq/types/payment_handle_connection.py new file mode 100644 index 0000000..a068170 --- /dev/null +++ b/src/linq/types/payment_handle_connection.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["PaymentHandleConnection"] + + +class PaymentHandleConnection(BaseModel): + connect_id: Optional[str] = None + """ + Returned only by `connect`, and only while the ceremony is pending. Nothing on + our side persists it — it comes back from the provider and is required again to + verify — so hold it until you submit the code. + """ + + handle: Optional[str] = None + + status: Optional[Literal["not_connected", "pending", "connected", "revoked"]] = None diff --git a/src/linq/types/payment_handle_verify_params.py b/src/linq/types/payment_handle_verify_params.py new file mode 100644 index 0000000..192e084 --- /dev/null +++ b/src/linq/types/payment_handle_verify_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["PaymentHandleVerifyParams"] + + +class PaymentHandleVerifyParams(TypedDict, total=False): + code: Required[str] + """The one-time code the customer received.""" + + connect_id: Required[str] + """The id returned by `connect`.""" diff --git a/src/linq/types/payment_provider.py b/src/linq/types/payment_provider.py new file mode 100644 index 0000000..7caf066 --- /dev/null +++ b/src/linq/types/payment_provider.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["PaymentProvider"] + + +class PaymentProvider(BaseModel): + provider: Optional[str] = None + + status: Optional[Literal["onboarding", "ready", "disabled"]] = None diff --git a/src/linq/types/payment_provider_connect_params.py b/src/linq/types/payment_provider_connect_params.py new file mode 100644 index 0000000..576d68b --- /dev/null +++ b/src/linq/types/payment_provider_connect_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["PaymentProviderConnectParams"] + + +class PaymentProviderConnectParams(TypedDict, total=False): + return_url: Required[str] + """Where to send the admin after they authorize the connection.""" diff --git a/src/linq/types/payment_provider_connect_response.py b/src/linq/types/payment_provider_connect_response.py new file mode 100644 index 0000000..c288c39 --- /dev/null +++ b/src/linq/types/payment_provider_connect_response.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["PaymentProviderConnectResponse"] + + +class PaymentProviderConnectResponse(BaseModel): + hosted_url: Optional[str] = None + """Send the admin here to authorize the connection.""" + + session_id: Optional[str] = None + + status: Optional[str] = None diff --git a/tests/api_resources/chats/test_messages.py b/tests/api_resources/chats/test_messages.py index 7ce6718..8dea3b6 100644 --- a/tests/api_resources/chats/test_messages.py +++ b/tests/api_resources/chats/test_messages.py @@ -86,6 +86,11 @@ def test_method_send_with_all_params(self, client: LinqAPIV3) -> None: message = client.chats.messages.send( chat_id="550e8400-e29b-41d4-a716-446655440000", message={ + "action": { + "action": "attach_card", + "experience": "agentcard", + "params": {"foo": "bar"}, + }, "effect": { "name": "confetti", "type": "screen", @@ -228,6 +233,11 @@ async def test_method_send_with_all_params(self, async_client: AsyncLinqAPIV3) - message = await async_client.chats.messages.send( chat_id="550e8400-e29b-41d4-a716-446655440000", message={ + "action": { + "action": "attach_card", + "experience": "agentcard", + "params": {"foo": "bar"}, + }, "effect": { "name": "confetti", "type": "screen", diff --git a/tests/api_resources/test_chats.py b/tests/api_resources/test_chats.py index 58046ba..2b84869 100644 --- a/tests/api_resources/test_chats.py +++ b/tests/api_resources/test_chats.py @@ -40,6 +40,11 @@ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None: chat = client.chats.create( from_="+12052535597", message={ + "action": { + "action": "attach_card", + "experience": "agentcard", + "params": {"foo": "bar"}, + }, "effect": { "name": "confetti", "type": "screen", @@ -436,6 +441,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3) chat = await async_client.chats.create( from_="+12052535597", message={ + "action": { + "action": "attach_card", + "experience": "agentcard", + "params": {"foo": "bar"}, + }, "effect": { "name": "confetti", "type": "screen", diff --git a/tests/api_resources/test_experiences.py b/tests/api_resources/test_experiences.py new file mode 100644 index 0000000..2ff00ed --- /dev/null +++ b/tests/api_resources/test_experiences.py @@ -0,0 +1,164 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from linq import LinqAPIV3, AsyncLinqAPIV3 +from linq.types import ExperienceListResponse, ExperienceRetrieveResponse +from tests.utils import assert_matches_type + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestExperiences: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: LinqAPIV3) -> None: + experience = client.experiences.retrieve( + "experience", + ) + assert_matches_type(ExperienceRetrieveResponse, experience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: LinqAPIV3) -> None: + response = client.experiences.with_raw_response.retrieve( + "experience", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + experience = response.parse() + assert_matches_type(ExperienceRetrieveResponse, experience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: LinqAPIV3) -> None: + with client.experiences.with_streaming_response.retrieve( + "experience", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + experience = response.parse() + assert_matches_type(ExperienceRetrieveResponse, experience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: LinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `experience` but received ''"): + client.experiences.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: LinqAPIV3) -> None: + experience = client.experiences.list() + assert_matches_type(ExperienceListResponse, experience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: LinqAPIV3) -> None: + response = client.experiences.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + experience = response.parse() + assert_matches_type(ExperienceListResponse, experience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: LinqAPIV3) -> None: + with client.experiences.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + experience = response.parse() + assert_matches_type(ExperienceListResponse, experience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncExperiences: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncLinqAPIV3) -> None: + experience = await async_client.experiences.retrieve( + "experience", + ) + assert_matches_type(ExperienceRetrieveResponse, experience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLinqAPIV3) -> None: + response = await async_client.experiences.with_raw_response.retrieve( + "experience", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + experience = await response.parse() + assert_matches_type(ExperienceRetrieveResponse, experience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLinqAPIV3) -> None: + async with async_client.experiences.with_streaming_response.retrieve( + "experience", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + experience = await response.parse() + assert_matches_type(ExperienceRetrieveResponse, experience, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `experience` but received ''"): + await async_client.experiences.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncLinqAPIV3) -> None: + experience = await async_client.experiences.list() + assert_matches_type(ExperienceListResponse, experience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncLinqAPIV3) -> None: + response = await async_client.experiences.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + experience = await response.parse() + assert_matches_type(ExperienceListResponse, experience, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncLinqAPIV3) -> None: + async with async_client.experiences.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + experience = await response.parse() + assert_matches_type(ExperienceListResponse, experience, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_messages.py b/tests/api_resources/test_messages.py index d23a7de..4c1d8fc 100644 --- a/tests/api_resources/test_messages.py +++ b/tests/api_resources/test_messages.py @@ -37,6 +37,11 @@ def test_method_create(self, client: LinqAPIV3) -> None: def test_method_create_with_all_params(self, client: LinqAPIV3) -> None: message = client.messages.create( message={ + "action": { + "action": "attach_card", + "experience": "agentcard", + "params": {"foo": "bar"}, + }, "effect": { "name": "confetti", "type": "screen", @@ -441,6 +446,11 @@ async def test_method_create(self, async_client: AsyncLinqAPIV3) -> None: async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3) -> None: message = await async_client.messages.create( message={ + "action": { + "action": "attach_card", + "experience": "agentcard", + "params": {"foo": "bar"}, + }, "effect": { "name": "confetti", "type": "screen", diff --git a/tests/api_resources/test_payment_handles.py b/tests/api_resources/test_payment_handles.py new file mode 100644 index 0000000..8350177 --- /dev/null +++ b/tests/api_resources/test_payment_handles.py @@ -0,0 +1,376 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from linq import LinqAPIV3, AsyncLinqAPIV3 +from linq.types import PaymentHandleConnection +from tests.utils import assert_matches_type + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestPaymentHandles: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_connect(self, client: LinqAPIV3) -> None: + payment_handle = client.payment_handles.connect( + "handle", + ) + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_connect(self, client: LinqAPIV3) -> None: + response = client.payment_handles.with_raw_response.connect( + "handle", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment_handle = response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_connect(self, client: LinqAPIV3) -> None: + with client.payment_handles.with_streaming_response.connect( + "handle", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment_handle = response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_connect(self, client: LinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `handle` but received ''"): + client.payment_handles.with_raw_response.connect( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_connection(self, client: LinqAPIV3) -> None: + payment_handle = client.payment_handles.connection( + "handle", + ) + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_connection(self, client: LinqAPIV3) -> None: + response = client.payment_handles.with_raw_response.connection( + "handle", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment_handle = response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_connection(self, client: LinqAPIV3) -> None: + with client.payment_handles.with_streaming_response.connection( + "handle", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment_handle = response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_connection(self, client: LinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `handle` but received ''"): + client.payment_handles.with_raw_response.connection( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_revoke(self, client: LinqAPIV3) -> None: + payment_handle = client.payment_handles.revoke( + "handle", + ) + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_revoke(self, client: LinqAPIV3) -> None: + response = client.payment_handles.with_raw_response.revoke( + "handle", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment_handle = response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_revoke(self, client: LinqAPIV3) -> None: + with client.payment_handles.with_streaming_response.revoke( + "handle", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment_handle = response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_revoke(self, client: LinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `handle` but received ''"): + client.payment_handles.with_raw_response.revoke( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_verify(self, client: LinqAPIV3) -> None: + payment_handle = client.payment_handles.verify( + handle="handle", + code="482913", + connect_id="cs_01HZY8", + ) + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_verify(self, client: LinqAPIV3) -> None: + response = client.payment_handles.with_raw_response.verify( + handle="handle", + code="482913", + connect_id="cs_01HZY8", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment_handle = response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_verify(self, client: LinqAPIV3) -> None: + with client.payment_handles.with_streaming_response.verify( + handle="handle", + code="482913", + connect_id="cs_01HZY8", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment_handle = response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_verify(self, client: LinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `handle` but received ''"): + client.payment_handles.with_raw_response.verify( + handle="", + code="482913", + connect_id="cs_01HZY8", + ) + + +class TestAsyncPaymentHandles: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_connect(self, async_client: AsyncLinqAPIV3) -> None: + payment_handle = await async_client.payment_handles.connect( + "handle", + ) + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_connect(self, async_client: AsyncLinqAPIV3) -> None: + response = await async_client.payment_handles.with_raw_response.connect( + "handle", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment_handle = await response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_connect(self, async_client: AsyncLinqAPIV3) -> None: + async with async_client.payment_handles.with_streaming_response.connect( + "handle", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment_handle = await response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_connect(self, async_client: AsyncLinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `handle` but received ''"): + await async_client.payment_handles.with_raw_response.connect( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_connection(self, async_client: AsyncLinqAPIV3) -> None: + payment_handle = await async_client.payment_handles.connection( + "handle", + ) + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_connection(self, async_client: AsyncLinqAPIV3) -> None: + response = await async_client.payment_handles.with_raw_response.connection( + "handle", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment_handle = await response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_connection(self, async_client: AsyncLinqAPIV3) -> None: + async with async_client.payment_handles.with_streaming_response.connection( + "handle", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment_handle = await response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_connection(self, async_client: AsyncLinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `handle` but received ''"): + await async_client.payment_handles.with_raw_response.connection( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_revoke(self, async_client: AsyncLinqAPIV3) -> None: + payment_handle = await async_client.payment_handles.revoke( + "handle", + ) + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_revoke(self, async_client: AsyncLinqAPIV3) -> None: + response = await async_client.payment_handles.with_raw_response.revoke( + "handle", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment_handle = await response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_revoke(self, async_client: AsyncLinqAPIV3) -> None: + async with async_client.payment_handles.with_streaming_response.revoke( + "handle", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment_handle = await response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_revoke(self, async_client: AsyncLinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `handle` but received ''"): + await async_client.payment_handles.with_raw_response.revoke( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_verify(self, async_client: AsyncLinqAPIV3) -> None: + payment_handle = await async_client.payment_handles.verify( + handle="handle", + code="482913", + connect_id="cs_01HZY8", + ) + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_verify(self, async_client: AsyncLinqAPIV3) -> None: + response = await async_client.payment_handles.with_raw_response.verify( + handle="handle", + code="482913", + connect_id="cs_01HZY8", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment_handle = await response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_verify(self, async_client: AsyncLinqAPIV3) -> None: + async with async_client.payment_handles.with_streaming_response.verify( + handle="handle", + code="482913", + connect_id="cs_01HZY8", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment_handle = await response.parse() + assert_matches_type(PaymentHandleConnection, payment_handle, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_verify(self, async_client: AsyncLinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `handle` but received ''"): + await async_client.payment_handles.with_raw_response.verify( + handle="", + code="482913", + connect_id="cs_01HZY8", + ) diff --git a/tests/api_resources/test_payment_providers.py b/tests/api_resources/test_payment_providers.py new file mode 100644 index 0000000..8a477a6 --- /dev/null +++ b/tests/api_resources/test_payment_providers.py @@ -0,0 +1,200 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from linq import LinqAPIV3, AsyncLinqAPIV3 +from linq.types import PaymentProvider, PaymentProviderConnectResponse +from tests.utils import assert_matches_type + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestPaymentProviders: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: LinqAPIV3) -> None: + payment_provider = client.payment_providers.retrieve( + "provider", + ) + assert_matches_type(PaymentProvider, payment_provider, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: LinqAPIV3) -> None: + response = client.payment_providers.with_raw_response.retrieve( + "provider", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment_provider = response.parse() + assert_matches_type(PaymentProvider, payment_provider, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: LinqAPIV3) -> None: + with client.payment_providers.with_streaming_response.retrieve( + "provider", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment_provider = response.parse() + assert_matches_type(PaymentProvider, payment_provider, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: LinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `provider` but received ''"): + client.payment_providers.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_connect(self, client: LinqAPIV3) -> None: + payment_provider = client.payment_providers.connect( + provider="provider", + return_url="https://partner.example/settings/payments", + ) + assert_matches_type(PaymentProviderConnectResponse, payment_provider, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_connect(self, client: LinqAPIV3) -> None: + response = client.payment_providers.with_raw_response.connect( + provider="provider", + return_url="https://partner.example/settings/payments", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment_provider = response.parse() + assert_matches_type(PaymentProviderConnectResponse, payment_provider, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_connect(self, client: LinqAPIV3) -> None: + with client.payment_providers.with_streaming_response.connect( + provider="provider", + return_url="https://partner.example/settings/payments", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment_provider = response.parse() + assert_matches_type(PaymentProviderConnectResponse, payment_provider, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_connect(self, client: LinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `provider` but received ''"): + client.payment_providers.with_raw_response.connect( + provider="", + return_url="https://partner.example/settings/payments", + ) + + +class TestAsyncPaymentProviders: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncLinqAPIV3) -> None: + payment_provider = await async_client.payment_providers.retrieve( + "provider", + ) + assert_matches_type(PaymentProvider, payment_provider, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLinqAPIV3) -> None: + response = await async_client.payment_providers.with_raw_response.retrieve( + "provider", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment_provider = await response.parse() + assert_matches_type(PaymentProvider, payment_provider, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLinqAPIV3) -> None: + async with async_client.payment_providers.with_streaming_response.retrieve( + "provider", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment_provider = await response.parse() + assert_matches_type(PaymentProvider, payment_provider, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `provider` but received ''"): + await async_client.payment_providers.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_connect(self, async_client: AsyncLinqAPIV3) -> None: + payment_provider = await async_client.payment_providers.connect( + provider="provider", + return_url="https://partner.example/settings/payments", + ) + assert_matches_type(PaymentProviderConnectResponse, payment_provider, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_connect(self, async_client: AsyncLinqAPIV3) -> None: + response = await async_client.payment_providers.with_raw_response.connect( + provider="provider", + return_url="https://partner.example/settings/payments", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment_provider = await response.parse() + assert_matches_type(PaymentProviderConnectResponse, payment_provider, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_connect(self, async_client: AsyncLinqAPIV3) -> None: + async with async_client.payment_providers.with_streaming_response.connect( + provider="provider", + return_url="https://partner.example/settings/payments", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment_provider = await response.parse() + assert_matches_type(PaymentProviderConnectResponse, payment_provider, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_connect(self, async_client: AsyncLinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `provider` but received ''"): + await async_client.payment_providers.with_raw_response.connect( + provider="", + return_url="https://partner.example/settings/payments", + ) diff --git a/tests/api_resources/test_payments.py b/tests/api_resources/test_payments.py new file mode 100644 index 0000000..30ae202 --- /dev/null +++ b/tests/api_resources/test_payments.py @@ -0,0 +1,388 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from linq import LinqAPIV3, AsyncLinqAPIV3 +from linq.types import Payment, PaymentCredentialsResponse +from tests.utils import assert_matches_type + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestPayments: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create(self, client: LinqAPIV3) -> None: + payment = client.payments.create( + amount_cents=2500, + currency="usd", + handle="+14155550123", + ) + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: LinqAPIV3) -> None: + payment = client.payments.create( + amount_cents=2500, + currency="usd", + handle="+14155550123", + description="Burger order", + merchant={ + "name": "DoorDash", + "url": "doordash.com", + }, + metadata={"foo": "string"}, + ) + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: LinqAPIV3) -> None: + response = client.payments.with_raw_response.create( + amount_cents=2500, + currency="usd", + handle="+14155550123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: LinqAPIV3) -> None: + with client.payments.with_streaming_response.create( + amount_cents=2500, + currency="usd", + handle="+14155550123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: LinqAPIV3) -> None: + payment = client.payments.retrieve( + "paymentId", + ) + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: LinqAPIV3) -> None: + response = client.payments.with_raw_response.retrieve( + "paymentId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: LinqAPIV3) -> None: + with client.payments.with_streaming_response.retrieve( + "paymentId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: LinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `payment_id` but received ''"): + client.payments.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_cancel(self, client: LinqAPIV3) -> None: + payment = client.payments.cancel( + "paymentId", + ) + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_cancel(self, client: LinqAPIV3) -> None: + response = client.payments.with_raw_response.cancel( + "paymentId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_cancel(self, client: LinqAPIV3) -> None: + with client.payments.with_streaming_response.cancel( + "paymentId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_cancel(self, client: LinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `payment_id` but received ''"): + client.payments.with_raw_response.cancel( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_credentials(self, client: LinqAPIV3) -> None: + payment = client.payments.credentials( + "paymentId", + ) + assert_matches_type(PaymentCredentialsResponse, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_credentials(self, client: LinqAPIV3) -> None: + response = client.payments.with_raw_response.credentials( + "paymentId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = response.parse() + assert_matches_type(PaymentCredentialsResponse, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_credentials(self, client: LinqAPIV3) -> None: + with client.payments.with_streaming_response.credentials( + "paymentId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = response.parse() + assert_matches_type(PaymentCredentialsResponse, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_credentials(self, client: LinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `payment_id` but received ''"): + client.payments.with_raw_response.credentials( + "", + ) + + +class TestAsyncPayments: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncLinqAPIV3) -> None: + payment = await async_client.payments.create( + amount_cents=2500, + currency="usd", + handle="+14155550123", + ) + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3) -> None: + payment = await async_client.payments.create( + amount_cents=2500, + currency="usd", + handle="+14155550123", + description="Burger order", + merchant={ + "name": "DoorDash", + "url": "doordash.com", + }, + metadata={"foo": "string"}, + ) + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncLinqAPIV3) -> None: + response = await async_client.payments.with_raw_response.create( + amount_cents=2500, + currency="usd", + handle="+14155550123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = await response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncLinqAPIV3) -> None: + async with async_client.payments.with_streaming_response.create( + amount_cents=2500, + currency="usd", + handle="+14155550123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = await response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncLinqAPIV3) -> None: + payment = await async_client.payments.retrieve( + "paymentId", + ) + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncLinqAPIV3) -> None: + response = await async_client.payments.with_raw_response.retrieve( + "paymentId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = await response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncLinqAPIV3) -> None: + async with async_client.payments.with_streaming_response.retrieve( + "paymentId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = await response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncLinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `payment_id` but received ''"): + await async_client.payments.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_cancel(self, async_client: AsyncLinqAPIV3) -> None: + payment = await async_client.payments.cancel( + "paymentId", + ) + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_cancel(self, async_client: AsyncLinqAPIV3) -> None: + response = await async_client.payments.with_raw_response.cancel( + "paymentId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = await response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_cancel(self, async_client: AsyncLinqAPIV3) -> None: + async with async_client.payments.with_streaming_response.cancel( + "paymentId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = await response.parse() + assert_matches_type(Payment, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_cancel(self, async_client: AsyncLinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `payment_id` but received ''"): + await async_client.payments.with_raw_response.cancel( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_credentials(self, async_client: AsyncLinqAPIV3) -> None: + payment = await async_client.payments.credentials( + "paymentId", + ) + assert_matches_type(PaymentCredentialsResponse, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_credentials(self, async_client: AsyncLinqAPIV3) -> None: + response = await async_client.payments.with_raw_response.credentials( + "paymentId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + payment = await response.parse() + assert_matches_type(PaymentCredentialsResponse, payment, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_credentials(self, async_client: AsyncLinqAPIV3) -> None: + async with async_client.payments.with_streaming_response.credentials( + "paymentId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + payment = await response.parse() + assert_matches_type(PaymentCredentialsResponse, payment, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_credentials(self, async_client: AsyncLinqAPIV3) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `payment_id` but received ''"): + await async_client.payments.with_raw_response.credentials( + "", + )