@drawbridge/drawbridge-utils 0.0.146 → 0.0.148

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2311,9 +2311,19 @@ var drawbridge_default2 = {
2311
2311
  // either. Unset, it degrades to the account sender rather than
2312
2312
  // refusing to start.
2313
2313
  { input: "email", key: "leadSender", credential: "SENDGRID_SEND_FROM_ADDRESS", label: "Lead sender", message: "The default for lead-facing mail when a merchant has not verified their own domain." },
2314
- { input: "text", key: "smsFrom", credential: "TWILIO_ACCOUNT_FROM", label: "SMS number", required: true },
2315
- { input: "password", key: "smsSid", credential: "TWILIO_ACCOUNT_SID", label: "Twilio account SID", redact: true, required: true },
2316
- { input: "password", key: "smsToken", credential: "TWILIO_AUTH_TOKEN", label: "Twilio auth token", redact: true, required: true }
2314
+ // NOT required, the three of them together. SendGrid alone is enough to
2315
+ // run the platform sign-in codes, notifications and every lead-facing
2316
+ // mail are email. Requiring Twilio here made the whole drawbridge
2317
+ // provider read not-live on a deployment that never sends SMS, over a
2318
+ // channel it was not using.
2319
+ //
2320
+ // Missing, SMS is LOCKED DOWN rather than broken: every send path
2321
+ // refuses cleanly (queue/notification.js cancels, the otc stream skips
2322
+ // and logs), the number-purchase routes deny, and identityDefaults.sms
2323
+ // hides the org's SMS settings entirely.
2324
+ { input: "text", key: "smsFrom", credential: "TWILIO_ACCOUNT_FROM", label: "SMS number", message: "Without Twilio credentials, SMS is disabled across the platform." },
2325
+ { input: "password", key: "smsSid", credential: "TWILIO_ACCOUNT_SID", label: "Twilio account SID", redact: true },
2326
+ { input: "password", key: "smsToken", credential: "TWILIO_AUTH_TOKEN", label: "Twilio auth token", redact: true }
2317
2327
  ]
2318
2328
  },
2319
2329
  // NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
