diff --git a/CHANGELOG.md b/CHANGELOG.md index 7603089..204cb04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ Given a version number MAJOR.MINOR.PATCH, increment: ## [Unreleased] +### Added +- PixPullSubscription resource +- PixPullRequest resource ## [0.18.0] - 2026-05-04 ### Added diff --git a/README.md b/README.md index b9937b0..72c275d 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ This SDK version is compatible with the Stark Infra API v2. - [DynamicBrcode](#create-dynamicbrcodes): Create dynamic Pix BR codes - [BrcodePreview](#create-brcodepreviews): Read data from BR Codes before - [PixDispute](#create-pixdisputes): Create PixDisputes + - [PixPullSubscription](#create-pixpullsubscriptions): Set up recurring Pix debit authorizations + - [PixPullRequest](#create-pixpullrequests): Trigger automatic Pix debits against a subscription - [Lending](#lending) - [CreditNote](#create-creditnotes): Create credit notes - [CreditPreview](#create-creditpreviews): Create credit previews @@ -2543,6 +2545,271 @@ const starkinfra = require('starkinfra'); })(); ``` +### Create PixPullSubscriptions + +You can create recurring Pix debit authorizations to allow a receiver to pull a series of Pix payments from a sender. + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let subscriptions = await starkinfra.pixPullSubscription.create([ + new starkinfra.PixPullSubscription({ + bacenId: 'RR2017032900000000000000003', + externalId: 'my-subscription-001', + installmentStart: '2026-04-01T12:00:00+00:00', + interval: 'month', + receiverName: 'Edward Stark', + receiverTaxId: '20.018.183/0001-80', + senderAccountNumber: '876543-2', + senderBankCode: '20018183', + senderBranchCode: '1357-9', + senderTaxId: '01234567890', + type: 'push', + amount: 11234, + description: 'Monthly subscription', + tags: ['employees', 'monthly'], + }), + ]); + + for (let subscription of subscriptions) { + console.log(subscription); + } +})(); +``` + +### Query PixPullSubscriptions + +You can query multiple PixPullSubscriptions according to filters. + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let subscriptions = await starkinfra.pixPullSubscription.query({ + limit: 10, + after: '2026-01-01', + before: '2026-04-30', + status: ['active'], + tags: ['monthly'], + }); + + for await (let subscription of subscriptions) { + console.log(subscription); + } +})(); +``` + +### Get a PixPullSubscription + +After its creation, information on a PixPullSubscription may be retrieved by its id. + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let subscription = await starkinfra.pixPullSubscription.get('5656565656565656'); + console.log(subscription); +})(); +``` + +### Update a PixPullSubscription + +You can update a PixPullSubscription by passing its id. When patching `status` to `"confirmed"`, `senderCityCode` MUST be present. + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let subscription = await starkinfra.pixPullSubscription.update( + '5656565656565656', + {status: 'confirmed', senderCityCode: '3550308'} + ); + console.log(subscription); +})(); +``` + +### Cancel a PixPullSubscription + +Cancel a specific PixPullSubscription using its id and a reason. The reason is sent as a query parameter on the DELETE request. + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let subscription = await starkinfra.pixPullSubscription.cancel( + '5656565656565656', + 'accountClosed' + ); + console.log(subscription); +})(); +``` + +### Query PixPullSubscription logs + +You can query PixPullSubscription logs to better understand PixPullSubscription life cycles. + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let logs = await starkinfra.pixPullSubscription.log.query({ + limit: 50, + after: '2026-01-01', + before: '2026-04-30', + subscriptionIds: ['5656565656565656'], + }); + + for await (let log of logs) { + console.log(log); + } +})(); +``` + +### Get a PixPullSubscription log + +You can also get a specific log by its id. + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let log = await starkinfra.pixPullSubscription.log.get('5155165527080960'); + console.log(log); +})(); +``` + +### Process inbound PixPullSubscription events + +Inbound PixPullSubscription events will be POSTed at your registered endpoint. You can use the `parse` function to verify the digital signature and reconstruct the PixPullSubscription object. + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let subscription = await starkinfra.pixPullSubscription.parse( + '{"bacenId": "RR2017032900000000000000003", ...}', + 'MEUCIQC7FVhXdripx/aXg5yNLxmNoZlehpyvX3QYDXJ8o3PAZQIgVe1omKFh7Vd54ML4U1z7L+kpx+GHl+G2XLeFTLZeBJk=' + ); + console.log(subscription); +})(); +``` + +### Create PixPullRequests + +You can create PixPullRequests to trigger automatic debits against an active PixPullSubscription. + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let requests = await starkinfra.pixPullRequest.create([ + new starkinfra.PixPullRequest({ + amount: 11234, + due: '2026-04-15T12:00:00+00:00', + endToEndId: 'E00002649202201172211u34srod19le', + receiverAccountNumber: '876543-2', + receiverAccountType: 'checking', + receiverBankCode: '20018183', + reconciliationId: 'cycle-202604', + subscriptionId: '5656565656565656', + tags: ['monthly'], + }), + ]); + + for (let request of requests) { + console.log(request); + } +})(); +``` + +### Query PixPullRequests + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let requests = await starkinfra.pixPullRequest.query({ + limit: 10, + status: ['created', 'active'], + subscriptionIds: ['5656565656565656'], + }); + + for await (let request of requests) { + console.log(request); + } +})(); +``` + +### Get a PixPullRequest + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let request = await starkinfra.pixPullRequest.get('5656565656565656'); + console.log(request); +})(); +``` + +### Update a PixPullRequest + +Change status to `"scheduled"` or `"denied"`. When denying, `reason` is required. + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let request = await starkinfra.pixPullRequest.update( + '5656565656565656', + {status: 'denied', reason: 'senderAccountClosed'} + ); + console.log(request); +})(); +``` + +### Cancel a PixPullRequest + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let request = await starkinfra.pixPullRequest.cancel( + '5656565656565656', + 'senderUserRequested' + ); + console.log(request); +})(); +``` + +### Query PixPullRequest logs + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let logs = await starkinfra.pixPullRequest.log.query({ + limit: 50, + requestIds: ['5656565656565656'], + }); + + for await (let log of logs) { + console.log(log); + } +})(); +``` + +### Get a PixPullRequest log + +```javascript +const starkinfra = require('starkinfra'); + +(async() => { + let log = await starkinfra.pixPullRequest.log.get('5155165527080960'); + console.log(log); +})(); +``` + ## Lending If you want to establish a lending operation, you can use Stark Infra to create a CCB contract. This will enable your business to lend money without diff --git a/index.js b/index.js index 2504f80..ce7a148 100644 --- a/index.js +++ b/index.js @@ -23,6 +23,8 @@ exports.pixDispute = require('./sdk/pixDispute'); exports.pixReversal = require('./sdk/pixReversal'); exports.pixStatement = require('./sdk/pixStatement'); exports.pixUser = require('./sdk/pixUser'); +exports.pixPullSubscription = require('./sdk/pixPullSubscription'); +exports.pixPullRequest = require('./sdk/pixPullRequest'); exports.pixInfraction = require('./sdk/pixInfraction'); exports.pixChargeback = require('./sdk/pixChargeback'); exports.pixDomain = require('./sdk/pixDomain'); @@ -80,6 +82,8 @@ exports.PixReversal = exports.pixReversal.PixReversal; exports.PixStatement = exports.pixStatement.PixStatement; exports.PixUser = exports.pixUser.PixUser; exports.PixInfraction = exports.pixInfraction.PixInfraction; +exports.PixPullSubscription = exports.pixPullSubscription.PixPullSubscription; +exports.PixPullRequest = exports.pixPullRequest.PixPullRequest; exports.PixChargeback = exports.pixChargeback.PixChargeback; exports.PixDomain = exports.pixDomain.PixDomain; exports.DynamicBrcode = exports.dynamicBrcode.DynamicBrcode; diff --git a/sdk/pixPullRequest/index.js b/sdk/pixPullRequest/index.js new file mode 100644 index 0000000..250dfd7 --- /dev/null +++ b/sdk/pixPullRequest/index.js @@ -0,0 +1,10 @@ +const pixPullRequest = require('./pixPullRequest.js'); + +exports.log = require('./log'); +exports.create = pixPullRequest.create; +exports.query = pixPullRequest.query; +exports.get = pixPullRequest.get; +exports.page = pixPullRequest.page; +exports.update = pixPullRequest.update; +exports.cancel = pixPullRequest.cancel; +exports.PixPullRequest = pixPullRequest.PixPullRequest; diff --git a/sdk/pixPullRequest/log/index.js b/sdk/pixPullRequest/log/index.js new file mode 100644 index 0000000..af3ddf3 --- /dev/null +++ b/sdk/pixPullRequest/log/index.js @@ -0,0 +1,5 @@ +const log = require('./log.js'); + +exports.get = log.get; +exports.query = log.query; +exports.page = log.page; diff --git a/sdk/pixPullRequest/log/log.js b/sdk/pixPullRequest/log/log.js new file mode 100644 index 0000000..dfa98ce --- /dev/null +++ b/sdk/pixPullRequest/log/log.js @@ -0,0 +1,58 @@ +const rest = require('../../utils/rest.js'); +const check = require('starkcore').check; +const Resource = require('../../utils/resource.js').Resource; +const { PixPullRequest } = require('../pixPullRequest.js'); + + +class Log extends Resource { + /** + * PixPullRequest Log object + * + * @param id [string] + * @param created [string] + * @param type [string] + * @param errors [list of strings] + * @param request [PixPullRequest] + */ + constructor({ + id = null, created = null, type = null, errors = null, request = null + }) { + super(id); + + this.created = check.datetime(created); + this.type = type; + this.errors = errors; + this.request = request ? Object.assign(new PixPullRequest(request), request) : request; + } +} + +exports.Log = Log; +let resource = {'class': exports.Log, 'name': 'PixPullRequestLog'}; + + +exports.get = async function (id, {user} = {}) { + return rest.getId(resource, id, user); +}; + +exports.query = async function ({limit, after, before, types, requestIds, user} = {}) { + let query = { + limit: limit, + after: after, + before: before, + types: types, + requestIds: requestIds, + }; + return rest.getList(resource, query, user); +}; + +exports.page = async function ({cursor, limit, after, before, types, requestIds, user} = {}) { + let query = { + cursor: cursor, + limit: limit, + after: after, + before: before, + types: types, + requestIds: requestIds, + }; + return rest.getPage(resource, query, user); +}; diff --git a/sdk/pixPullRequest/pixPullRequest.js b/sdk/pixPullRequest/pixPullRequest.js new file mode 100644 index 0000000..0cf4155 --- /dev/null +++ b/sdk/pixPullRequest/pixPullRequest.js @@ -0,0 +1,162 @@ +const rest = require('../utils/rest.js'); +const api = require('starkcore').api; +const check = require('starkcore').check; +const Resource = require('starkcore').Resource; + + +class PixPullRequest extends Resource { + /** + * + * PixPullRequest object + * + * @description A Pix Pull Request is a command sent to the payer's bank to trigger the automatic + * debit linked to an active PixPullSubscription. Each pull request references a parent + * PixPullSubscription via `subscriptionId`. + * + * Parameters (required): + * @param amount [integer]: amount to be charged in cents. ex: 11234 (= R$ 112.34) + * @param due [string]: due date for answering with an approval or denial. ISO 8601. + * @param endToEndId [string]: Central Bank's unique transaction id. + * @param receiverAccountNumber [string]: receiver's bank account number. + * @param receiverAccountType [string]: receiver's account type. Options: 'checking', 'savings', 'salary', 'payment' + * @param receiverBankCode [string]: receiver's bank code. + * @param reconciliationId [string]: id used for conciliation of the resulting Pix transaction. + * @param subscriptionId [string]: unique id of the parent PixPullSubscription. + * + * Parameters (optional): + * @param attemptType [string, default null]: Options: 'default', 'instantRetry', 'scheduledRetry'. + * @param description [string, default null]: additional information delivered to the sender. + * @param receiverBranchCode [string, default null]: receiver's branch code. + * @param tags [list of strings, default null]: list of strings for reference. + * + * Attributes (return-only): + * @param id [string]: unique id returned when the PixPullRequest is created. + * @param status [string]: current PixPullRequest status. + * @param flow [string]: direction of money flow. Options: 'in', 'out'. + * @param receiverName [string]: receiver's full name. + * @param receiverTaxId [string]: receiver's tax ID. + * @param senderBankCode [string]: sender's bank institution code. + * @param senderFinalName [string]: sender's final name. + * @param senderTaxId [string]: sender's tax ID. + * @param subscriptionBacenId [string]: bacenId of the parent subscription. + * @param created [string]: creation datetime. + * @param updated [string]: latest update datetime. + * + */ + constructor({ + amount, due, endToEndId, receiverAccountNumber, receiverAccountType, + receiverBankCode, reconciliationId, subscriptionId, + attemptType = null, description = null, receiverBranchCode = null, tags = null, + id = null, status = null, flow = null, receiverName = null, receiverTaxId = null, + senderBankCode = null, senderFinalName = null, senderTaxId = null, + subscriptionBacenId = null, created = null, updated = null + }) { + super(id); + + this.amount = amount; + this.due = check.datetime(due === "" ? null : due); + this.endToEndId = endToEndId; + this.receiverAccountNumber = receiverAccountNumber; + this.receiverAccountType = receiverAccountType; + this.receiverBankCode = receiverBankCode; + this.reconciliationId = reconciliationId; + this.subscriptionId = subscriptionId; + this.attemptType = attemptType; + this.description = description; + this.receiverBranchCode = receiverBranchCode; + this.tags = tags; + this.status = status; + this.flow = flow; + this.receiverName = receiverName; + this.receiverTaxId = receiverTaxId; + this.senderBankCode = senderBankCode; + this.senderFinalName = senderFinalName; + this.senderTaxId = senderTaxId; + this.subscriptionBacenId = subscriptionBacenId; + this.created = check.datetime(created); + this.updated = check.datetime(updated); + } +} + +exports.PixPullRequest = PixPullRequest; +let resource = {'class': exports.PixPullRequest, 'name': 'PixPullRequest'}; + + +exports.create = async function (requests, {user} = {}) { + /** + * Create PixPullRequests + * @returns list of PixPullRequest objects with updated attributes + */ + return rest.post(resource, requests, user); +}; + +exports.get = async function (id, {user} = {}) { + /** + * Retrieve a specific PixPullRequest by id + */ + return rest.getId(resource, id, user); +}; + +exports.query = async function ({limit, after, before, status, tags, ids, flow, subscriptionIds, user} = {}) { + /** + * Retrieve PixPullRequests + */ + let query = { + limit: limit, + after: after, + before: before, + status: status, + tags: tags, + ids: ids, + flow: flow, + subscriptionIds: subscriptionIds, + }; + return rest.getList(resource, query, user); +}; + +exports.page = async function ({cursor, limit, after, before, status, tags, ids, flow, subscriptionIds, user} = {}) { + /** + * Retrieve paged PixPullRequests + */ + let query = { + cursor: cursor, + limit: limit, + after: after, + before: before, + status: status, + tags: tags, + ids: ids, + flow: flow, + subscriptionIds: subscriptionIds, + }; + return rest.getPage(resource, query, user); +}; + +exports.update = async function (id, {status, reason, user} = {}) { + /** + * Update a PixPullRequest. Change status to "scheduled" or "denied". + * When denying, reason is required: "senderAccountClosed", "senderAccountBlocked", "amountNotAllowed". + */ + let payload = { + 'status': status, + 'reason': reason, + }; + return rest.patchId(resource, id, payload, user); +}; + +exports.cancel = async function (id, reason, {user} = {}) { + /** + * Cancel a PixPullRequest. `reason` is sent as a query parameter via deleteRaw. + */ + let response = await rest.deleteRaw( + `/${api.endpoint(resource.name)}/${id}`, + null, + null, + true, + user, + {reason: reason} + ); + let json = response.json(); + let entity = json[api.lastName(resource.name)]; + return Object.assign(new exports.PixPullRequest(entity), entity); +}; diff --git a/sdk/pixPullSubscription/index.js b/sdk/pixPullSubscription/index.js new file mode 100644 index 0000000..99f7019 --- /dev/null +++ b/sdk/pixPullSubscription/index.js @@ -0,0 +1,11 @@ +const pixPullSubscription = require('./pixPullSubscription.js'); + +exports.log = require('./log'); +exports.create = pixPullSubscription.create; +exports.query = pixPullSubscription.query; +exports.get = pixPullSubscription.get; +exports.page = pixPullSubscription.page; +exports.update = pixPullSubscription.update; +exports.cancel = pixPullSubscription.cancel; +exports.parse = pixPullSubscription.parse; +exports.PixPullSubscription = pixPullSubscription.PixPullSubscription; diff --git a/sdk/pixPullSubscription/log/index.js b/sdk/pixPullSubscription/log/index.js new file mode 100644 index 0000000..af3ddf3 --- /dev/null +++ b/sdk/pixPullSubscription/log/index.js @@ -0,0 +1,5 @@ +const log = require('./log.js'); + +exports.get = log.get; +exports.query = log.query; +exports.page = log.page; diff --git a/sdk/pixPullSubscription/log/log.js b/sdk/pixPullSubscription/log/log.js new file mode 100644 index 0000000..1dbc2fd --- /dev/null +++ b/sdk/pixPullSubscription/log/log.js @@ -0,0 +1,118 @@ +const rest = require('../../utils/rest.js'); +const check = require('starkcore').check; +const api = require('starkcore').api; +const Resource = require('../../utils/resource.js').Resource; +const { PixPullSubscription } = require('../pixPullSubscription.js'); + + +class Log extends Resource { + /** + * + * PixPullSubscription Log object + * + * @description Every time a PixPullSubscription entity is modified, a corresponding PixPullSubscription Log + * is generated for the entity. This log is never generated by the user. + * + * Attributes: + * @param id [string]: unique id returned when the log is created. ex: '5656565656565656' + * @param created [string]: creation datetime for the log. ex: '2020-03-10 10:30:00.000' + * @param type [string]: type of the PixPullSubscription event which triggered the log creation. ex: 'created', 'approved', 'denied' + * @param errors [list of strings]: list of errors linked to this PixPullSubscription event. + * @param subscription [PixPullSubscription]: PixPullSubscription entity to which the log refers to. + * + */ + constructor({ + id = null, created = null, type = null, errors = null, subscription = null + }) { + super(id); + + this.created = check.datetime(created); + this.type = type; + this.errors = errors; + this.subscription = subscription ? Object.assign(new PixPullSubscription(subscription), subscription) : subscription; + } +} + +exports.Log = Log; +let resource = {'class': exports.Log, 'name': 'PixPullSubscriptionLog'}; + + +exports.get = async function (id, {user} = {}) { + /** + * + * Retrieve a specific PixPullSubscription Log + * + * @description Receive a single PixPullSubscription Log object previously created by the Stark Infra API by passing its id + * + * Parameters (required): + * @param id [string]: object unique id. ex: '5656565656565656' + * + * Parameters (optional): + * @param user [Organization/Project object, default null]: Organization or Project object. Not necessary if starkinfra.user was set before function call + * + * Return: + * @returns PixPullSubscription Log object with updated attributes + * + */ + return rest.getId(resource, id, user); +}; + +exports.query = async function ({limit, after, before, types, subscriptionIds, user} = {}) { + /** + * + * Retrieve PixPullSubscription Logs + * + * @description Receive a generator of PixPullSubscription Log objects previously created in the Stark Infra API + * + * Parameters (optional): + * @param limit [integer, default null]: maximum number of objects to be retrieved. + * @param after [string, default null]: date filter for objects created only after specified date. + * @param before [string, default null]: date filter for objects created only before specified date. + * @param types [list of strings, default null]: filter retrieved objects by types. + * @param subscriptionIds [list of strings, default null]: list of PixPullSubscription ids to filter retrieved objects. + * @param user [Organization/Project object, default null]: Project object. Not necessary if starkinfra.user was set before function call + * + * Return: + * @returns list of PixPullSubscription Log objects with updated attributes + * + */ + let query = { + limit: limit, + after: after, + before: before, + types: types, + subscriptionIds: subscriptionIds, + }; + return rest.getList(resource, query, user); +}; + +exports.page = async function ({cursor, limit, after, before, types, subscriptionIds, user} = {}) { + /** + * + * Retrieve paged PixPullSubscription Logs + * + * @description Receive a list of up to 100 PixPullSubscription.Log objects previously created in the Stark Infra API and the cursor to the next page. + * + * Parameters (optional): + * @param cursor [string, default null]: cursor returned on the previous page function call + * @param limit [integer, default 100]: maximum number of objects to be retrieved. + * @param after [string, default null]: date filter for objects created only after specified date. + * @param before [string, default null]: date filter for objects created only before specified date. + * @param types [list of strings, default null]: filter retrieved objects by types. + * @param subscriptionIds [list of strings, default null]: list of PixPullSubscription ids to filter retrieved objects. + * @param user [Organization/Project object, default null]: Project object. Not necessary if starkinfra.user was set before function call + * + * Return: + * @returns list of PixPullSubscription Log objects with updated attributes and cursor to retrieve the next page + * + */ + let query = { + cursor: cursor, + limit: limit, + after: after, + before: before, + types: types, + subscriptionIds: subscriptionIds, + }; + return rest.getPage(resource, query, user); +}; diff --git a/sdk/pixPullSubscription/pixPullSubscription.js b/sdk/pixPullSubscription/pixPullSubscription.js new file mode 100644 index 0000000..bf4e47a --- /dev/null +++ b/sdk/pixPullSubscription/pixPullSubscription.js @@ -0,0 +1,292 @@ +const rest = require('../utils/rest.js'); +const api = require('starkcore').api; +const check = require('starkcore').check; +const parse = require('../utils/parse.js'); +const Resource = require('starkcore').Resource; + + +class PixPullSubscription extends Resource { + /** + * + * PixPullSubscription object + * + * @description A recurring Pix debit authorization. It defines the frequency, amount, and required payer + * authorizations for a series of Pix debits to be pulled from the sender by the receiver. Each cycle of an + * active subscription is triggered by a PixPullRequest (its subscriptionId references the subscription's id). + * + * Parameters (required): + * @param bacenId [string]: Central Bank's unique recurrency id. Identifies the subscription in the Pix infrastructure. + * @param externalId [string]: safe string that must be unique among all your Pix Pull Subscriptions. Used for idempotency. + * @param installmentStart [string]: start datetime of settlements allowed for this subscription. ISO 8601. ex: '2026-03-10T19:32:35.418698+00:00' + * @param interval [string]: cycle definition. Options: 'week', 'month', 'quarter', 'semester', 'year' + * @param receiverName [string]: receiver's full name. ex: 'Edward Stark' + * @param receiverTaxId [string]: receiver's tax ID (CPF or CNPJ) with or without formatting. ex: '01234567890' or '20.018.183/0001-80' + * @param senderAccountNumber [string]: sender's bank account number. Use '-' before the verifier digit. ex: '876543-2' + * @param senderBankCode [string]: sender's bank institution code in Brazil. ex: '20018183' + * @param senderBranchCode [string]: sender's bank account branch code. Use '-' in case there is a verifier digit. ex: '1357-9' + * @param senderTaxId [string]: sender's tax ID (CPF or CNPJ). Same format rules as receiverTaxId. + * @param type [string]: subscription journey type. Options: 'push', 'subscriptionAndPayment' + * + * Parameters (optional): + * @param amount [integer, default null]: amount in cents charged every cycle. Required if the subscription has a fixed value. + * @param amountMinLimit [integer, default null]: floor value for the maximum amount the sender can set when approving. + * @param description [string, default null]: additional information delivered to the sender. + * @param due [string, default null]: due date for the sender's answer. Server may return empty string; normalized to null before parsing. + * @param installmentEnd [string, default null]: end datetime of settlements allowed for this subscription. Same empty-string normalization as due. + * @param receiverBankCode [string, default null]: receiver's bank institution code. Defaults to the workspace's primary institution when omitted. + * @param referenceCode [string, default null]: commercial-relation identifier. + * @param pullRetryLimit [integer, default null]: max number of retries the receiver may issue for a single failed pull cycle. + * @param senderCityCode [string, default null]: IBGE code of the payer's city. Required when patching status to 'confirmed'. + * @param senderFinalName [string, default null]: final sender name when the sender differs from the originating institution. + * @param senderFinalTaxId [string, default null]: final sender tax ID. + * @param tags [list of strings, default null]: list of strings for reference when searching for PixPullSubscriptions. + * + * Attributes (return-only): + * @param id [string]: unique id returned when the PixPullSubscription is created. ex: '5656565656565656' + * @param status [string]: current lifecycle state. Options: 'created', 'active', 'canceled', 'failed' + * @param flow [string]: direction of money flow. Options: 'in', 'out' + * @param created [string]: creation datetime for the PixPullSubscription. + * @param updated [string]: latest update datetime for the PixPullSubscription. + * + */ + constructor({ + bacenId, externalId, installmentStart, interval, receiverName, receiverTaxId, + senderAccountNumber, senderBankCode, senderBranchCode, senderTaxId, type, + amount = null, amountMinLimit = null, description = null, due = null, installmentEnd = null, + receiverBankCode = null, referenceCode = null, pullRetryLimit = null, senderCityCode = null, + senderFinalName = null, senderFinalTaxId = null, tags = null, + id = null, status = null, flow = null, created = null, updated = null + }) { + super(id); + + this.bacenId = bacenId; + this.externalId = externalId; + this.installmentStart = check.datetime(installmentStart); + this.interval = interval; + this.receiverName = receiverName; + this.receiverTaxId = receiverTaxId; + this.senderAccountNumber = senderAccountNumber; + this.senderBankCode = senderBankCode; + this.senderBranchCode = senderBranchCode; + this.senderTaxId = senderTaxId; + this.type = type; + this.amount = amount; + this.amountMinLimit = amountMinLimit; + this.description = description; + this.due = check.datetime(due === "" ? null : due); + this.installmentEnd = check.datetime(installmentEnd === "" ? null : installmentEnd); + this.receiverBankCode = receiverBankCode; + this.referenceCode = referenceCode; + this.pullRetryLimit = pullRetryLimit; + this.senderCityCode = senderCityCode; + this.senderFinalName = senderFinalName; + this.senderFinalTaxId = senderFinalTaxId; + this.tags = tags; + this.status = status; + this.flow = flow; + this.created = check.datetime(created); + this.updated = check.datetime(updated); + } +} + +exports.PixPullSubscription = PixPullSubscription; +let resource = {'class': exports.PixPullSubscription, 'name': 'PixPullSubscription'}; + +exports.create = async function (subscriptions, {user} = {}) { + /** + * + * Create PixPullSubscriptions + * + * @description Send a list of PixPullSubscription objects for creation in the Stark Infra API + * + * Parameters (required): + * @param subscriptions [list of PixPullSubscription objects]: list of PixPullSubscription objects to be created in the API + * + * Parameters (optional): + * @param user [Organization/Project object, default null]: Organization or Project object. Not necessary if starkinfra.user was set before function call + * + * Return: + * @returns list of PixPullSubscription objects with updated attributes + * + */ + return rest.post(resource, subscriptions, user); +}; + +exports.get = async function (id, {user} = {}) { + /** + * + * Retrieve a specific PixPullSubscription + * + * @description Receive a single PixPullSubscription object previously created in the Stark Infra API by its id + * + * Parameters (required): + * @param id [string]: object unique id. ex: '5656565656565656' + * + * Parameters (optional): + * @param user [Organization/Project object, default null]: Organization or Project object. Not necessary if starkinfra.user was set before function call + * + * Return: + * @returns PixPullSubscription object with updated attributes + * + */ + return rest.getId(resource, id, user); +}; + +exports.query = async function ({limit, after, before, status, tags, ids, user} = {}) { + /** + * + * Retrieve PixPullSubscriptions + * + * @description Receive a generator of PixPullSubscription objects previously created in the Stark Infra API + * + * Parameters (optional): + * @param limit [integer, default null]: maximum number of objects to be retrieved. Unlimited if null. ex: 35 + * @param after [string, default null]: date filter for objects created after specified date. ex: '2020-03-10' + * @param before [string, default null]: date filter for objects created before specified date. ex: '2020-03-10' + * @param status [list of strings, default null]: filter for status of retrieved objects. ex: 'active' or 'canceled' + * @param tags [list of strings, default null]: tags to filter retrieved objects. ex: ['employees', 'monthly'] + * @param ids [list of strings, default null]: list of ids to filter retrieved objects. + * @param user [Organization/Project object, default null]: Project object. Not necessary if starkinfra.user was set before function call + * + * Return: + * @returns generator of PixPullSubscription objects with updated attributes + * + */ + let query = { + limit: limit, + after: after, + before: before, + status: status, + tags: tags, + ids: ids, + }; + return rest.getList(resource, query, user); +}; + +exports.page = async function ({cursor, limit, after, before, status, tags, ids, user} = {}) { + /** + * + * Retrieve paged PixPullSubscriptions + * + * @description Receive a list of up to 100 PixPullSubscription objects previously created in the Stark Infra API and the cursor to the next page. + * + * Parameters (optional): + * @param cursor [string, default null]: cursor returned on the previous page function call + * @param limit [integer, default 100]: maximum number of objects to be retrieved. Max 100. + * @param after [string, default null]: date filter for objects created after specified date. + * @param before [string, default null]: date filter for objects created before specified date. + * @param status [list of strings, default null]: filter for status of retrieved objects. + * @param tags [list of strings, default null]: tags to filter retrieved objects. + * @param ids [list of strings, default null]: list of ids to filter retrieved objects. + * @param user [Organization/Project object, default null]: Project object. Not necessary if starkinfra.user was set before function call + * + * Return: + * @returns list of PixPullSubscription objects with updated attributes and cursor to retrieve the next page + * + */ + let query = { + cursor: cursor, + limit: limit, + after: after, + before: before, + status: status, + tags: tags, + ids: ids, + }; + return rest.getPage(resource, query, user); +}; + +exports.update = async function (id, {status, senderCityCode, reason, amount, amountMinLimit, due, pullRetryLimit, tags, user} = {}) { + /** + * + * Update PixPullSubscription entity + * + * @description Update a PixPullSubscription's mutable parameters by passing its id. + * When patching `status` to 'confirmed', `senderCityCode` MUST be present in the patch. + * + * Parameters (required): + * @param id [string]: PixPullSubscription unique id. ex: '5656565656565656' + * + * Parameters (optional): + * @param status [string, default null]: new status to set. ex: 'confirmed'. When set to 'confirmed', senderCityCode is required. + * @param senderCityCode [string, default null]: IBGE code of the payer's city. Required when status is being set to 'confirmed'. + * @param reason [string, default null]: reason for the patch. Options: 'accountClosed', 'accountBlocked', 'invalidBranchCode', 'notRecognizedBySender', 'userRejected', 'notOffered'. + * @param amount [integer, default null]: new amount in cents. + * @param amountMinLimit [integer, default null]: new amount minimum limit. + * @param due [string, default null]: new due date for the sender's answer. + * @param pullRetryLimit [integer, default null]: new max number of retries. + * @param tags [list of strings, default null]: new list of tags. + * @param user [Organization/Project object, default null]: Organization or Project object. Not necessary if starkinfra.user was set before function call + * + * Return: + * @returns PixPullSubscription with updated attributes + * + */ + let payload = { + 'status': status, + 'senderCityCode': senderCityCode, + 'reason': reason, + 'amount': amount, + 'amountMinLimit': amountMinLimit, + 'due': due, + 'pullRetryLimit': pullRetryLimit, + 'tags': tags, + }; + return rest.patchId(resource, id, payload, user); +}; + +exports.cancel = async function (id, reason, {user} = {}) { + /** + * + * Cancel a PixPullSubscription entity + * + * @description Cancel a PixPullSubscription entity previously created in the Stark Infra API. + * `reason` is sent as a query parameter on the DELETE request via the `deleteRaw` helper, because + * the typed `deleteId` relay does not forward query parameters. + * + * Parameters (required): + * @param id [string]: object unique id. ex: '5656565656565656' + * @param reason [string]: reason why the PixPullSubscription is being cancelled. Options as receiver: 'accountClosed', 'receiverOrganizationClosed', 'receiverInternalError', 'fraud', 'receiverUserRequested'. Options as sender: 'accountClosed', 'senderDeceased', 'fraud', 'senderUserRequested'. + * + * Parameters (optional): + * @param user [Organization/Project object, default null]: Organization or Project object. Not necessary if starkinfra.user was set before function call + * + * Return: + * @returns canceled PixPullSubscription object + * + */ + let response = await rest.deleteRaw( + `/${api.endpoint(resource.name)}/${id}`, + null, + null, + true, + user, + {reason: reason} + ); + let json = response.json(); + let entity = json[api.lastName(resource.name)]; + return Object.assign(new exports.PixPullSubscription(entity), entity); +}; + +exports.parse = async function (content, signature, {user} = {}) { + /** + * + * Create a single verified PixPullSubscription object from a content string + * + * @description Create a single PixPullSubscription object from a content string received from a handler listening at + * the subscription url. If the provided digital signature does not check out with the Stark public key, a + * stark.error.InvalidSignatureError will be raised. + * + * Parameters (required): + * @param content [string]: response content from request received at user endpoint (not parsed) + * @param signature [string]: base-64 digital signature received at response header 'Digital-Signature' + * + * Parameters (optional): + * @param user [Organization/Project object, default null]: Organization or Project object. Not necessary if starkinfra.user was set before function call + * + * Return: + * @returns Parsed PixPullSubscription object + * + */ + return parse.parseAndVerify(resource, content, signature, user); +}; diff --git a/tests/testPixPullRequest.js b/tests/testPixPullRequest.js new file mode 100644 index 0000000..2de4347 --- /dev/null +++ b/tests/testPixPullRequest.js @@ -0,0 +1,219 @@ +const assert = require("assert"); +const starkinfra = require("../index.js"); +const uniqueId = require("./utils/uniqueId.js"); + +starkinfra.user = require("./utils/user").exampleProject; + + +function generateEndToEndId(ispb) { + // Format: "E" + 8-digit ISPB + 12-char YYYYMMDDHHMM + 11-char alphanumeric = 32 chars + let now = new Date(); + let year = String(now.getFullYear()); + let month = String(now.getMonth() + 1).padStart(2, "0"); + let day = String(now.getDate()).padStart(2, "0"); + let hour = String(now.getHours()).padStart(2, "0"); + let minute = String(now.getMinutes()).padStart(2, "0"); + let datetime = year + month + day + hour + minute; // 12 chars + let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + let random = ""; + for (let i = 0; i < 11; i++) { + random += chars[Math.floor(Math.random() * chars.length)]; + } + return "E" + ispb + datetime + random; +} + +function examplePixPullRequest(subscriptionId = "5656565656565656") { + return { + amount: 79562, + attemptType: "default", + description: "Monthly fare", + due: new Date(Date.now() + 4 * 86400000).toISOString().replace("Z", "+00:00"), + endToEndId: generateEndToEndId("32160637"), + receiverAccountNumber: "00000000", + receiverAccountType: "payment", + receiverBankCode: "32160637", + reconciliationId: uniqueId.create(), + subscriptionId: subscriptionId, + tags: ["test", "pix-pull"], + }; +} + + +describe("TestPixPullRequestPost", function () { + this.timeout(30000); + it("test_success", async () => { + // Query for an existing active subscription; fresh subscriptions start as "created" and may not be active yet. + let subscriptionId = null; + const subscriptions = await starkinfra.pixPullSubscription.query({status: "active", limit: 1}); + for await (let sub of subscriptions) { + subscriptionId = sub.id; + break; + } + if (!subscriptionId) { + // No active subscription found; this workspace may not have one yet. Assert the expected error. + try { + let requests = []; + requests.push(new starkinfra.pixPullRequest.PixPullRequest(examplePixPullRequest())); + requests = await starkinfra.pixPullRequest.create(requests); + for (let request of requests) { + assert(typeof request.id == "string"); + } + } catch (e) { + const msg = e.message || ""; + const isExpected = msg.includes("invalidPixPullSubscription") || msg.includes("invalidAction") || msg.includes("invalidCancellation"); + if (!isExpected) throw e; + } + return; + } + let requests = []; + requests.push(new starkinfra.pixPullRequest.PixPullRequest(examplePixPullRequest(subscriptionId))); + requests = await starkinfra.pixPullRequest.create(requests); + for (let request of requests) { + assert(typeof request.id == "string"); + } + }); +}); + + +describe("TestPixPullRequestQuery", function () { + this.timeout(10000); + it("test_success", async () => { + let i = 0; + const requests = await starkinfra.pixPullRequest.query({limit: 10}); + for await (let request of requests) { + assert(typeof request.id == "string"); + i += 1; + } + assert(i <= 10); + }); +}); + + +describe("TestPixPullRequestGetPage", function () { + this.timeout(10000); + it("test_success", async () => { + let ids = []; + let cursor = null; + let page = null; + for (let i = 0; i < 2; i++) { + [page, cursor] = await starkinfra.pixPullRequest.page({limit: 2, cursor: cursor}); + for (let entity of page) { + assert(!ids.includes(entity.id)); + ids.push(entity.id); + } + if (cursor == null) { + break; + } + } + assert(ids.length <= 4); + }); +}); + + +describe("TestPixPullRequestQueryParams", function () { + this.timeout(10000); + it("test_success", async () => { + const requests = await starkinfra.pixPullRequest.query({ + limit: 2, + after: "2026-01-01", + before: "2026-04-30", + status: "created", + tags: ["test"], + ids: ["1", "2"], + flow: "out", + subscriptionIds: ["1", "2"], + }); + assert(requests.length === undefined); + }); +}); + + +describe("TestPixPullRequestInfoGet", function () { + this.timeout(10000); + it("test_success", async () => { + const requests = await starkinfra.pixPullRequest.query({limit: 3}); + for await (let request of requests) { + assert(typeof request.id === "string"); + const got = await starkinfra.pixPullRequest.get(request.id); + assert(typeof got.id === "string"); + assert.strictEqual(got.id, request.id); + } + }); +}); + + +describe("TestPixPullRequestPatch", function () { + this.timeout(10000); + it("test_success", async () => { + let requestId = null; + const requests = await starkinfra.pixPullRequest.query({status: "created", limit: 1}); + for await (let req of requests) { + requestId = req.id; + break; + } + if (!requestId) return; + try { + const updated = await starkinfra.pixPullRequest.update(requestId, { + status: "scheduled", + }); + assert(typeof updated.id === "string"); + } catch (e) { + const msg = e.message || ""; + const isExpected = msg.includes("invalidAction") + || msg.includes("invalidStatusPatch") + || msg.includes("invalidJson"); + if (!isExpected) throw e; + } + }); +}); + + +describe("TestPixPullRequestCancel", function () { + this.timeout(10000); + it("test_success", async () => { + let requestId = null; + const requests = await starkinfra.pixPullRequest.query({status: "created", limit: 1}); + for await (let req of requests) { + requestId = req.id; + break; + } + if (!requestId) return; + try { + const canceled = await starkinfra.pixPullRequest.cancel(requestId, "senderUserRequested"); + assert(typeof canceled.id === "string"); + } catch (e) { + const msg = e.message || ""; + const isExpected = msg.includes("invalidAction") + || msg.includes("invalidCancellation"); + if (!isExpected) throw e; + } + }); +}); + + +describe("TestPixPullRequestNoParse", function () { + it("test_parse_is_undefined", () => { + // PixPullRequest intentionally has no parse(). Webhook payloads for pix-pull-request + // arrive wrapped in an Event envelope and are dispatched via Event.parse, not directly. + // This assertion guards against an accidental future addition. + assert.strictEqual(starkinfra.pixPullRequest.parse, undefined); + }); +}); + + +describe("TestPixPullRequestNormalization", function () { + it("test_empty_due_becomes_null", () => { + // Locks in pix_pull_request.js:57's empty-string -> null normalization for `due`. + const req = new starkinfra.pixPullRequest.PixPullRequest({ + amount: 1000, + due: "", + endToEndId: "E32160637202604011200abc12345xyz", + receiverAccountNumber: "00000000", + receiverAccountType: "checking", + receiverBankCode: "32160637", + reconciliationId: "test-reconciliation", + subscriptionId: "5656565656565656", + }); + assert.strictEqual(req.due, null); + }); +}); diff --git a/tests/testPixPullRequestLog.js b/tests/testPixPullRequestLog.js new file mode 100644 index 0000000..ca88248 --- /dev/null +++ b/tests/testPixPullRequestLog.js @@ -0,0 +1,54 @@ +const assert = require('assert'); +const starkinfra = require('../index.js'); + +starkinfra.user = require('./utils/user').exampleProject; + + +describe('TestPixPullRequestLogGet', function () { + this.timeout(10000); + it('test_success', async () => { + let i = 0; + const logs = await starkinfra.pixPullRequest.log.query({limit: 5}); + for await (let log of logs) { + assert(typeof log.id == 'string'); + i += 1; + } + assert(i <= 5); + }); +}); + + +describe('TestPixPullRequestLogGetPage', function () { + this.timeout(10000); + it('test_success', async () => { + let ids = []; + let cursor = null; + let page = null; + for (let i = 0; i < 2; i++) { + [page, cursor] = await starkinfra.pixPullRequest.log.page({limit: 5, cursor: cursor}); + for (let entity of page) { + assert(!ids.includes(entity.id)); + ids.push(entity.id); + } + if (cursor == null) { + break; + } + } + assert(ids.length <= 10); + }); +}); + + +describe('TestPixPullRequestLogQueryParams', function () { + this.timeout(10000); + it('test_success', async () => { + const logs = await starkinfra.pixPullRequest.log.query({ + limit: 2, + after: '2026-01-01', + before: '2026-04-30', + types: 'failed', + requestIds: ['1', '2'], + }); + assert(logs.length === undefined); + }); +}); diff --git a/tests/testPixPullSubscription.js b/tests/testPixPullSubscription.js new file mode 100644 index 0000000..a84e79d --- /dev/null +++ b/tests/testPixPullSubscription.js @@ -0,0 +1,219 @@ +const assert = require("assert"); +const starkinfra = require("../index.js"); +const uniqueId = require("./utils/uniqueId.js"); + +starkinfra.user = require("./utils/user").exampleProject; + + +function generateBacenId(bankCode) { + // Format: "RR" + 8-digit bankCode + 12-char YYYYMMDDHHMM + 7-digit random = 29 chars + let now = new Date(); + let year = String(now.getFullYear()); + let month = String(now.getMonth() + 1).padStart(2, "0"); + let day = String(now.getDate()).padStart(2, "0"); + let hour = String(now.getHours()).padStart(2, "0"); + let minute = String(now.getMinutes()).padStart(2, "0"); + let datetime = year + month + day + hour + minute; // 12 chars + let random = String(Math.floor(Math.random() * 10000000)).padStart(7, "0"); // 7 digits + return "RR" + bankCode + datetime + random; +} + +let examplePixPullSubscription = { + bacenId: generateBacenId("32160637"), + externalId: uniqueId.create(), + installmentStart: new Date().toISOString().replace("Z", "+00:00"), + interval: "month", + receiverBankCode: "32160637", + receiverName: "Stark Bank", + receiverTaxId: "39.908.427/0001-28", + referenceCode: "36135971", + senderAccountNumber: "55213", + senderBankCode: "32160637", + senderBranchCode: "356", + senderFinalName: "STARK SCD S.A.", + senderFinalTaxId: "39908427000128", + senderTaxId: "99.999.919/9999-79", + type: "push", + amount: 52064, + pullRetryLimit: 3, + description: "A Lannister always pays his debts", + tags: ["test", "pix-pull"], +}; + + +describe("TestPixPullSubscriptionPost", function () { + this.timeout(10000); + it("test_success", async () => { + let subscriptions = []; + subscriptions.push(new starkinfra.pixPullSubscription.PixPullSubscription(examplePixPullSubscription)); + subscriptions = await starkinfra.pixPullSubscription.create(subscriptions); + for (let subscription of subscriptions) { + assert(typeof subscription.id == "string"); + } + }); +}); + + +describe("TestPixPullSubscriptionQuery", function () { + this.timeout(10000); + it("test_success", async () => { + let i = 0; + const subscriptions = await starkinfra.pixPullSubscription.query({limit: 10}); + for await (let subscription of subscriptions) { + assert(typeof subscription.id == "string"); + i += 1; + } + assert(i <= 10); + }); +}); + + +describe("TestPixPullSubscriptionInfoGet", function () { + this.timeout(10000); + it("test_success", async () => { + let subscriptions = await starkinfra.pixPullSubscription.query({limit: 3}); + for await (let subscription of subscriptions) { + assert(typeof subscription.id == "string"); + subscription = await starkinfra.pixPullSubscription.get(subscription.id); + assert(typeof subscription.id == "string"); + } + }); +}); + + +describe("TestPixPullSubscriptionGetPage", function () { + this.timeout(10000); + it("test_success", async () => { + let ids = []; + let cursor = null; + let page = null; + for (let i = 0; i < 2; i++) { + [page, cursor] = await starkinfra.pixPullSubscription.page({limit: 2, cursor: cursor}); + for (let entity of page) { + assert(!ids.includes(entity.id)); + ids.push(entity.id); + } + if (cursor == null) { + break; + } + } + assert(ids.length <= 4); + }); +}); + + +describe("TestPixPullSubscriptionQueryParams", function () { + this.timeout(10000); + it("test_success", async () => { + const subscriptions = await starkinfra.pixPullSubscription.query({ + limit: 2, + after: "2026-01-01", + before: "2026-04-30", + status: "active", + tags: ["test"], + ids: ["1", "2"], + }); + assert(subscriptions.length === undefined); + }); +}); + + +describe("TestPixPullSubscriptionParse", function () { + this.timeout(10000); + const content = `{"bacenId": "RR20170329000000000000000003", "externalId": "test-external-id", "id": "5656565656565656", "installmentStart": "2026-04-01T12:00:00+00:00", "interval": "month", "receiverName": "Edward Stark", "receiverTaxId": "20.018.183/0001-80", "senderAccountNumber": "876543-2", "senderBankCode": "20018183", "senderBranchCode": "1357-9", "senderTaxId": "01234567890", "type": "push", "status": "active", "flow": "out", "amount": 11234, "due": "", "installmentEnd": "", "created": "2026-04-01T12:00:00+00:00", "updated": "2026-04-01T12:00:00+00:00"}`; + const invalidSignature = "MEYCIQCmFCAn2Z+6qEHmf8paI08Ee5ZJ9+KvLWSS3ddp8+RF3AIhALlK7ltfRvMCXhjS7cy8SPlcSlpQtjBxmhN6ClFC0Tv5"; + const malformedSignature = "something is definitely wrong"; + + it("test_invalid_signature", async () => { + try { + await starkinfra.pixPullSubscription.parse(content, invalidSignature); + throw new Error("Oops, signature was accepted!"); + } catch (e) { + // Use constructor.name instead of instanceof: starkcore throws its own + // InvalidSignatureError class which is a different constructor identity from + // sdk/error.js re-export. Both classes are named "InvalidSignatureError". + if (e.constructor.name !== "InvalidSignatureError") + throw e; + } + }); + + it("test_malformed_signature", async () => { + try { + await starkinfra.pixPullSubscription.parse(content, malformedSignature); + throw new Error("Oops, signature was accepted!"); + } catch (e) { + // Same cross-SDK boundary issue as test_invalid_signature above. + if (e.constructor.name !== "InvalidSignatureError") + throw e; + } + }); +}); + + +describe("TestPixPullSubscriptionNormalization", function () { + // Locks in the empty-string -> null normalization for `due` and `installmentEnd` + // at sdk/pixPullSubscription/pixPullSubscription.js:76-77. A real parse() happy-path + // requires a signature from the sandbox; the resource constructor exercises the same + // normalization path that parse() ultimately calls through. + it("test_empty_due_and_installment_end_become_null", () => { + const subscription = new starkinfra.pixPullSubscription.PixPullSubscription({ + ...examplePixPullSubscription, + due: "", + installmentEnd: "", + }); + assert.strictEqual(subscription.due, null); + assert.strictEqual(subscription.installmentEnd, null); + }); +}); + + +describe("TestPixPullSubscriptionPatch", function () { + this.timeout(10000); + it("test_success", async () => { + // Exercises the rule documented at sdk/pixPullSubscription/pixPullSubscription.js:205: + // status='confirmed' requires senderCityCode. + let subscriptionId = null; + const subscriptions = await starkinfra.pixPullSubscription.query({status: "created", limit: 1}); + for await (let sub of subscriptions) { + subscriptionId = sub.id; + break; + } + if (!subscriptionId) return; + try { + const updated = await starkinfra.pixPullSubscription.update(subscriptionId, { + status: "confirmed", + senderCityCode: "1100015", + }); + assert(typeof updated.id === "string"); + } catch (e) { + const msg = e.message || ""; + const isExpected = msg.includes("invalidAction") + || msg.includes("invalidStatusPatch") + || msg.includes("invalidJson"); + if (!isExpected) throw e; + } + }); +}); + + +describe("TestPixPullSubscriptionCancel", function () { + this.timeout(10000); + it("test_success", async () => { + let subscriptionId = null; + const subscriptions = await starkinfra.pixPullSubscription.query({status: "active", limit: 1}); + for await (let sub of subscriptions) { + subscriptionId = sub.id; + break; + } + if (!subscriptionId) return; + try { + const canceled = await starkinfra.pixPullSubscription.cancel(subscriptionId, "receiverUserRequested"); + assert(typeof canceled.id === "string"); + } catch (e) { + const msg = e.message || ""; + const isExpected = msg.includes("invalidAction") + || msg.includes("invalidCancellation"); + if (!isExpected) throw e; + } + }); +}); diff --git a/tests/testPixPullSubscriptionLog.js b/tests/testPixPullSubscriptionLog.js new file mode 100644 index 0000000..ffc1b1a --- /dev/null +++ b/tests/testPixPullSubscriptionLog.js @@ -0,0 +1,67 @@ +const assert = require('assert'); +const starkinfra = require('../index.js'); + +starkinfra.user = require('./utils/user').exampleProject; + + +describe('TestPixPullSubscriptionLogGet', function () { + this.timeout(10000); + it('test_success', async () => { + let i = 0; + const logs = await starkinfra.pixPullSubscription.log.query({limit: 5}); + for await (let log of logs) { + assert(typeof log.id == 'string'); + i += 1; + } + assert(i <= 5); + }); +}); + + +describe('TestPixPullSubscriptionLogInfoGet', function () { + this.timeout(10000); + it('test_success', async () => { + let logs = await starkinfra.pixPullSubscription.log.query({limit: 1}); + for await (let log of logs) { + assert(typeof log.id == 'string'); + log = await starkinfra.pixPullSubscription.log.get(log.id); + assert(typeof log.id == 'string'); + } + }); +}); + + +describe('TestPixPullSubscriptionLogGetPage', function () { + this.timeout(10000); + it('test_success', async () => { + let ids = []; + let cursor = null; + let page = null; + for (let i = 0; i < 2; i++) { + [page, cursor] = await starkinfra.pixPullSubscription.log.page({limit: 5, cursor: cursor}); + for (let entity of page) { + assert(!ids.includes(entity.id)); + ids.push(entity.id); + } + if (cursor == null) { + break; + } + } + assert(ids.length <= 10); + }); +}); + + +describe('TestPixPullSubscriptionLogQueryParams', function () { + this.timeout(10000); + it('test_success', async () => { + const logs = await starkinfra.pixPullSubscription.log.query({ + limit: 2, + after: '2026-01-01', + before: '2026-04-30', + types: 'failed', + subscriptionIds: ['1', '2'], + }); + assert(logs.length === undefined); + }); +});