Skip to content

Add SubscriptionEvent wrapping/disable/enable and Invoice updateStatus/disable [PIP-311]#129

Open
wscourge wants to merge 7 commits intomainfrom
wiktor/pip-311-node-sdk-feature-updates
Open

Add SubscriptionEvent wrapping/disable/enable and Invoice updateStatus/disable [PIP-311]#129
wscourge wants to merge 7 commits intomainfrom
wiktor/pip-311-node-sdk-feature-updates

Conversation

@wscourge
Copy link
Copy Markdown
Contributor

@wscourge wscourge commented Mar 17, 2026

Summary

Closes PIP-311

  • Add auto-wrapping of flat params into subscription_event envelope for create/update/delete, with backward compatibility for already-enveloped inputs
  • Add SubscriptionEvent.disable / SubscriptionEvent.enable via /disabled_state endpoint
  • Fix bug where passing undefined callback to _method() blocked request body from being sent
  • Add Invoice.updateStatus (PUT /v1/data_sources/:ds/invoices/:ext_id/status)
  • Add Invoice.disable / Invoice.enable via /disabled_state endpoint
  • Add Account.retrieve include param validation (warns on unknown fields)
  • Expand test coverage: body-wrapping verification, callback style, error handling, and Account include query param

Backwards compatibility review

BREAKING change

Method Before After
Invoice.updateStatus (config, uuid, body)PATCH /v1/invoices/:uuid (config, dsUuid, invoiceExtId, body)PUT /v1/data_sources/:ds/invoices/:ext_id/status

This is a new method that did not exist on main, so no existing callers are broken. The signature was corrected before merge to match the actual API.

Endpoint path corrections (vs earlier commits on this branch)

Method Wrong path (earlier commits) Correct path (final)
Invoice.disable PATCH /v1/invoices/:uuid/disable PATCH /v1/invoices/:uuid/disabled_state + body {disabled: true}
SubscriptionEvent.disable PATCH /v1/subscription_events/:id/disable PATCH /v1/subscription_events/:id/disabled_state + body {disabled: true}
SubscriptionEvent.enable PATCH /v1/subscription_events/:id/enable PATCH /v1/subscription_events/:id/disabled_state + body {disabled: false}

Additive (zero risk)

New method Notes
Invoice.updateStatus(config, dsUuid, extId, body[, cb]) PUT /v1/data_sources/:ds/invoices/:ext_id/status
Invoice.disable(config, uuid[, body][, cb]) PATCH /v1/invoices/:uuid/disabled_state — auto-sends {disabled: true}
Invoice.enable(config, uuid[, cb]) PATCH /v1/invoices/:uuid/disabled_state — auto-sends {disabled: false}
SubscriptionEvent.disable(config, id[, cb]) PATCH /v1/subscription_events/:id/disabled_state
SubscriptionEvent.enable(config, id[, cb]) PATCH /v1/subscription_events/:id/disabled_state

Behaviour changes (safe)

Change Detail Why it's safe
SubscriptionEvent.create now accepts flat params wrapParams auto-wraps {foo: 1} to {subscription_event: {foo: 1}} Callers already passing the envelope are detected (data.subscription_event truthy) and not double-wrapped. Flat-param callers were previously sending the wrong shape to the API — this is a bug fix.
SubscriptionEvent.updateWithParams / deleteWithParams same wrapping Identical logic to above Same reasoning — envelope callers unaffected, flat callers get fixed.
Resource._method filters trailing undefined args Array.from(arguments).splice(1).filter(a => a !== undefined) On main, fn(config, data, undefined) would pop undefined as cb, then pop data as the next candidate — silently losing the request body. After the fix, undefined is stripped and data is correctly identified. No legitimate call path passes undefined intentionally.
Account.retrieve validates include param Warns (does not throw) if unknown field names are passed Purely additive console warning; no behaviour change for valid inputs

Not changed

  • SubscriptionEvent.all — untouched
  • Invoice.create / Invoice.all / Invoice.retrieve / Invoice.destroy / Invoice.destroy_all — untouched
  • All other SDK resources — untouched
  • Callback and promise interfaces — both supported, no signature changes

Test plan

  • All 221 unit tests pass (npm test)
  • 23 integration tests pass against live API (test_pip_310.js)
  • Verified envelope wrapping via nock body capture
  • Verified backward compatibility (pre-wrapped params not double-wrapped)
  • Verified error responses return correct status codes
  • Rejection guards on all error tests (no silent passes)
  • Verified disable/enable sends correct {disabled: true/false} body
  • CI green on Node 16, 18, 20

