@drawbridge/drawbridge-utils 0.0.147 → 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,
@@ -4179,15 +4189,40 @@ var shopify_default2 = {
4179
4189
  });
4180
4190
  const usageLine = (_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId;
4181
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
+ }
4216
+ }
4182
4217
  } catch (probeError) {
4183
4218
  billingProbeFailed = true;
4184
- (_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", {
4185
4220
  connectionId: connection2.id,
4186
4221
  message: probeError == null ? void 0 : probeError.message,
4187
4222
  shop: connection2.shop
4188
4223
  });
4189
4224
  }
4190
- 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)) {
4191
4226
  writes.push({
4192
4227
  collection: "connection",
4193
4228
  data: {
@@ -4409,7 +4444,15 @@ var shopify_default2 = {
4409
4444
  // and classification against it is case-sensitive. Optional: empty
4410
4445
  // means the default, and the sender still refuses any handle that
4411
4446
  // disagrees with the transaction it minted.
4412
- { 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 }
4413
4456
  ]
4414
4457
  },
4415
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.
@@ -4958,6 +4968,52 @@ var shopify = {
4958
4968
 
4959
4969
  if( usageLine ) metered = usageLine;
4960
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 {
4998
+
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
+ }
5014
+
5015
+ }
5016
+
4961
5017
  } catch ( probeError ) {
4962
5018
 
4963
5019
  billingProbeFailed = true;
@@ -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.
@@ -4958,6 +4968,52 @@ var shopify = {
4958
4968
 
4959
4969
  if( usageLine ) metered = usageLine;
4960
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 {
4998
+
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
+ }
5014
+
5015
+ }
5016
+
4961
5017
  } catch ( probeError ) {
4962
5018
 
4963
5019
  billingProbeFailed = true;
@@ -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,
@@ -4105,15 +4115,40 @@ var shopify_default2 = {
4105
4115
  });
4106
4116
  const usageLine = (_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId;
4107
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
+ }
4142
+ }
4108
4143
  } catch (probeError) {
4109
4144
  billingProbeFailed = true;
4110
- (_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", {
4111
4146
  connectionId: connection2.id,
4112
4147
  message: probeError == null ? void 0 : probeError.message,
4113
4148
  shop: connection2.shop
4114
4149
  });
4115
4150
  }
4116
- 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)) {
4117
4152
  writes.push({
4118
4153
  collection: "connection",
4119
4154
  data: {
@@ -4335,7 +4370,15 @@ var shopify_default2 = {
4335
4370
  // and classification against it is case-sensitive. Optional: empty
4336
4371
  // means the default, and the sender still refuses any handle that
4337
4372
  // disagrees with the transaction it minted.
4338
- { 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 }
4339
4382
  ]
4340
4383
  },
4341
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,
@@ -4104,15 +4114,40 @@ var shopify_default2 = {
4104
4114
  });
4105
4115
  const usageLine = (_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId;
4106
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
+ }
4141
+ }
4107
4142
  } catch (probeError) {
4108
4143
  billingProbeFailed = true;
4109
- (_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", {
4110
4145
  connectionId: connection2.id,
4111
4146
  message: probeError == null ? void 0 : probeError.message,
4112
4147
  shop: connection2.shop
4113
4148
  });
4114
4149
  }
4115
- 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)) {
4116
4151
  writes.push({
4117
4152
  collection: "connection",
4118
4153
  data: {
@@ -4334,7 +4369,15 @@ var shopify_default2 = {
4334
4369
  // and classification against it is case-sensitive. Optional: empty
4335
4370
  // means the default, and the sender still refuses any handle that
4336
4371
  // disagrees with the transaction it minted.
4337
- { 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 }
4338
4381
  ]
4339
4382
  },
4340
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,
@@ -4060,15 +4070,40 @@ var shopify_default2 = {
4060
4070
  });
4061
4071
  const usageLine = (_a = subscriptions.find((subscription) => subscription.usageLineItemId)) == null ? void 0 : _a.usageLineItemId;
4062
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
+ }
4097
+ }
4063
4098
  } catch (probeError) {
4064
4099
  billingProbeFailed = true;
4065
- (_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", {
4066
4101
  connectionId: connection2.id,
4067
4102
  message: probeError == null ? void 0 : probeError.message,
4068
4103
  shop: connection2.shop
4069
4104
  });
4070
4105
  }
4071
- 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)) {
4072
4107
  writes.push({
4073
4108
  collection: "connection",
4074
4109
  data: {
@@ -4290,7 +4325,15 @@ var shopify_default2 = {
4290
4325
  // and classification against it is case-sensitive. Optional: empty
4291
4326
  // means the default, and the sender still refuses any handle that
4292
4327
  // disagrees with the transaction it minted.
4293
- { 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 }
4294
4337
  ]
4295
4338
  },
4296
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.147"
219
+ "version": "0.0.148"
220
220
  }