@@ -4137,7 +4147,7 @@ var shopify_default2 = {
4137
4147
  // against the ones the app now needs, and queues a webhook
4138
4148
  // reconciliation.
4139
4149
  health: async ({ connection: connection2, workflow }, { adminToken, logger: logger2, read, reconcileScopes, resolveSettings, rotateToken, shopify } = {}) => {
4140
- var _a, _b, _c;
4150
+ var _a, _b, _c, _d;
4141
4151
  const request2 = {
4142
4152
  connectionId: workflow.connection,
4143
4153
  organizationId: workflow.organization,
@@ -4177,18 +4187,42 @@ var shopify_default2 = {
4177
4187
  adminAccessToken,
4178
4188
  domain: connection2.shop
4179
4189
  });
4180
- if (subscriptions.length && subscriptions.every((subscription) => typeof subscription.metered === "boolean")) {
4181
- metered = ((_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId) || null;
4190
+ const usageLine = (_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId;
4191
+ if (usageLine) metered = usageLine;
4192
+ const partnerRow = (read == null ? void 0 : read.get) ? await read.get({ collection: "provider", query: { slug: "shopify" } }) : null;
4193
+ const partner = (partnerRow == null ? void 0 : partnerRow.settings) ? decrypt(partnerRow.settings) : {};
4194
+ if (partner.partnerToken && partner.partnerOrgId && partner.partnerAppId && (source == null ? void 0 : source.id) && ((_b = shopify.partner) == null ? void 0 : _b.getUsageChargeEvents)) {
4195
+ const accruals = await shopify.partner.getUsageChargeEvents({
4196
+ appId: "gid://shopify/App/" + partner.partnerAppId,
4197
+ first: 1,
4198
+ occurredAtMin: new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString(),
4199
+ organizationId: partner.partnerOrgId,
4200
+ partnerToken: partner.partnerToken,
4201
+ shopId: "gid://shopify/Shop/" + source.id
4202
+ });
4203
+ if (accruals.length) {
4204
+ metered = String(accruals[0].chargeId || accruals[0].id);
4205
+ } else {
4206
+ const unaccrued = await read.get({
4207
+ collection: "order",
4208
+ query: {
4209
+ "billed.date": { $lt: new Date(Date.now() - 6 * 60 * 60 * 1e3) },
4210
+ organization: workflow.organization,
4211
+ "provider.slug": "shopify"
4212
+ }
4213
+ });
4214
+ if (unaccrued) metered = null;
4215
+ }
4182
4216
  }
4183
4217
  } catch (probeError) {
4184
4218
  billingProbeFailed = true;
4185
- (_b = logger2 == null ? void 0 : logger2.warn) == null ? void 0 : _b.call(logger2, "shopify.health.billing.probe.failed", {
4219
+ (_c = logger2 == null ? void 0 : logger2.warn) == null ? void 0 : _c.call(logger2, "shopify.health.billing.probe.failed", {
4186
4220
  connectionId: connection2.id,
4187
4221
  message: probeError == null ? void 0 : probeError.message,
4188
4222
  shop: connection2.shop
4189
4223
  });
4190
4224
  }
4191
- if (sourceRepaired || metered !== void 0 && metered !== (((_c = connection2.source) == null ? void 0 : _c.metered) ?? void 0)) {
4225
+ if (sourceRepaired || metered !== void 0 && metered !== (((_d = connection2.source) == null ? void 0 : _d.metered) ?? void 0)) {
4192
4226
  writes.push({
4193
4227
  collection: "connection",
4194
4228
  data: {
@@ -4410,7 +4444,15 @@ var shopify_default2 = {
4410
4444
  // and classification against it is case-sensitive. Optional: empty
4411
4445
  // means the default, and the sender still refuses any handle that
4412
4446
  // disagrees with the transaction it minted.
4413
- { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true }
4447
+ { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true },
4448
+ // The Partner API credential set — the ONE place meter billing is
4449
+ // observable (CHARGE_USAGE accrual events). Org id and numeric app
4450
+ // id come from the Partner Dashboard; the token from Settings →
4451
+ // Partner API clients (Manage-apps permission). All optional: the
4452
+ // accrual probe simply stays off until they exist.
4453
+ { input: "text", key: "partnerOrgId", label: "Partner organization ID", required: false, setting: true },
4454
+ { input: "text", key: "partnerAppId", label: "Partner app ID (numeric)", required: false, setting: true },
4455
+ { input: "password", key: "partnerToken", label: "Partner API token", redact: true, required: false, setting: true }
4414
4456
  ]
4415
4457
  },
4416
4458
  // A pre-launch integration: it only surfaces once the App Store listing
@@ -2413,9 +2413,19 @@ var drawbridge = {
2413
2413
  // either. Unset, it degrades to the account sender rather than
2414
2414
  // refusing to start.
2415
2415
  { input : 'email', key : 'leadSender', credential : 'SENDGRID_SEND_FROM_ADDRESS', label : 'Lead sender', message : 'The default for lead-facing mail when a merchant has not verified their own domain.' },
2416
- { input : 'text', key : 'smsFrom', credential : 'TWILIO_ACCOUNT_FROM', label : 'SMS number', required : true },
2417
- { input : 'password', key : 'smsSid', credential : 'TWILIO_ACCOUNT_SID', label : 'Twilio account SID', redact : true, required : true },
2418
- { input : 'password', key : 'smsToken', credential : 'TWILIO_AUTH_TOKEN', label : 'Twilio auth token', redact : true, required : true }
2416
+ // NOT required, the three of them together. SendGrid alone is enough to
2417
+ // run the platform sign-in codes, notifications and every lead-facing
2418
+ // mail are email. Requiring Twilio here made the whole drawbridge
2419
+ // provider read not-live on a deployment that never sends SMS, over a
2420
+ // channel it was not using.
2421
+ //
2422
+ // Missing, SMS is LOCKED DOWN rather than broken: every send path
2423
+ // refuses cleanly (queue/notification.js cancels, the otc stream skips
2424
+ // and logs), the number-purchase routes deny, and identityDefaults.sms
2425
+ // hides the org's SMS settings entirely.
2426
+ { input : 'text', key : 'smsFrom', credential : 'TWILIO_ACCOUNT_FROM', label : 'SMS number', message : 'Without Twilio credentials, SMS is disabled across the platform.' },
2427
+ { input : 'password', key : 'smsSid', credential : 'TWILIO_ACCOUNT_SID', label : 'Twilio account SID', redact : true },
2428
+ { input : 'password', key : 'smsToken', credential : 'TWILIO_AUTH_TOKEN', label : 'Twilio auth token', redact : true }
2419
2429
  ]
2420
2430
  },
2421
2431
  // NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
@@ -4943,18 +4953,64 @@ var shopify = {
4943
4953
  domain : connection.shop
4944
4954
  });
4945
4955
 
4946
- // Only an ACTIVE approval says anything about metering — no
4947
- // subscriptions (mid-install, plan pending) leaves the stamp
4948
- // untouched rather than asserting null from silence. The
4949
- // every-boolean guard is version skew insurance: an older
4950
- // @drawbridge/shopify returns subscriptions without the flag
4951
- // (and without usageLineItemId), and reading that silence as
4952
- // unmetered would nag every healthy merchant to re-approve a
4953
- // plan that is fine the flag being boolean proves an SDK
4954
- // that also reports the line id.
4955
- if( subscriptions.length && subscriptions.every( ( subscription ) => typeof subscription.metered === 'boolean' ) ){
4956
+ // A usage line on the approval is POSITIVE evidence and gets
4957
+ // stamped. Its ABSENCE proves nothing: measured 2026-09-04
4958
+ // (both 2026-04 and unstable, against a plan whose meter
4959
+ // exists), managed-pricing App-Events meters never
4960
+ // materialize as subscription line items — AppUsagePricing is
4961
+ // the legacy usage-charge model. Reading absence as
4962
+ // "unmetered" flagged healthy stores, so absence is NO
4963
+ // VERDICT: the stamp, the Issue, the card and the
4964
+ // meter-missing pager all stay silent until a signal that can
4965
+ // actually observe the meter exists (Partner API
4966
+ // USAGE_CHARGE_APPLIED accrual events are that signal).
4967
+ const usageLine = subscriptions.find( ( subscription ) => subscription.usageLineItemId )?.usageLineItemId;
4968
+
4969
+ if( usageLine ) metered = usageLine;
4970
+
4971
+ // THE REAL METER SIGNAL — Partner API accrual. A CHARGE_USAGE
4972
+ // event for this shop is proof billing works: its charge id
4973
+ // becomes the stamp (overriding the line-item probe, which
4974
+ // managed pricing keeps blind). No accrual AND a billed order
4975
+ // old enough that one should exist is the HONEST unmetered
4976
+ // verdict — the null that re-arms the card, the Issue and the
4977
+ // pager on truth instead of the Admin API's silence. Off
4978
+ // entirely until the provider row carries the credentials.
4979
+ const partnerRow = read?.get ? await read.get({ collection : 'provider', query : { slug : 'shopify' } }) : null;
4980
+ const partner = partnerRow?.settings ? decrypt( partnerRow.settings ) : {};
4981
+
4982
+ if( partner.partnerToken && partner.partnerOrgId && partner.partnerAppId && source?.id && shopify.partner?.getUsageChargeEvents ){
4983
+
4984
+ const accruals = await shopify.partner.getUsageChargeEvents({
4985
+ appId : 'gid://shopify/App/' + partner.partnerAppId,
4986
+ first : 1,
4987
+ occurredAtMin : new Date( Date.now() - 30 * 24 * 60 * 60 * 1000 ).toISOString(),
4988
+ organizationId : partner.partnerOrgId,
4989
+ partnerToken : partner.partnerToken,
4990
+ shopId : 'gid://shopify/Shop/' + source.id
4991
+ });
4992
+
4993
+ if( accruals.length ){
4994
+
4995
+ metered = String( accruals[ 0 ].chargeId || accruals[ 0 ].id );
4996
+
4997
+ } else {
4956
4998
 
4957
- metered = subscriptions.find( ( subscription ) => subscription.usageLineItemId )?.usageLineItemId || null;
4999
+ // Only a store that SHOULD have accrued gets the
5000
+ // unmetered verdict: an order stamped billed six-plus
5001
+ // hours ago with still no accrual event.
5002
+ const unaccrued = await read.get({
5003
+ collection : 'order',
5004
+ query : {
5005
+ 'billed.date' : { $lt : new Date( Date.now() - 6 * 60 * 60 * 1000 ) },
5006
+ organization : workflow.organization,
5007
+ 'provider.slug' : 'shopify'
5008
+ }
5009
+ });
5010
+
5011
+ if( unaccrued ) metered = null;
5012
+
5013
+ }
4958
5014
 
4959
5015
  }
4960
5016
 
@@ -5265,7 +5321,15 @@ var shopify = {
5265
5321
  // and classification against it is case-sensitive. Optional: empty
5266
5322
  // means the default, and the sender still refuses any handle that
5267
5323
  // disagrees with the transaction it minted.
5268
- { format : 'slug', input : 'text', key : 'orderEventHandle', label : 'Order event handle', required : false, setting : true }
5324
+ { format : 'slug', input : 'text', key : 'orderEventHandle', label : 'Order event handle', required : false, setting : true },
5325
+ // The Partner API credential set — the ONE place meter billing is
5326
+ // observable (CHARGE_USAGE accrual events). Org id and numeric app
5327
+ // id come from the Partner Dashboard; the token from Settings →
5328
+ // Partner API clients (Manage-apps permission). All optional: the
5329
+ // accrual probe simply stays off until they exist.
5330
+ { input : 'text', key : 'partnerOrgId', label : 'Partner organization ID', required : false, setting : true },
5331
+ { input : 'text', key : 'partnerAppId', label : 'Partner app ID (numeric)', required : false, setting : true },
5332
+ { input : 'password', key : 'partnerToken', label : 'Partner API token', redact : true, required : false, setting : true }
5269
5333
  ]
5270
5334
  },
5271
5335
  // A pre-launch integration: it only surfaces once the App Store listing
@@ -2413,9 +2413,19 @@ var drawbridge = {
2413
2413
  // either. Unset, it degrades to the account sender rather than
2414
2414
  // refusing to start.
2415
2415
  { input : 'email', key : 'leadSender', credential : 'SENDGRID_SEND_FROM_ADDRESS', label : 'Lead sender', message : 'The default for lead-facing mail when a merchant has not verified their own domain.' },
2416
- { input : 'text', key : 'smsFrom', credential : 'TWILIO_ACCOUNT_FROM', label : 'SMS number', required : true },
2417
- { input : 'password', key : 'smsSid', credential : 'TWILIO_ACCOUNT_SID', label : 'Twilio account SID', redact : true, required : true },
2418
- { input : 'password', key : 'smsToken', credential : 'TWILIO_AUTH_TOKEN', label : 'Twilio auth token', redact : true, required : true }
2416
+ // NOT required, the three of them together. SendGrid alone is enough to
2417
+ // run the platform sign-in codes, notifications and every lead-facing
2418
+ // mail are email. Requiring Twilio here made the whole drawbridge
2419
+ // provider read not-live on a deployment that never sends SMS, over a
2420
+ // channel it was not using.
2421
+ //
2422
+ // Missing, SMS is LOCKED DOWN rather than broken: every send path
2423
+ // refuses cleanly (queue/notification.js cancels, the otc stream skips
2424
+ // and logs), the number-purchase routes deny, and identityDefaults.sms
2425
+ // hides the org's SMS settings entirely.
2426
+ { input : 'text', key : 'smsFrom', credential : 'TWILIO_ACCOUNT_FROM', label : 'SMS number', message : 'Without Twilio credentials, SMS is disabled across the platform.' },
2427
+ { input : 'password', key : 'smsSid', credential : 'TWILIO_ACCOUNT_SID', label : 'Twilio account SID', redact : true },
2428
+ { input : 'password', key : 'smsToken', credential : 'TWILIO_AUTH_TOKEN', label : 'Twilio auth token', redact : true }
2419
2429
  ]
2420
2430
  },
2421
2431
  // NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
@@ -4943,18 +4953,64 @@ var shopify = {
4943
4953
  domain : connection.shop
4944
4954
  });
4945
4955
 
4946
- // Only an ACTIVE approval says anything about metering — no
4947
- // subscriptions (mid-install, plan pending) leaves the stamp
4948
- // untouched rather than asserting null from silence. The
4949
- // every-boolean guard is version skew insurance: an older
4950
- // @drawbridge/shopify returns subscriptions without the flag
4951
- // (and without usageLineItemId), and reading that silence as
4952
- // unmetered would nag every healthy merchant to re-approve a
4953
- // plan that is fine the flag being boolean proves an SDK
4954
- // that also reports the line id.
4955
- if( subscriptions.length && subscriptions.every( ( subscription ) => typeof subscription.metered === 'boolean' ) ){
4956
+ // A usage line on the approval is POSITIVE evidence and gets
4957
+ // stamped. Its ABSENCE proves nothing: measured 2026-09-04
4958
+ // (both 2026-04 and unstable, against a plan whose meter
4959
+ // exists), managed-pricing App-Events meters never
4960
+ // materialize as subscription line items — AppUsagePricing is
4961
+ // the legacy usage-charge model. Reading absence as
4962
+ // "unmetered" flagged healthy stores, so absence is NO
4963
+ // VERDICT: the stamp, the Issue, the card and the
4964
+ // meter-missing pager all stay silent until a signal that can
4965
+ // actually observe the meter exists (Partner API
4966
+ // USAGE_CHARGE_APPLIED accrual events are that signal).
4967
+ const usageLine = subscriptions.find( ( subscription ) => subscription.usageLineItemId )?.usageLineItemId;
4968
+
4969
+ if( usageLine ) metered = usageLine;
4970
+
4971
+ // THE REAL METER SIGNAL — Partner API accrual. A CHARGE_USAGE
4972
+ // event for this shop is proof billing works: its charge id
4973
+ // becomes the stamp (overriding the line-item probe, which
4974
+ // managed pricing keeps blind). No accrual AND a billed order
4975
+ // old enough that one should exist is the HONEST unmetered
4976
+ // verdict — the null that re-arms the card, the Issue and the
4977
+ // pager on truth instead of the Admin API's silence. Off
4978
+ // entirely until the provider row carries the credentials.
4979
+ const partnerRow = read?.get ? await read.get({ collection : 'provider', query : { slug : 'shopify' } }) : null;
4980
+ const partner = partnerRow?.settings ? decrypt( partnerRow.settings ) : {};
4981
+
4982
+ if( partner.partnerToken && partner.partnerOrgId && partner.partnerAppId && source?.id && shopify.partner?.getUsageChargeEvents ){
4983
+
4984
+ const accruals = await shopify.partner.getUsageChargeEvents({
4985
+ appId : 'gid://shopify/App/' + partner.partnerAppId,
4986
+ first : 1,
4987
+ occurredAtMin : new Date( Date.now() - 30 * 24 * 60 * 60 * 1000 ).toISOString(),
4988
+ organizationId : partner.partnerOrgId,
4989
+ partnerToken : partner.partnerToken,
4990
+ shopId : 'gid://shopify/Shop/' + source.id
4991
+ });
4992
+
4993
+ if( accruals.length ){
4994
+
4995
+ metered = String( accruals[ 0 ].chargeId || accruals[ 0 ].id );
4996
+
4997
+ } else {
4956
4998
 
4957
- metered = subscriptions.find( ( subscription ) => subscription.usageLineItemId )?.usageLineItemId || null;
4999
+ // Only a store that SHOULD have accrued gets the
5000
+ // unmetered verdict: an order stamped billed six-plus
5001
+ // hours ago with still no accrual event.
5002
+ const unaccrued = await read.get({
5003
+ collection : 'order',
5004
+ query : {
5005
+ 'billed.date' : { $lt : new Date( Date.now() - 6 * 60 * 60 * 1000 ) },
5006
+ organization : workflow.organization,
5007
+ 'provider.slug' : 'shopify'
5008
+ }
5009
+ });
5010
+
5011
+ if( unaccrued ) metered = null;
5012
+
5013
+ }
4958
5014
 
4959
5015
  }
4960
5016
 
@@ -5265,7 +5321,15 @@ var shopify = {
5265
5321
  // and classification against it is case-sensitive. Optional: empty
5266
5322
  // means the default, and the sender still refuses any handle that
5267
5323
  // disagrees with the transaction it minted.
5268
- { format : 'slug', input : 'text', key : 'orderEventHandle', label : 'Order event handle', required : false, setting : true }
5324
+ { format : 'slug', input : 'text', key : 'orderEventHandle', label : 'Order event handle', required : false, setting : true },
5325
+ // The Partner API credential set — the ONE place meter billing is
5326
+ // observable (CHARGE_USAGE accrual events). Org id and numeric app
5327
+ // id come from the Partner Dashboard; the token from Settings →
5328
+ // Partner API clients (Manage-apps permission). All optional: the
5329
+ // accrual probe simply stays off until they exist.
5330
+ { input : 'text', key : 'partnerOrgId', label : 'Partner organization ID', required : false, setting : true },
5331
+ { input : 'text', key : 'partnerAppId', label : 'Partner app ID (numeric)', required : false, setting : true },
5332
+ { input : 'password', key : 'partnerToken', label : 'Partner API token', redact : true, required : false, setting : true }
5269
5333
  ]
5270
5334
  },
5271
5335
  // A pre-launch integration: it only surfaces once the App Store listing
@@ -2237,9 +2237,19 @@ var drawbridge_default2 = {
2237
2237
  // either. Unset, it degrades to the account sender rather than
2238
2238
  // refusing to start.
2239
2239
  { input: "email", key: "leadSender", credential: "SENDGRID_SEND_FROM_ADDRESS", label: "Lead sender", message: "The default for lead-facing mail when a merchant has not verified their own domain." },
2240
- { input: "text", key: "smsFrom", credential: "TWILIO_ACCOUNT_FROM", label: "SMS number", required: true },
2241
- { input: "password", key: "smsSid", credential: "TWILIO_ACCOUNT_SID", label: "Twilio account SID", redact: true, required: true },
2242
- { input: "password", key: "smsToken", credential: "TWILIO_AUTH_TOKEN", label: "Twilio auth token", redact: true, required: true }
2240
+ // NOT required, the three of them together. SendGrid alone is enough to
2241
+ // run the platform sign-in codes, notifications and every lead-facing
2242
+ // mail are email. Requiring Twilio here made the whole drawbridge
2243
+ // provider read not-live on a deployment that never sends SMS, over a
2244
+ // channel it was not using.
2245
+ //
2246
+ // Missing, SMS is LOCKED DOWN rather than broken: every send path
2247
+ // refuses cleanly (queue/notification.js cancels, the otc stream skips
2248
+ // and logs), the number-purchase routes deny, and identityDefaults.sms
2249
+ // hides the org's SMS settings entirely.
2250
+ { input: "text", key: "smsFrom", credential: "TWILIO_ACCOUNT_FROM", label: "SMS number", message: "Without Twilio credentials, SMS is disabled across the platform." },
2251
+ { input: "password", key: "smsSid", credential: "TWILIO_ACCOUNT_SID", label: "Twilio account SID", redact: true },
2252
+ { input: "password", key: "smsToken", credential: "TWILIO_AUTH_TOKEN", label: "Twilio auth token", redact: true }
2243
2253
  ]
2244
2254
  },
2245
2255
  // NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
@@ -4063,7 +4073,7 @@ var shopify_default2 = {
4063
4073
  // against the ones the app now needs, and queues a webhook
4064
4074
  // reconciliation.
4065
4075
  health: async ({ connection: connection2, workflow }, { adminToken, logger: logger2, read, reconcileScopes, resolveSettings, rotateToken, shopify } = {}) => {
4066
- var _a, _b, _c;
4076
+ var _a, _b, _c, _d;
4067
4077
  const request2 = {
4068
4078
  connectionId: workflow.connection,
4069
4079
  organizationId: workflow.organization,
@@ -4103,18 +4113,42 @@ var shopify_default2 = {
4103
4113
  adminAccessToken,
4104
4114
  domain: connection2.shop
4105
4115
  });
4106
- if (subscriptions.length && subscriptions.every((subscription) => typeof subscription.metered === "boolean")) {
4107
- metered = ((_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId) || null;
4116
+ const usageLine = (_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId;
4117
+ if (usageLine) metered = usageLine;
4118
+ const partnerRow = (read == null ? void 0 : read.get) ? await read.get({ collection: "provider", query: { slug: "shopify" } }) : null;
4119
+ const partner = (partnerRow == null ? void 0 : partnerRow.settings) ? decrypt(partnerRow.settings) : {};
4120
+ if (partner.partnerToken && partner.partnerOrgId && partner.partnerAppId && (source == null ? void 0 : source.id) && ((_b = shopify.partner) == null ? void 0 : _b.getUsageChargeEvents)) {
4121
+ const accruals = await shopify.partner.getUsageChargeEvents({
4122
+ appId: "gid://shopify/App/" + partner.partnerAppId,
4123
+ first: 1,
4124
+ occurredAtMin: new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString(),
4125
+ organizationId: partner.partnerOrgId,
4126
+ partnerToken: partner.partnerToken,
4127
+ shopId: "gid://shopify/Shop/" + source.id
4128
+ });
4129
+ if (accruals.length) {
4130
+ metered = String(accruals[0].chargeId || accruals[0].id);
4131
+ } else {
4132
+ const unaccrued = await read.get({
4133
+ collection: "order",
4134
+ query: {
4135
+ "billed.date": { $lt: new Date(Date.now() - 6 * 60 * 60 * 1e3) },
4136
+ organization: workflow.organization,
4137
+ "provider.slug": "shopify"
4138
+ }
4139
+ });
4140
+ if (unaccrued) metered = null;
4141
+ }
4108
4142
  }
4109
4143
  } catch (probeError) {
4110
4144
  billingProbeFailed = true;
4111
- (_b = logger2 == null ? void 0 : logger2.warn) == null ? void 0 : _b.call(logger2, "shopify.health.billing.probe.failed", {
4145
+ (_c = logger2 == null ? void 0 : logger2.warn) == null ? void 0 : _c.call(logger2, "shopify.health.billing.probe.failed", {
4112
4146
  connectionId: connection2.id,
4113
4147
  message: probeError == null ? void 0 : probeError.message,
4114
4148
  shop: connection2.shop
4115
4149
  });
4116
4150
  }
4117
- if (sourceRepaired || metered !== void 0 && metered !== (((_c = connection2.source) == null ? void 0 : _c.metered) ?? void 0)) {
4151
+ if (sourceRepaired || metered !== void 0 && metered !== (((_d = connection2.source) == null ? void 0 : _d.metered) ?? void 0)) {
4118
4152
  writes.push({
4119
4153
  collection: "connection",
4120
4154
  data: {
@@ -4336,7 +4370,15 @@ var shopify_default2 = {
4336
4370
  // and classification against it is case-sensitive. Optional: empty
4337
4371
  // means the default, and the sender still refuses any handle that
4338
4372
  // disagrees with the transaction it minted.
4339
- { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true }
4373
+ { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true },
4374
+ // The Partner API credential set — the ONE place meter billing is
4375
+ // observable (CHARGE_USAGE accrual events). Org id and numeric app
4376
+ // id come from the Partner Dashboard; the token from Settings →
4377
+ // Partner API clients (Manage-apps permission). All optional: the
4378
+ // accrual probe simply stays off until they exist.
4379
+ { input: "text", key: "partnerOrgId", label: "Partner organization ID", required: false, setting: true },
4380
+ { input: "text", key: "partnerAppId", label: "Partner app ID (numeric)", required: false, setting: true },
4381
+ { input: "password", key: "partnerToken", label: "Partner API token", redact: true, required: false, setting: true }
4340
4382
  ]
4341
4383
  },
4342
4384
  // A pre-launch integration: it only surfaces once the App Store listing
@@ -2217,9 +2217,19 @@ var drawbridge_default2 = {
2217
2217
  // either. Unset, it degrades to the account sender rather than
2218
2218
  // refusing to start.
2219
2219
  { input: "email", key: "leadSender", credential: "SENDGRID_SEND_FROM_ADDRESS", label: "Lead sender", message: "The default for lead-facing mail when a merchant has not verified their own domain." },
2220
- { input: "text", key: "smsFrom", credential: "TWILIO_ACCOUNT_FROM", label: "SMS number", required: true },
2221
- { input: "password", key: "smsSid", credential: "TWILIO_ACCOUNT_SID", label: "Twilio account SID", redact: true, required: true },
2222
- { input: "password", key: "smsToken", credential: "TWILIO_AUTH_TOKEN", label: "Twilio auth token", redact: true, required: true }
2220
+ // NOT required, the three of them together. SendGrid alone is enough to
2221
+ // run the platform sign-in codes, notifications and every lead-facing
2222
+ // mail are email. Requiring Twilio here made the whole drawbridge
2223
+ // provider read not-live on a deployment that never sends SMS, over a
2224
+ // channel it was not using.
2225
+ //
2226
+ // Missing, SMS is LOCKED DOWN rather than broken: every send path
2227
+ // refuses cleanly (queue/notification.js cancels, the otc stream skips
2228
+ // and logs), the number-purchase routes deny, and identityDefaults.sms
2229
+ // hides the org's SMS settings entirely.
2230
+ { input: "text", key: "smsFrom", credential: "TWILIO_ACCOUNT_FROM", label: "SMS number", message: "Without Twilio credentials, SMS is disabled across the platform." },
2231
+ { input: "password", key: "smsSid", credential: "TWILIO_ACCOUNT_SID", label: "Twilio account SID", redact: true },
2232
+ { input: "password", key: "smsToken", credential: "TWILIO_AUTH_TOKEN", label: "Twilio auth token", redact: true }
2223
2233
  ]
2224
2234
  },
2225
2235
  // NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
@@ -4062,7 +4072,7 @@ var shopify_default2 = {
4062
4072
  // against the ones the app now needs, and queues a webhook
4063
4073
  // reconciliation.
4064
4074
  health: async ({ connection: connection2, workflow }, { adminToken, logger: logger2, read, reconcileScopes, resolveSettings, rotateToken, shopify } = {}) => {
4065
- var _a, _b, _c;
4075
+ var _a, _b, _c, _d;
4066
4076
  const request2 = {
4067
4077
  connectionId: workflow.connection,
4068
4078
  organizationId: workflow.organization,
@@ -4102,18 +4112,42 @@ var shopify_default2 = {
4102
4112
  adminAccessToken,
4103
4113
  domain: connection2.shop
4104
4114
  });
4105
- if (subscriptions.length && subscriptions.every((subscription) => typeof subscription.metered === "boolean")) {
4106
- metered = ((_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId) || null;
4115
+ const usageLine = (_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId;
4116
+ if (usageLine) metered = usageLine;
4117
+ const partnerRow = (read == null ? void 0 : read.get) ? await read.get({ collection: "provider", query: { slug: "shopify" } }) : null;
4118
+ const partner = (partnerRow == null ? void 0 : partnerRow.settings) ? decrypt(partnerRow.settings) : {};
4119
+ if (partner.partnerToken && partner.partnerOrgId && partner.partnerAppId && (source == null ? void 0 : source.id) && ((_b = shopify.partner) == null ? void 0 : _b.getUsageChargeEvents)) {
4120
+ const accruals = await shopify.partner.getUsageChargeEvents({
4121
+ appId: "gid://shopify/App/" + partner.partnerAppId,
4122
+ first: 1,
4123
+ occurredAtMin: new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString(),
4124
+ organizationId: partner.partnerOrgId,
4125
+ partnerToken: partner.partnerToken,
4126
+ shopId: "gid://shopify/Shop/" + source.id
4127
+ });
4128
+ if (accruals.length) {
4129
+ metered = String(accruals[0].chargeId || accruals[0].id);
4130
+ } else {
4131
+ const unaccrued = await read.get({
4132
+ collection: "order",
4133
+ query: {
4134
+ "billed.date": { $lt: new Date(Date.now() - 6 * 60 * 60 * 1e3) },
4135
+ organization: workflow.organization,
4136
+ "provider.slug": "shopify"
4137
+ }
4138
+ });
4139
+ if (unaccrued) metered = null;
4140
+ }
4107
4141
  }
4108
4142
  } catch (probeError) {
4109
4143
  billingProbeFailed = true;
4110
- (_b = logger2 == null ? void 0 : logger2.warn) == null ? void 0 : _b.call(logger2, "shopify.health.billing.probe.failed", {
4144
+ (_c = logger2 == null ? void 0 : logger2.warn) == null ? void 0 : _c.call(logger2, "shopify.health.billing.probe.failed", {
4111
4145
  connectionId: connection2.id,
4112
4146
  message: probeError == null ? void 0 : probeError.message,
4113
4147
  shop: connection2.shop
4114
4148
  });
4115
4149
  }
4116
- if (sourceRepaired || metered !== void 0 && metered !== (((_c = connection2.source) == null ? void 0 : _c.metered) ?? void 0)) {
4150
+ if (sourceRepaired || metered !== void 0 && metered !== (((_d = connection2.source) == null ? void 0 : _d.metered) ?? void 0)) {
4117
4151
  writes.push({
4118
4152
  collection: "connection",
4119
4153
  data: {
@@ -4335,7 +4369,15 @@ var shopify_default2 = {
4335
4369
  // and classification against it is case-sensitive. Optional: empty
4336
4370
  // means the default, and the sender still refuses any handle that
4337
4371
  // disagrees with the transaction it minted.
4338
- { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true }
4372
+ { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true },
4373
+ // The Partner API credential set — the ONE place meter billing is
4374
+ // observable (CHARGE_USAGE accrual events). Org id and numeric app
4375
+ // id come from the Partner Dashboard; the token from Settings →
4376
+ // Partner API clients (Manage-apps permission). All optional: the
4377
+ // accrual probe simply stays off until they exist.
4378
+ { input: "text", key: "partnerOrgId", label: "Partner organization ID", required: false, setting: true },
4379
+ { input: "text", key: "partnerAppId", label: "Partner app ID (numeric)", required: false, setting: true },
4380
+ { input: "password", key: "partnerToken", label: "Partner API token", redact: true, required: false, setting: true }
4339
4381
  ]
4340
4382
  },
4341
4383
  // A pre-launch integration: it only surfaces once the App Store listing
package/dist/providers.js CHANGED
@@ -2173,9 +2173,19 @@ var drawbridge_default2 = {
2173
2173
  // either. Unset, it degrades to the account sender rather than
2174
2174
  // refusing to start.
2175
2175
  { input: "email", key: "leadSender", credential: "SENDGRID_SEND_FROM_ADDRESS", label: "Lead sender", message: "The default for lead-facing mail when a merchant has not verified their own domain." },
2176
- { input: "text", key: "smsFrom", credential: "TWILIO_ACCOUNT_FROM", label: "SMS number", required: true },
2177
- { input: "password", key: "smsSid", credential: "TWILIO_ACCOUNT_SID", label: "Twilio account SID", redact: true, required: true },
2178
- { input: "password", key: "smsToken", credential: "TWILIO_AUTH_TOKEN", label: "Twilio auth token", redact: true, required: true }
2176
+ // NOT required, the three of them together. SendGrid alone is enough to
2177
+ // run the platform sign-in codes, notifications and every lead-facing
2178
+ // mail are email. Requiring Twilio here made the whole drawbridge
2179
+ // provider read not-live on a deployment that never sends SMS, over a
2180
+ // channel it was not using.
2181
+ //
2182
+ // Missing, SMS is LOCKED DOWN rather than broken: every send path
2183
+ // refuses cleanly (queue/notification.js cancels, the otc stream skips
2184
+ // and logs), the number-purchase routes deny, and identityDefaults.sms
2185
+ // hides the org's SMS settings entirely.
2186
+ { input: "text", key: "smsFrom", credential: "TWILIO_ACCOUNT_FROM", label: "SMS number", message: "Without Twilio credentials, SMS is disabled across the platform." },
2187
+ { input: "password", key: "smsSid", credential: "TWILIO_ACCOUNT_SID", label: "Twilio account SID", redact: true },
2188
+ { input: "password", key: "smsToken", credential: "TWILIO_AUTH_TOKEN", label: "Twilio auth token", redact: true }
2179
2189
  ]
2180
2190
  },
2181
2191
  // NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
@@ -4018,7 +4028,7 @@ var shopify_default2 = {
4018
4028
  // against the ones the app now needs, and queues a webhook
4019
4029
  // reconciliation.
4020
4030
  health: async ({ connection: connection2, workflow }, { adminToken, logger: logger2, read, reconcileScopes, resolveSettings, rotateToken, shopify } = {}) => {
4021
- var _a, _b, _c;
4031
+ var _a, _b, _c, _d;
4022
4032
  const request2 = {
4023
4033
  connectionId: workflow.connection,
4024
4034
  organizationId: workflow.organization,
@@ -4058,18 +4068,42 @@ var shopify_default2 = {
4058
4068
  adminAccessToken,
4059
4069
  domain: connection2.shop
4060
4070
  });
4061
- if (subscriptions.length && subscriptions.every((subscription) => typeof subscription.metered === "boolean")) {
4062
- metered = ((_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId) || null;
4071
+ const usageLine = (_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId;
4072
+ if (usageLine) metered = usageLine;
4073
+ const partnerRow = (read == null ? void 0 : read.get) ? await read.get({ collection: "provider", query: { slug: "shopify" } }) : null;
4074
+ const partner = (partnerRow == null ? void 0 : partnerRow.settings) ? decrypt(partnerRow.settings) : {};
4075
+ if (partner.partnerToken && partner.partnerOrgId && partner.partnerAppId && (source == null ? void 0 : source.id) && ((_b = shopify.partner) == null ? void 0 : _b.getUsageChargeEvents)) {
4076
+ const accruals = await shopify.partner.getUsageChargeEvents({
4077
+ appId: "gid://shopify/App/" + partner.partnerAppId,
4078
+ first: 1,
4079
+ occurredAtMin: new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString(),
4080
+ organizationId: partner.partnerOrgId,
4081
+ partnerToken: partner.partnerToken,
4082
+ shopId: "gid://shopify/Shop/" + source.id
4083
+ });
4084
+ if (accruals.length) {
4085
+ metered = String(accruals[0].chargeId || accruals[0].id);
4086
+ } else {
4087
+ const unaccrued = await read.get({
4088
+ collection: "order",
4089
+ query: {
4090
+ "billed.date": { $lt: new Date(Date.now() - 6 * 60 * 60 * 1e3) },
4091
+ organization: workflow.organization,
4092
+ "provider.slug": "shopify"
4093
+ }
4094
+ });
4095
+ if (unaccrued) metered = null;
4096
+ }
4063
4097
  }
4064
4098
  } catch (probeError) {
4065
4099
  billingProbeFailed = true;
4066
- (_b = logger2 == null ? void 0 : logger2.warn) == null ? void 0 : _b.call(logger2, "shopify.health.billing.probe.failed", {
4100
+ (_c = logger2 == null ? void 0 : logger2.warn) == null ? void 0 : _c.call(logger2, "shopify.health.billing.probe.failed", {
4067
4101
  connectionId: connection2.id,
4068
4102
  message: probeError == null ? void 0 : probeError.message,
4069
4103
  shop: connection2.shop
4070
4104
  });
4071
4105
  }
4072
- if (sourceRepaired || metered !== void 0 && metered !== (((_c = connection2.source) == null ? void 0 : _c.metered) ?? void 0)) {
4106
+ if (sourceRepaired || metered !== void 0 && metered !== (((_d = connection2.source) == null ? void 0 : _d.metered) ?? void 0)) {
4073
4107
  writes.push({
4074
4108
  collection: "connection",
4075
4109
  data: {
@@ -4291,7 +4325,15 @@ var shopify_default2 = {
4291
4325
  // and classification against it is case-sensitive. Optional: empty
4292
4326
  // means the default, and the sender still refuses any handle that
4293
4327
  // disagrees with the transaction it minted.
4294
- { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true }
4328
+ { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true },
4329
+ // The Partner API credential set — the ONE place meter billing is
4330
+ // observable (CHARGE_USAGE accrual events). Org id and numeric app
4331
+ // id come from the Partner Dashboard; the token from Settings →
4332
+ // Partner API clients (Manage-apps permission). All optional: the
4333
+ // accrual probe simply stays off until they exist.
4334
+ { input: "text", key: "partnerOrgId", label: "Partner organization ID", required: false, setting: true },
4335
+ { input: "text", key: "partnerAppId", label: "Partner app ID (numeric)", required: false, setting: true },
4336
+ { input: "password", key: "partnerToken", label: "Partner API token", redact: true, required: false, setting: true }
4295
4337
  ]
4296
4338
  },
4297
4339
  // A pre-launch integration: it only surfaces once the App Store listing
package/package.json CHANGED
@@ -216,5 +216,5 @@
216
216
  "prepublishOnly": ". \"$HOME/.nvm/nvm.sh\" && nvm use && tsup && node --test"
217
217
  },
218
218
  "types": "dist/index.d.ts",
219
- "version": "0.0.146"
219
+ "version": "0.0.148"
220
220
  }