Testing instructions

Account: WiktorOnboarding (ID: acc_d0ea225e-f0f1-40ab-92cf-659dce5f2b76). Impersonate user wiktor.plaga@chartmogul.com to find the API key. The test uses data source ds_45d064ca-fcf8-11f0-903f-33618f80d753 ("Via API 3", Custom) for create/update/delete operations, and ds_bdb16dbc-d5f3-11f0-b731-f7f85427b781 ("WiktorChartmogulStripe", Stripe) for disable/enable (which is only supported on connector data sources).

cd chartmogul-node
git checkout wiktor/pip-311-node-sdk-feature-updates
npm install
export CHARTMOGUL_API_KEY='<api_key>'
node

SubscriptionEvent flat param wrapping (create)

const ChartMogul = require('./');
const config = new ChartMogul.Config(process.env.CHARTMOGUL_API_KEY);

// Flat params — SDK auto-wraps into { subscription_event: { ... } }
const event = await ChartMogul.SubscriptionEvent.create(config, {
  customer_external_id: 'cus_TbosMb4qlH97x6',
  data_source_uuid: 'ds_bdb16dbc-d5f3-11f0-b731-f7f85427b781',
  event_type: 'subscription_cancelled',
  event_date: '2026-01-01T00:00:00Z',
  effective_date: '2026-02-01T00:00:00Z',
  external_id: 'test_flat_' + Date.now(),
  subscription_external_id: 'si_TbotiB3CvV3ogI'
});
console.log('Created:', event.id, event.external_id);

SubscriptionEvent envelope backward compat (create)

// Envelope style — SDK detects existing key and does NOT double-wrap
const event2 = await ChartMogul.SubscriptionEvent.create(config, {
  subscription_event: {
    customer_external_id: 'cus_TbosMb4qlH97x6',
    data_source_uuid: 'ds_bdb16dbc-d5f3-11f0-b731-f7f85427b781',
    event_type: 'subscription_cancelled',
    event_date: '2026-01-01T00:00:00Z',
    effective_date: '2026-02-01T00:00:00Z',
    external_id: 'test_env_' + Date.now(),
    subscription_external_id: 'si_TbotiB3CvV3ogI'
  }
});
console.log('Created (envelope):', event2.id);

SubscriptionEvent flat param wrapping (update)

const updated = await ChartMogul.SubscriptionEvent.updateWithParams(config, {
  id: event.id,
  event_type: 'subscription_cancelled'
});
console.log('Updated:', updated.id, updated.event_type);

SubscriptionEvent flat param wrapping (delete)

await ChartMogul.SubscriptionEvent.deleteWithParams(config, { id: event.id });
console.log('Deleted event', event.id);

SubscriptionEvent disable / enable

// Disable (uses Stripe data source event — Custom DS events do not support disable)
const disabled = await ChartMogul.SubscriptionEvent.disable(config, event2.id);
console.log('Disabled:', disabled.disabled); // true

// Enable
const enabled = await ChartMogul.SubscriptionEvent.enable(config, event2.id);
console.log('Enabled:', enabled.disabled); // false

// Cleanup
await ChartMogul.SubscriptionEvent.deleteWithParams(config, { id: event2.id });

Invoice updateStatus

// First create a customer and invoice
const cust = await ChartMogul.Customer.create(config, {
  data_source_uuid: 'ds_45d064ca-fcf8-11f0-903f-33618f80d753',
  external_id: 'test_cust_' + Date.now(),
  name: 'Test Customer'
});

const extId = 'test_inv_' + Date.now();
const inv = await ChartMogul.Invoice.create(config, cust.uuid, {
  invoices: [{
    external_id: extId,
    date: '2026-01-01T00:00:00Z',
    currency: 'USD',
    due_date: '2026-01-15T00:00:00Z',
    line_items: [{ type: 'one_time', description: 'Test', amount_in_cents: 1000, quantity: 1 }],
    transactions: [{ date: '2026-01-05T00:00:00Z', type: 'payment', result: 'successful' }]
  }]
});

// Update status using data_source_uuid + invoice external_id
const voided = await ChartMogul.Invoice.updateStatus(config,
  'ds_45d064ca-fcf8-11f0-903f-33618f80d753', extId, { status: 'voided' }
);
console.log('Status:', voided.invoice.status); // 'voided'

Invoice disable / enable

