@omega.js/manager 0.1.0
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.
- package/.claude-plugin/marketplace.json +14 -0
- package/LICENSE +98 -0
- package/README.md +165 -0
- package/bin/omega +2 -0
- package/bin/omg +2 -0
- package/claude-plugin/.claude-plugin/plugin.json +11 -0
- package/claude-plugin/.mcp.json +9 -0
- package/claude-plugin/README.md +139 -0
- package/claude-plugin/hooks/gate/mark.sh +58 -0
- package/claude-plugin/hooks/gate/run.sh +97 -0
- package/claude-plugin/hooks/guard/run.sh +249 -0
- package/claude-plugin/hooks/hooks.json +63 -0
- package/claude-plugin/hooks/inject/run.sh +127 -0
- package/claude-plugin/hooks/lib/omega-gate.sh +20 -0
- package/claude-plugin/hooks/lib/omega-scope.sh +41 -0
- package/claude-plugin/hooks/lib/omega-skills.sh +209 -0
- package/claude-plugin/hooks/quality/run.sh +132 -0
- package/claude-plugin/hooks/shape/run.sh +45 -0
- package/claude-plugin/mcp-router-launch.js +29 -0
- package/claude-plugin/skills/README.md +48 -0
- package/claude-plugin/skills/accessibility/SKILL.md +31 -0
- package/claude-plugin/skills/analytics/SKILL.md +30 -0
- package/claude-plugin/skills/backend/SKILL.md +25 -0
- package/claude-plugin/skills/brandcheck/SKILL.md +29 -0
- package/claude-plugin/skills/browser/SKILL.md +64 -0
- package/claude-plugin/skills/client/SKILL.md +24 -0
- package/claude-plugin/skills/desktop/SKILL.md +25 -0
- package/claude-plugin/skills/extension/SKILL.md +25 -0
- package/claude-plugin/skills/main/SKILL.md +54 -0
- package/claude-plugin/skills/manager/SKILL.md +25 -0
- package/claude-plugin/skills/seo/SKILL.md +36 -0
- package/claude-plugin/skills/theme/SKILL.md +33 -0
- package/claude-plugin/skills/web/SKILL.md +29 -0
- package/dist/cli-run.js +34 -0
- package/dist/cli.js +29 -0
- package/dist/commands/build.js +17 -0
- package/dist/commands/clean.js +16 -0
- package/dist/commands/company.js +46 -0
- package/dist/commands/deploy.js +143 -0
- package/dist/commands/dev.js +437 -0
- package/dist/commands/devlog.js +25 -0
- package/dist/commands/help.js +48 -0
- package/dist/commands/manage.js +53 -0
- package/dist/commands/migrate.js +76 -0
- package/dist/commands/onboard.js +31 -0
- package/dist/commands/pipeline.js +319 -0
- package/dist/commands/test.js +380 -0
- package/dist/commands/update.js +128 -0
- package/dist/commands/version.js +8 -0
- package/dist/company-init.js +167 -0
- package/dist/company.js +283 -0
- package/dist/config.js +1359 -0
- package/dist/devlog/index.js +226 -0
- package/dist/devlog/lib/collect-commits.js +136 -0
- package/dist/devlog/lib/generate-post.js +201 -0
- package/dist/devlog/lib/ghostii.js +122 -0
- package/dist/devlog/lib/project-map.js +98 -0
- package/dist/devlog/lib/publish-website.js +88 -0
- package/dist/index.js +45 -0
- package/dist/lib/agents-md.js +201 -0
- package/dist/lib/analytics-secret.js +22 -0
- package/dist/lib/argv.js +21 -0
- package/dist/lib/auth-admin.js +120 -0
- package/dist/lib/automation-client.js +210 -0
- package/dist/lib/backend-marketing.js +49 -0
- package/dist/lib/brand.js +240 -0
- package/dist/lib/bundle-id.js +53 -0
- package/dist/lib/claude-settings.js +125 -0
- package/dist/lib/company-scaffold.js +213 -0
- package/dist/lib/company.js +227 -0
- package/dist/lib/config-flow.js +350 -0
- package/dist/lib/config-write.js +41 -0
- package/dist/lib/custom-target.js +75 -0
- package/dist/lib/domain-utils.js +19 -0
- package/dist/lib/duration.js +39 -0
- package/dist/lib/env-order.js +236 -0
- package/dist/lib/env-secret.js +78 -0
- package/dist/lib/firestore-rest.js +326 -0
- package/dist/lib/framework-bin.js +156 -0
- package/dist/lib/gitignore.js +63 -0
- package/dist/lib/google-auth.js +495 -0
- package/dist/lib/google-token.js +66 -0
- package/dist/lib/jwt.js +31 -0
- package/dist/lib/legacy-oauth.js +59 -0
- package/dist/lib/node-version.js +127 -0
- package/dist/lib/owner-plan.js +78 -0
- package/dist/lib/package-scripts.js +152 -0
- package/dist/lib/preflight.js +470 -0
- package/dist/lib/product-create.js +144 -0
- package/dist/lib/run-command.js +74 -0
- package/dist/lib/run-gates.js +70 -0
- package/dist/lib/run-output.js +42 -0
- package/dist/lib/run-summary.js +384 -0
- package/dist/lib/scaffold.js +427 -0
- package/dist/lib/service-input.js +207 -0
- package/dist/lib/service-runner.js +466 -0
- package/dist/lib/stale.js +24 -0
- package/dist/lib/target-selection.js +155 -0
- package/dist/lib/verb-fanout.js +128 -0
- package/dist/lib/verify-live.js +188 -0
- package/dist/manage.js +311 -0
- package/dist/omega-bin.js +7 -0
- package/dist/onboard.js +532 -0
- package/dist/services/account/ensure/users.js +259 -0
- package/dist/services/account/index.js +97 -0
- package/dist/services/account/lib/backend-client.js +107 -0
- package/dist/services/account/lib/password.js +36 -0
- package/dist/services/account/lib/resolve-password.js +100 -0
- package/dist/services/advertising/ensure/sites.js +101 -0
- package/dist/services/advertising/index.js +101 -0
- package/dist/services/advertising/lib/adsense-api.js +41 -0
- package/dist/services/ai/ensure/keys.js +21 -0
- package/dist/services/ai/index.js +36 -0
- package/dist/services/analytics/ensure/google-firebase-link.js +230 -0
- package/dist/services/analytics/ensure/google-streams.js +256 -0
- package/dist/services/analytics/ensure/meta-pixel.js +22 -0
- package/dist/services/analytics/ensure/tiktok-pixel.js +22 -0
- package/dist/services/analytics/index.js +134 -0
- package/dist/services/analytics/lib/analytics-api.js +156 -0
- package/dist/services/analytics/lib/meta-api.js +83 -0
- package/dist/services/analytics/lib/pixel-account.js +78 -0
- package/dist/services/analytics/lib/pixel-provision.js +152 -0
- package/dist/services/analytics/lib/pixel-specs.js +57 -0
- package/dist/services/analytics/lib/pixel-token.js +88 -0
- package/dist/services/analytics/lib/property-flow.js +77 -0
- package/dist/services/analytics/lib/tiktok-api.js +157 -0
- package/dist/services/analytics/lib/tiktok-auth.js +153 -0
- package/dist/services/assets/ensure/logo-gen.js +68 -0
- package/dist/services/assets/index.js +109 -0
- package/dist/services/assets/lib/assets-config.js +168 -0
- package/dist/services/assets/lib/brandmark-api.js +160 -0
- package/dist/services/assets/lib/derived.js +113 -0
- package/dist/services/assets/lib/font-loader.js +67 -0
- package/dist/services/assets/lib/reconcile.js +122 -0
- package/dist/services/assets/lib/reset.js +118 -0
- package/dist/services/assets/lib/svg-logo-generator.js +154 -0
- package/dist/services/assets/lib/svg-to-black.js +32 -0
- package/dist/services/assets/write/favicons.js +82 -0
- package/dist/services/assets/write/icons.js +64 -0
- package/dist/services/assets/write/process.js +96 -0
- package/dist/services/assets/write/reconcile.js +30 -0
- package/dist/services/assets/write/social-icons.js +108 -0
- package/dist/services/assets/write/templates.js +345 -0
- package/dist/services/bookmark/ensure/sync.js +255 -0
- package/dist/services/bookmark/index.js +17 -0
- package/dist/services/campaigns/ensure/contact-person.js +40 -0
- package/dist/services/campaigns/ensure/custom-fields.js +84 -0
- package/dist/services/campaigns/ensure/domain-auth.js +92 -0
- package/dist/services/campaigns/ensure/event-webhook.js +89 -0
- package/dist/services/campaigns/ensure/link-branding.js +187 -0
- package/dist/services/campaigns/ensure/list.js +50 -0
- package/dist/services/campaigns/ensure/segments.js +109 -0
- package/dist/services/campaigns/ensure/sender-identity.js +153 -0
- package/dist/services/campaigns/ensure/unsubscribe-groups.js +116 -0
- package/dist/services/campaigns/index.js +79 -0
- package/dist/services/campaigns/lib/dns-sync.js +81 -0
- package/dist/services/campaigns/lib/segment-query.js +138 -0
- package/dist/services/campaigns/lib/sendgrid-api.js +287 -0
- package/dist/services/captcha/ensure/site-key.js +83 -0
- package/dist/services/captcha/index.js +78 -0
- package/dist/services/captcha/lib/console-url.js +45 -0
- package/dist/services/captcha/lib/recaptcha-api.js +36 -0
- package/dist/services/certificates/ensure/api-key.js +22 -0
- package/dist/services/certificates/ensure/bundle-ids.js +115 -0
- package/dist/services/certificates/ensure/certificates.js +187 -0
- package/dist/services/certificates/ensure/profiles.js +143 -0
- package/dist/services/certificates/index.js +225 -0
- package/dist/services/certificates/lib/apple-api.js +183 -0
- package/dist/services/certificates/lib/certificate-manager.js +252 -0
- package/dist/services/certificates/lib/identifier-manager.js +107 -0
- package/dist/services/certificates/lib/keychain.js +135 -0
- package/dist/services/certificates/lib/manual-walkthrough.js +165 -0
- package/dist/services/certificates/lib/profile-manager.js +115 -0
- package/dist/services/chat/data/baseline-knowledge.md +96 -0
- package/dist/services/chat/ensure/chat.js +106 -0
- package/dist/services/chat/ensure/user.js +45 -0
- package/dist/services/chat/index.js +139 -0
- package/dist/services/chat/lib/baseline-knowledge.js +137 -0
- package/dist/services/cloud/ensure/authentication.js +267 -0
- package/dist/services/cloud/ensure/billing.js +120 -0
- package/dist/services/cloud/ensure/cloud-messaging.js +105 -0
- package/dist/services/cloud/ensure/database.js +60 -0
- package/dist/services/cloud/ensure/firestore.js +79 -0
- package/dist/services/cloud/ensure/functions.js +49 -0
- package/dist/services/cloud/ensure/hosting.js +398 -0
- package/dist/services/cloud/ensure/oauth-consent.js +248 -0
- package/dist/services/cloud/ensure/project-settings.js +72 -0
- package/dist/services/cloud/ensure/sdk-config.js +95 -0
- package/dist/services/cloud/ensure/service-account.js +152 -0
- package/dist/services/cloud/ensure/services.js +103 -0
- package/dist/services/cloud/ensure/storage.js +47 -0
- package/dist/services/cloud/index.js +130 -0
- package/dist/services/cloud/lib/access-heal.js +130 -0
- package/dist/services/cloud/lib/firebase-api.js +662 -0
- package/dist/services/cloud/lib/project-flow.js +102 -0
- package/dist/services/directory/ensure/entry.js +48 -0
- package/dist/services/directory/index.js +64 -0
- package/dist/services/directory/lib/blocks.js +104 -0
- package/dist/services/disperse/index.js +45 -0
- package/dist/services/disperse/write/certs.js +117 -0
- package/dist/services/domain/ensure/nameservers.js +179 -0
- package/dist/services/domain/index.js +69 -0
- package/dist/services/domain/lib/namecheap-api.js +167 -0
- package/dist/services/domain/lib/registrars.js +46 -0
- package/dist/services/domain/lib/whitelist-walkthrough.js +76 -0
- package/dist/services/edge/ensure/cache-rules.js +105 -0
- package/dist/services/edge/ensure/dns-records.js +304 -0
- package/dist/services/edge/ensure/email-routing.js +292 -0
- package/dist/services/edge/ensure/rules-configuration.js +98 -0
- package/dist/services/edge/ensure/rules-managed-transforms.js +101 -0
- package/dist/services/edge/ensure/rules-redirect.js +107 -0
- package/dist/services/edge/ensure/rules-response-headers.js +90 -0
- package/dist/services/edge/ensure/rules-security.js +142 -0
- package/dist/services/edge/ensure/speed-scheduled-tests.js +98 -0
- package/dist/services/edge/ensure/workers.js +219 -0
- package/dist/services/edge/ensure/zone-settings.js +108 -0
- package/dist/services/edge/ensure/zone.js +183 -0
- package/dist/services/edge/index.js +73 -0
- package/dist/services/edge/lib/cloudflare-api.js +84 -0
- package/dist/services/edge/lib/dns-records-helpers.js +518 -0
- package/dist/services/edge/lib/read-cache.js +16 -0
- package/dist/services/edge/lib/ruleset-helper.js +108 -0
- package/dist/services/edge/workers/omega-api-proxy.js +43 -0
- package/dist/services/email/data/baseline-filter.md +4 -0
- package/dist/services/email/data/baseline-knowledge.md +86 -0
- package/dist/services/email/ensure/agent.js +107 -0
- package/dist/services/email/ensure/user.js +45 -0
- package/dist/services/email/index.js +138 -0
- package/dist/services/email/lib/baseline.js +82 -0
- package/dist/services/email/lib/knowledge-file.js +53 -0
- package/dist/services/forms/ensure/form.js +46 -0
- package/dist/services/forms/ensure/user.js +45 -0
- package/dist/services/forms/index.js +138 -0
- package/dist/services/migrations/ensure/notifications.js +189 -0
- package/dist/services/migrations/ensure/orders.js +34 -0
- package/dist/services/migrations/ensure/payment-provider.js +132 -0
- package/dist/services/migrations/ensure/payments-intents.js +34 -0
- package/dist/services/migrations/ensure/state-retirement.js +266 -0
- package/dist/services/migrations/ensure/targets-rename.js +124 -0
- package/dist/services/migrations/ensure/users.js +1176 -0
- package/dist/services/migrations/index.js +101 -0
- package/dist/services/migrations/lib/attribution-touch.js +64 -0
- package/dist/services/migrations/lib/ensure-metadata.js +144 -0
- package/dist/services/migrations/lib/migration-runner.js +473 -0
- package/dist/services/migrations/lib/sanitize-strings.js +112 -0
- package/dist/services/migrations/lib/schema-validator.js +154 -0
- package/dist/services/monitoring/ensure/dsn.js +54 -0
- package/dist/services/monitoring/ensure/projects.js +135 -0
- package/dist/services/monitoring/index.js +54 -0
- package/dist/services/monitoring/lib/sentry-api.js +94 -0
- package/dist/services/newsletter/ensure/custom-fields.js +90 -0
- package/dist/services/newsletter/ensure/publication.js +94 -0
- package/dist/services/newsletter/ensure/segments.js +140 -0
- package/dist/services/newsletter/ensure/webhook.js +108 -0
- package/dist/services/newsletter/index.js +64 -0
- package/dist/services/newsletter/lib/beehiiv-api.js +124 -0
- package/dist/services/newsletter/lib/segment-automation.js +577 -0
- package/dist/services/payment/ensure/chargebee-account.js +30 -0
- package/dist/services/payment/ensure/chargebee-products.js +359 -0
- package/dist/services/payment/ensure/chargebee-webhook.js +113 -0
- package/dist/services/payment/ensure/paypal-account.js +36 -0
- package/dist/services/payment/ensure/paypal-products.js +374 -0
- package/dist/services/payment/ensure/paypal-webhook.js +128 -0
- package/dist/services/payment/ensure/stripe-account.js +93 -0
- package/dist/services/payment/ensure/stripe-disputes.js +56 -0
- package/dist/services/payment/ensure/stripe-products.js +245 -0
- package/dist/services/payment/ensure/stripe-radar.js +74 -0
- package/dist/services/payment/ensure/stripe-webhook.js +130 -0
- package/dist/services/payment/index.js +163 -0
- package/dist/services/payment/lib/chargebee-api.js +338 -0
- package/dist/services/payment/lib/payment-utils.js +116 -0
- package/dist/services/payment/lib/paypal-api.js +354 -0
- package/dist/services/payment/lib/provider-setup.js +134 -0
- package/dist/services/payment/lib/stripe-api.js +152 -0
- package/dist/services/repo/ensure/org.js +70 -0
- package/dist/services/repo/ensure/pages.js +72 -0
- package/dist/services/repo/ensure/repo.js +90 -0
- package/dist/services/repo/index.js +52 -0
- package/dist/services/repo/lib/github-api.js +196 -0
- package/dist/services/search/ensure/ga-link.js +57 -0
- package/dist/services/search/ensure/property.js +137 -0
- package/dist/services/search/ensure/sitemaps.js +70 -0
- package/dist/services/search/index.js +61 -0
- package/dist/services/search/lib/search-console-api.js +92 -0
- package/dist/services/seo/ensure/github-repos.js +271 -0
- package/dist/services/seo/index.js +55 -0
- package/dist/services/seo/lib/gh-api.js +161 -0
- package/dist/services/seo/templates/developer-tool/.github/workflows/maintenance.yml +25 -0
- package/dist/services/seo/templates/developer-tool/.nvmrc +1 -0
- package/dist/services/seo/templates/developer-tool/_README.md.js +121 -0
- package/dist/services/seo/templates/developer-tool/_package.json.js +33 -0
- package/dist/services/seo/templates/developer-tool/src/index.js +120 -0
- package/dist/services/seo/templates/index.js +57 -0
- package/dist/services/server/ensure/brands.js +63 -0
- package/dist/services/server/index.js +42 -0
- package/dist/services/testing/ensure/target-checks.js +97 -0
- package/dist/services/testing/index.js +11 -0
- package/dist/services/testing/lib/checks.js +467 -0
- package/dist/services/update/index.js +20 -0
- package/dist/services/update/lib/cache.js +59 -0
- package/dist/services/update/lib/fingerprint.js +157 -0
- package/dist/services/update/write/targets.js +206 -0
- package/dist/services/workspace/ensure/agents.js +42 -0
- package/dist/services/workspace/ensure/claude-settings.js +34 -0
- package/dist/services/workspace/ensure/config.js +54 -0
- package/dist/services/workspace/ensure/defaults.js +60 -0
- package/dist/services/workspace/ensure/env-keys.js +55 -0
- package/dist/services/workspace/ensure/env-order.js +66 -0
- package/dist/services/workspace/ensure/env-rules.js +97 -0
- package/dist/services/workspace/ensure/gitignore.js +18 -0
- package/dist/services/workspace/ensure/scripts.js +126 -0
- package/dist/services/workspace/ensure/structure.js +97 -0
- package/dist/services/workspace/ensure/translation-sdk.js +148 -0
- package/dist/services/workspace/ensure/workflows.js +54 -0
- package/dist/services/workspace/index.js +8 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/config/company.js +31 -0
- package/dist/vendor/config/defaults.js +173 -0
- package/dist/vendor/config/demo.js +18 -0
- package/dist/vendor/config/desktop-artifacts.js +110 -0
- package/dist/vendor/config/edit.js +769 -0
- package/dist/vendor/config/env-delivery.js +145 -0
- package/dist/vendor/config/env-rules.js +93 -0
- package/dist/vendor/config/env-schema.js +1078 -0
- package/dist/vendor/config/env.js +445 -0
- package/dist/vendor/config/hooks.js +97 -0
- package/dist/vendor/config/index.js +237 -0
- package/dist/vendor/config/instances.js +208 -0
- package/dist/vendor/config/load.js +490 -0
- package/dist/vendor/config/merge.js +68 -0
- package/dist/vendor/config/order.js +139 -0
- package/dist/vendor/config/ports.js +374 -0
- package/dist/vendor/config/providers.js +32 -0
- package/dist/vendor/config/repo.js +142 -0
- package/dist/vendor/config/retired-keys.js +430 -0
- package/dist/vendor/config/schema.js +1610 -0
- package/dist/vendor/config/secrets.js +50 -0
- package/dist/vendor/config/seed.js +34 -0
- package/dist/vendor/config/site-global.js +205 -0
- package/dist/vendor/config/validate.js +554 -0
- package/dist/vendor/config/winback.js +61 -0
- package/dist/vendor/devkit/attach-log-file.js +262 -0
- package/dist/vendor/devkit/certs.js +199 -0
- package/dist/vendor/devkit/ci-workflows.js +520 -0
- package/dist/vendor/devkit/cli-router.js +156 -0
- package/dist/vendor/devkit/command-path.js +46 -0
- package/dist/vendor/devkit/deploy-record.js +180 -0
- package/dist/vendor/devkit/flows.js +317 -0
- package/dist/vendor/devkit/local-https.js +360 -0
- package/dist/vendor/devkit/local.js +1905 -0
- package/dist/vendor/devkit/logger.js +128 -0
- package/dist/vendor/devkit/omega-bin.js +345 -0
- package/dist/vendor/devkit/prompt.js +187 -0
- package/dist/vendor/devkit/safe-install.js +18 -0
- package/dist/vendor/devkit/stop-signals.js +28 -0
- package/dist/vendor/devkit/test/scope.js +162 -0
- package/dist/vendor/devkit/translate/cache.js +84 -0
- package/dist/vendor/devkit/translate/engine.js +243 -0
- package/dist/vendor/devkit/translate/index.js +49 -0
- package/dist/vendor/devkit/translate/languages.js +134 -0
- package/dist/vendor/devkit/translate/providers.js +188 -0
- package/dist/vendor/devkit/update.js +569 -0
- package/docs/AGENTS.md +200 -0
- package/docs/account.md +45 -0
- package/docs/advertising.md +46 -0
- package/docs/ai.md +34 -0
- package/docs/analytics.md +69 -0
- package/docs/assets.md +57 -0
- package/docs/bookmark.md +26 -0
- package/docs/brand.md +151 -0
- package/docs/campaigns.md +96 -0
- package/docs/captcha.md +46 -0
- package/docs/certificates.md +68 -0
- package/docs/chat.md +47 -0
- package/docs/cloud.md +110 -0
- package/docs/company.md +64 -0
- package/docs/directory.md +140 -0
- package/docs/disperse.md +46 -0
- package/docs/domain.md +56 -0
- package/docs/edge.md +234 -0
- package/docs/email.md +42 -0
- package/docs/forms.md +48 -0
- package/docs/index.md +109 -0
- package/docs/migration.md +203 -0
- package/docs/migrations.md +56 -0
- package/docs/monitoring.md +41 -0
- package/docs/newsletter.md +48 -0
- package/docs/payment.md +83 -0
- package/docs/repo.md +48 -0
- package/docs/search.md +44 -0
- package/docs/seo.md +34 -0
- package/docs/server.md +37 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/testing.md +34 -0
- package/docs/update.md +36 -0
- package/docs/workspace.md +64 -0
- package/package.json +88 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure Stripe products and prices exist for all paid products.
|
|
3
|
+
*
|
|
4
|
+
* Product resolution: config productId → state (stripeProducts map) →
|
|
5
|
+
* metadata match against the account's products (self-heal when state is
|
|
6
|
+
* lost — every product we create carries { brandId, productId } metadata) →
|
|
7
|
+
* create. Created/matched IDs are written back into omega.json5
|
|
8
|
+
* (payment.products[id=…].stripe.productId — comment-preserving) and
|
|
9
|
+
* mirrored in state. Prices are managed by interval + amount — matching
|
|
10
|
+
* active prices are kept, stale ones archived (Stripe can't delete prices),
|
|
11
|
+
* missing ones created. omega-manager's version had no dry-run guard and no
|
|
12
|
+
* archived-product skip here; both are standard now.
|
|
13
|
+
*/
|
|
14
|
+
const chalk = require('chalk').default;
|
|
15
|
+
const { paidProducts, productDisplayName, productImage } = require('../lib/payment-utils.js');
|
|
16
|
+
const { writeBrandConfig } = require('../../../lib/config-write.js');
|
|
17
|
+
|
|
18
|
+
// Map config interval names to Stripe price intervals
|
|
19
|
+
const FREQUENCY_TO_INTERVAL = {
|
|
20
|
+
annually: 'year',
|
|
21
|
+
monthly: 'month',
|
|
22
|
+
weekly: 'week',
|
|
23
|
+
daily: 'day',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build desired product details from brand/product config.
|
|
28
|
+
* Images are only managed when brand.images.brandmark is configured.
|
|
29
|
+
*/
|
|
30
|
+
function buildProductDetails(brandConfig, brandId, product) {
|
|
31
|
+
return {
|
|
32
|
+
name: productDisplayName(brandConfig, product),
|
|
33
|
+
description: product.description || brandConfig.brand.description || '',
|
|
34
|
+
images: productImage(brandConfig) ? [productImage(brandConfig)] : null,
|
|
35
|
+
url: brandConfig.brand.url,
|
|
36
|
+
metadata: { brandId, productId: product.id },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Diff a Stripe product against desired and return the update payload
|
|
42
|
+
*/
|
|
43
|
+
function getProductUpdates(stripeProduct, desired) {
|
|
44
|
+
const updates = {};
|
|
45
|
+
|
|
46
|
+
if (stripeProduct.name !== desired.name) {
|
|
47
|
+
updates.name = desired.name;
|
|
48
|
+
}
|
|
49
|
+
if ((stripeProduct.description || '') !== desired.description) {
|
|
50
|
+
updates.description = desired.description;
|
|
51
|
+
}
|
|
52
|
+
if (stripeProduct.url !== desired.url) {
|
|
53
|
+
updates.url = desired.url;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (desired.images && JSON.stringify(stripeProduct.images || []) !== JSON.stringify(desired.images)) {
|
|
57
|
+
updates.images = desired.images;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Metadata — ensure all desired keys exist with correct values
|
|
61
|
+
const currentMeta = stripeProduct.metadata || {};
|
|
62
|
+
const metadataUpdates = {};
|
|
63
|
+
for (const [key, value] of Object.entries(desired.metadata)) {
|
|
64
|
+
if (currentMeta[key] !== value) {
|
|
65
|
+
metadataUpdates[key] = value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (Object.keys(metadataUpdates).length > 0) {
|
|
69
|
+
updates.metadata = { ...currentMeta, ...metadataUpdates };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Object.keys(updates).length > 0 ? updates : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Ensure active prices on a Stripe product match config amounts.
|
|
77
|
+
* Creates missing prices, archives actives at the wrong amount.
|
|
78
|
+
*/
|
|
79
|
+
async function ensurePrices(api, stripeProductId, product, dryRun) {
|
|
80
|
+
const activePrices = await api.listPricesForProduct(stripeProductId);
|
|
81
|
+
|
|
82
|
+
if (product.type === 'subscription') {
|
|
83
|
+
for (const interval of Object.keys(product.prices)) {
|
|
84
|
+
const expectedAmount = product.prices[interval];
|
|
85
|
+
|
|
86
|
+
if (!expectedAmount) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const expectedCents = Math.round(expectedAmount * 100);
|
|
91
|
+
const stripeInterval = FREQUENCY_TO_INTERVAL[interval] || 'month';
|
|
92
|
+
|
|
93
|
+
const match = activePrices.find((p) =>
|
|
94
|
+
p.active
|
|
95
|
+
&& p.recurring?.interval === stripeInterval
|
|
96
|
+
&& p.unit_amount === expectedCents
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (match) {
|
|
100
|
+
console.log(` ${chalk.green('✓')} ${interval}: $${expectedAmount} ${chalk.dim(match.id)}`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Archive active prices at the wrong amount for this interval
|
|
105
|
+
const stale = activePrices.filter((p) =>
|
|
106
|
+
p.active
|
|
107
|
+
&& p.recurring?.interval === stripeInterval
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
for (const old of stale) {
|
|
111
|
+
if (dryRun) {
|
|
112
|
+
console.log(` ${chalk.yellow('↓')} ${interval}: would archive $${old.unit_amount / 100} ${chalk.dim(old.id)} ${chalk.yellow('[DRY RUN]')}`);
|
|
113
|
+
} else {
|
|
114
|
+
await api.archivePrice(old.id);
|
|
115
|
+
console.log(` ${chalk.yellow('↓')} ${interval}: archived $${old.unit_amount / 100} ${chalk.dim(old.id)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (dryRun) {
|
|
120
|
+
console.log(` ${chalk.cyan('+')} ${interval}: would create $${expectedAmount} ${chalk.yellow('[DRY RUN]')}`);
|
|
121
|
+
} else {
|
|
122
|
+
const newPrice = await api.createRecurringPrice(stripeProductId, expectedAmount, interval);
|
|
123
|
+
console.log(` ${chalk.green('✓')} ${interval}: created $${expectedAmount} ${chalk.cyan(newPrice.id)}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (product.type === 'one-time') {
|
|
129
|
+
const expectedAmount = product.prices.once;
|
|
130
|
+
|
|
131
|
+
if (expectedAmount) {
|
|
132
|
+
const expectedCents = Math.round(expectedAmount * 100);
|
|
133
|
+
const match = activePrices.find((p) => p.active && !p.recurring && p.unit_amount === expectedCents);
|
|
134
|
+
|
|
135
|
+
if (match) {
|
|
136
|
+
console.log(` ${chalk.green('✓')} once: $${expectedAmount} ${chalk.dim(match.id)}`);
|
|
137
|
+
} else {
|
|
138
|
+
const stale = activePrices.filter((p) => p.active && !p.recurring);
|
|
139
|
+
for (const old of stale) {
|
|
140
|
+
if (dryRun) {
|
|
141
|
+
console.log(` ${chalk.yellow('↓')} once: would archive $${old.unit_amount / 100} ${chalk.dim(old.id)} ${chalk.yellow('[DRY RUN]')}`);
|
|
142
|
+
} else {
|
|
143
|
+
await api.archivePrice(old.id);
|
|
144
|
+
console.log(` ${chalk.yellow('↓')} once: archived $${old.unit_amount / 100} ${chalk.dim(old.id)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (dryRun) {
|
|
149
|
+
console.log(` ${chalk.cyan('+')} once: would create $${expectedAmount} ${chalk.yellow('[DRY RUN]')}`);
|
|
150
|
+
} else {
|
|
151
|
+
const newPrice = await api.createOneTimePrice(stripeProductId, expectedAmount);
|
|
152
|
+
console.log(` ${chalk.green('✓')} once: created $${expectedAmount} ${chalk.cyan(newPrice.id)}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = async function ensureStripeProducts(context) {
|
|
160
|
+
const { brandConfig, brandId, stripeApi: api, options } = context;
|
|
161
|
+
const dryRun = options?.dryRun || false;
|
|
162
|
+
|
|
163
|
+
if (!api) {
|
|
164
|
+
console.log(` ${chalk.dim('⊘ Stripe not configured')}`);
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (dryRun) {
|
|
169
|
+
console.log(` ${chalk.yellow('[DRY RUN]')} No changes will be made`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const products = paidProducts(brandConfig);
|
|
173
|
+
const configEdits = {};
|
|
174
|
+
let catalog = null; // Lazy-listed only when a product has no configured ID
|
|
175
|
+
|
|
176
|
+
for (const product of products) {
|
|
177
|
+
console.log(` ${chalk.bold(product.name)} (${product.id}):`);
|
|
178
|
+
|
|
179
|
+
const desired = buildProductDetails(brandConfig, brandId, product);
|
|
180
|
+
|
|
181
|
+
// --- Resolve the Stripe product: config → metadata match → create ---
|
|
182
|
+
const stripeId = product.stripe?.productId || null;
|
|
183
|
+
let stripeProduct = null;
|
|
184
|
+
|
|
185
|
+
if (stripeId) {
|
|
186
|
+
stripeProduct = await api.getProduct(stripeId);
|
|
187
|
+
} else {
|
|
188
|
+
// Self-heal: our products carry { brandId, productId } metadata
|
|
189
|
+
catalog = catalog || await api.listAllProducts();
|
|
190
|
+
stripeProduct = catalog.find((p) =>
|
|
191
|
+
p.active !== false
|
|
192
|
+
&& p.metadata?.brandId === brandId
|
|
193
|
+
&& p.metadata?.productId === product.id
|
|
194
|
+
) || null;
|
|
195
|
+
|
|
196
|
+
if (stripeProduct) {
|
|
197
|
+
console.log(` ${chalk.green('✓')} Matched existing product by metadata: ${chalk.dim(stripeProduct.id)}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (stripeProduct) {
|
|
202
|
+
console.log(` ${chalk.green('✓')} Product exists: ${chalk.dim(stripeProduct.id)}`);
|
|
203
|
+
|
|
204
|
+
const updates = getProductUpdates(stripeProduct, desired);
|
|
205
|
+
if (updates) {
|
|
206
|
+
const fields = Object.keys(updates).join(', ');
|
|
207
|
+
if (dryRun) {
|
|
208
|
+
console.log(` ${chalk.cyan('~')} Would update: ${chalk.dim(fields)} ${chalk.yellow('[DRY RUN]')}`);
|
|
209
|
+
} else {
|
|
210
|
+
await api.updateProduct(stripeProduct.id, updates);
|
|
211
|
+
console.log(` ${chalk.green('✓')} Updated: ${chalk.dim(fields)}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
if (dryRun) {
|
|
216
|
+
console.log(` ${chalk.cyan('+')} Would create product: ${chalk.dim(desired.name)} ${chalk.yellow('[DRY RUN]')}`);
|
|
217
|
+
// Can't manage prices without a real product ID
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
stripeProduct = await api.createProduct({
|
|
222
|
+
name: desired.name,
|
|
223
|
+
brandId,
|
|
224
|
+
productId: product.id,
|
|
225
|
+
description: desired.description,
|
|
226
|
+
images: desired.images || undefined,
|
|
227
|
+
url: desired.url,
|
|
228
|
+
});
|
|
229
|
+
console.log(` ${chalk.green('✓')} Created product: ${chalk.cyan(stripeProduct.id)}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (stripeProduct.id !== product.stripe?.productId) {
|
|
233
|
+
configEdits[`payment.products[id=${product.id}].stripe.productId`] = stripeProduct.id;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// --- Ensure prices match config amounts ---
|
|
237
|
+
await ensurePrices(api, stripeProduct.id, product, dryRun);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (Object.keys(configEdits).length > 0) {
|
|
241
|
+
writeBrandConfig(context, configEdits);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { output: { stripeSync: { productsProcessed: products.length } } };
|
|
245
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Report the desired Stripe Radar rules.
|
|
3
|
+
*
|
|
4
|
+
* Stripe has no Radar rules API (Dashboard-only), so this follows the
|
|
5
|
+
* no-API manual-action pattern (the search service's ga-link): print the desired
|
|
6
|
+
* rules + the Dashboard deep-link, confirm interactively, and stay warned
|
|
7
|
+
* until confirmed. A confirmation nothing can re-check is the one kind of
|
|
8
|
+
* reconcile flag config keeps (#434): it lands at
|
|
9
|
+
* `payment.providers.stripe.radarConfirmed`. Rules come from
|
|
10
|
+
* payment.providers.stripe.radar — manager defaults with per-brand
|
|
11
|
+
* override. Cannot mutate by construction.
|
|
12
|
+
*/
|
|
13
|
+
const chalk = require('chalk').default;
|
|
14
|
+
const { confirm, pressEnterToOpen } = require('../../../vendor/devkit/prompt.js');
|
|
15
|
+
const { writeBrandConfig } = require('../../../lib/config-write.js');
|
|
16
|
+
const { canPrompt } = require('../../../lib/run-gates.js');
|
|
17
|
+
|
|
18
|
+
const CONFIG_PATH = 'payment.providers.stripe.radarConfirmed';
|
|
19
|
+
|
|
20
|
+
module.exports = async function ensureStripeRadar(context) {
|
|
21
|
+
const { brandConfig, stripeApi: api, serviceData, options = {} } = context;
|
|
22
|
+
|
|
23
|
+
if (!api) {
|
|
24
|
+
console.log(` ${chalk.dim('⊘ Stripe not configured')}`);
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const desiredRules = brandConfig.payment?.providers?.stripe?.radar || [];
|
|
29
|
+
if (desiredRules.length === 0) {
|
|
30
|
+
console.log(` ${chalk.dim('⊘ No Radar rules configured')}`);
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (brandConfig.payment?.providers?.stripe?.radarConfirmed === true) {
|
|
35
|
+
console.log(` ${chalk.green('✓')} Radar rules confirmed (${desiredRules.length} rules)`);
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const radarUrl = serviceData.stripeAccountId
|
|
40
|
+
? `https://dashboard.stripe.com/${serviceData.stripeAccountId}/radar/rules`
|
|
41
|
+
: 'https://dashboard.stripe.com/radar/rules';
|
|
42
|
+
|
|
43
|
+
console.log(` ${chalk.yellow('⚠')} Stripe has no Radar rules API — add these in the Dashboard:`);
|
|
44
|
+
console.log('');
|
|
45
|
+
for (const rule of desiredRules) {
|
|
46
|
+
const actionColor = rule.action === 'block' ? chalk.red
|
|
47
|
+
: rule.action === 'review' ? chalk.yellow
|
|
48
|
+
: chalk.cyan;
|
|
49
|
+
console.log(` ${actionColor(rule.action.padEnd(24))} ${chalk.dim(rule.predicate)}`);
|
|
50
|
+
if (rule.description) {
|
|
51
|
+
console.log(` ${' '.repeat(24)} ${chalk.dim(`↳ ${rule.description}`)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
console.log('');
|
|
55
|
+
|
|
56
|
+
if (canPrompt(options)) {
|
|
57
|
+
await pressEnterToOpen(radarUrl, 'the Stripe Radar rules page');
|
|
58
|
+
const done = await confirm({ message: 'Radar rules added in the Dashboard?', default: false });
|
|
59
|
+
if (done) {
|
|
60
|
+
writeBrandConfig(context, { [CONFIG_PATH]: true });
|
|
61
|
+
console.log(` ${chalk.green('✓')} Radar rules confirmed (${desiredRules.length} rules)`);
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
console.log(` ${chalk.dim('→')} Radar rules: ${chalk.cyan(radarUrl)}`);
|
|
66
|
+
console.log(` ${chalk.dim('→')} (rerun in an interactive terminal to confirm)`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
status: 'warned',
|
|
71
|
+
reason: 'Radar rules not confirmed — add them in the Stripe Dashboard',
|
|
72
|
+
output: { stripeRadar: { rules: desiredRules.length, radarUrl } },
|
|
73
|
+
};
|
|
74
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure a Stripe webhook endpoint exists for this brand's backend.
|
|
3
|
+
*
|
|
4
|
+
* Matches by exact URL, deletes every OTHER endpoint on the brand's API host
|
|
5
|
+
* (stale twins — one endpoint per host, and endpoints on other hosts are never
|
|
6
|
+
* touched), re-enables endpoints Stripe auto-disabled after repeated failures,
|
|
7
|
+
* diffs enabled_events, updates when drifted, creates when missing. Skipped for
|
|
8
|
+
* shared Firebase projects (no api.{domain} backend to receive events).
|
|
9
|
+
* Requires OMEGA_WEBHOOK_KEY.
|
|
10
|
+
*/
|
|
11
|
+
const chalk = require('chalk').default;
|
|
12
|
+
const { buildWebhookUrl, diffEventSets, redactWebhookUrl, staleWebhookEndpoints } = require('../lib/payment-utils.js');
|
|
13
|
+
|
|
14
|
+
// Events the backend handles for subscription/payment processing
|
|
15
|
+
const ENABLED_EVENTS = [
|
|
16
|
+
// Subscriptions
|
|
17
|
+
'customer.subscription.created',
|
|
18
|
+
'customer.subscription.updated',
|
|
19
|
+
'customer.subscription.deleted',
|
|
20
|
+
'customer.subscription.paused',
|
|
21
|
+
'customer.subscription.resumed',
|
|
22
|
+
'customer.subscription.trial_will_end',
|
|
23
|
+
|
|
24
|
+
// Invoices (subscription renewals + failed payments)
|
|
25
|
+
'invoice.created',
|
|
26
|
+
'invoice.paid',
|
|
27
|
+
'invoice.payment_succeeded',
|
|
28
|
+
'invoice.payment_failed',
|
|
29
|
+
'invoice.payment_action_required',
|
|
30
|
+
|
|
31
|
+
// One-time purchases (Checkout + charges)
|
|
32
|
+
'checkout.session.completed',
|
|
33
|
+
'charge.succeeded',
|
|
34
|
+
'charge.failed',
|
|
35
|
+
'charge.refunded',
|
|
36
|
+
|
|
37
|
+
// Payment intents (covers both subscription and one-time)
|
|
38
|
+
'payment_intent.succeeded',
|
|
39
|
+
'payment_intent.payment_failed',
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
module.exports = async function ensureStripeWebhook(context) {
|
|
43
|
+
const { brandConfig, stripeApi: api, options } = context;
|
|
44
|
+
const dryRun = options?.dryRun || false;
|
|
45
|
+
|
|
46
|
+
if (!api) {
|
|
47
|
+
console.log(` ${chalk.dim('⊘ Stripe not configured')}`);
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Shared Firebase project — no backend deploys from this brand, so api.{domain} doesn't exist
|
|
52
|
+
if (brandConfig.cloud?.shared === true) {
|
|
53
|
+
console.log(` ${chalk.dim('⊘ Shared Firebase project — no brand API for webhooks')}`);
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!process.env.OMEGA_WEBHOOK_KEY) {
|
|
58
|
+
// Unreachable by design (#635): the key is OMEGA's own (`generated:` in
|
|
59
|
+
// the env schema) and the payment service's setup mints it through the
|
|
60
|
+
// shared contract before any operation runs. Absent here = broken wiring.
|
|
61
|
+
throw new Error('OMEGA_WEBHOOK_KEY absent after the payment setup ran — its REQUIRES entry or its requestServiceInput call is missing');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const desiredUrl = buildWebhookUrl(brandConfig, 'stripe');
|
|
65
|
+
|
|
66
|
+
const { data: webhooks = [] } = await api.listWebhookEndpoints();
|
|
67
|
+
|
|
68
|
+
// Exactly ONE endpoint on the brand's API host: the desired URL. Legacy twins
|
|
69
|
+
// there receive every event and answer 400 forever (#570).
|
|
70
|
+
for (const stale of staleWebhookEndpoints(webhooks, desiredUrl)) {
|
|
71
|
+
if (dryRun) {
|
|
72
|
+
console.log(` ${chalk.cyan('-')} Would delete stale webhook ${chalk.dim(redactWebhookUrl(stale.url))} ${chalk.yellow('[DRY RUN]')}`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await api.deleteWebhookEndpoint(stale.id);
|
|
77
|
+
console.log(` ${chalk.yellow('⚠')} Deleted stale webhook ${chalk.dim(`(${stale.id})`)} ${redactWebhookUrl(stale.url)}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const existing = webhooks.find((wh) => wh.url === desiredUrl);
|
|
81
|
+
|
|
82
|
+
if (existing) {
|
|
83
|
+
// Re-enable if disabled (Stripe auto-disables after too many failures)
|
|
84
|
+
if (existing.status === 'disabled') {
|
|
85
|
+
if (dryRun) {
|
|
86
|
+
console.log(` ${chalk.cyan('~')} Would re-enable disabled webhook ${chalk.yellow('[DRY RUN]')}`);
|
|
87
|
+
} else {
|
|
88
|
+
console.log(` ${chalk.yellow('⚠')} Webhook is disabled — re-enabling...`);
|
|
89
|
+
await api.updateWebhookEndpoint(existing.id, { disabled: false });
|
|
90
|
+
console.log(` ${chalk.green('✓')} Webhook re-enabled`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const diff = diffEventSets(existing.enabled_events || [], ENABLED_EVENTS);
|
|
95
|
+
|
|
96
|
+
if (!diff) {
|
|
97
|
+
console.log(` ${chalk.green('✓')} Webhook up to date ${chalk.dim(`(${existing.id})`)}`);
|
|
98
|
+
return { output: { stripeWebhook: { id: existing.id, updated: false } } };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (diff.missing.length > 0) {
|
|
102
|
+
console.log(` Adding events: ${chalk.cyan(diff.missing.join(', '))}`);
|
|
103
|
+
}
|
|
104
|
+
if (diff.extra.length > 0) {
|
|
105
|
+
console.log(` Removing events: ${chalk.dim(diff.extra.join(', '))}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (dryRun) {
|
|
109
|
+
console.log(` ${chalk.cyan('~')} Would update webhook events ${chalk.yellow('[DRY RUN]')}`);
|
|
110
|
+
return { output: { stripeWebhook: { id: existing.id, planned: 'update' } } };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await api.updateWebhookEndpoint(existing.id, { enabled_events: ENABLED_EVENTS });
|
|
114
|
+
console.log(` ${chalk.green('✓')} Webhook events updated ${chalk.dim(`(${existing.id})`)}`);
|
|
115
|
+
|
|
116
|
+
return { output: { stripeWebhook: { id: existing.id, updated: true } } };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (dryRun) {
|
|
120
|
+
console.log(` ${chalk.cyan('+')} Would create webhook ${chalk.yellow('[DRY RUN]')}`);
|
|
121
|
+
console.log(` ${chalk.dim(redactWebhookUrl(desiredUrl))}`);
|
|
122
|
+
return { output: { stripeWebhook: { planned: 'create' } } };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const webhook = await api.createWebhookEndpoint(desiredUrl, ENABLED_EVENTS);
|
|
126
|
+
console.log(` ${chalk.green('✓')} Webhook created: ${chalk.dim(webhook.id)}`);
|
|
127
|
+
console.log(` ${chalk.dim(redactWebhookUrl(desiredUrl))}`);
|
|
128
|
+
|
|
129
|
+
return { output: { stripeWebhook: { id: webhook.id, created: true } } };
|
|
130
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment service — reconciles the brand's payment providers to
|
|
3
|
+
* payment.products: Stripe (account settings, products + prices, webhook,
|
|
4
|
+
* plus manual-guidance Radar/dispute checks), PayPal (catalog products +
|
|
5
|
+
* billing plans, webhook), and Chargebee (item family → items → item prices,
|
|
6
|
+
* webhook). Product IDs are written back to omega.json5
|
|
7
|
+
* (payment.products[id=…] — comment-preserving) and mirrored in state.
|
|
8
|
+
*
|
|
9
|
+
* Coinbase Commerce (crypto, #642) reconciles NOTHING — no catalog, no webhook
|
|
10
|
+
* API — so it has no operations and no client; its whole surface here is the
|
|
11
|
+
* credential ask, gated on payment.providers.coinbase.enabled.
|
|
12
|
+
*
|
|
13
|
+
* Auth is per provider, from config + the brand .env:
|
|
14
|
+
* - Stripe: STRIPE_SECRET_KEY
|
|
15
|
+
* - PayPal: payment.providers.paypal.clientId + PAYPAL_CLIENT_SECRET
|
|
16
|
+
* - Chargebee: payment.providers.chargebee.site + CHARGEBEE_API_KEY
|
|
17
|
+
* - Coinbase: COINBASE_COMMERCE_API_KEY (no public half at all)
|
|
18
|
+
* When an enabled provider's credentials are missing, interactive runs
|
|
19
|
+
* offer the setup flow (lib/provider-setup.js — dashboard browser open,
|
|
20
|
+
* public keys land in omega.json5, secrets in the brand .env); otherwise
|
|
21
|
+
* the provider is skipped per-operation with a dim note. The service only
|
|
22
|
+
* skips when NO provider is configured. The webhook operations additionally
|
|
23
|
+
* need OMEGA_WEBHOOK_KEY, which the setup MINTS through the shared contract
|
|
24
|
+
* (it is OMEGA's own key, #635) rather than asking anyone for.
|
|
25
|
+
*
|
|
26
|
+
* --provider=stripe|paypal|chargebee|coinbase narrows the run to one provider's
|
|
27
|
+
* operations (omega-manager's flag, unchanged).
|
|
28
|
+
*/
|
|
29
|
+
const { serviceInputSpec } = require('../../config.js');
|
|
30
|
+
const { createServiceRunner } = require('../../lib/service-runner.js');
|
|
31
|
+
const { requestServiceInput } = require('../../lib/service-input.js');
|
|
32
|
+
const { StripeAPI } = require('./lib/stripe-api.js');
|
|
33
|
+
const { PayPalAPI } = require('./lib/paypal-api.js');
|
|
34
|
+
const { ChargebeeAPI } = require('./lib/chargebee-api.js');
|
|
35
|
+
const { paidProducts } = require('./lib/payment-utils.js');
|
|
36
|
+
const { providerSetupFlow } = require('./lib/provider-setup.js');
|
|
37
|
+
|
|
38
|
+
const PROVIDERS = ['paypal', 'stripe', 'chargebee', 'coinbase'];
|
|
39
|
+
|
|
40
|
+
module.exports.run = createServiceRunner({
|
|
41
|
+
serviceDir: __dirname,
|
|
42
|
+
setup: async (context) => {
|
|
43
|
+
const payment = context.brandConfig.payment || {};
|
|
44
|
+
|
|
45
|
+
if (payment.enabled === false) {
|
|
46
|
+
return { skip: true, reason: 'payment.enabled = false' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const url = (context.brandConfig.brand?.url || '').replace(/^https?:\/\//, '').replace(/\/$/, '');
|
|
50
|
+
if (!url) {
|
|
51
|
+
return { skip: true, reason: 'no brand.url configured' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (paidProducts(context.brandConfig).length === 0) {
|
|
55
|
+
return { skip: true, reason: 'no paid products in payment.products' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const providerFilter = context.options?.provider || null;
|
|
59
|
+
if (providerFilter && !PROVIDERS.includes(providerFilter)) {
|
|
60
|
+
return { skip: true, reason: `unknown --provider "${providerFilter}" (paypal | stripe | chargebee | coinbase)` };
|
|
61
|
+
}
|
|
62
|
+
const wanted = (name) => !providerFilter || providerFilter === name;
|
|
63
|
+
|
|
64
|
+
// Resolve each provider; when an enabled one is missing credentials,
|
|
65
|
+
// interactive runs offer the setup flow (which lands them in
|
|
66
|
+
// omega.json5 + the brand .env + process.env) before giving up.
|
|
67
|
+
// Unconfigured ones stay null and their operations print a dim note.
|
|
68
|
+
// Tests inject fake clients via context.stripeApi / paypalApi /
|
|
69
|
+
// chargebeeApi (injected clients never trigger the flow).
|
|
70
|
+
const providers = () => context.brandConfig.payment.providers || {};
|
|
71
|
+
|
|
72
|
+
let stripeApi = null;
|
|
73
|
+
if (wanted('stripe') && providers().stripe !== false) {
|
|
74
|
+
if (!context.stripeApi && (!process.env.STRIPE_SECRET_KEY || !providers().stripe?.publishableKey)) {
|
|
75
|
+
await providerSetupFlow(context, 'stripe');
|
|
76
|
+
}
|
|
77
|
+
if (providers().stripe !== false) {
|
|
78
|
+
stripeApi = context.stripeApi
|
|
79
|
+
|| (process.env.STRIPE_SECRET_KEY ? new StripeAPI(process.env.STRIPE_SECRET_KEY) : null);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let paypalApi = null;
|
|
84
|
+
if (wanted('paypal') && providers().paypal !== false) {
|
|
85
|
+
if (!context.paypalApi && (!providers().paypal?.clientId || !process.env.PAYPAL_CLIENT_SECRET)) {
|
|
86
|
+
await providerSetupFlow(context, 'paypal');
|
|
87
|
+
}
|
|
88
|
+
const clientId = providers().paypal !== false ? providers().paypal?.clientId : null;
|
|
89
|
+
paypalApi = context.paypalApi
|
|
90
|
+
|| (clientId && process.env.PAYPAL_CLIENT_SECRET ? new PayPalAPI(clientId, process.env.PAYPAL_CLIENT_SECRET) : null);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let chargebeeApi = null;
|
|
94
|
+
if (wanted('chargebee') && providers().chargebee !== false) {
|
|
95
|
+
if (!context.chargebeeApi && (!providers().chargebee?.site || !process.env.CHARGEBEE_API_KEY)) {
|
|
96
|
+
await providerSetupFlow(context, 'chargebee');
|
|
97
|
+
}
|
|
98
|
+
const site = providers().chargebee !== false ? providers().chargebee?.site : null;
|
|
99
|
+
chargebeeApi = context.chargebeeApi
|
|
100
|
+
|| (site && process.env.CHARGEBEE_API_KEY ? new ChargebeeAPI(site, process.env.CHARGEBEE_API_KEY) : null);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Coinbase Commerce (crypto, [#642](https://github.com/Omega-JS-Stack/omega/issues/642))
|
|
104
|
+
// has NO operations and therefore no API client: a charge is created ad hoc
|
|
105
|
+
// at checkout, there is no product catalog to reconcile, and the webhook
|
|
106
|
+
// endpoint is set by hand in its dashboard (the API manages none). Its whole
|
|
107
|
+
// manage-time surface is the CREDENTIAL, so the shared setup contract asks
|
|
108
|
+
// for it right here (#608) and nothing below reads it.
|
|
109
|
+
//
|
|
110
|
+
// Gated on the provider being switched ON, because `enabled` IS the whole
|
|
111
|
+
// switch for this one (there is no public datum to gate on — the API key is
|
|
112
|
+
// the entire credential): a brand that never turned crypto on is never asked
|
|
113
|
+
// for a key it has no use for.
|
|
114
|
+
if (wanted('coinbase') && providers().coinbase?.enabled === true && !process.env.COINBASE_COMMERCE_API_KEY) {
|
|
115
|
+
await providerSetupFlow(context, 'coinbase');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!stripeApi && !paypalApi && !chargebeeApi) {
|
|
119
|
+
return {
|
|
120
|
+
skip: true,
|
|
121
|
+
reason: providerFilter === 'coinbase'
|
|
122
|
+
// Asked for above and that is all there is: crypto reconciles nothing
|
|
123
|
+
? 'coinbase has nothing to reconcile — Coinbase Commerce has no product catalog and no webhook API; its key is the whole surface'
|
|
124
|
+
: providerFilter
|
|
125
|
+
? `provider "${providerFilter}" not configured`
|
|
126
|
+
: 'no payment provider configured (STRIPE_SECRET_KEY, paypal.clientId + PAYPAL_CLIENT_SECRET, or chargebee.site + CHARGEBEE_API_KEY)',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// The webhook key is OMEGA's OWN (#635): the shared helper mints it in
|
|
131
|
+
// place when this service runs before the workspace one did, so every
|
|
132
|
+
// webhook operation below can read it unconditionally.
|
|
133
|
+
const gate = await requestServiceInput(context, serviceInputSpec('payment', { names: ['OMEGA_WEBHOOK_KEY'] }));
|
|
134
|
+
if (gate) return gate;
|
|
135
|
+
|
|
136
|
+
// Stripe account ID → state (dashboard deep-links for the radar/disputes
|
|
137
|
+
// guidance; omega-manager's bookmark service reads it too). Non-fatal.
|
|
138
|
+
let serviceData;
|
|
139
|
+
if (stripeApi) {
|
|
140
|
+
try {
|
|
141
|
+
const account = await stripeApi.getAccount();
|
|
142
|
+
serviceData = { stripeAccountId: account.id };
|
|
143
|
+
} catch {
|
|
144
|
+
// Deep-links fall back to the account-less dashboard URLs
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Narrow operations to the filtered provider (operation names are prefixed)
|
|
149
|
+
let operations;
|
|
150
|
+
if (providerFilter) {
|
|
151
|
+
operations = context.operations.filter((op) => op.name.startsWith(providerFilter));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
stripeApi,
|
|
156
|
+
paypalApi,
|
|
157
|
+
chargebeeApi,
|
|
158
|
+
domain: url,
|
|
159
|
+
serviceData,
|
|
160
|
+
operations,
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
});
|