From fe3a225f8f43b163dc221adf1e42f8e71a076abc Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Tue, 28 Feb 2023 14:38:31 -0500 Subject: [PATCH] Add `billing-contact` config option --- cmd/serve.go | 3 +++ docs/config.md | 5 ++++ docs/releases.md | 1 + server/config.go | 1 + server/server.go | 1 + server/server.yml | 3 +++ server/types.go | 1 + web/public/config.js | 3 ++- web/public/static/langs/en.json | 2 ++ web/src/components/UpgradeDialog.js | 38 ++++++++++++++++++++++++----- 10 files changed, 51 insertions(+), 7 deletions(-) diff --git a/cmd/serve.go b/cmd/serve.go index b9806bd..f869cd4 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -84,6 +84,7 @@ var flagsServe = append( altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "stripe-secret-key", Aliases: []string{"stripe_secret_key"}, EnvVars: []string{"NTFY_STRIPE_SECRET_KEY"}, Value: "", Usage: "key used for the Stripe API communication, this enables payments"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "stripe-webhook-key", Aliases: []string{"stripe_webhook_key"}, EnvVars: []string{"NTFY_STRIPE_WEBHOOK_KEY"}, Value: "", Usage: "key required to validate the authenticity of incoming webhooks from Stripe"}), + altsrc.NewStringFlag(&cli.StringFlag{Name: "billing-contact", Aliases: []string{"billing_contact"}, EnvVars: []string{"NTFY_BILLING_CONTACT"}, Value: "", Usage: "e-mail or website to display in upgrade dialog (only if payments are enabled)"}), ) var cmdServe = &cli.Command{ @@ -159,6 +160,7 @@ func execServe(c *cli.Context) error { behindProxy := c.Bool("behind-proxy") stripeSecretKey := c.String("stripe-secret-key") stripeWebhookKey := c.String("stripe-webhook-key") + billingContact := c.String("billing-contact") // Check values if firebaseKeyFile != "" && !util.FileExists(firebaseKeyFile) { @@ -305,6 +307,7 @@ func execServe(c *cli.Context) error { conf.BehindProxy = behindProxy conf.StripeSecretKey = stripeSecretKey conf.StripeWebhookKey = stripeWebhookKey + conf.BillingContact = billingContact conf.EnableWeb = enableWeb conf.EnableSignup = enableSignup conf.EnableLogin = enableLogin diff --git a/docs/config.md b/docs/config.md index c54377e..59ee30c 100644 --- a/docs/config.md +++ b/docs/config.md @@ -839,6 +839,8 @@ config options: enables payments in the ntfy web app (e.g. Upgrade dialog). See [API keys](https://dashboard.stripe.com/apikeys). * `stripe-webhook-key` is the key required to validate the authenticity of incoming webhooks from Stripe. Webhooks are essential to keep the local database in sync with the payment provider. See [Webhooks](https://dashboard.stripe.com/webhooks). +* `billing-contact` is an email address or website displayed in the "Upgrade tier" dialog to let people reach + out with billing questions. If unset, nothing will be displayed. In addition to setting these two options, you also need to define a [Stripe webhook](https://dashboard.stripe.com/webhooks) for the `customer.subscription.updated` and `customer.subscription.deleted` event, which points @@ -849,6 +851,7 @@ Here's an example: ``` yaml stripe-secret-key: "sk_test_ZmhzZGtmbGhkc2tqZmhzYcO2a2hmbGtnaHNkbGtnaGRsc2hnbG" stripe-webhook-key: "whsec_ZnNkZnNIRExBSFNES0hBRFNmaHNka2ZsaGR" +billing-contact: "phil@example.com" ``` ## Rate limiting @@ -1194,6 +1197,7 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`). | `enable-reservations` | `NTFY_ENABLE_RESERVATIONS` | *boolean* (`true` or `false`) | `false` | Allows users to reserve topics (if their tier allows it) | | `stripe-secret-key` | `NTFY_STRIPE_SECRET_KEY` | *string* | - | Payments: Key used for the Stripe API communication, this enables payments | | `stripe-webhook-key` | `NTFY_STRIPE_WEBHOOK_KEY` | *string* | - | Payments: Key required to validate the authenticity of incoming webhooks from Stripe | +| `billing-contact` | `NTFY_BILLING_CONTACT` | *email address* or *website* | - | Payments: Email or website displayed in Upgrade dialog as a billing contact | The format for a *duration* is: `(smh)`, e.g. 30s, 20m or 1h. The format for a *size* is: `(GMK)`, e.g. 1G, 200M or 4000k. @@ -1277,6 +1281,7 @@ OPTIONS: --behind-proxy, --behind_proxy, -P if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting) (default: false) [$NTFY_BEHIND_PROXY] --stripe-secret-key value, --stripe_secret_key value key used for the Stripe API communication, this enables payments [$NTFY_STRIPE_SECRET_KEY] --stripe-webhook-key value, --stripe_webhook_key value key required to validate the authenticity of incoming webhooks from Stripe [$NTFY_STRIPE_WEBHOOK_KEY] + --billing-contact value, --billing_contact value e-mail or website to display in upgrade dialog (only if payments are enabled) [$NTFY_BILLING_CONTACT] --help, -h show help (default: false) ``` diff --git a/docs/releases.md b/docs/releases.md index eca56ef..4697937 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -21,6 +21,7 @@ are no closed-source features. So if you'd like to run your own server, you can! * Upgrade dialog: Disable submit button for free tier (no ticket) * Allow multiple `log-level-overrides` on the same field (no ticket) * Actually remove `ntfy publish --env-topic` flag (as per [deprecations](deprecations.md), no ticket) +* Added `billing-contact` config option (no ticket) ## ntfy server v2.1.0 Released February 25, 2023 diff --git a/server/config.go b/server/config.go index 04ac00b..b29fb06 100644 --- a/server/config.go +++ b/server/config.go @@ -128,6 +128,7 @@ type Config struct { StripeSecretKey string StripeWebhookKey string StripePriceCacheDuration time.Duration + BillingContact string EnableWeb bool EnableSignup bool // Enable creation of accounts via API and UI EnableLogin bool diff --git a/server/server.go b/server/server.go index c849c56..c1eab9b 100644 --- a/server/server.go +++ b/server/server.go @@ -485,6 +485,7 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi EnableSignup: s.config.EnableSignup, EnablePayments: s.config.StripeSecretKey != "", EnableReservations: s.config.EnableReservations, + BillingContact: s.config.BillingContact, DisallowedTopics: s.config.DisallowedTopics, } b, err := json.MarshalIndent(response, "", " ") diff --git a/server/server.yml b/server/server.yml index 91ae8b0..cb50633 100644 --- a/server/server.yml +++ b/server/server.yml @@ -240,9 +240,12 @@ # enables payments in the ntfy web app (e.g. Upgrade dialog). See https://dashboard.stripe.com/apikeys. # - stripe-webhook-key is the key required to validate the authenticity of incoming webhooks from Stripe. # Webhooks are essential up keep the local database in sync with the payment provider. See https://dashboard.stripe.com/webhooks. +# - billing-contact is an email address or website displayed in the "Upgrade tier" dialog to let people reach +# out with billing questions. If unset, nothing will be displayed. # # stripe-secret-key: # stripe-webhook-key: +# billing-contact: # Logging options # diff --git a/server/types.go b/server/types.go index cd91ecc..b11424f 100644 --- a/server/types.go +++ b/server/types.go @@ -341,6 +341,7 @@ type apiConfigResponse struct { EnableSignup bool `json:"enable_signup"` EnablePayments bool `json:"enable_payments"` EnableReservations bool `json:"enable_reservations"` + BillingContact string `json:"billing_contact"` DisallowedTopics []string `json:"disallowed_topics"` } diff --git a/web/public/config.js b/web/public/config.js index 264eda1..30da691 100644 --- a/web/public/config.js +++ b/web/public/config.js @@ -6,11 +6,12 @@ // During web development, you may change values here for rapid testing. var config = { - base_url: "https://127.0.0.1", // to test against a different server + base_url: window.location.origin, // Change to test against a different server app_root: "/app", enable_login: true, enable_signup: true, enable_payments: true, enable_reservations: true, + billing_contact: "", disallowed_topics: ["docs", "static", "file", "app", "account", "settings", "signup", "login", "v1"] }; diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json index 06fe139..babdd1d 100644 --- a/web/public/static/langs/en.json +++ b/web/public/static/langs/en.json @@ -236,6 +236,8 @@ "account_upgrade_dialog_tier_price_billed_yearly": "{{price}} billed annually. Save {{save}}.", "account_upgrade_dialog_tier_selected_label": "Selected", "account_upgrade_dialog_tier_current_label": "Current", + "account_upgrade_dialog_billing_contact_email": "For billing questions, please contact us directly.", + "account_upgrade_dialog_billing_contact_website": "For billing questions, please refer to our website.", "account_upgrade_dialog_button_cancel": "Cancel", "account_upgrade_dialog_button_redirect_signup": "Sign up now", "account_upgrade_dialog_button_pay_now": "Pay now and subscribe", diff --git a/web/src/components/UpgradeDialog.js b/web/src/components/UpgradeDialog.js index 6937e31..43be16f 100644 --- a/web/src/components/UpgradeDialog.js +++ b/web/src/components/UpgradeDialog.js @@ -3,9 +3,8 @@ import {useContext, useEffect, useState} from 'react'; import Dialog from '@mui/material/Dialog'; import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; -import {Alert, Badge, CardActionArea, CardContent, Chip, ListItem, Stack, Switch, useMediaQuery} from "@mui/material"; +import {Alert, CardActionArea, CardContent, Chip, Link, ListItem, Switch, useMediaQuery} from "@mui/material"; import theme from "./theme"; -import DialogFooter from "./DialogFooter"; import Button from "@mui/material/Button"; import accountApi, {SubscriptionInterval} from "../app/AccountApi"; import session from "../app/Session"; @@ -22,6 +21,8 @@ import ListItemText from "@mui/material/ListItemText"; import Box from "@mui/material/Box"; import {NavLink} from "react-router-dom"; import {UnauthorizedError} from "../app/errors"; +import DialogContentText from "@mui/material/DialogContentText"; +import DialogActions from "@mui/material/DialogActions"; const UpgradeDialog = (props) => { const { t } = useTranslation(); @@ -204,10 +205,35 @@ const UpgradeDialog = (props) => { } - - - - + + + {config.billing_contact.indexOf('@') !== -1 && + <> }}/>{" "} + } + {config.billing_contact.match(`^http?s://`) && + <> }}/>{" "} + } + {error} + + + + + + ); };