// Disable only works on connector (Stripe/Chargebee/etc.) invoices, not Custom DS
// Find a Stripe invoice first:
const invs = await ChartMogul.Invoice.all(config);
const stripeInv = invs.invoices.find(i => !i.disabled && i.external_id.startsWith('in_'));

const dis = await ChartMogul.Invoice.disable(config, stripeInv.uuid);
console.log('Disabled:', dis.disabled, 'at:', dis.disabled_at); // true, <timestamp>

const en = await ChartMogul.Invoice.enable(config, stripeInv.uuid);
console.log('Re-enabled:', en.disabled); // false

Account.retrieve include param validation

// Valid include — no warning
const acct = await ChartMogul.Account.retrieve(config, {
  include: 'refund_handling,churn_recognition'
});
console.log('Refund handling:', acct.refund_handling);

// Invalid include — prints console warning, still makes the request
const acct2 = await ChartMogul.Account.retrieve(config, {
  include: 'bogus_field'
});
// Console output: [chartmogul] Account.retrieve: unknown include field(s): bogus_field. Allowed: ...

Cleanup

await ChartMogul.Customer.destroy(config, cust.uuid);

🤖 Generated with Claude Code

wscourge and others added 3 commits March 17, 2026 17:05
…passing bug

- Wrap flat params in `subscription_event` envelope for create/update/delete
- Add disable/enable state toggling endpoints
- Fix bug where undefined callback arg blocked data extraction in _method()
- Add tests verifying body wrapping and error handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Invoice.updateStatus (PATCH /v1/invoices/:uuid)
- Add Invoice.disable (PATCH /v1/invoices/:uuid/disable)
- Add tests for happy path, callback style, body params, and error cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@wscourge wscourge marked this pull request as ready for review March 17, 2026 16:16
@wscourge wscourge requested a review from Copilot March 17, 2026 16:17
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the ChartMogul Node SDK to support newer API behaviors for Subscription Events, Invoices, and Account retrieval by adding/adjusting client methods and expanding test coverage for these behaviors.

Changes:

  • Add support for flat (non-enveloped) SubscriptionEvent params by auto-wrapping into a subscription_event envelope, while keeping backward compatibility for already-enveloped inputs.
  • Add SubscriptionEvent.enable/disable and Invoice.updateStatus/disable SDK methods and corresponding tests.
  • Expand invoice/account test fixtures and assertions to cover new/optional response fields (e.g., error, id, and account include query param).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
lib/chartmogul/subscription_event.js Adds param-wrapping overrides for create/update/delete and adds enable/disable endpoints.
lib/chartmogul/invoice.js Adds PATCH helpers for invoice status updates and invoice disable endpoint.
test/chartmogul/subscription-event.js Updates tests to validate envelope wrapping + adds enable/disable tests and negative cases.
test/chartmogul/invoice.js Adds tests for invoice status updates/disable + extends fixtures with error fields.
test/chartmogul/account.js Adds assertions for id and supports include query param + negative case.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- Use hasOwnProperty check in wrapParams to handle falsy envelope values
- Use typeof callback === 'function' instead of truthy check

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@wscourge wscourge changed the title Wiktor/pip 311 node sdk feature updates Add SubscriptionEvent wrapping/disable/enable and Invoice updateStatus/disable [PIP-311] Mar 18, 2026
wscourge and others added 3 commits March 23, 2026 15:42
…arden tests

- Refactor SubscriptionEvent wrappers into shared wrapMethod helper
- Simplify wrapParams to use truthiness check instead of hasOwnProperty
- Fix root cause of undefined callback bug in Resource._method by filtering trailing undefined args
- Add rejection guards to all .catch-only error tests (invoice, subscription-event, account)
- Add callback-style test for SubscriptionEvent.create
- Add body capture assertion to Invoice.disable body params test
- Add clarifying comment on Invoice.updateStatus shared path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…route

- Invoice.updateStatus: PATCH /v1/invoices/:uuid -> PUT /v1/data_sources/:ds/invoices/:ext_id/status
- Invoice.disable: /disable -> /disabled_state with { disabled: true } body
- SubscriptionEvent.disable/enable: /disable,/enable -> /disabled_state with body toggle
- Add Invoice.enable convenience wrapper
- Add DRY wrapMethod helper for SubscriptionEvent
- Harden error tests with rejection guards
- Add callback test for SubscriptionEvent.create
- Verify body payloads in disable tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Validates the include query param against known field names
(churn_recognition, churn_when_zero_mrr, auto_churn_subscription,
refund_handling, proximate_movement_reclassification) and warns
on unknown values without throwing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants