# This file was auto-generated by Fern from our API Definition.

import json
import typing
from json.decoder import JSONDecodeError

from .. import core
from ..core.api_error import ApiError
from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
from ..core.http_response import AsyncHttpResponse, HttpResponse
from ..core.jsonable_encoder import jsonable_encoder
from ..core.pagination import AsyncPager, BaseHttpResponse, SyncPager
from ..core.request_options import RequestOptions
from ..core.serialization import convert_and_respect_annotation_metadata
from ..core.unchecked_base_model import construct_type
from ..requests.create_invoice_attachment_request_data import CreateInvoiceAttachmentRequestDataParams
from ..requests.invoice import InvoiceParams
from ..requests.invoice_query import InvoiceQueryParams
from ..types.cancel_invoice_response import CancelInvoiceResponse
from ..types.create_invoice_attachment_response import CreateInvoiceAttachmentResponse
from ..types.create_invoice_response import CreateInvoiceResponse
from ..types.delete_invoice_attachment_response import DeleteInvoiceAttachmentResponse
from ..types.delete_invoice_response import DeleteInvoiceResponse
from ..types.get_invoice_response import GetInvoiceResponse
from ..types.invoice import Invoice
from ..types.list_invoices_response import ListInvoicesResponse
from ..types.publish_invoice_response import PublishInvoiceResponse
from ..types.search_invoices_response import SearchInvoicesResponse
from ..types.update_invoice_response import UpdateInvoiceResponse

# this is used as the default value for optional parameters
OMIT = typing.cast(typing.Any, ...)


