@drawbridge/drawbridge-utils 0.0.149 → 0.0.151

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.
@@ -860,8 +860,8 @@ var attentive_default2 = {
860
860
  // keep the stored value. `required` drives the live check.
861
861
  provider: {
862
862
  fields: [
863
- { input: "text", key: "clientId", credential: "ATTENTIVE_OAUTH_CLIENT_ID", label: "Client ID", required: true },
864
- { input: "password", key: "clientSecret", credential: "ATTENTIVE_OAUTH_CLIENT_SECRET", label: "Client secret", redact: true, required: true }
863
+ { input: "text", key: "clientId", credential: "ATTENTIVE_OAUTH_CLIENT_ID", label: "Client ID", message: "From our app's registration in the Attentive developer portal.", required: true },
864
+ { input: "password", key: "clientSecret", credential: "ATTENTIVE_OAUTH_CLIENT_SECRET", label: "Client secret", message: "Beside the Client ID in the Attentive developer portal.", redact: true, required: true }
865
865
  ]
866
866
  },
867
867
  requires: [
@@ -2302,7 +2302,7 @@ var drawbridge_default2 = {
2302
2302
  provider: {
2303
2303
  fields: [
2304
2304
  { input: "email", key: "accountSender", credential: "SENDGRID_FROM_ADDRESS", label: "Account sender", message: "Verification codes and security alerts send from here.", required: true },
2305
- { input: "password", key: "apiKey", credential: "SENDGRID_API_KEY", label: "SendGrid API key", redact: true, required: true },
2305
+ { input: "password", key: "apiKey", credential: "SENDGRID_API_KEY", label: "SendGrid API key", message: "SendGrid dashboard, Settings, API Keys \u2014 full access for sends.", redact: true, required: true },
2306
2306
  // NOT required. The CRM sync is best-effort internal tooling and no-ops
2307
2307
  // without a token — requiring it would make the whole drawbridge provider
2308
2308
  // read not-live over something no merchant ever sees.
@@ -2322,8 +2322,8 @@ var drawbridge_default2 = {
2322
2322
  // and logs), the number-purchase routes deny, and identityDefaults.sms
2323
2323
  // hides the org's SMS settings entirely.
2324
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 }
2325
+ { input: "password", key: "smsSid", credential: "TWILIO_ACCOUNT_SID", label: "Twilio account SID", message: "Twilio console home \u2014 the AC\u2026 identifier on the account dashboard.", redact: true },
2326
+ { input: "password", key: "smsToken", credential: "TWILIO_AUTH_TOKEN", label: "Twilio auth token", message: "Beside the account SID on the Twilio console dashboard.", redact: true }
2327
2327
  ]
2328
2328
  },
2329
2329
  // NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
@@ -2913,8 +2913,8 @@ var klaviyo_default2 = {
2913
2913
  // `requires` that names the same variables.
2914
2914
  provider: {
2915
2915
  fields: [
2916
- { input: "text", key: "clientId", credential: "KLAVIYO_OAUTH_CLIENT_ID", label: "Client ID", required: true },
2917
- { input: "password", key: "clientSecret", credential: "KLAVIYO_OAUTH_CLIENT_SECRET", label: "Client secret", redact: true, required: true }
2916
+ { input: "text", key: "clientId", credential: "KLAVIYO_OAUTH_CLIENT_ID", label: "Client ID", message: "From our app in the Klaviyo developer portal (developers.klaviyo.com).", required: true },
2917
+ { input: "password", key: "clientSecret", credential: "KLAVIYO_OAUTH_CLIENT_SECRET", label: "Client secret", message: "Beside the Client ID on the Klaviyo app.", redact: true, required: true }
2918
2918
  ]
2919
2919
  },
2920
2920
  requires: [
@@ -3259,8 +3259,8 @@ var mailchimp_default2 = {
3259
3259
  // same variables.
3260
3260
  provider: {
3261
3261
  fields: [
3262
- { input: "text", key: "clientId", credential: "MAILCHIMP_OAUTH_CLIENT_ID", label: "Client ID", required: true },
3263
- { input: "password", key: "clientSecret", credential: "MAILCHIMP_OAUTH_CLIENT_SECRET", label: "Client secret", redact: true, required: true }
3262
+ { input: "text", key: "clientId", credential: "MAILCHIMP_OAUTH_CLIENT_ID", label: "Client ID", message: "From our registered app \u2014 Mailchimp account, Extras, Registered apps.", required: true },
3263
+ { input: "password", key: "clientSecret", credential: "MAILCHIMP_OAUTH_CLIENT_SECRET", label: "Client secret", message: "Beside the Client ID on the registered app.", redact: true, required: true }
3264
3264
  ]
3265
3265
  },
3266
3266
  // The OAuth client this deployment registered. Without both, the vendor drops
@@ -4194,24 +4194,32 @@ var shopify_default2 = {
4194
4194
  if (partner.partnerToken && partner.partnerOrgId && partner.partnerAppId && (source == null ? void 0 : source.id) && ((_b = shopify.partner) == null ? void 0 : _b.getUsageChargeEvents)) {
4195
4195
  const accruals = await shopify.partner.getUsageChargeEvents({
4196
4196
  appId: "gid://shopify/App/" + partner.partnerAppId,
4197
- first: 1,
4197
+ first: 5,
4198
4198
  occurredAtMin: new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString(),
4199
4199
  organizationId: partner.partnerOrgId,
4200
4200
  partnerToken: partner.partnerToken,
4201
4201
  shopId: "gid://shopify/Shop/" + source.id
4202
4202
  });
4203
- if (accruals.length) {
4204
- metered = String(accruals[0].chargeId || accruals[0].id);
4205
- } else {
4206
- const unaccrued = await read.get({
4203
+ const accrued = accruals.find((event) => Number(event.usageQuantity) > 0);
4204
+ if (accrued) {
4205
+ metered = String(accrued.chargeId || accrued.id);
4206
+ writes.push({
4207
4207
  collection: "order",
4208
+ data: {
4209
+ $set: { "billed.receipt": metered }
4210
+ },
4211
+ // Top-level, not an option — the controller takes
4212
+ // `multiple` beside `options`, and the shell
4213
+ // forwards it as such.
4214
+ multiple: true,
4215
+ operation: "update",
4208
4216
  query: {
4209
- "billed.date": { $lt: new Date(Date.now() - 6 * 60 * 60 * 1e3) },
4217
+ "billed.date": { $gte: new Date(accrued.occurredAt || 0) },
4218
+ "billed.receipt": { $in: [null] },
4210
4219
  organization: workflow.organization,
4211
4220
  "provider.slug": "shopify"
4212
4221
  }
4213
4222
  });
4214
- if (unaccrued) metered = null;
4215
4223
  }
4216
4224
  }
4217
4225
  } catch (probeError) {
@@ -4433,10 +4441,10 @@ var shopify_default2 = {
4433
4441
  // never go live from the admin screen.
4434
4442
  provider: {
4435
4443
  fields: [
4436
- { input: "text", key: "apiKey", credential: "SHOPIFY_API_KEY", label: "API key", required: true },
4437
- { input: "password", key: "apiSecret", credential: "SHOPIFY_API_SECRET", label: "API secret", redact: true, required: true },
4438
- { input: "text", key: "appHandle", credential: "SHOPIFY_APP_HANDLE", label: "App handle", required: true },
4439
- { input: "text", key: "listingUrl", credential: "SHOPIFY_APP_LISTING_URL", label: "App listing URL", required: true },
4444
+ { input: "text", key: "apiKey", credential: "SHOPIFY_API_KEY", label: "API key", message: "The app's Client ID \u2014 Dev Dashboard, the app, Settings.", required: true },
4445
+ { input: "password", key: "apiSecret", credential: "SHOPIFY_API_SECRET", label: "API secret", message: "The app's Client secret, beside the Client ID in the app's settings.", redact: true, required: true },
4446
+ { input: "text", key: "appHandle", credential: "SHOPIFY_APP_HANDLE", label: "App handle", message: "The app's URL handle (for example drawbridge-development), from the app's configuration.", required: true },
4447
+ { input: "text", key: "listingUrl", credential: "SHOPIFY_APP_LISTING_URL", label: "App listing URL", message: 'Where "View on the Shopify App Store" sends merchants \u2014 the public listing, or the Dev Dashboard install link for the dev app.', required: true },
4440
4448
  // The usage meter's event handle, when the plan config's meter ever
4441
4449
  // changes: stored value overrides the built-in default
4442
4450
  // (events.order.handle) at the mint point, and `format : 'slug'`
@@ -4444,15 +4452,15 @@ var shopify_default2 = {
4444
4452
  // and classification against it is case-sensitive. Optional: empty
4445
4453
  // means the default, and the sender still refuses any handle that
4446
4454
  // disagrees with the transaction it minted.
4447
- { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true },
4455
+ { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", message: "Must match the usage meter's Handle field on the plan exactly \u2014 lowercase and hyphens. Leave empty to use drawbridge-orders.", required: false, setting: true },
4448
4456
  // The Partner API credential set — the ONE place meter billing is
4449
4457
  // observable (CHARGE_USAGE accrual events). Org id and numeric app
4450
4458
  // id come from the Partner Dashboard; the token from Settings →
4451
4459
  // Partner API clients (Manage-apps permission). All optional: the
4452
4460
  // 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 }
4461
+ { input: "text", key: "partnerOrgId", label: "Partner organization ID", message: "The number in the Partner Dashboard URL \u2014 partners.shopify.com/<this number>/\u2026 \u2014 NOT the admin organization_id from the app listing link.", required: false, setting: true },
4462
+ { input: "text", key: "partnerAppId", label: "Partner app ID (numeric)", message: "The numeric id in the app's Partner Dashboard URL \u2014 not the Client ID.", required: false, setting: true },
4463
+ { input: "password", key: "partnerToken", label: "Partner API token", message: "Partner Dashboard, Settings, Partner API clients \u2014 create one with Manage apps and paste the prtapi_ token.", redact: true, required: false, setting: true }
4456
4464
  ]
4457
4465
  },
4458
4466
  // A pre-launch integration: it only surfaces once the App Store listing
@@ -1193,8 +1193,8 @@ var attentive = {
1193
1193
  // keep the stored value. `required` drives the live check.
1194
1194
  provider : {
1195
1195
  fields : [
1196
- { input : 'text', key : 'clientId', credential : 'ATTENTIVE_OAUTH_CLIENT_ID', label : 'Client ID', required : true },
1197
- { input : 'password', key : 'clientSecret', credential : 'ATTENTIVE_OAUTH_CLIENT_SECRET', label : 'Client secret', redact : true, required : true }
1196
+ { input : 'text', key : 'clientId', credential : 'ATTENTIVE_OAUTH_CLIENT_ID', label : 'Client ID', message : 'From our app\'s registration in the Attentive developer portal.', required : true },
1197
+ { input : 'password', key : 'clientSecret', credential : 'ATTENTIVE_OAUTH_CLIENT_SECRET', label : 'Client secret', message : 'Beside the Client ID in the Attentive developer portal.', redact : true, required : true }
1198
1198
  ]
1199
1199
  },
1200
1200
  requires : [
@@ -2404,7 +2404,7 @@ var drawbridge = {
2404
2404
  provider : {
2405
2405
  fields : [
2406
2406
  { input : 'email', key : 'accountSender', credential : 'SENDGRID_FROM_ADDRESS', label : 'Account sender', message : 'Verification codes and security alerts send from here.', required : true },
2407
- { input : 'password', key : 'apiKey', credential : 'SENDGRID_API_KEY', label : 'SendGrid API key', redact : true, required : true },
2407
+ { input : 'password', key : 'apiKey', credential : 'SENDGRID_API_KEY', label : 'SendGrid API key', message : 'SendGrid dashboard, Settings, API Keys — full access for sends.', redact : true, required : true },
2408
2408
  // NOT required. The CRM sync is best-effort internal tooling and no-ops
2409
2409
  // without a token — requiring it would make the whole drawbridge provider
2410
2410
  // read not-live over something no merchant ever sees.
@@ -2424,8 +2424,8 @@ var drawbridge = {
2424
2424
  // and logs), the number-purchase routes deny, and identityDefaults.sms
2425
2425
  // hides the org's SMS settings entirely.
2426
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 }
2427
+ { input : 'password', key : 'smsSid', credential : 'TWILIO_ACCOUNT_SID', label : 'Twilio account SID', message : 'Twilio console home — the AC… identifier on the account dashboard.', redact : true },
2428
+ { input : 'password', key : 'smsToken', credential : 'TWILIO_AUTH_TOKEN', label : 'Twilio auth token', message : 'Beside the account SID on the Twilio console dashboard.', redact : true }
2429
2429
  ]
2430
2430
  },
2431
2431
  // NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
@@ -3179,8 +3179,8 @@ var klaviyo = {
3179
3179
  // `requires` that names the same variables.
3180
3180
  provider : {
3181
3181
  fields : [
3182
- { input : 'text', key : 'clientId', credential : 'KLAVIYO_OAUTH_CLIENT_ID', label : 'Client ID', required : true },
3183
- { input : 'password', key : 'clientSecret', credential : 'KLAVIYO_OAUTH_CLIENT_SECRET', label : 'Client secret', redact : true, required : true }
3182
+ { input : 'text', key : 'clientId', credential : 'KLAVIYO_OAUTH_CLIENT_ID', label : 'Client ID', message : 'From our app in the Klaviyo developer portal (developers.klaviyo.com).', required : true },
3183
+ { input : 'password', key : 'clientSecret', credential : 'KLAVIYO_OAUTH_CLIENT_SECRET', label : 'Client secret', message : 'Beside the Client ID on the Klaviyo app.', redact : true, required : true }
3184
3184
  ]
3185
3185
  },
3186
3186
  requires : [
@@ -3643,8 +3643,8 @@ var mailchimp = {
3643
3643
  // same variables.
3644
3644
  provider : {
3645
3645
  fields : [
3646
- { input : 'text', key : 'clientId', credential : 'MAILCHIMP_OAUTH_CLIENT_ID', label : 'Client ID', required : true },
3647
- { input : 'password', key : 'clientSecret', credential : 'MAILCHIMP_OAUTH_CLIENT_SECRET', label : 'Client secret', redact : true, required : true }
3646
+ { input : 'text', key : 'clientId', credential : 'MAILCHIMP_OAUTH_CLIENT_ID', label : 'Client ID', message : 'From our registered app — Mailchimp account, Extras, Registered apps.', required : true },
3647
+ { input : 'password', key : 'clientSecret', credential : 'MAILCHIMP_OAUTH_CLIENT_SECRET', label : 'Client secret', message : 'Beside the Client ID on the registered app.', redact : true, required : true }
3648
3648
  ]
3649
3649
  },
3650
3650
  // The OAuth client this deployment registered. Without both, the vendor drops
@@ -4983,35 +4983,60 @@ var shopify = {
4983
4983
 
4984
4984
  const accruals = await shopify.partner.getUsageChargeEvents({
4985
4985
  appId : 'gid://shopify/App/' + partner.partnerAppId,
4986
- first : 1,
4986
+ first : 5,
4987
4987
  occurredAtMin : new Date( Date.now() - 30 * 24 * 60 * 60 * 1000 ).toISOString(),
4988
4988
  organizationId : partner.partnerOrgId,
4989
4989
  partnerToken : partner.partnerToken,
4990
4990
  shopId : 'gid://shopify/Shop/' + source.id
4991
4991
  });
4992
4992
 
4993
- if( accruals.length ){
4993
+ // A usage ROW exists per subscription even before anything
4994
+ // accrues (measured: a pre-meter approval shows qty 0), so
4995
+ // the row is not the proof — a POSITIVE usageQuantity is.
4996
+ const accrued = accruals.find( ( event ) => Number( event.usageQuantity ) > 0 );
4994
4997
 
4995
- metered = String( accruals[ 0 ].chargeId || accruals[ 0 ].id );
4998
+ if( accrued ){
4996
4999
 
4997
- } else {
5000
+ metered = String( accrued.chargeId || accrued.id );
4998
5001
 
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({
5002
+ // THE BUCKET STAMPS ITS ORDERS. Partner accrual rows
5003
+ // are per-subscription, not per-order, so the charge id
5004
+ // is the invoice bucket every order billed since that
5005
+ // subscription's approval rolled into — written onto
5006
+ // billed.receipt (many-update; $in [null] matches both
5007
+ // null and never-stamped). Orders billed before the
5008
+ // window belong to earlier, possibly unbillable
5009
+ // approvals and honestly stay unstamped.
5010
+ writes.push({
5003
5011
  collection : 'order',
5012
+ data : {
5013
+ $set : { 'billed.receipt' : metered }
5014
+ },
5015
+ // Top-level, not an option — the controller takes
5016
+ // `multiple` beside `options`, and the shell
5017
+ // forwards it as such.
5018
+ multiple : true,
5019
+ operation : 'update',
5004
5020
  query : {
5005
- 'billed.date' : { $lt : new Date( Date.now() - 6 * 60 * 60 * 1000 ) },
5021
+ 'billed.date' : { $gte : new Date( accrued.occurredAt || 0 ) },
5022
+ 'billed.receipt' : { $in : [ null ] },
5006
5023
  organization : workflow.organization,
5007
5024
  'provider.slug' : 'shopify'
5008
5025
  }
5009
5026
  });
5010
5027
 
5011
- if( unaccrued ) metered = null;
5012
-
5013
5028
  }
5014
5029
 
5030
+ // NO negative verdict from an empty ledger: measured
5031
+ // 2026-09-04, CHARGE_USAGE rows only materialize at
5032
+ // BILLING moments (cycle end, cancellation/replacement)
5033
+ // — usage accrues invisibly between them, so a
5034
+ // first-cycle store with billed orders and no row is
5035
+ // the NORMAL state, not an unmetered one. The honest
5036
+ // negative is a boundary reconciliation (a charge-out
5037
+ // row whose quantity falls short of the window's billed
5038
+ // sum) — until that exists, absence stays a no-verdict.
5039
+
5015
5040
  }
5016
5041
 
5017
5042
  } catch ( probeError ) {
@@ -5310,10 +5335,10 @@ var shopify = {
5310
5335
  // never go live from the admin screen.
5311
5336
  provider : {
5312
5337
  fields : [
5313
- { input : 'text', key : 'apiKey', credential : 'SHOPIFY_API_KEY', label : 'API key', required : true },
5314
- { input : 'password', key : 'apiSecret', credential : 'SHOPIFY_API_SECRET', label : 'API secret', redact : true, required : true },
5315
- { input : 'text', key : 'appHandle', credential : 'SHOPIFY_APP_HANDLE', label : 'App handle', required : true },
5316
- { input : 'text', key : 'listingUrl', credential : 'SHOPIFY_APP_LISTING_URL', label : 'App listing URL', required : true },
5338
+ { input : 'text', key : 'apiKey', credential : 'SHOPIFY_API_KEY', label : 'API key', message : 'The app\'s Client ID — Dev Dashboard, the app, Settings.', required : true },
5339
+ { input : 'password', key : 'apiSecret', credential : 'SHOPIFY_API_SECRET', label : 'API secret', message : 'The app\'s Client secret, beside the Client ID in the app\'s settings.', redact : true, required : true },
5340
+ { input : 'text', key : 'appHandle', credential : 'SHOPIFY_APP_HANDLE', label : 'App handle', message : 'The app\'s URL handle (for example drawbridge-development), from the app\'s configuration.', required : true },
5341
+ { input : 'text', key : 'listingUrl', credential : 'SHOPIFY_APP_LISTING_URL', label : 'App listing URL', message : 'Where "View on the Shopify App Store" sends merchants — the public listing, or the Dev Dashboard install link for the dev app.', required : true },
5317
5342
  // The usage meter's event handle, when the plan config's meter ever
5318
5343
  // changes: stored value overrides the built-in default
5319
5344
  // (events.order.handle) at the mint point, and `format : 'slug'`
@@ -5321,15 +5346,15 @@ var shopify = {
5321
5346
  // and classification against it is case-sensitive. Optional: empty
5322
5347
  // means the default, and the sender still refuses any handle that
5323
5348
  // disagrees with the transaction it minted.
5324
- { format : 'slug', input : 'text', key : 'orderEventHandle', label : 'Order event handle', required : false, setting : true },
5349
+ { format : 'slug', input : 'text', key : 'orderEventHandle', label : 'Order event handle', message : 'Must match the usage meter\'s Handle field on the plan exactly — lowercase and hyphens. Leave empty to use drawbridge-orders.', required : false, setting : true },
5325
5350
  // The Partner API credential set — the ONE place meter billing is
5326
5351
  // observable (CHARGE_USAGE accrual events). Org id and numeric app
5327
5352
  // id come from the Partner Dashboard; the token from Settings →
5328
5353
  // Partner API clients (Manage-apps permission). All optional: the
5329
5354
  // 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 }
5355
+ { input : 'text', key : 'partnerOrgId', label : 'Partner organization ID', message : 'The number in the Partner Dashboard URL — partners.shopify.com/<this number>/… — NOT the admin organization_id from the app listing link.', required : false, setting : true },
5356
+ { input : 'text', key : 'partnerAppId', label : 'Partner app ID (numeric)', message : 'The numeric id in the app\'s Partner Dashboard URL — not the Client ID.', required : false, setting : true },
5357
+ { input : 'password', key : 'partnerToken', label : 'Partner API token', message : 'Partner Dashboard, Settings, Partner API clients — create one with Manage apps and paste the prtapi_ token.', redact : true, required : false, setting : true }
5333
5358
  ]
5334
5359
  },
5335
5360
  // A pre-launch integration: it only surfaces once the App Store listing
@@ -1193,8 +1193,8 @@ var attentive = {
1193
1193
  // keep the stored value. `required` drives the live check.
1194
1194
  provider : {
1195
1195
  fields : [
1196
- { input : 'text', key : 'clientId', credential : 'ATTENTIVE_OAUTH_CLIENT_ID', label : 'Client ID', required : true },
1197
- { input : 'password', key : 'clientSecret', credential : 'ATTENTIVE_OAUTH_CLIENT_SECRET', label : 'Client secret', redact : true, required : true }
1196
+ { input : 'text', key : 'clientId', credential : 'ATTENTIVE_OAUTH_CLIENT_ID', label : 'Client ID', message : 'From our app\'s registration in the Attentive developer portal.', required : true },
1197
+ { input : 'password', key : 'clientSecret', credential : 'ATTENTIVE_OAUTH_CLIENT_SECRET', label : 'Client secret', message : 'Beside the Client ID in the Attentive developer portal.', redact : true, required : true }
1198
1198
  ]
1199
1199
  },
1200
1200
  requires : [
@@ -2404,7 +2404,7 @@ var drawbridge = {
2404
2404
  provider : {
2405
2405
  fields : [
2406
2406
  { input : 'email', key : 'accountSender', credential : 'SENDGRID_FROM_ADDRESS', label : 'Account sender', message : 'Verification codes and security alerts send from here.', required : true },
2407
- { input : 'password', key : 'apiKey', credential : 'SENDGRID_API_KEY', label : 'SendGrid API key', redact : true, required : true },
2407
+ { input : 'password', key : 'apiKey', credential : 'SENDGRID_API_KEY', label : 'SendGrid API key', message : 'SendGrid dashboard, Settings, API Keys — full access for sends.', redact : true, required : true },
2408
2408
  // NOT required. The CRM sync is best-effort internal tooling and no-ops
2409
2409
  // without a token — requiring it would make the whole drawbridge provider
2410
2410
  // read not-live over something no merchant ever sees.
@@ -2424,8 +2424,8 @@ var drawbridge = {
2424
2424
  // and logs), the number-purchase routes deny, and identityDefaults.sms
2425
2425
  // hides the org's SMS settings entirely.
2426
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 }
2427
+ { input : 'password', key : 'smsSid', credential : 'TWILIO_ACCOUNT_SID', label : 'Twilio account SID', message : 'Twilio console home — the AC… identifier on the account dashboard.', redact : true },
2428
+ { input : 'password', key : 'smsToken', credential : 'TWILIO_AUTH_TOKEN', label : 'Twilio auth token', message : 'Beside the account SID on the Twilio console dashboard.', redact : true }
2429
2429
  ]
2430
2430
  },
2431
2431
  // NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
@@ -3179,8 +3179,8 @@ var klaviyo = {
3179
3179
  // `requires` that names the same variables.
3180
3180
  provider : {
3181
3181
  fields : [
3182
- { input : 'text', key : 'clientId', credential : 'KLAVIYO_OAUTH_CLIENT_ID', label : 'Client ID', required : true },
3183
- { input : 'password', key : 'clientSecret', credential : 'KLAVIYO_OAUTH_CLIENT_SECRET', label : 'Client secret', redact : true, required : true }
3182
+ { input : 'text', key : 'clientId', credential : 'KLAVIYO_OAUTH_CLIENT_ID', label : 'Client ID', message : 'From our app in the Klaviyo developer portal (developers.klaviyo.com).', required : true },
3183
+ { input : 'password', key : 'clientSecret', credential : 'KLAVIYO_OAUTH_CLIENT_SECRET', label : 'Client secret', message : 'Beside the Client ID on the Klaviyo app.', redact : true, required : true }
3184
3184
  ]
3185
3185
  },
3186
3186
  requires : [
@@ -3643,8 +3643,8 @@ var mailchimp = {
3643
3643
  // same variables.
3644
3644
  provider : {
3645
3645
  fields : [
3646
- { input : 'text', key : 'clientId', credential : 'MAILCHIMP_OAUTH_CLIENT_ID', label : 'Client ID', required : true },
3647
- { input : 'password', key : 'clientSecret', credential : 'MAILCHIMP_OAUTH_CLIENT_SECRET', label : 'Client secret', redact : true, required : true }
3646
+ { input : 'text', key : 'clientId', credential : 'MAILCHIMP_OAUTH_CLIENT_ID', label : 'Client ID', message : 'From our registered app — Mailchimp account, Extras, Registered apps.', required : true },
3647
+ { input : 'password', key : 'clientSecret', credential : 'MAILCHIMP_OAUTH_CLIENT_SECRET', label : 'Client secret', message : 'Beside the Client ID on the registered app.', redact : true, required : true }
3648
3648
  ]
3649
3649
  },
3650
3650
  // The OAuth client this deployment registered. Without both, the vendor drops
@@ -4983,35 +4983,60 @@ var shopify = {
4983
4983
 
4984
4984
  const accruals = await shopify.partner.getUsageChargeEvents({
4985
4985
  appId : 'gid://shopify/App/' + partner.partnerAppId,
4986
- first : 1,
4986
+ first : 5,
4987
4987
  occurredAtMin : new Date( Date.now() - 30 * 24 * 60 * 60 * 1000 ).toISOString(),
4988
4988
  organizationId : partner.partnerOrgId,
4989
4989
  partnerToken : partner.partnerToken,
4990
4990
  shopId : 'gid://shopify/Shop/' + source.id
4991
4991
  });
4992
4992
 
4993
- if( accruals.length ){
4993
+ // A usage ROW exists per subscription even before anything
4994
+ // accrues (measured: a pre-meter approval shows qty 0), so
4995
+ // the row is not the proof — a POSITIVE usageQuantity is.
4996
+ const accrued = accruals.find( ( event ) => Number( event.usageQuantity ) > 0 );
4994
4997
 
4995
- metered = String( accruals[ 0 ].chargeId || accruals[ 0 ].id );
4998
+ if( accrued ){
4996
4999
 
4997
- } else {
5000
+ metered = String( accrued.chargeId || accrued.id );
4998
5001
 
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({
5002
+ // THE BUCKET STAMPS ITS ORDERS. Partner accrual rows
5003
+ // are per-subscription, not per-order, so the charge id
5004
+ // is the invoice bucket every order billed since that
5005
+ // subscription's approval rolled into — written onto
5006
+ // billed.receipt (many-update; $in [null] matches both
5007
+ // null and never-stamped). Orders billed before the
5008
+ // window belong to earlier, possibly unbillable
5009
+ // approvals and honestly stay unstamped.
5010
+ writes.push({
5003
5011
  collection : 'order',
5012
+ data : {
5013
+ $set : { 'billed.receipt' : metered }
5014
+ },
5015
+ // Top-level, not an option — the controller takes
5016
+ // `multiple` beside `options`, and the shell
5017
+ // forwards it as such.
5018
+ multiple : true,
5019
+ operation : 'update',
5004
5020
  query : {
5005
- 'billed.date' : { $lt : new Date( Date.now() - 6 * 60 * 60 * 1000 ) },
5021
+ 'billed.date' : { $gte : new Date( accrued.occurredAt || 0 ) },
5022
+ 'billed.receipt' : { $in : [ null ] },
5006
5023
  organization : workflow.organization,
5007
5024
  'provider.slug' : 'shopify'
5008
5025
  }
5009
5026
  });
5010
5027
 
5011
- if( unaccrued ) metered = null;
5012
-
5013
5028
  }
5014
5029
 
5030
+ // NO negative verdict from an empty ledger: measured
5031
+ // 2026-09-04, CHARGE_USAGE rows only materialize at
5032
+ // BILLING moments (cycle end, cancellation/replacement)
5033
+ // — usage accrues invisibly between them, so a
5034
+ // first-cycle store with billed orders and no row is
5035
+ // the NORMAL state, not an unmetered one. The honest
5036
+ // negative is a boundary reconciliation (a charge-out
5037
+ // row whose quantity falls short of the window's billed
5038
+ // sum) — until that exists, absence stays a no-verdict.
5039
+
5015
5040
  }
5016
5041
 
5017
5042
  } catch ( probeError ) {
@@ -5310,10 +5335,10 @@ var shopify = {
5310
5335
  // never go live from the admin screen.
5311
5336
  provider : {
5312
5337
  fields : [
5313
- { input : 'text', key : 'apiKey', credential : 'SHOPIFY_API_KEY', label : 'API key', required : true },
5314
- { input : 'password', key : 'apiSecret', credential : 'SHOPIFY_API_SECRET', label : 'API secret', redact : true, required : true },
5315
- { input : 'text', key : 'appHandle', credential : 'SHOPIFY_APP_HANDLE', label : 'App handle', required : true },
5316
- { input : 'text', key : 'listingUrl', credential : 'SHOPIFY_APP_LISTING_URL', label : 'App listing URL', required : true },
5338
+ { input : 'text', key : 'apiKey', credential : 'SHOPIFY_API_KEY', label : 'API key', message : 'The app\'s Client ID — Dev Dashboard, the app, Settings.', required : true },
5339
+ { input : 'password', key : 'apiSecret', credential : 'SHOPIFY_API_SECRET', label : 'API secret', message : 'The app\'s Client secret, beside the Client ID in the app\'s settings.', redact : true, required : true },
5340
+ { input : 'text', key : 'appHandle', credential : 'SHOPIFY_APP_HANDLE', label : 'App handle', message : 'The app\'s URL handle (for example drawbridge-development), from the app\'s configuration.', required : true },
5341
+ { input : 'text', key : 'listingUrl', credential : 'SHOPIFY_APP_LISTING_URL', label : 'App listing URL', message : 'Where "View on the Shopify App Store" sends merchants — the public listing, or the Dev Dashboard install link for the dev app.', required : true },
5317
5342
  // The usage meter's event handle, when the plan config's meter ever
5318
5343
  // changes: stored value overrides the built-in default
5319
5344
  // (events.order.handle) at the mint point, and `format : 'slug'`
@@ -5321,15 +5346,15 @@ var shopify = {
5321
5346
  // and classification against it is case-sensitive. Optional: empty
5322
5347
  // means the default, and the sender still refuses any handle that
5323
5348
  // disagrees with the transaction it minted.
5324
- { format : 'slug', input : 'text', key : 'orderEventHandle', label : 'Order event handle', required : false, setting : true },
5349
+ { format : 'slug', input : 'text', key : 'orderEventHandle', label : 'Order event handle', message : 'Must match the usage meter\'s Handle field on the plan exactly — lowercase and hyphens. Leave empty to use drawbridge-orders.', required : false, setting : true },
5325
5350
  // The Partner API credential set — the ONE place meter billing is
5326
5351
  // observable (CHARGE_USAGE accrual events). Org id and numeric app
5327
5352
  // id come from the Partner Dashboard; the token from Settings →
5328
5353
  // Partner API clients (Manage-apps permission). All optional: the
5329
5354
  // 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 }
5355
+ { input : 'text', key : 'partnerOrgId', label : 'Partner organization ID', message : 'The number in the Partner Dashboard URL — partners.shopify.com/<this number>/… — NOT the admin organization_id from the app listing link.', required : false, setting : true },
5356
+ { input : 'text', key : 'partnerAppId', label : 'Partner app ID (numeric)', message : 'The numeric id in the app\'s Partner Dashboard URL — not the Client ID.', required : false, setting : true },
5357
+ { input : 'password', key : 'partnerToken', label : 'Partner API token', message : 'Partner Dashboard, Settings, Partner API clients — create one with Manage apps and paste the prtapi_ token.', redact : true, required : false, setting : true }
5333
5358
  ]
5334
5359
  },
5335
5360
  // A pre-launch integration: it only surfaces once the App Store listing
@@ -786,8 +786,8 @@ var attentive_default2 = {
786
786
  // keep the stored value. `required` drives the live check.
787
787
  provider: {
788
788
  fields: [
789
- { input: "text", key: "clientId", credential: "ATTENTIVE_OAUTH_CLIENT_ID", label: "Client ID", required: true },
790
- { input: "password", key: "clientSecret", credential: "ATTENTIVE_OAUTH_CLIENT_SECRET", label: "Client secret", redact: true, required: true }
789
+ { input: "text", key: "clientId", credential: "ATTENTIVE_OAUTH_CLIENT_ID", label: "Client ID", message: "From our app's registration in the Attentive developer portal.", required: true },
790
+ { input: "password", key: "clientSecret", credential: "ATTENTIVE_OAUTH_CLIENT_SECRET", label: "Client secret", message: "Beside the Client ID in the Attentive developer portal.", redact: true, required: true }
791
791
  ]
792
792
  },
793
793
  requires: [
@@ -2228,7 +2228,7 @@ var drawbridge_default2 = {
2228
2228
  provider: {
2229
2229
  fields: [
2230
2230
  { input: "email", key: "accountSender", credential: "SENDGRID_FROM_ADDRESS", label: "Account sender", message: "Verification codes and security alerts send from here.", required: true },
2231
- { input: "password", key: "apiKey", credential: "SENDGRID_API_KEY", label: "SendGrid API key", redact: true, required: true },
2231
+ { input: "password", key: "apiKey", credential: "SENDGRID_API_KEY", label: "SendGrid API key", message: "SendGrid dashboard, Settings, API Keys \u2014 full access for sends.", redact: true, required: true },
2232
2232
  // NOT required. The CRM sync is best-effort internal tooling and no-ops
2233
2233
  // without a token — requiring it would make the whole drawbridge provider
2234
2234
  // read not-live over something no merchant ever sees.
@@ -2248,8 +2248,8 @@ var drawbridge_default2 = {
2248
2248
  // and logs), the number-purchase routes deny, and identityDefaults.sms
2249
2249
  // hides the org's SMS settings entirely.
2250
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 }
2251
+ { input: "password", key: "smsSid", credential: "TWILIO_ACCOUNT_SID", label: "Twilio account SID", message: "Twilio console home \u2014 the AC\u2026 identifier on the account dashboard.", redact: true },
2252
+ { input: "password", key: "smsToken", credential: "TWILIO_AUTH_TOKEN", label: "Twilio auth token", message: "Beside the account SID on the Twilio console dashboard.", redact: true }
2253
2253
  ]
2254
2254
  },
2255
2255
  // NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
@@ -2839,8 +2839,8 @@ var klaviyo_default2 = {
2839
2839
  // `requires` that names the same variables.
2840
2840
  provider: {
2841
2841
  fields: [
2842
- { input: "text", key: "clientId", credential: "KLAVIYO_OAUTH_CLIENT_ID", label: "Client ID", required: true },
2843
- { input: "password", key: "clientSecret", credential: "KLAVIYO_OAUTH_CLIENT_SECRET", label: "Client secret", redact: true, required: true }
2842
+ { input: "text", key: "clientId", credential: "KLAVIYO_OAUTH_CLIENT_ID", label: "Client ID", message: "From our app in the Klaviyo developer portal (developers.klaviyo.com).", required: true },
2843
+ { input: "password", key: "clientSecret", credential: "KLAVIYO_OAUTH_CLIENT_SECRET", label: "Client secret", message: "Beside the Client ID on the Klaviyo app.", redact: true, required: true }
2844
2844
  ]
2845
2845
  },
2846
2846
  requires: [
@@ -3185,8 +3185,8 @@ var mailchimp_default2 = {
3185
3185
  // same variables.
3186
3186
  provider: {
3187
3187
  fields: [
3188
- { input: "text", key: "clientId", credential: "MAILCHIMP_OAUTH_CLIENT_ID", label: "Client ID", required: true },
3189
- { input: "password", key: "clientSecret", credential: "MAILCHIMP_OAUTH_CLIENT_SECRET", label: "Client secret", redact: true, required: true }
3188
+ { input: "text", key: "clientId", credential: "MAILCHIMP_OAUTH_CLIENT_ID", label: "Client ID", message: "From our registered app \u2014 Mailchimp account, Extras, Registered apps.", required: true },
3189
+ { input: "password", key: "clientSecret", credential: "MAILCHIMP_OAUTH_CLIENT_SECRET", label: "Client secret", message: "Beside the Client ID on the registered app.", redact: true, required: true }
3190
3190
  ]
3191
3191
  },
3192
3192
  // The OAuth client this deployment registered. Without both, the vendor drops
@@ -4120,24 +4120,32 @@ var shopify_default2 = {
4120
4120
  if (partner.partnerToken && partner.partnerOrgId && partner.partnerAppId && (source == null ? void 0 : source.id) && ((_b = shopify.partner) == null ? void 0 : _b.getUsageChargeEvents)) {
4121
4121
  const accruals = await shopify.partner.getUsageChargeEvents({
4122
4122
  appId: "gid://shopify/App/" + partner.partnerAppId,
4123
- first: 1,
4123
+ first: 5,
4124
4124
  occurredAtMin: new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString(),
4125
4125
  organizationId: partner.partnerOrgId,
4126
4126
  partnerToken: partner.partnerToken,
4127
4127
  shopId: "gid://shopify/Shop/" + source.id
4128
4128
  });
4129
- if (accruals.length) {
4130
- metered = String(accruals[0].chargeId || accruals[0].id);
4131
- } else {
4132
- const unaccrued = await read.get({
4129
+ const accrued = accruals.find((event) => Number(event.usageQuantity) > 0);
4130
+ if (accrued) {
4131
+ metered = String(accrued.chargeId || accrued.id);
4132
+ writes.push({
4133
4133
  collection: "order",
4134
+ data: {
4135
+ $set: { "billed.receipt": metered }
4136
+ },
4137
+ // Top-level, not an option — the controller takes
4138
+ // `multiple` beside `options`, and the shell
4139
+ // forwards it as such.
4140
+ multiple: true,
4141
+ operation: "update",
4134
4142
  query: {
4135
- "billed.date": { $lt: new Date(Date.now() - 6 * 60 * 60 * 1e3) },
4143
+ "billed.date": { $gte: new Date(accrued.occurredAt || 0) },
4144
+ "billed.receipt": { $in: [null] },
4136
4145
  organization: workflow.organization,
4137
4146
  "provider.slug": "shopify"
4138
4147
  }
4139
4148
  });
4140
- if (unaccrued) metered = null;
4141
4149
  }
4142
4150
  }
4143
4151
  } catch (probeError) {
@@ -4359,10 +4367,10 @@ var shopify_default2 = {
4359
4367
  // never go live from the admin screen.
4360
4368
  provider: {
4361
4369
  fields: [
4362
- { input: "text", key: "apiKey", credential: "SHOPIFY_API_KEY", label: "API key", required: true },
4363
- { input: "password", key: "apiSecret", credential: "SHOPIFY_API_SECRET", label: "API secret", redact: true, required: true },
4364
- { input: "text", key: "appHandle", credential: "SHOPIFY_APP_HANDLE", label: "App handle", required: true },
4365
- { input: "text", key: "listingUrl", credential: "SHOPIFY_APP_LISTING_URL", label: "App listing URL", required: true },
4370
+ { input: "text", key: "apiKey", credential: "SHOPIFY_API_KEY", label: "API key", message: "The app's Client ID \u2014 Dev Dashboard, the app, Settings.", required: true },
4371
+ { input: "password", key: "apiSecret", credential: "SHOPIFY_API_SECRET", label: "API secret", message: "The app's Client secret, beside the Client ID in the app's settings.", redact: true, required: true },
4372
+ { input: "text", key: "appHandle", credential: "SHOPIFY_APP_HANDLE", label: "App handle", message: "The app's URL handle (for example drawbridge-development), from the app's configuration.", required: true },
4373
+ { input: "text", key: "listingUrl", credential: "SHOPIFY_APP_LISTING_URL", label: "App listing URL", message: 'Where "View on the Shopify App Store" sends merchants \u2014 the public listing, or the Dev Dashboard install link for the dev app.', required: true },
4366
4374
  // The usage meter's event handle, when the plan config's meter ever
4367
4375
  // changes: stored value overrides the built-in default
4368
4376
  // (events.order.handle) at the mint point, and `format : 'slug'`
@@ -4370,15 +4378,15 @@ var shopify_default2 = {
4370
4378
  // and classification against it is case-sensitive. Optional: empty
4371
4379
  // means the default, and the sender still refuses any handle that
4372
4380
  // disagrees with the transaction it minted.
4373
- { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true },
4381
+ { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", message: "Must match the usage meter's Handle field on the plan exactly \u2014 lowercase and hyphens. Leave empty to use drawbridge-orders.", required: false, setting: true },
4374
4382
  // The Partner API credential set — the ONE place meter billing is
4375
4383
  // observable (CHARGE_USAGE accrual events). Org id and numeric app
4376
4384
  // id come from the Partner Dashboard; the token from Settings →
4377
4385
  // Partner API clients (Manage-apps permission). All optional: the
4378
4386
  // 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 }
4387
+ { input: "text", key: "partnerOrgId", label: "Partner organization ID", message: "The number in the Partner Dashboard URL \u2014 partners.shopify.com/<this number>/\u2026 \u2014 NOT the admin organization_id from the app listing link.", required: false, setting: true },
4388
+ { input: "text", key: "partnerAppId", label: "Partner app ID (numeric)", message: "The numeric id in the app's Partner Dashboard URL \u2014 not the Client ID.", required: false, setting: true },
4389
+ { input: "password", key: "partnerToken", label: "Partner API token", message: "Partner Dashboard, Settings, Partner API clients \u2014 create one with Manage apps and paste the prtapi_ token.", redact: true, required: false, setting: true }
4382
4390
  ]
4383
4391
  },
4384
4392
  // A pre-launch integration: it only surfaces once the App Store listing
@@ -766,8 +766,8 @@ var attentive_default2 = {
766
766
  // keep the stored value. `required` drives the live check.
767
767
  provider: {
768
768
  fields: [
769
- { input: "text", key: "clientId", credential: "ATTENTIVE_OAUTH_CLIENT_ID", label: "Client ID", required: true },
770
- { input: "password", key: "clientSecret", credential: "ATTENTIVE_OAUTH_CLIENT_SECRET", label: "Client secret", redact: true, required: true }
769
+ { input: "text", key: "clientId", credential: "ATTENTIVE_OAUTH_CLIENT_ID", label: "Client ID", message: "From our app's registration in the Attentive developer portal.", required: true },
770
+ { input: "password", key: "clientSecret", credential: "ATTENTIVE_OAUTH_CLIENT_SECRET", label: "Client secret", message: "Beside the Client ID in the Attentive developer portal.", redact: true, required: true }
771
771
  ]
772
772
  },
773
773
  requires: [
@@ -2208,7 +2208,7 @@ var drawbridge_default2 = {
2208
2208
  provider: {
2209
2209
  fields: [
2210
2210
  { input: "email", key: "accountSender", credential: "SENDGRID_FROM_ADDRESS", label: "Account sender", message: "Verification codes and security alerts send from here.", required: true },
2211
- { input: "password", key: "apiKey", credential: "SENDGRID_API_KEY", label: "SendGrid API key", redact: true, required: true },
2211
+ { input: "password", key: "apiKey", credential: "SENDGRID_API_KEY", label: "SendGrid API key", message: "SendGrid dashboard, Settings, API Keys \u2014 full access for sends.", redact: true, required: true },
2212
2212
  // NOT required. The CRM sync is best-effort internal tooling and no-ops
2213
2213
  // without a token — requiring it would make the whole drawbridge provider
2214
2214
  // read not-live over something no merchant ever sees.
@@ -2228,8 +2228,8 @@ var drawbridge_default2 = {
2228
2228
  // and logs), the number-purchase routes deny, and identityDefaults.sms
2229
2229
  // hides the org's SMS settings entirely.
2230
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 }
2231
+ { input: "password", key: "smsSid", credential: "TWILIO_ACCOUNT_SID", label: "Twilio account SID", message: "Twilio console home \u2014 the AC\u2026 identifier on the account dashboard.", redact: true },
2232
+ { input: "password", key: "smsToken", credential: "TWILIO_AUTH_TOKEN", label: "Twilio auth token", message: "Beside the account SID on the Twilio console dashboard.", redact: true }
2233
2233
  ]
2234
2234
  },
2235
2235
  // NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
@@ -2819,8 +2819,8 @@ var klaviyo_default2 = {
2819
2819
  // `requires` that names the same variables.
2820
2820
  provider: {
2821
2821
  fields: [
2822
- { input: "text", key: "clientId", credential: "KLAVIYO_OAUTH_CLIENT_ID", label: "Client ID", required: true },
2823
- { input: "password", key: "clientSecret", credential: "KLAVIYO_OAUTH_CLIENT_SECRET", label: "Client secret", redact: true, required: true }
2822
+ { input: "text", key: "clientId", credential: "KLAVIYO_OAUTH_CLIENT_ID", label: "Client ID", message: "From our app in the Klaviyo developer portal (developers.klaviyo.com).", required: true },
2823
+ { input: "password", key: "clientSecret", credential: "KLAVIYO_OAUTH_CLIENT_SECRET", label: "Client secret", message: "Beside the Client ID on the Klaviyo app.", redact: true, required: true }
2824
2824
  ]
2825
2825
  },
2826
2826
  requires: [
@@ -3165,8 +3165,8 @@ var mailchimp_default2 = {
3165
3165
  // same variables.
3166
3166
  provider: {
3167
3167
  fields: [
3168
- { input: "text", key: "clientId", credential: "MAILCHIMP_OAUTH_CLIENT_ID", label: "Client ID", required: true },
3169
- { input: "password", key: "clientSecret", credential: "MAILCHIMP_OAUTH_CLIENT_SECRET", label: "Client secret", redact: true, required: true }
3168
+ { input: "text", key: "clientId", credential: "MAILCHIMP_OAUTH_CLIENT_ID", label: "Client ID", message: "From our registered app \u2014 Mailchimp account, Extras, Registered apps.", required: true },
3169
+ { input: "password", key: "clientSecret", credential: "MAILCHIMP_OAUTH_CLIENT_SECRET", label: "Client secret", message: "Beside the Client ID on the registered app.", redact: true, required: true }
3170
3170
  ]
3171
3171
  },
3172
3172
  // The OAuth client this deployment registered. Without both, the vendor drops
@@ -4119,24 +4119,32 @@ var shopify_default2 = {
4119
4119
  if (partner.partnerToken && partner.partnerOrgId && partner.partnerAppId && (source == null ? void 0 : source.id) && ((_b = shopify.partner) == null ? void 0 : _b.getUsageChargeEvents)) {
4120
4120
  const accruals = await shopify.partner.getUsageChargeEvents({
4121
4121
  appId: "gid://shopify/App/" + partner.partnerAppId,
4122
- first: 1,
4122
+ first: 5,
4123
4123
  occurredAtMin: new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString(),
4124
4124
  organizationId: partner.partnerOrgId,
4125
4125
  partnerToken: partner.partnerToken,
4126
4126
  shopId: "gid://shopify/Shop/" + source.id
4127
4127
  });
4128
- if (accruals.length) {
4129
- metered = String(accruals[0].chargeId || accruals[0].id);
4130
- } else {
4131
- const unaccrued = await read.get({
4128
+ const accrued = accruals.find((event) => Number(event.usageQuantity) > 0);
4129
+ if (accrued) {
4130
+ metered = String(accrued.chargeId || accrued.id);
4131
+ writes.push({
4132
4132
  collection: "order",
4133
+ data: {
4134
+ $set: { "billed.receipt": metered }
4135
+ },
4136
+ // Top-level, not an option — the controller takes
4137
+ // `multiple` beside `options`, and the shell
4138
+ // forwards it as such.
4139
+ multiple: true,
4140
+ operation: "update",
4133
4141
  query: {
4134
- "billed.date": { $lt: new Date(Date.now() - 6 * 60 * 60 * 1e3) },
4142
+ "billed.date": { $gte: new Date(accrued.occurredAt || 0) },
4143
+ "billed.receipt": { $in: [null] },
4135
4144
  organization: workflow.organization,
4136
4145
  "provider.slug": "shopify"
4137
4146
  }
4138
4147
  });
4139
- if (unaccrued) metered = null;
4140
4148
  }
4141
4149
  }
4142
4150
  } catch (probeError) {
@@ -4358,10 +4366,10 @@ var shopify_default2 = {
4358
4366
  // never go live from the admin screen.
4359
4367
  provider: {
4360
4368
  fields: [
4361
- { input: "text", key: "apiKey", credential: "SHOPIFY_API_KEY", label: "API key", required: true },
4362
- { input: "password", key: "apiSecret", credential: "SHOPIFY_API_SECRET", label: "API secret", redact: true, required: true },
4363
- { input: "text", key: "appHandle", credential: "SHOPIFY_APP_HANDLE", label: "App handle", required: true },
4364
- { input: "text", key: "listingUrl", credential: "SHOPIFY_APP_LISTING_URL", label: "App listing URL", required: true },
4369
+ { input: "text", key: "apiKey", credential: "SHOPIFY_API_KEY", label: "API key", message: "The app's Client ID \u2014 Dev Dashboard, the app, Settings.", required: true },
4370
+ { input: "password", key: "apiSecret", credential: "SHOPIFY_API_SECRET", label: "API secret", message: "The app's Client secret, beside the Client ID in the app's settings.", redact: true, required: true },
4371
+ { input: "text", key: "appHandle", credential: "SHOPIFY_APP_HANDLE", label: "App handle", message: "The app's URL handle (for example drawbridge-development), from the app's configuration.", required: true },
4372
+ { input: "text", key: "listingUrl", credential: "SHOPIFY_APP_LISTING_URL", label: "App listing URL", message: 'Where "View on the Shopify App Store" sends merchants \u2014 the public listing, or the Dev Dashboard install link for the dev app.', required: true },
4365
4373
  // The usage meter's event handle, when the plan config's meter ever
4366
4374
  // changes: stored value overrides the built-in default
4367
4375
  // (events.order.handle) at the mint point, and `format : 'slug'`
@@ -4369,15 +4377,15 @@ var shopify_default2 = {
4369
4377
  // and classification against it is case-sensitive. Optional: empty
4370
4378
  // means the default, and the sender still refuses any handle that
4371
4379
  // disagrees with the transaction it minted.
4372
- { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true },
4380
+ { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", message: "Must match the usage meter's Handle field on the plan exactly \u2014 lowercase and hyphens. Leave empty to use drawbridge-orders.", required: false, setting: true },
4373
4381
  // The Partner API credential set — the ONE place meter billing is
4374
4382
  // observable (CHARGE_USAGE accrual events). Org id and numeric app
4375
4383
  // id come from the Partner Dashboard; the token from Settings →
4376
4384
  // Partner API clients (Manage-apps permission). All optional: the
4377
4385
  // 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 }
4386
+ { input: "text", key: "partnerOrgId", label: "Partner organization ID", message: "The number in the Partner Dashboard URL \u2014 partners.shopify.com/<this number>/\u2026 \u2014 NOT the admin organization_id from the app listing link.", required: false, setting: true },
4387
+ { input: "text", key: "partnerAppId", label: "Partner app ID (numeric)", message: "The numeric id in the app's Partner Dashboard URL \u2014 not the Client ID.", required: false, setting: true },
4388
+ { input: "password", key: "partnerToken", label: "Partner API token", message: "Partner Dashboard, Settings, Partner API clients \u2014 create one with Manage apps and paste the prtapi_ token.", redact: true, required: false, setting: true }
4381
4389
  ]
4382
4390
  },
4383
4391
  // A pre-launch integration: it only surfaces once the App Store listing
package/dist/providers.js CHANGED
@@ -722,8 +722,8 @@ var attentive_default2 = {
722
722
  // keep the stored value. `required` drives the live check.
723
723
  provider: {
724
724
  fields: [
725
- { input: "text", key: "clientId", credential: "ATTENTIVE_OAUTH_CLIENT_ID", label: "Client ID", required: true },
726
- { input: "password", key: "clientSecret", credential: "ATTENTIVE_OAUTH_CLIENT_SECRET", label: "Client secret", redact: true, required: true }
725
+ { input: "text", key: "clientId", credential: "ATTENTIVE_OAUTH_CLIENT_ID", label: "Client ID", message: "From our app's registration in the Attentive developer portal.", required: true },
726
+ { input: "password", key: "clientSecret", credential: "ATTENTIVE_OAUTH_CLIENT_SECRET", label: "Client secret", message: "Beside the Client ID in the Attentive developer portal.", redact: true, required: true }
727
727
  ]
728
728
  },
729
729
  requires: [
@@ -2164,7 +2164,7 @@ var drawbridge_default2 = {
2164
2164
  provider: {
2165
2165
  fields: [
2166
2166
  { input: "email", key: "accountSender", credential: "SENDGRID_FROM_ADDRESS", label: "Account sender", message: "Verification codes and security alerts send from here.", required: true },
2167
- { input: "password", key: "apiKey", credential: "SENDGRID_API_KEY", label: "SendGrid API key", redact: true, required: true },
2167
+ { input: "password", key: "apiKey", credential: "SENDGRID_API_KEY", label: "SendGrid API key", message: "SendGrid dashboard, Settings, API Keys \u2014 full access for sends.", redact: true, required: true },
2168
2168
  // NOT required. The CRM sync is best-effort internal tooling and no-ops
2169
2169
  // without a token — requiring it would make the whole drawbridge provider
2170
2170
  // read not-live over something no merchant ever sees.
@@ -2184,8 +2184,8 @@ var drawbridge_default2 = {
2184
2184
  // and logs), the number-purchase routes deny, and identityDefaults.sms
2185
2185
  // hides the org's SMS settings entirely.
2186
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 }
2187
+ { input: "password", key: "smsSid", credential: "TWILIO_ACCOUNT_SID", label: "Twilio account SID", message: "Twilio console home \u2014 the AC\u2026 identifier on the account dashboard.", redact: true },
2188
+ { input: "password", key: "smsToken", credential: "TWILIO_AUTH_TOKEN", label: "Twilio auth token", message: "Beside the account SID on the Twilio console dashboard.", redact: true }
2189
2189
  ]
2190
2190
  },
2191
2191
  // NOTHING, and HUBSPOT_ACCESS_TOKEN in particular must not be here.
@@ -2775,8 +2775,8 @@ var klaviyo_default2 = {
2775
2775
  // `requires` that names the same variables.
2776
2776
  provider: {
2777
2777
  fields: [
2778
- { input: "text", key: "clientId", credential: "KLAVIYO_OAUTH_CLIENT_ID", label: "Client ID", required: true },
2779
- { input: "password", key: "clientSecret", credential: "KLAVIYO_OAUTH_CLIENT_SECRET", label: "Client secret", redact: true, required: true }
2778
+ { input: "text", key: "clientId", credential: "KLAVIYO_OAUTH_CLIENT_ID", label: "Client ID", message: "From our app in the Klaviyo developer portal (developers.klaviyo.com).", required: true },
2779
+ { input: "password", key: "clientSecret", credential: "KLAVIYO_OAUTH_CLIENT_SECRET", label: "Client secret", message: "Beside the Client ID on the Klaviyo app.", redact: true, required: true }
2780
2780
  ]
2781
2781
  },
2782
2782
  requires: [
@@ -3121,8 +3121,8 @@ var mailchimp_default2 = {
3121
3121
  // same variables.
3122
3122
  provider: {
3123
3123
  fields: [
3124
- { input: "text", key: "clientId", credential: "MAILCHIMP_OAUTH_CLIENT_ID", label: "Client ID", required: true },
3125
- { input: "password", key: "clientSecret", credential: "MAILCHIMP_OAUTH_CLIENT_SECRET", label: "Client secret", redact: true, required: true }
3124
+ { input: "text", key: "clientId", credential: "MAILCHIMP_OAUTH_CLIENT_ID", label: "Client ID", message: "From our registered app \u2014 Mailchimp account, Extras, Registered apps.", required: true },
3125
+ { input: "password", key: "clientSecret", credential: "MAILCHIMP_OAUTH_CLIENT_SECRET", label: "Client secret", message: "Beside the Client ID on the registered app.", redact: true, required: true }
3126
3126
  ]
3127
3127
  },
3128
3128
  // The OAuth client this deployment registered. Without both, the vendor drops
@@ -4075,24 +4075,32 @@ var shopify_default2 = {
4075
4075
  if (partner.partnerToken && partner.partnerOrgId && partner.partnerAppId && (source == null ? void 0 : source.id) && ((_b = shopify.partner) == null ? void 0 : _b.getUsageChargeEvents)) {
4076
4076
  const accruals = await shopify.partner.getUsageChargeEvents({
4077
4077
  appId: "gid://shopify/App/" + partner.partnerAppId,
4078
- first: 1,
4078
+ first: 5,
4079
4079
  occurredAtMin: new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString(),
4080
4080
  organizationId: partner.partnerOrgId,
4081
4081
  partnerToken: partner.partnerToken,
4082
4082
  shopId: "gid://shopify/Shop/" + source.id
4083
4083
  });
4084
- if (accruals.length) {
4085
- metered = String(accruals[0].chargeId || accruals[0].id);
4086
- } else {
4087
- const unaccrued = await read.get({
4084
+ const accrued = accruals.find((event) => Number(event.usageQuantity) > 0);
4085
+ if (accrued) {
4086
+ metered = String(accrued.chargeId || accrued.id);
4087
+ writes.push({
4088
4088
  collection: "order",
4089
+ data: {
4090
+ $set: { "billed.receipt": metered }
4091
+ },
4092
+ // Top-level, not an option — the controller takes
4093
+ // `multiple` beside `options`, and the shell
4094
+ // forwards it as such.
4095
+ multiple: true,
4096
+ operation: "update",
4089
4097
  query: {
4090
- "billed.date": { $lt: new Date(Date.now() - 6 * 60 * 60 * 1e3) },
4098
+ "billed.date": { $gte: new Date(accrued.occurredAt || 0) },
4099
+ "billed.receipt": { $in: [null] },
4091
4100
  organization: workflow.organization,
4092
4101
  "provider.slug": "shopify"
4093
4102
  }
4094
4103
  });
4095
- if (unaccrued) metered = null;
4096
4104
  }
4097
4105
  }
4098
4106
  } catch (probeError) {
@@ -4314,10 +4322,10 @@ var shopify_default2 = {
4314
4322
  // never go live from the admin screen.
4315
4323
  provider: {
4316
4324
  fields: [
4317
- { input: "text", key: "apiKey", credential: "SHOPIFY_API_KEY", label: "API key", required: true },
4318
- { input: "password", key: "apiSecret", credential: "SHOPIFY_API_SECRET", label: "API secret", redact: true, required: true },
4319
- { input: "text", key: "appHandle", credential: "SHOPIFY_APP_HANDLE", label: "App handle", required: true },
4320
- { input: "text", key: "listingUrl", credential: "SHOPIFY_APP_LISTING_URL", label: "App listing URL", required: true },
4325
+ { input: "text", key: "apiKey", credential: "SHOPIFY_API_KEY", label: "API key", message: "The app's Client ID \u2014 Dev Dashboard, the app, Settings.", required: true },
4326
+ { input: "password", key: "apiSecret", credential: "SHOPIFY_API_SECRET", label: "API secret", message: "The app's Client secret, beside the Client ID in the app's settings.", redact: true, required: true },
4327
+ { input: "text", key: "appHandle", credential: "SHOPIFY_APP_HANDLE", label: "App handle", message: "The app's URL handle (for example drawbridge-development), from the app's configuration.", required: true },
4328
+ { input: "text", key: "listingUrl", credential: "SHOPIFY_APP_LISTING_URL", label: "App listing URL", message: 'Where "View on the Shopify App Store" sends merchants \u2014 the public listing, or the Dev Dashboard install link for the dev app.', required: true },
4321
4329
  // The usage meter's event handle, when the plan config's meter ever
4322
4330
  // changes: stored value overrides the built-in default
4323
4331
  // (events.order.handle) at the mint point, and `format : 'slug'`
@@ -4325,15 +4333,15 @@ var shopify_default2 = {
4325
4333
  // and classification against it is case-sensitive. Optional: empty
4326
4334
  // means the default, and the sender still refuses any handle that
4327
4335
  // disagrees with the transaction it minted.
4328
- { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", required: false, setting: true },
4336
+ { format: "slug", input: "text", key: "orderEventHandle", label: "Order event handle", message: "Must match the usage meter's Handle field on the plan exactly \u2014 lowercase and hyphens. Leave empty to use drawbridge-orders.", required: false, setting: true },
4329
4337
  // The Partner API credential set — the ONE place meter billing is
4330
4338
  // observable (CHARGE_USAGE accrual events). Org id and numeric app
4331
4339
  // id come from the Partner Dashboard; the token from Settings →
4332
4340
  // Partner API clients (Manage-apps permission). All optional: the
4333
4341
  // 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 }
4342
+ { input: "text", key: "partnerOrgId", label: "Partner organization ID", message: "The number in the Partner Dashboard URL \u2014 partners.shopify.com/<this number>/\u2026 \u2014 NOT the admin organization_id from the app listing link.", required: false, setting: true },
4343
+ { input: "text", key: "partnerAppId", label: "Partner app ID (numeric)", message: "The numeric id in the app's Partner Dashboard URL \u2014 not the Client ID.", required: false, setting: true },
4344
+ { input: "password", key: "partnerToken", label: "Partner API token", message: "Partner Dashboard, Settings, Partner API clients \u2014 create one with Manage apps and paste the prtapi_ token.", redact: true, required: false, setting: true }
4337
4345
  ]
4338
4346
  },
4339
4347
  // 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.149"
219
+ "version": "0.0.151"
220
220
  }