diff --git a/CHANGELOG.md b/CHANGELOG.md index eba42ea..caa0831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ Given a version number MAJOR.MINOR.PATCH, increment: ## [Unreleased] +### Added +- PaymentLink resource ## [2.34.0] - 2026-05-07 ### Changed diff --git a/README.md b/README.md index ee8ee0c..a97535f 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ is as easy as sending a text message to your client! - [DarfPayments](#create-darf-payment): Pay DARFs - [PaymentPreviews](#preview-payment-information-before-executing-the-payment): Preview all sorts of payments - [PaymentRequest](#create-payment-requests-to-be-approved-by-authorized-people-in-a-cost-center): Request a payment approval to a cost center + - [PaymentLinks](#create-paymentlinks): Shareable links to collect payments with credit/debit cards - [CorporateHolders](#create-corporateholders): Manage cardholders - [CorporateCards](#create-corporatecards): Create virtual and/or physical cards - [CorporateInvoices](#create-corporateinvoices): Add money to your corporate balance @@ -1964,6 +1965,177 @@ for request in requests: print(request) ``` +## Create PaymentLinks + +You can create shareable PaymentLinks to collect payments from your customers using credit or debit cards. + +```python +import starkbank + +links = starkbank.paymentlink.create([ + starkbank.PaymentLink( + name="Assinatura Premium", + amount=15000, # R$ 150.00 in cents + usage_mode="single", + allowed_methods=["credit", "debit"], + allowed_installments=[ + starkbank.paymentlink.AllowedInstallment(count=1, total_amount=15000), + starkbank.paymentlink.AllowedInstallment(count=2, total_amount=15750), + starkbank.paymentlink.AllowedInstallment(count=3, total_amount=16500), + ], + expiration=36000, # 10 hours in seconds + description="Plano trinta dias", + success_url="https://merchant.com/obrigado", + tags=["campanha-abril", "plano-premium"], + items=[ + starkbank.paymentlink.Item( + code="PREM-001", + description="Plano Premium Mensal", + quantity=1, + unit_price=15000, + total_price=15000, + discount=0, + ), + ], + metadata={ + "customerId": "5678901234567890", + "orderId": "ORD-2026-001", + }, + ) +]) + +for link in links: + print(link) +``` + +**Note**: Instead of using PaymentLink objects, you can also pass each link element in dictionary format + +## Get a PaymentLink + +After its creation, information on a PaymentLink may be retrieved by its id. + +```python +import starkbank + +link = starkbank.paymentlink.get("5155165527080960") + +print(link) +``` + +## Query PaymentLinks + +You can query multiple PaymentLinks according to filters. + +```python +import starkbank +from datetime import datetime + +links = starkbank.paymentlink.query( + status="active", + after=datetime(2020, 1, 1), + before=datetime(2020, 3, 1), +) + +for link in links: + print(link) +``` + +## Update a PaymentLink + +You can cancel an active PaymentLink by passing 'canceling' in the status. + +```python +import starkbank + +link = starkbank.paymentlink.update("5155165527080960", status="canceling") + +print(link) +``` + +## Query PaymentLink logs + +Logs help you understand the life cycle of a PaymentLink. + +```python +import starkbank + +logs = starkbank.paymentlink.log.query( + payment_link_ids=["5155165527080960"], + limit=50, +) + +for log in logs: + print(log) +``` + +## Get a PaymentLink log + +You can get a single log by its id. + +```python +import starkbank + +log = starkbank.paymentlink.log.get("5155165527080960") + +print(log) +``` + +## Query PaymentLink attempts + +You can query payment attempts made against a PaymentLink. + +```python +import starkbank + +attempts = starkbank.paymentlink.attempt.query( + payment_link_ids=["5155165527080960"], + limit=50, +) + +for attempt in attempts: + print(attempt) +``` + +## Get a PaymentLink attempt + +You can get a single PaymentLink attempt by its id. + +```python +import starkbank + +attempt = starkbank.paymentlink.attempt.get("5155165527080960") + +print(attempt) +``` + +## Query PaymentLink attempt logs + +You can query logs of payment attempts made against a PaymentLink. + +```python +import starkbank + +logs = starkbank.paymentlink.attempt.log.query( + payment_link_attempt_ids=["5155165527080960"], + limit=50, +) + +for log in logs: + print(log) +``` + +## Get a PaymentLink attempt log + +You can get a single PaymentLink attempt log by its id. + +```python +import starkbank + +log = starkbank.paymentlink.attempt.log.get("5155165527080960") + +print(log) +``` + ## Corporate ## Create CorporateHolders diff --git a/starkbank/__init__.py b/starkbank/__init__.py index 23aa03c..18fceb1 100644 --- a/starkbank/__init__.py +++ b/starkbank/__init__.py @@ -103,6 +103,9 @@ from . import paymentrequest from .paymentrequest.__paymentrequest import PaymentRequest +from . import paymentlink +from .paymentlink.__paymentlink import PaymentLink + from . import invoice from .invoice.__invoice import Invoice diff --git a/starkbank/paymentlink/__init__.py b/starkbank/paymentlink/__init__.py new file mode 100644 index 0000000..fdfab88 --- /dev/null +++ b/starkbank/paymentlink/__init__.py @@ -0,0 +1,7 @@ +from .__paymentlink import create, get, query, page, update +from .log.__log import Log +from . import log +from .attempt.__attempt import Attempt +from . import attempt +from .allowedinstallment.__allowedinstallment import AllowedInstallment +from .item.__item import Item diff --git a/starkbank/paymentlink/__paymentlink.py b/starkbank/paymentlink/__paymentlink.py new file mode 100644 index 0000000..c6d3259 --- /dev/null +++ b/starkbank/paymentlink/__paymentlink.py @@ -0,0 +1,199 @@ +from datetime import datetime +from ..utils import rest +from starkcore.utils.api import from_api_json +from starkcore.utils.resource import Resource +from starkcore.utils.checks import check_date, check_datetime, check_timedelta +from .allowedinstallment.__allowedinstallment import AllowedInstallment +from .allowedinstallment.__allowedinstallment import _sub_resource as _allowed_installment_sub_resource +from .item.__item import Item +from .item.__item import _sub_resource as _item_sub_resource + + +class PaymentLink(Resource): + """# PaymentLink object + When you initialize a PaymentLink, the entity will not be automatically + sent to the Stark Bank API. The 'create' function sends the objects + to the Stark Bank API and returns the list of created objects. + ## Parameters (required): + - name [string]: payment link name displayed to the payer. ex: "Assinatura Premium" + - amount [integer]: payment link total amount in cents. ex: 15000 (= R$ 150.00) + - usage_mode [string]: defines how many times the link can be paid. ex: "single" or "multi" + - allowed_methods [list of strings]: list of accepted payment methods. ex: ["credit", "debit"] + ## Parameters (conditionally required): + - allowed_installments [list of PaymentLink.AllowedInstallment objects]: list of accepted installment options. Required when allowed_methods includes "credit" or "debit". ex: [PaymentLink.AllowedInstallment(count=1, total_amount=15000)] + ## Parameters (optional): + - expiration [integer or datetime.timedelta, default None]: time interval in seconds between creation and expiration when sent to the API. ex: 36000 (= 10 hours). On API responses this field is returned as a datetime.datetime instead. + - description [string, default None]: payment link description displayed to the payer. ex: "Plano trinta dias" + - success_url [string, default None]: URL where the payer is redirected after a successful payment. ex: "https://merchant.com/obrigado" + - tags [list of strings, default None]: list of strings for tagging. ex: ["campanha-abril", "plano-premium"] + - timestamp [datetime.datetime or string, default None]: payment link reference timestamp. ex: datetime.datetime(2026, 4, 8, 12, 0, 0, 0) + - items [list of PaymentLink.Item objects, default None]: list of PaymentLink.Item objects describing the purchase contents. ex: [PaymentLink.Item(code="PREM-001", description="Plano Premium Mensal", quantity=1, unit_price=15000, total_price=15000, discount=0)] + - metadata [dictionary, default None]: dictionary object used to store additional information about the PaymentLink object. ex: {"customerId": "5678901234567890", "orderId": "ORD-2026-001"} + ## Attributes (return-only): + - id [string]: unique id returned when PaymentLink is created. ex: "5656565656565656" + - status [string]: current PaymentLink status. ex: "active", "expired", "paid", "canceling" or "canceled" + - token [string]: public token used to access the PaymentLink web page. ex: "b3c836df0d744fad9c8adcd960304930" + - url [string]: public URL where the payer can complete the payment. ex: "https://starkv2.sandbox.starkbank.com/paymentlink/b3c836df0d744fad9c8adcd960304930" + - created [datetime.datetime]: creation datetime for the PaymentLink. ex: datetime.datetime(2026, 4, 8, 12, 0, 0, 0) + - updated [datetime.datetime]: latest update datetime for the PaymentLink. ex: datetime.datetime(2026, 4, 8, 12, 0, 0, 0) + """ + + def __init__(self, name, amount, usage_mode, allowed_methods, allowed_installments=None, expiration=None, + description=None, success_url=None, tags=None, timestamp=None, items=None, metadata=None, + id=None, status=None, token=None, url=None, created=None, updated=None): + Resource.__init__(self, id=id) + + self.name = name + self.amount = amount + self.usage_mode = usage_mode + self.allowed_methods = allowed_methods + self.allowed_installments = _parse_allowed_installments(allowed_installments) + self.expiration = _parse_expiration(expiration) + self.description = description + self.success_url = success_url + self.tags = tags + # API returns "" (not omitted) when the link was created without a timestamp. + self.timestamp = check_datetime(timestamp or None) + self.items = _parse_items(items) + self.metadata = metadata + self.status = status + self.token = token + self.url = url + self.created = check_datetime(created) + self.updated = check_datetime(updated) + + +_resource = {"class": PaymentLink, "name": "PaymentLink"} + + +def _parse_allowed_installments(allowed_installments): + if allowed_installments is None: + return None + parsed_allowed_installments = [] + for allowed_installment in allowed_installments: + if isinstance(allowed_installment, AllowedInstallment): + parsed_allowed_installments.append(allowed_installment) + continue + parsed_allowed_installments.append(from_api_json(_allowed_installment_sub_resource, allowed_installment)) + return parsed_allowed_installments + + +def _parse_expiration(expiration): + # API quirk: `expiration` is sent as a seconds interval on create + # but returned as an ISO datetime on read (routes.md:214). + if isinstance(expiration, (str, datetime)): + return check_datetime(expiration) + return check_timedelta(expiration) + + +def _parse_items(items): + if items is None: + return None + parsed_items = [] + for item in items: + if isinstance(item, Item): + parsed_items.append(item) + continue + parsed_items.append(from_api_json(_item_sub_resource, item)) + return parsed_items + + +def create(links, user=None): + """# Create PaymentLinks + Send a list of PaymentLink objects for creation in the Stark Bank API. + Each PaymentLink with `allowed_methods` containing `"credit"` or `"debit"` must include + `allowed_installments`. The API accepts up to 100 PaymentLinks per request. + ## Parameters (required): + - links [list of PaymentLink objects]: list of PaymentLink objects to be created in the API + ## Parameters (optional): + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - list of PaymentLink objects with updated attributes + """ + return rest.post_multi(resource=_resource, entities=links, user=user) + + +def get(id, user=None): + """# Retrieve a specific PaymentLink + Receive a single PaymentLink object previously created in the Stark Bank API by its id + ## Parameters (required): + - id [string]: object unique id. ex: "5656565656565656" + ## Parameters (optional): + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - PaymentLink object with updated attributes + """ + return rest.get_id(resource=_resource, id=id, user=user) + + +def query(limit=None, after=None, before=None, status=None, tags=None, ids=None, user=None): + """# Retrieve PaymentLinks + Receive a generator of PaymentLink objects previously created in the Stark Bank API + ## Parameters (optional): + - limit [integer, default None]: maximum number of objects to be retrieved. Unlimited if None. ex: 35 + - after [datetime.date or string, default None]: date filter for objects created or updated only after specified date. ex: datetime.date(2020, 3, 10) + - before [datetime.date or string, default None]: date filter for objects created or updated only before specified date. ex: datetime.date(2020, 3, 10) + - status [string or list of strings, default None]: filter for status of retrieved objects. ex: "active" or ["active", "paid"]. Accepted values: "active", "expired", "paid", "canceling", "canceled" + - tags [list of strings, default None]: tags to filter retrieved objects. ex: ["tony", "stark"] + - ids [list of strings, default None]: list of ids to filter retrieved objects. Accepts up to 30 ids. ex: ["5656565656565656", "4545454545454545"] + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - generator of PaymentLink objects with updated attributes + """ + return rest.get_stream( + resource=_resource, + limit=limit, + after=check_date(after), + before=check_date(before), + status=status, + tags=tags, + ids=ids, + user=user, + ) + + +def page(cursor=None, limit=None, after=None, before=None, status=None, tags=None, ids=None, user=None): + """# Retrieve paged PaymentLinks + Receive a list of up to 100 PaymentLink objects previously created in the Stark Bank API and the cursor to the next page. + Use this function instead of query if you want to manually page your requests. + ## Parameters (optional): + - cursor [string, default None]: cursor returned on the previous page function call + - limit [integer, default 100]: maximum number of objects to be retrieved. It must be an integer between 1 and 100. ex: 50 + - after [datetime.date or string, default None]: date filter for objects created or updated only after specified date. ex: datetime.date(2020, 3, 10) + - before [datetime.date or string, default None]: date filter for objects created or updated only before specified date. ex: datetime.date(2020, 3, 10) + - status [string or list of strings, default None]: filter for status of retrieved objects. ex: "active" or ["active", "paid"]. Accepted values: "active", "expired", "paid", "canceling", "canceled" + - tags [list of strings, default None]: tags to filter retrieved objects. ex: ["tony", "stark"] + - ids [list of strings, default None]: list of ids to filter retrieved objects. Accepts up to 30 ids. ex: ["5656565656565656", "4545454545454545"] + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - list of PaymentLink objects with updated attributes + - cursor to retrieve the next page of PaymentLink objects + """ + return rest.get_page( + resource=_resource, + cursor=cursor, + limit=limit, + after=check_date(after), + before=check_date(before), + status=status, + tags=tags, + ids=ids, + user=user, + ) + + +def update(id, status=None, user=None): + """# Update PaymentLink entity + Update a PaymentLink by passing id. You may cancel an active PaymentLink by passing 'canceling' in the status. + ## Parameters (required): + - id [string]: PaymentLink id. ex: '5656565656565656' + ## Parameters (optional): + - status [string, default None]: You may cancel the PaymentLink by passing 'canceling' in the status. Only "canceling" is accepted by the API + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - target PaymentLink with updated attributes + """ + payload = { + "status": status, + } + return rest.patch_id(resource=_resource, id=id, user=user, payload=payload) diff --git a/starkbank/paymentlink/allowedinstallment/__allowedinstallment.py b/starkbank/paymentlink/allowedinstallment/__allowedinstallment.py new file mode 100644 index 0000000..d987f30 --- /dev/null +++ b/starkbank/paymentlink/allowedinstallment/__allowedinstallment.py @@ -0,0 +1,20 @@ +from starkcore.utils.subresource import SubResource + + +class AllowedInstallment(SubResource): + """# PaymentLink.AllowedInstallment object + Represents an installment option accepted by a PaymentLink, declaring + the number of installments and the total amount charged to the payer + for that option. + ## Parameters (required): + - count [integer]: number of installments. ex: 3 + - total_amount [integer]: total amount in cents charged to the payer for this installment count. ex: 16500 (= R$ 165.00) + """ + + def __init__(self, count, total_amount): + super().__init__() + self.count = count + self.total_amount = total_amount + + +_sub_resource = {"class": AllowedInstallment, "name": "AllowedInstallment"} diff --git a/starkbank/paymentlink/allowedinstallment/__init__.py b/starkbank/paymentlink/allowedinstallment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/starkbank/paymentlink/attempt/__attempt.py b/starkbank/paymentlink/attempt/__attempt.py new file mode 100644 index 0000000..a769169 --- /dev/null +++ b/starkbank/paymentlink/attempt/__attempt.py @@ -0,0 +1,103 @@ +from ...utils import rest +from starkcore.utils.resource import Resource +from starkcore.utils.checks import check_datetime, check_date + + +class Attempt(Resource): + """# PaymentLink.Attempt object + Each payment attempt against a PaymentLink generates a PaymentLink.Attempt + record. It carries information about the payment method used, the resulting + payment and the lifecycle status of the attempt. + ## Attributes (return-only): + - id [string]: unique id that identifies the payment attempt. ex: "5656565656565656" + - payment_link_id [string]: ID of the PaymentLink that originated the attempt. ex: "5764309015642112" + - method [string]: payment method used in the attempt. ex: "credit", "debit" + - payment [string]: reference to the underlying payment created from this attempt. ex: "merchant-session/6543210987654321" + - amount [integer]: amount charged in this attempt in cents. ex: 15000 (= R$ 150.00) + - status [string]: current attempt status. ex: "created", "success", "failed" or "expired" + - created [datetime.datetime]: datetime representing the moment when the attempt was created. ex: datetime.datetime(2020, 3, 10, 10, 30, 0, 0) + - updated [datetime.datetime]: latest update datetime for the attempt. ex: datetime.datetime(2020, 3, 10, 10, 30, 0, 0) + """ + + def __init__(self, id, payment_link_id, method, payment, amount, status, created, updated): + Resource.__init__(self, id=id) + + self.payment_link_id = payment_link_id + self.method = method + self.payment = payment + self.amount = amount + self.status = status + self.created = check_datetime(created) + self.updated = check_datetime(updated) + + +_resource = {"class": Attempt, "name": "PaymentLinkAttempt"} + + +def get(id, user=None): + """# Retrieve a specific paymentlink.Attempt + Receive a single paymentlink.Attempt object previously created by the Stark Bank API by its id + ## Parameters (required): + - id [string]: object unique id. ex: "5656565656565656" + ## Parameters (optional): + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - paymentlink.Attempt object with updated attributes + """ + return rest.get_id(resource=_resource, id=id, user=user) + + +def query(limit=None, after=None, before=None, status=None, payment_link_ids=None, ids=None, user=None): + """# Retrieve paymentlink.Attempts + Receive a generator of paymentlink.Attempt objects previously created in the Stark Bank API + ## Parameters (optional): + - limit [integer, default None]: maximum number of objects to be retrieved. Unlimited if None. ex: 35 + - after [datetime.date or string, default None]: date filter for objects created only after specified date. ex: datetime.date(2020, 3, 10) + - before [datetime.date or string, default None]: date filter for objects created only before specified date. ex: datetime.date(2020, 3, 10) + - status [string, default None]: filter for status of retrieved objects. ex: "success" or "failed" + - payment_link_ids [list of strings, default None]: list of PaymentLink ids to filter attempts. ex: ["5656565656565656", "4545454545454545"] + - ids [list of strings, default None]: list of ids to filter retrieved objects. ex: ["5656565656565656", "4545454545454545"] + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - generator of paymentlink.Attempt objects with updated attributes + """ + return rest.get_stream( + resource=_resource, + limit=limit, + after=check_date(after), + before=check_date(before), + status=status, + payment_link_ids=payment_link_ids, + ids=ids, + user=user, + ) + + +def page(cursor=None, limit=None, after=None, before=None, status=None, payment_link_ids=None, ids=None, user=None): + """# Retrieve paged paymentlink.Attempts + Receive a list of up to 100 paymentlink.Attempt objects previously created in the Stark Bank API and the cursor to the next page. + Use this function instead of query if you want to manually page your requests. + ## Parameters (optional): + - cursor [string, default None]: cursor returned on the previous page function call + - limit [integer, default 100]: maximum number of objects to be retrieved. It must be an integer between 1 and 100. ex: 50 + - after [datetime.date or string, default None]: date filter for objects created only after specified date. ex: datetime.date(2020, 3, 10) + - before [datetime.date or string, default None]: date filter for objects created only before specified date. ex: datetime.date(2020, 3, 10) + - status [string, default None]: filter for status of retrieved objects. ex: "success" or "failed" + - payment_link_ids [list of strings, default None]: list of PaymentLink ids to filter attempts. ex: ["5656565656565656", "4545454545454545"] + - ids [list of strings, default None]: list of ids to filter retrieved objects. ex: ["5656565656565656", "4545454545454545"] + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - list of paymentlink.Attempt objects with updated attributes + - cursor to retrieve the next page of paymentlink.Attempt objects + """ + return rest.get_page( + resource=_resource, + cursor=cursor, + limit=limit, + after=check_date(after), + before=check_date(before), + status=status, + payment_link_ids=payment_link_ids, + ids=ids, + user=user, + ) diff --git a/starkbank/paymentlink/attempt/__init__.py b/starkbank/paymentlink/attempt/__init__.py new file mode 100644 index 0000000..080a68f --- /dev/null +++ b/starkbank/paymentlink/attempt/__init__.py @@ -0,0 +1,3 @@ +from .__attempt import query, page, get +from .log.__log import Log +from . import log diff --git a/starkbank/paymentlink/attempt/log/__init__.py b/starkbank/paymentlink/attempt/log/__init__.py new file mode 100644 index 0000000..a8a69ba --- /dev/null +++ b/starkbank/paymentlink/attempt/log/__init__.py @@ -0,0 +1 @@ +from .__log import query, page, get diff --git a/starkbank/paymentlink/attempt/log/__log.py b/starkbank/paymentlink/attempt/log/__log.py new file mode 100644 index 0000000..20c6713 --- /dev/null +++ b/starkbank/paymentlink/attempt/log/__log.py @@ -0,0 +1,96 @@ +from ....utils import rest +from starkcore.utils.api import from_api_json +from starkcore.utils.resource import Resource +from starkcore.utils.checks import check_datetime, check_date +from ..__attempt import _resource as _paymentlink_attempt_resource + + +class Log(Resource): + """# paymentlink.attempt.Log object + Every time a PaymentLink.Attempt entity is updated, a corresponding + paymentlink.attempt.Log is generated for the entity. This log is never + generated by the user, but it can be retrieved to check additional + information on the PaymentLink.Attempt. + ## Attributes (return-only): + - id [string]: unique id returned when the log is created. ex: "5656565656565656" + - attempt [PaymentLink.Attempt]: PaymentLink.Attempt entity to which the log refers to. + - errors [list of strings]: list of errors linked to this PaymentLink.Attempt event + - type [string]: type of the PaymentLink.Attempt event which triggered the log creation. ex: "created", "success", "expired" or "failed" + - created [datetime.datetime]: creation datetime for the log. ex: datetime.datetime(2020, 3, 10, 10, 30, 0, 0) + """ + + def __init__(self, id, created, type, errors, attempt): + Resource.__init__(self, id=id) + + self.created = check_datetime(created) + self.type = type + self.errors = errors + self.attempt = from_api_json(_paymentlink_attempt_resource, attempt) + + +_resource = {"class": Log, "name": "PaymentLinkAttemptLog"} + + +def get(id, user=None): + """# Retrieve a specific paymentlink.attempt.Log + Receive a single paymentlink.attempt.Log object previously created by the Stark Bank API by its id + ## Parameters (required): + - id [string]: object unique id. ex: "5656565656565656" + ## Parameters (optional): + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - paymentlink.attempt.Log object with updated attributes + """ + return rest.get_id(resource=_resource, id=id, user=user) + + +def query(limit=None, after=None, before=None, types=None, payment_link_attempt_ids=None, user=None): + """# Retrieve paymentlink.attempt.Logs + Receive a generator of paymentlink.attempt.Log objects previously created in the Stark Bank API + ## Parameters (optional): + - limit [integer, default None]: maximum number of objects to be retrieved. Unlimited if None. ex: 35 + - after [datetime.date or string, default None]: date filter for objects created only after specified date. ex: datetime.date(2020, 3, 10) + - before [datetime.date or string, default None]: date filter for objects created only before specified date. ex: datetime.date(2020, 3, 10) + - types [list of strings, default None]: filter for log event types. ex: ["created", "success"] + - payment_link_attempt_ids [list of strings, default None]: list of PaymentLink.Attempt ids to filter logs. ex: ["5656565656565656", "4545454545454545"] + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - generator of paymentlink.attempt.Log objects with updated attributes + """ + return rest.get_stream( + resource=_resource, + limit=limit, + after=check_date(after), + before=check_date(before), + types=types, + payment_link_attempt_ids=payment_link_attempt_ids, + user=user, + ) + + +def page(cursor=None, limit=None, after=None, before=None, types=None, payment_link_attempt_ids=None, user=None): + """# Retrieve paged paymentlink.attempt.Logs + Receive a list of up to 100 paymentlink.attempt.Log objects previously created in the Stark Bank API and the cursor to the next page. + Use this function instead of query if you want to manually page your requests. + ## Parameters (optional): + - cursor [string, default None]: cursor returned on the previous page function call + - limit [integer, default 100]: maximum number of objects to be retrieved. It must be an integer between 1 and 100. ex: 50 + - after [datetime.date or string, default None]: date filter for objects created only after specified date. ex: datetime.date(2020, 3, 10) + - before [datetime.date or string, default None]: date filter for objects created only before specified date. ex: datetime.date(2020, 3, 10) + - types [list of strings, default None]: filter for log event types. ex: ["created", "success"] + - payment_link_attempt_ids [list of strings, default None]: list of PaymentLink.Attempt ids to filter logs. ex: ["5656565656565656", "4545454545454545"] + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - list of paymentlink.attempt.Log objects with updated attributes + - cursor to retrieve the next page of paymentlink.attempt.Log objects + """ + return rest.get_page( + resource=_resource, + cursor=cursor, + limit=limit, + after=check_date(after), + before=check_date(before), + types=types, + payment_link_attempt_ids=payment_link_attempt_ids, + user=user, + ) diff --git a/starkbank/paymentlink/item/__init__.py b/starkbank/paymentlink/item/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/starkbank/paymentlink/item/__item.py b/starkbank/paymentlink/item/__item.py new file mode 100644 index 0000000..ddfc30a --- /dev/null +++ b/starkbank/paymentlink/item/__item.py @@ -0,0 +1,27 @@ +from starkcore.utils.subresource import SubResource + + +class Item(SubResource): + """# PaymentLink.Item object + Represents a single item displayed on a PaymentLink purchase summary. + ## Parameters (required): + - code [string]: merchant code for the item. ex: "PREM-001" + - description [string]: human-readable description of the item. ex: "Plano Premium Mensal" + - quantity [integer]: quantity purchased of this item. ex: 1 + - unit_price [integer]: price per unit in cents. ex: 15000 (= R$ 150.00) + - total_price [integer]: total price in cents for this item (quantity * unit_price minus discount). ex: 15000 (= R$ 150.00) + ## Parameters (optional): + - discount [integer, default None]: discount applied to the item in cents. ex: 0 + """ + + def __init__(self, code, description, quantity, unit_price, total_price, discount=None): + super().__init__() + self.code = code + self.description = description + self.quantity = quantity + self.unit_price = unit_price + self.total_price = total_price + self.discount = discount + + +_sub_resource = {"class": Item, "name": "Item"} diff --git a/starkbank/paymentlink/log/__init__.py b/starkbank/paymentlink/log/__init__.py new file mode 100644 index 0000000..a8a69ba --- /dev/null +++ b/starkbank/paymentlink/log/__init__.py @@ -0,0 +1 @@ +from .__log import query, page, get diff --git a/starkbank/paymentlink/log/__log.py b/starkbank/paymentlink/log/__log.py new file mode 100644 index 0000000..0b15791 --- /dev/null +++ b/starkbank/paymentlink/log/__log.py @@ -0,0 +1,95 @@ +from ...utils import rest +from starkcore.utils.api import from_api_json +from starkcore.utils.resource import Resource +from starkcore.utils.checks import check_datetime, check_date +from ..__paymentlink import _resource as _paymentlink_resource + + +class Log(Resource): + """# paymentlink.Log object + Every time a PaymentLink entity is updated, a corresponding paymentlink.Log + is generated for the entity. This log is never generated by the + user, but it can be retrieved to check additional information on the PaymentLink. + ## Attributes (return-only): + - id [string]: unique id returned when the log is created. ex: "5656565656565656" + - link [PaymentLink]: PaymentLink entity to which the log refers to. + - errors [list of strings]: list of errors linked to this PaymentLink event + - type [string]: type of the PaymentLink event which triggered the log creation. ex: "created", "canceling", "canceled", "expired" or "paid" + - created [datetime.datetime]: creation datetime for the log. ex: datetime.datetime(2020, 3, 10, 10, 30, 0, 0) + """ + + def __init__(self, id, created, type, errors, link): + Resource.__init__(self, id=id) + + self.created = check_datetime(created) + self.type = type + self.errors = errors + self.link = from_api_json(_paymentlink_resource, link) + + +_resource = {"class": Log, "name": "PaymentLinkLog"} + + +def get(id, user=None): + """# Retrieve a specific paymentlink.Log + Receive a single paymentlink.Log object previously created by the Stark Bank API by its id + ## Parameters (required): + - id [string]: object unique id. ex: "5656565656565656" + ## Parameters (optional): + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - paymentlink.Log object with updated attributes + """ + return rest.get_id(resource=_resource, id=id, user=user) + + +def query(limit=None, after=None, before=None, types=None, payment_link_ids=None, user=None): + """# Retrieve paymentlink.Logs + Receive a generator of paymentlink.Log objects previously created in the Stark Bank API + ## Parameters (optional): + - limit [integer, default None]: maximum number of objects to be retrieved. Unlimited if None. ex: 35 + - after [datetime.date or string, default None]: date filter for objects created only after specified date. ex: datetime.date(2020, 3, 10) + - before [datetime.date or string, default None]: date filter for objects created only before specified date. ex: datetime.date(2020, 3, 10) + - types [list of strings, default None]: filter for log event types. ex: ["created", "paid"] + - payment_link_ids [list of strings, default None]: list of PaymentLink ids to filter logs. ex: ["5656565656565656", "4545454545454545"] + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - generator of paymentlink.Log objects with updated attributes + """ + return rest.get_stream( + resource=_resource, + limit=limit, + after=check_date(after), + before=check_date(before), + types=types, + payment_link_ids=payment_link_ids, + user=user, + ) + + +def page(cursor=None, limit=None, after=None, before=None, types=None, payment_link_ids=None, user=None): + """# Retrieve paged paymentlink.Logs + Receive a list of up to 100 paymentlink.Log objects previously created in the Stark Bank API and the cursor to the next page. + Use this function instead of query if you want to manually page your requests. + ## Parameters (optional): + - cursor [string, default None]: cursor returned on the previous page function call + - limit [integer, default 100]: maximum number of objects to be retrieved. It must be an integer between 1 and 100. ex: 50 + - after [datetime.date or string, default None]: date filter for objects created only after specified date. ex: datetime.date(2020, 3, 10) + - before [datetime.date or string, default None]: date filter for objects created only before specified date. ex: datetime.date(2020, 3, 10) + - types [list of strings, default None]: filter for log event types. ex: ["created", "paid"] + - payment_link_ids [list of strings, default None]: list of PaymentLink ids to filter logs. ex: ["5656565656565656", "4545454545454545"] + - user [Organization/Project object, default None]: Organization or Project object. Not necessary if starkbank.user was set before function call + ## Return: + - list of paymentlink.Log objects with updated attributes + - cursor to retrieve the next page of paymentlink.Log objects + """ + return rest.get_page( + resource=_resource, + cursor=cursor, + limit=limit, + after=check_date(after), + before=check_date(before), + types=types, + payment_link_ids=payment_link_ids, + user=user, + ) diff --git a/tests/sdk/test_payment_link.py b/tests/sdk/test_payment_link.py new file mode 100644 index 0000000..f4ec01d --- /dev/null +++ b/tests/sdk/test_payment_link.py @@ -0,0 +1,77 @@ +import starkbank +from datetime import date, timedelta +from unittest import TestCase, main +from tests.utils.user import exampleProject +from tests.utils.paymentLink import generateExamplePaymentLinksJson + + +starkbank.user = exampleProject + + +class TestPaymentLinkPost(TestCase): + + def test_success(self): + links = generateExamplePaymentLinksJson(n=5) + links = starkbank.paymentlink.create(links) + self.assertEqual(len(links), 5) + for link in links: + self.assertIsNotNone(link.id) + print(link) + + +class TestPaymentLinkQuery(TestCase): + + def test_success(self): + links = list(starkbank.paymentlink.query(limit=10)) + assert len(links) == 10 + + def test_success_with_params(self): + links = starkbank.paymentlink.query( + limit=10, + after=date.today() - timedelta(days=100), + before=date.today(), + status="active", + tags=["sdk-test", "payment-link"], + ids=["1", "2", "3"], + ) + self.assertEqual(len(list(links)), 0) + + +class TestPaymentLinkPage(TestCase): + + def test_success(self): + cursor = None + ids = [] + for _ in range(2): + links, cursor = starkbank.paymentlink.page(limit=2, cursor=cursor) + for link in links: + print(link) + self.assertFalse(link.id in ids) + ids.append(link.id) + if cursor is None: + break + self.assertTrue(len(ids) == 4) + + +class TestPaymentLinkGet(TestCase): + + def test_success(self): + links = starkbank.paymentlink.query() + link_id = next(links).id + link = starkbank.paymentlink.get(id=link_id) + self.assertIsNotNone(link.id) + self.assertEqual(link.id, link_id) + + +class TestPaymentLinkUpdate(TestCase): + + def test_success_cancel(self): + links = starkbank.paymentlink.query(status="active", limit=1) + for link in links: + self.assertIsNotNone(link.id) + updated_link = starkbank.paymentlink.update(link.id, status="canceling") + self.assertEqual(updated_link.status, "canceling") + + +if __name__ == '__main__': + main() diff --git a/tests/sdk/test_payment_link_attempt.py b/tests/sdk/test_payment_link_attempt.py new file mode 100644 index 0000000..00bbf3f --- /dev/null +++ b/tests/sdk/test_payment_link_attempt.py @@ -0,0 +1,58 @@ +import starkbank +from datetime import date, timedelta +from unittest import TestCase, main +from tests.utils.user import exampleProject + + +starkbank.user = exampleProject + + +class TestPaymentLinkAttemptQuery(TestCase): + + def test_success(self): + attempts = list(starkbank.paymentlink.attempt.query(limit=10)) + print("Number of attempts:", len(attempts)) + for attempt in attempts: + self.assertIsNotNone(attempt.id) + self.assertIsNotNone(attempt.payment_link_id) + + def test_success_with_params(self): + attempts = starkbank.paymentlink.attempt.query( + limit=10, + after=date.today() - timedelta(days=100), + before=date.today(), + status="success", + payment_link_ids=["1", "2", "3"], + ids=["1", "2", "3"], + ) + self.assertEqual(len(list(attempts)), 0) + + +class TestPaymentLinkAttemptPage(TestCase): + + def test_success(self): + cursor = None + ids = [] + for _ in range(2): + attempts, cursor = starkbank.paymentlink.attempt.page(limit=2, cursor=cursor) + for attempt in attempts: + print(attempt) + self.assertFalse(attempt.id in ids) + ids.append(attempt.id) + if cursor is None: + break + self.assertTrue(len(ids) == 4) + + +class TestPaymentLinkAttemptGet(TestCase): + + def test_success(self): + attempts = starkbank.paymentlink.attempt.query() + attempt_id = next(attempts).id + attempt = starkbank.paymentlink.attempt.get(id=attempt_id) + self.assertIsNotNone(attempt.id) + self.assertEqual(attempt.id, attempt_id) + + +if __name__ == '__main__': + main() diff --git a/tests/sdk/test_payment_link_attempt_log.py b/tests/sdk/test_payment_link_attempt_log.py new file mode 100644 index 0000000..43aac0b --- /dev/null +++ b/tests/sdk/test_payment_link_attempt_log.py @@ -0,0 +1,48 @@ +import starkbank +from unittest import TestCase, main +from tests.utils.user import exampleProject + + +starkbank.user = exampleProject + + +class TestPaymentLinkAttemptLogQuery(TestCase): + + def test_success(self): + logs = list(starkbank.paymentlink.attempt.log.query(limit=10)) + logs = list(starkbank.paymentlink.attempt.log.query( + limit=10, + payment_link_attempt_ids={log.attempt.id for log in logs}, + types={log.type for log in logs}, + )) + print("Number of logs:", len(logs)) + + +class TestPaymentLinkAttemptLogPage(TestCase): + + def test_success(self): + cursor = None + ids = [] + for _ in range(2): + logs, cursor = starkbank.paymentlink.attempt.log.page(limit=2, cursor=cursor) + for log in logs: + print(log) + self.assertFalse(log.id in ids) + ids.append(log.id) + if cursor is None: + break + self.assertTrue(len(ids) == 4) + + +class TestPaymentLinkAttemptLogGet(TestCase): + + def test_success(self): + logs = starkbank.paymentlink.attempt.log.query() + log_id = next(logs).id + log = starkbank.paymentlink.attempt.log.get(id=log_id) + self.assertIsNotNone(log.id) + self.assertEqual(log.id, log_id) + + +if __name__ == '__main__': + main() diff --git a/tests/sdk/test_payment_link_log.py b/tests/sdk/test_payment_link_log.py new file mode 100644 index 0000000..107b73f --- /dev/null +++ b/tests/sdk/test_payment_link_log.py @@ -0,0 +1,48 @@ +import starkbank +from unittest import TestCase, main +from tests.utils.user import exampleProject + + +starkbank.user = exampleProject + + +class TestPaymentLinkLogQuery(TestCase): + + def test_success(self): + logs = list(starkbank.paymentlink.log.query(limit=10)) + logs = list(starkbank.paymentlink.log.query( + limit=10, + payment_link_ids={log.link.id for log in logs}, + types={log.type for log in logs}, + )) + print("Number of logs:", len(logs)) + + +class TestPaymentLinkLogPage(TestCase): + + def test_success(self): + cursor = None + ids = [] + for _ in range(2): + logs, cursor = starkbank.paymentlink.log.page(limit=2, cursor=cursor) + for log in logs: + print(log) + self.assertFalse(log.id in ids) + ids.append(log.id) + if cursor is None: + break + self.assertTrue(len(ids) == 4) + + +class TestPaymentLinkLogGet(TestCase): + + def test_success(self): + logs = starkbank.paymentlink.log.query() + log_id = next(logs).id + log = starkbank.paymentlink.log.get(id=log_id) + self.assertIsNotNone(log.id) + self.assertEqual(log.id, log_id) + + +if __name__ == '__main__': + main() diff --git a/tests/utils/paymentLink.py b/tests/utils/paymentLink.py new file mode 100644 index 0000000..91e45d8 --- /dev/null +++ b/tests/utils/paymentLink.py @@ -0,0 +1,65 @@ +# coding: utf-8 +from copy import deepcopy +from datetime import datetime, timezone +from uuid import uuid4 +from random import randint, choice +from starkbank import PaymentLink +from starkbank.paymentlink import AllowedInstallment, Item + + +example_payment_link = PaymentLink( + name="Assinatura Premium", + amount=15000, + usage_mode="single", + allowed_methods=["credit", "debit"], + allowed_installments=[ + AllowedInstallment(count=1, total_amount=15000), + AllowedInstallment(count=2, total_amount=15500), + AllowedInstallment(count=3, total_amount=16500), + ], + expiration=36000, + description="Plano Premium Mensal", + success_url="https://merchant.com/obrigado", + tags=["sdk-test", "payment-link"], + items=[ + Item( + code="PREM-001", + description="Plano Premium Mensal", + quantity=1, + unit_price=15000, + total_price=15000, + discount=0, + ), + ], + metadata={"customerId": "5678901234567890", "orderId": "ORD-2026-001"}, + timestamp=datetime.now(timezone.utc), +) + + +def generateExamplePaymentLinksJson(n=1): + links = [] + for _ in range(n): + link = deepcopy(example_payment_link) + link.amount = randint(10000, 50000) + link.usage_mode = choice(["single", "multi"]) + link.metadata = { + "customerId": str(uuid4()), + "orderId": "ORD-" + str(uuid4()), + } + link.allowed_installments = [ + AllowedInstallment(count=1, total_amount=link.amount), + AllowedInstallment(count=2, total_amount=link.amount + 500), + AllowedInstallment(count=3, total_amount=link.amount + 1500), + ] + link.items = [ + Item( + code="ITEM-" + str(uuid4())[:8], + description="Plano Premium Mensal", + quantity=1, + unit_price=link.amount, + total_price=link.amount, + discount=0, + ), + ] + links.append(link) + return links