class RawInvoicesClient:
    def __init__(self, *, client_wrapper: SyncClientWrapper):
        self._client_wrapper = client_wrapper

    def list(
        self,
        *,
        location_id: str,
        cursor: typing.Optional[str] = None,
        limit: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> SyncPager[Invoice]:
        """
        Returns a list of invoices for a given location. The response
        is paginated. If truncated, the response includes a `cursor` that you
        use in a subsequent request to retrieve the next set of invoices.

        Parameters
        ----------
        location_id : str
            The ID of the location for which to list invoices.

        cursor : typing.Optional[str]
            A pagination cursor returned by a previous call to this endpoint.
            Provide this cursor to retrieve the next set of results for your original query.

            For more information, see [Pagination](https://developer.squareup.com/docs/build-basics/common-api-patterns/pagination).

        limit : typing.Optional[int]
            The maximum number of invoices to return (200 is the maximum `limit`).
            If not provided, the server uses a default limit of 100 invoices.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        SyncPager[Invoice]
            Success
        """
        _response = self._client_wrapper.httpx_client.request(
            "v2/invoices",
            method="GET",
            params={
                "location_id": location_id,
                "cursor": cursor,
                "limit": limit,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _parsed_response = typing.cast(
                    ListInvoicesResponse,
                    construct_type(
                        type_=ListInvoicesResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                _items = _parsed_response.invoices
                _parsed_next = _parsed_response.cursor
                _has_next = _parsed_next is not None and _parsed_next != ""
                _get_next = lambda: self.list(
                    location_id=location_id,
                    cursor=_parsed_next,
                    limit=limit,
                    request_options=request_options,
                )
                return SyncPager(
                    has_next=_has_next, items=_items, get_next=_get_next, response=BaseHttpResponse(response=_response)
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def create(
        self,
        *,
        invoice: InvoiceParams,
        idempotency_key: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[CreateInvoiceResponse]:
        """
        Creates a draft [invoice](entity:Invoice)
        for an order created using the Orders API.

        A draft invoice remains in your account and no action is taken.
        You must publish the invoice before Square can process it (send it to the customer's email address or charge the customer’s card on file).

        Parameters
        ----------
        invoice : InvoiceParams
            The invoice to create.

        idempotency_key : typing.Optional[str]
            A unique string that identifies the `CreateInvoice` request. If you do not
            provide `idempotency_key` (or provide an empty string as the value), the endpoint
            treats each request as independent.

            For more information, see [Idempotency](https://developer.squareup.com/docs/build-basics/common-api-patterns/idempotency).

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[CreateInvoiceResponse]
            Success
        """
        _response = self._client_wrapper.httpx_client.request(
            "v2/invoices",
            method="POST",
            json={
                "invoice": convert_and_respect_annotation_metadata(
                    object_=invoice, annotation=InvoiceParams, direction="write"
                ),
                "idempotency_key": idempotency_key,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CreateInvoiceResponse,
                    construct_type(
                        type_=CreateInvoiceResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def search(
        self,
        *,
        query: InvoiceQueryParams,
        limit: typing.Optional[int] = OMIT,
        cursor: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[SearchInvoicesResponse]:
        """
        Searches for invoices from a location specified in
        the filter. You can optionally specify customers in the filter for whom to
        retrieve invoices. In the current implementation, you can only specify one location and
        optionally one customer.

        The response is paginated. If truncated, the response includes a `cursor`
        that you use in a subsequent request to retrieve the next set of invoices.

        Parameters
        ----------
        query : InvoiceQueryParams
            Describes the query criteria for searching invoices.

        limit : typing.Optional[int]
            The maximum number of invoices to return (200 is the maximum `limit`).
            If not provided, the server uses a default limit of 100 invoices.

        cursor : typing.Optional[str]
            A pagination cursor returned by a previous call to this endpoint.
            Provide this cursor to retrieve the next set of results for your original query.

            For more information, see [Pagination](https://developer.squareup.com/docs/build-basics/common-api-patterns/pagination).

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[SearchInvoicesResponse]
            Success
        """
        _response = self._client_wrapper.httpx_client.request(
            "v2/invoices/search",
            method="POST",
            json={
                "query": convert_and_respect_annotation_metadata(
                    object_=query, annotation=InvoiceQueryParams, direction="write"
                ),
                "limit": limit,
                "cursor": cursor,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    SearchInvoicesResponse,
                    construct_type(
                        type_=SearchInvoicesResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def get(
        self, invoice_id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[GetInvoiceResponse]:
        """
        Retrieves an invoice by invoice ID.

        Parameters
        ----------
        invoice_id : str
            The ID of the invoice to retrieve.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[GetInvoiceResponse]
            Success
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}",
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    GetInvoiceResponse,
                    construct_type(
                        type_=GetInvoiceResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def update(
        self,
        invoice_id: str,
        *,
        invoice: InvoiceParams,
        idempotency_key: typing.Optional[str] = OMIT,
        fields_to_clear: typing.Optional[typing.Sequence[str]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[UpdateInvoiceResponse]:
        """
        Updates an invoice. This endpoint supports sparse updates, so you only need
        to specify the fields you want to change along with the required `version` field.
        Some restrictions apply to updating invoices. For example, you cannot change the
        `order_id` or `location_id` field.

        Parameters
        ----------
        invoice_id : str
            The ID of the invoice to update.

        invoice : InvoiceParams
            The invoice fields to add, change, or clear. Fields can be cleared using
            null values or the `remove` field (for individual payment requests or reminders).
            The current invoice `version` is also required. For more information, including requirements,
            limitations, and more examples, see [Update an Invoice](https://developer.squareup.com/docs/invoices-api/update-invoices).

        idempotency_key : typing.Optional[str]
            A unique string that identifies the `UpdateInvoice` request. If you do not
            provide `idempotency_key` (or provide an empty string as the value), the endpoint
            treats each request as independent.

            For more information, see [Idempotency](https://developer.squareup.com/docs/build-basics/common-api-patterns/idempotency).

        fields_to_clear : typing.Optional[typing.Sequence[str]]
            The list of fields to clear. Although this field is currently supported, we
            recommend using null values or the `remove` field when possible. For examples, see
            [Update an Invoice](https://developer.squareup.com/docs/invoices-api/update-invoices).

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[UpdateInvoiceResponse]
            Success
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}",
            method="PUT",
            json={
                "invoice": convert_and_respect_annotation_metadata(
                    object_=invoice, annotation=InvoiceParams, direction="write"
                ),
                "idempotency_key": idempotency_key,
                "fields_to_clear": fields_to_clear,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    UpdateInvoiceResponse,
                    construct_type(
                        type_=UpdateInvoiceResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def delete(
        self,
        invoice_id: str,
        *,
        version: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[DeleteInvoiceResponse]:
        """
        Deletes the specified invoice. When an invoice is deleted, the
        associated order status changes to CANCELED. You can only delete a draft
        invoice (you cannot delete a published invoice, including one that is scheduled for processing).

        Parameters
        ----------
        invoice_id : str
            The ID of the invoice to delete.

        version : typing.Optional[int]
            The version of the [invoice](entity:Invoice) to delete.
            If you do not know the version, you can call [GetInvoice](api-endpoint:Invoices-GetInvoice) or
            [ListInvoices](api-endpoint:Invoices-ListInvoices).

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[DeleteInvoiceResponse]
            Success
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}",
            method="DELETE",
            params={
                "version": version,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    DeleteInvoiceResponse,
                    construct_type(
                        type_=DeleteInvoiceResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def create_invoice_attachment(
        self,
        invoice_id: str,
        *,
        request: typing.Optional[CreateInvoiceAttachmentRequestDataParams] = OMIT,
        image_file: typing.Optional[core.File] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[CreateInvoiceAttachmentResponse]:
        """
        Uploads a file and attaches it to an invoice. This endpoint accepts HTTP multipart/form-data file uploads
        with a JSON `request` part and a `file` part. The `file` part must be a `readable stream` that contains a file
        in a supported format: GIF, JPEG, PNG, TIFF, BMP, or PDF.

        Invoices can have up to 10 attachments with a total file size of 25 MB. Attachments can be added only to invoices
        in the `DRAFT`, `SCHEDULED`, `UNPAID`, or `PARTIALLY_PAID` state.

        __NOTE:__ When testing in the Sandbox environment, the total file size is limited to 1 KB.

        Parameters
        ----------
        invoice_id : str
            The ID of the [invoice](entity:Invoice) to attach the file to.

        request : typing.Optional[CreateInvoiceAttachmentRequestDataParams]

        image_file : typing.Optional[core.File]
            See core.File for more documentation

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[CreateInvoiceAttachmentResponse]
            Success
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}/attachments",
            method="POST",
            data={},
            files={
                **(
                    {"request": (None, json.dumps(jsonable_encoder(request)), "application/json; charset=utf-8")}
                    if request is not OMIT
                    else {}
                ),
                **(
                    {"image_file": core.with_content_type(file=image_file, default_content_type="image/jpeg")}
                    if image_file is not None
                    else {}
                ),
            },
            request_options=request_options,
            omit=OMIT,
            force_multipart=True,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CreateInvoiceAttachmentResponse,
                    construct_type(
                        type_=CreateInvoiceAttachmentResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def delete_invoice_attachment(
        self, invoice_id: str, attachment_id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[DeleteInvoiceAttachmentResponse]:
        """
        Removes an attachment from an invoice and permanently deletes the file. Attachments can be removed only
        from invoices in the `DRAFT`, `SCHEDULED`, `UNPAID`, or `PARTIALLY_PAID` state.

        Parameters
        ----------
        invoice_id : str
            The ID of the [invoice](entity:Invoice) to delete the attachment from.

        attachment_id : str
            The ID of the [attachment](entity:InvoiceAttachment) to delete.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[DeleteInvoiceAttachmentResponse]
            Success
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}/attachments/{jsonable_encoder(attachment_id)}",
            method="DELETE",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    DeleteInvoiceAttachmentResponse,
                    construct_type(
                        type_=DeleteInvoiceAttachmentResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def cancel(
        self, invoice_id: str, *, version: int, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[CancelInvoiceResponse]:
        """
        Cancels an invoice. The seller cannot collect payments for
        the canceled invoice.

        You cannot cancel an invoice in the `DRAFT` state or in a terminal state: `PAID`, `REFUNDED`, `CANCELED`, or `FAILED`.

        Parameters
        ----------
        invoice_id : str
            The ID of the [invoice](entity:Invoice) to cancel.

        version : int
            The version of the [invoice](entity:Invoice) to cancel.
            If you do not know the version, you can call
            [GetInvoice](api-endpoint:Invoices-GetInvoice) or [ListInvoices](api-endpoint:Invoices-ListInvoices).

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[CancelInvoiceResponse]
            Success
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}/cancel",
            method="POST",
            json={
                "version": version,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CancelInvoiceResponse,
                    construct_type(
                        type_=CancelInvoiceResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    def publish(
        self,
        invoice_id: str,
        *,
        version: int,
        idempotency_key: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[PublishInvoiceResponse]:
        """
        Publishes the specified draft invoice.

        After an invoice is published, Square
        follows up based on the invoice configuration. For example, Square
        sends the invoice to the customer's email address, charges the customer's card on file, or does
        nothing. Square also makes the invoice available on a Square-hosted invoice page.

        The invoice `status` also changes from `DRAFT` to a status
        based on the invoice configuration. For example, the status changes to `UNPAID` if
        Square emails the invoice or `PARTIALLY_PAID` if Square charges a card on file for a portion of the
        invoice amount.

        In addition to the required `ORDERS_WRITE` and `INVOICES_WRITE` permissions, `CUSTOMERS_READ`
        and `PAYMENTS_WRITE` are required when publishing invoices configured for card-on-file payments.

        Parameters
        ----------
        invoice_id : str
            The ID of the invoice to publish.

        version : int
            The version of the [invoice](entity:Invoice) to publish.
            This must match the current version of the invoice; otherwise, the request is rejected.

        idempotency_key : typing.Optional[str]
            A unique string that identifies the `PublishInvoice` request. If you do not
            provide `idempotency_key` (or provide an empty string as the value), the endpoint
            treats each request as independent.

            For more information, see [Idempotency](https://developer.squareup.com/docs/build-basics/common-api-patterns/idempotency).

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[PublishInvoiceResponse]
            Success
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}/publish",
            method="POST",
            json={
                "version": version,
                "idempotency_key": idempotency_key,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    PublishInvoiceResponse,
                    construct_type(
                        type_=PublishInvoiceResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)


class AsyncRawInvoicesClient:
    def __init__(self, *, client_wrapper: AsyncClientWrapper):
        self._client_wrapper = client_wrapper

    async def list(
        self,
        *,
        location_id: str,
        cursor: typing.Optional[str] = None,
        limit: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncPager[Invoice]:
        """
        Returns a list of invoices for a given location. The response
        is paginated. If truncated, the response includes a `cursor` that you
        use in a subsequent request to retrieve the next set of invoices.

        Parameters
        ----------
        location_id : str
            The ID of the location for which to list invoices.

        cursor : typing.Optional[str]
            A pagination cursor returned by a previous call to this endpoint.
            Provide this cursor to retrieve the next set of results for your original query.

            For more information, see [Pagination](https://developer.squareup.com/docs/build-basics/common-api-patterns/pagination).

        limit : typing.Optional[int]
            The maximum number of invoices to return (200 is the maximum `limit`).
            If not provided, the server uses a default limit of 100 invoices.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncPager[Invoice]
            Success
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v2/invoices",
            method="GET",
            params={
                "location_id": location_id,
                "cursor": cursor,
                "limit": limit,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _parsed_response = typing.cast(
                    ListInvoicesResponse,
                    construct_type(
                        type_=ListInvoicesResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                _items = _parsed_response.invoices
                _parsed_next = _parsed_response.cursor
                _has_next = _parsed_next is not None and _parsed_next != ""

                async def _get_next():
                    return await self.list(
                        location_id=location_id,
                        cursor=_parsed_next,
                        limit=limit,
                        request_options=request_options,
                    )

                return AsyncPager(
                    has_next=_has_next, items=_items, get_next=_get_next, response=BaseHttpResponse(response=_response)
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def create(
        self,
        *,
        invoice: InvoiceParams,
        idempotency_key: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[CreateInvoiceResponse]:
        """
        Creates a draft [invoice](entity:Invoice)
        for an order created using the Orders API.

        A draft invoice remains in your account and no action is taken.
        You must publish the invoice before Square can process it (send it to the customer's email address or charge the customer’s card on file).

        Parameters
        ----------
        invoice : InvoiceParams
            The invoice to create.

        idempotency_key : typing.Optional[str]
            A unique string that identifies the `CreateInvoice` request. If you do not
            provide `idempotency_key` (or provide an empty string as the value), the endpoint
            treats each request as independent.

            For more information, see [Idempotency](https://developer.squareup.com/docs/build-basics/common-api-patterns/idempotency).

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[CreateInvoiceResponse]
            Success
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v2/invoices",
            method="POST",
            json={
                "invoice": convert_and_respect_annotation_metadata(
                    object_=invoice, annotation=InvoiceParams, direction="write"
                ),
                "idempotency_key": idempotency_key,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CreateInvoiceResponse,
                    construct_type(
                        type_=CreateInvoiceResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def search(
        self,
        *,
        query: InvoiceQueryParams,
        limit: typing.Optional[int] = OMIT,
        cursor: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[SearchInvoicesResponse]:
        """
        Searches for invoices from a location specified in
        the filter. You can optionally specify customers in the filter for whom to
        retrieve invoices. In the current implementation, you can only specify one location and
        optionally one customer.

        The response is paginated. If truncated, the response includes a `cursor`
        that you use in a subsequent request to retrieve the next set of invoices.

        Parameters
        ----------
        query : InvoiceQueryParams
            Describes the query criteria for searching invoices.

        limit : typing.Optional[int]
            The maximum number of invoices to return (200 is the maximum `limit`).
            If not provided, the server uses a default limit of 100 invoices.

        cursor : typing.Optional[str]
            A pagination cursor returned by a previous call to this endpoint.
            Provide this cursor to retrieve the next set of results for your original query.

            For more information, see [Pagination](https://developer.squareup.com/docs/build-basics/common-api-patterns/pagination).

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[SearchInvoicesResponse]
            Success
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v2/invoices/search",
            method="POST",
            json={
                "query": convert_and_respect_annotation_metadata(
                    object_=query, annotation=InvoiceQueryParams, direction="write"
                ),
                "limit": limit,
                "cursor": cursor,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    SearchInvoicesResponse,
                    construct_type(
                        type_=SearchInvoicesResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def get(
        self, invoice_id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[GetInvoiceResponse]:
        """
        Retrieves an invoice by invoice ID.

        Parameters
        ----------
        invoice_id : str
            The ID of the invoice to retrieve.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[GetInvoiceResponse]
            Success
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}",
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    GetInvoiceResponse,
                    construct_type(
                        type_=GetInvoiceResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def update(
        self,
        invoice_id: str,
        *,
        invoice: InvoiceParams,
        idempotency_key: typing.Optional[str] = OMIT,
        fields_to_clear: typing.Optional[typing.Sequence[str]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[UpdateInvoiceResponse]:
        """
        Updates an invoice. This endpoint supports sparse updates, so you only need
        to specify the fields you want to change along with the required `version` field.
        Some restrictions apply to updating invoices. For example, you cannot change the
        `order_id` or `location_id` field.

        Parameters
        ----------
        invoice_id : str
            The ID of the invoice to update.

        invoice : InvoiceParams
            The invoice fields to add, change, or clear. Fields can be cleared using
            null values or the `remove` field (for individual payment requests or reminders).
            The current invoice `version` is also required. For more information, including requirements,
            limitations, and more examples, see [Update an Invoice](https://developer.squareup.com/docs/invoices-api/update-invoices).

        idempotency_key : typing.Optional[str]
            A unique string that identifies the `UpdateInvoice` request. If you do not
            provide `idempotency_key` (or provide an empty string as the value), the endpoint
            treats each request as independent.

            For more information, see [Idempotency](https://developer.squareup.com/docs/build-basics/common-api-patterns/idempotency).

        fields_to_clear : typing.Optional[typing.Sequence[str]]
            The list of fields to clear. Although this field is currently supported, we
            recommend using null values or the `remove` field when possible. For examples, see
            [Update an Invoice](https://developer.squareup.com/docs/invoices-api/update-invoices).

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[UpdateInvoiceResponse]
            Success
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}",
            method="PUT",
            json={
                "invoice": convert_and_respect_annotation_metadata(
                    object_=invoice, annotation=InvoiceParams, direction="write"
                ),
                "idempotency_key": idempotency_key,
                "fields_to_clear": fields_to_clear,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    UpdateInvoiceResponse,
                    construct_type(
                        type_=UpdateInvoiceResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def delete(
        self,
        invoice_id: str,
        *,
        version: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[DeleteInvoiceResponse]:
        """
        Deletes the specified invoice. When an invoice is deleted, the
        associated order status changes to CANCELED. You can only delete a draft
        invoice (you cannot delete a published invoice, including one that is scheduled for processing).

        Parameters
        ----------
        invoice_id : str
            The ID of the invoice to delete.

        version : typing.Optional[int]
            The version of the [invoice](entity:Invoice) to delete.
            If you do not know the version, you can call [GetInvoice](api-endpoint:Invoices-GetInvoice) or
            [ListInvoices](api-endpoint:Invoices-ListInvoices).

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[DeleteInvoiceResponse]
            Success
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}",
            method="DELETE",
            params={
                "version": version,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    DeleteInvoiceResponse,
                    construct_type(
                        type_=DeleteInvoiceResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def create_invoice_attachment(
        self,
        invoice_id: str,
        *,
        request: typing.Optional[CreateInvoiceAttachmentRequestDataParams] = OMIT,
        image_file: typing.Optional[core.File] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[CreateInvoiceAttachmentResponse]:
        """
        Uploads a file and attaches it to an invoice. This endpoint accepts HTTP multipart/form-data file uploads
        with a JSON `request` part and a `file` part. The `file` part must be a `readable stream` that contains a file
        in a supported format: GIF, JPEG, PNG, TIFF, BMP, or PDF.

        Invoices can have up to 10 attachments with a total file size of 25 MB. Attachments can be added only to invoices
        in the `DRAFT`, `SCHEDULED`, `UNPAID`, or `PARTIALLY_PAID` state.

        __NOTE:__ When testing in the Sandbox environment, the total file size is limited to 1 KB.

        Parameters
        ----------
        invoice_id : str
            The ID of the [invoice](entity:Invoice) to attach the file to.

        request : typing.Optional[CreateInvoiceAttachmentRequestDataParams]

        image_file : typing.Optional[core.File]
            See core.File for more documentation

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[CreateInvoiceAttachmentResponse]
            Success
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}/attachments",
            method="POST",
            data={},
            files={
                **(
                    {"request": (None, json.dumps(jsonable_encoder(request)), "application/json; charset=utf-8")}
                    if request is not OMIT
                    else {}
                ),
                **(
                    {"image_file": core.with_content_type(file=image_file, default_content_type="image/jpeg")}
                    if image_file is not None
                    else {}
                ),
            },
            request_options=request_options,
            omit=OMIT,
            force_multipart=True,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CreateInvoiceAttachmentResponse,
                    construct_type(
                        type_=CreateInvoiceAttachmentResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def delete_invoice_attachment(
        self, invoice_id: str, attachment_id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[DeleteInvoiceAttachmentResponse]:
        """
        Removes an attachment from an invoice and permanently deletes the file. Attachments can be removed only
        from invoices in the `DRAFT`, `SCHEDULED`, `UNPAID`, or `PARTIALLY_PAID` state.

        Parameters
        ----------
        invoice_id : str
            The ID of the [invoice](entity:Invoice) to delete the attachment from.

        attachment_id : str
            The ID of the [attachment](entity:InvoiceAttachment) to delete.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[DeleteInvoiceAttachmentResponse]
            Success
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}/attachments/{jsonable_encoder(attachment_id)}",
            method="DELETE",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    DeleteInvoiceAttachmentResponse,
                    construct_type(
                        type_=DeleteInvoiceAttachmentResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def cancel(
        self, invoice_id: str, *, version: int, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[CancelInvoiceResponse]:
        """
        Cancels an invoice. The seller cannot collect payments for
        the canceled invoice.

        You cannot cancel an invoice in the `DRAFT` state or in a terminal state: `PAID`, `REFUNDED`, `CANCELED`, or `FAILED`.

        Parameters
        ----------
        invoice_id : str
            The ID of the [invoice](entity:Invoice) to cancel.

        version : int
            The version of the [invoice](entity:Invoice) to cancel.
            If you do not know the version, you can call
            [GetInvoice](api-endpoint:Invoices-GetInvoice) or [ListInvoices](api-endpoint:Invoices-ListInvoices).

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[CancelInvoiceResponse]
            Success
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}/cancel",
            method="POST",
            json={
                "version": version,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    CancelInvoiceResponse,
                    construct_type(
                        type_=CancelInvoiceResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)

    async def publish(
        self,
        invoice_id: str,
        *,
        version: int,
        idempotency_key: typing.Optional[str] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[PublishInvoiceResponse]:
        """
        Publishes the specified draft invoice.

        After an invoice is published, Square
        follows up based on the invoice configuration. For example, Square
        sends the invoice to the customer's email address, charges the customer's card on file, or does
        nothing. Square also makes the invoice available on a Square-hosted invoice page.

        The invoice `status` also changes from `DRAFT` to a status
        based on the invoice configuration. For example, the status changes to `UNPAID` if
        Square emails the invoice or `PARTIALLY_PAID` if Square charges a card on file for a portion of the
        invoice amount.

        In addition to the required `ORDERS_WRITE` and `INVOICES_WRITE` permissions, `CUSTOMERS_READ`
        and `PAYMENTS_WRITE` are required when publishing invoices configured for card-on-file payments.

        Parameters
        ----------
        invoice_id : str
            The ID of the invoice to publish.

        version : int
            The version of the [invoice](entity:Invoice) to publish.
            This must match the current version of the invoice; otherwise, the request is rejected.

        idempotency_key : typing.Optional[str]
            A unique string that identifies the `PublishInvoice` request. If you do not
            provide `idempotency_key` (or provide an empty string as the value), the endpoint
            treats each request as independent.

            For more information, see [Idempotency](https://developer.squareup.com/docs/build-basics/common-api-patterns/idempotency).

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[PublishInvoiceResponse]
            Success
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v2/invoices/{jsonable_encoder(invoice_id)}/publish",
            method="POST",
            json={
                "version": version,
                "idempotency_key": idempotency_key,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    PublishInvoiceResponse,
                    construct_type(
                        type_=PublishInvoiceResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
        raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
