diff --git a/automation/roles/bubble/files/bubble_role.json b/automation/roles/bubble/files/bubble_role.json index fbf07007..5e586e28 100644 --- a/automation/roles/bubble/files/bubble_role.json +++ b/automation/roles/bubble/files/bubble_role.json @@ -10,6 +10,7 @@ {"name": "public_base_uri", "value": "[[publicBaseUri]]"}, {"name": "sage_node", "value": "[[sageNode]]"}, {"name": "install_type", "value": "[[installType]]"}, + {"name": "promo_code_policy", "value": "[[#compare fork '==' true]][[configuration.promoCodePolicy]][[else]]disabled[[/compare]]"}, {"name": "default_locale", "value": "[[network.locale]]"}, {"name": "time_zone", "value": "[[network.timezone]]"}, {"name": "bubble_version", "value": "[[configuration.version]]"}, diff --git a/automation/roles/bubble/templates/bubble.env.j2 b/automation/roles/bubble/templates/bubble.env.j2 index a381c638..fd38359b 100644 --- a/automation/roles/bubble/templates/bubble.env.j2 +++ b/automation/roles/bubble/templates/bubble.env.j2 @@ -2,6 +2,7 @@ export PUBLIC_BASE_URI={{ public_base_uri }} export BUBBLE_ASSETS_DIR=/home/bubble/site export SELF_NODE={{ node_uuid }} export SAGE_NODE={{ sage_node }} +export BUBBLE_PROMO_CODE_POLICY={{ promo_code_policy }} export LETSENCRYPT_EMAIL={{ letsencrypt_email }} export BUBBLE_SERVER_PORT={{ admin_port }} export BUBBLE_TEST_MODE={{ test_mode }} diff --git a/bubble-server/src/main/java/bubble/auth/PromoCodePolicy.java b/bubble-server/src/main/java/bubble/auth/PromoCodePolicy.java new file mode 100644 index 00000000..4df1ca87 --- /dev/null +++ b/bubble-server/src/main/java/bubble/auth/PromoCodePolicy.java @@ -0,0 +1,17 @@ +package bubble.auth; + +import com.fasterxml.jackson.annotation.JsonCreator; + +import static bubble.ApiConstants.enumFromString; + +public enum PromoCodePolicy { + + disabled, optional, required; + + @JsonCreator public static PromoCodePolicy fromString (String v) { return enumFromString(PromoCodePolicy.class, v); } + + public boolean disabled () { return this == disabled; } + public boolean enabled () { return !disabled(); } + public boolean required () { return this == required; } + +} diff --git a/bubble-server/src/main/java/bubble/model/account/AccountRegistration.java b/bubble-server/src/main/java/bubble/model/account/AccountRegistration.java index 8bfae8de..94ccc098 100644 --- a/bubble-server/src/main/java/bubble/model/account/AccountRegistration.java +++ b/bubble-server/src/main/java/bubble/model/account/AccountRegistration.java @@ -3,11 +3,14 @@ package bubble.model.account; import lombok.Getter; import lombok.Setter; +import static org.cobbzilla.util.daemon.ZillaRuntime.empty; + public class AccountRegistration extends Account { @Getter @Setter private String password; @Getter @Setter private String promoCode; + public boolean hasPromoCode () { return !empty(promoCode); } @Getter @Setter private AccountContact contact; public boolean hasContact () { return contact != null; } diff --git a/bubble-server/src/main/java/bubble/resources/account/AuthResource.java b/bubble-server/src/main/java/bubble/resources/account/AuthResource.java index b9f0f117..b541e1e7 100644 --- a/bubble-server/src/main/java/bubble/resources/account/AuthResource.java +++ b/bubble-server/src/main/java/bubble/resources/account/AuthResource.java @@ -208,7 +208,11 @@ public class AuthResource { if (!planDAO.getSupportedCurrencies().contains(currency)) { currency = currencyForLocale(getDEFAULT_LOCALE()); } - errors.addAll(promoService.validatePromotions(request.getPromoCode(), currency)); + if (configuration.promoCodeRequired() && !request.hasPromoCode()) { + errors.addViolation("err.promoCode.required"); + } else { + errors.addAll(promoService.validatePromotions(request.getPromoCode(), currency)); + } } if (errors.isInvalid()) return invalid(errors); diff --git a/bubble-server/src/main/java/bubble/server/BubbleConfiguration.java b/bubble-server/src/main/java/bubble/server/BubbleConfiguration.java index 20201248..d936807b 100644 --- a/bubble-server/src/main/java/bubble/server/BubbleConfiguration.java +++ b/bubble-server/src/main/java/bubble/server/BubbleConfiguration.java @@ -2,6 +2,7 @@ package bubble.server; import bubble.ApiConstants; import bubble.BubbleHandlebars; +import bubble.auth.PromoCodePolicy; import bubble.client.BubbleApiClient; import bubble.cloud.CloudServiceDriver; import bubble.dao.account.AccountDAO; @@ -77,6 +78,7 @@ public class BubbleConfiguration extends PgRestServerConfiguration public static final String TAG_CLOUD_CONFIGS = "cloudConfigs"; public static final String TAG_LOCKED = "locked"; public static final String TAG_SSL_PORT = "sslPort"; + public static final String TAG_PROMO_CODE_POLICY = "promoCodePolicy"; public static final String DEFAULT_LOCAL_STORAGE_DIR = HOME_DIR + "/.bubble_local_storage"; @@ -256,6 +258,11 @@ public class BubbleConfiguration extends PgRestServerConfiguration .map(Class::getName) .collect(Collectors.toList()); + @Getter @Setter private PromoCodePolicy promoCodePolicy = PromoCodePolicy.disabled; + public boolean promoCodesEnabled () { return isSageLauncher() && promoCodePolicy.enabled(); } + public boolean promoCodesDisabled () { return !isSageLauncher() || promoCodePolicy.disabled(); } + public boolean promoCodeRequired () { return isSageLauncher() && promoCodePolicy.required(); } + private final AtomicReference> publicSystemConfigs = new AtomicReference<>(); public Map getPublicSystemConfigs () { synchronized (publicSystemConfigs) { @@ -270,6 +277,7 @@ public class BubbleConfiguration extends PgRestServerConfiguration {TAG_NETWORK_UUID, thisNetwork == null ? null : thisNetwork.getUuid()}, {TAG_SAGE_LAUNCHER, thisNetwork == null || isSageLauncher()}, {TAG_PAYMENTS_ENABLED, cloudDAO.paymentsEnabled()}, + {TAG_PROMO_CODE_POLICY, getPromoCodePolicy().name()}, {TAG_CLOUD_DRIVERS, getCloudDriverClasses()}, {TAG_ENTITY_CLASSES, getSortedSimpleEntityClassMap()}, {TAG_LOCALES, getAllLocales()}, diff --git a/bubble-server/src/main/java/bubble/service/bill/PromotionService.java b/bubble-server/src/main/java/bubble/service/bill/PromotionService.java index 55037071..58d46653 100644 --- a/bubble-server/src/main/java/bubble/service/bill/PromotionService.java +++ b/bubble-server/src/main/java/bubble/service/bill/PromotionService.java @@ -28,7 +28,8 @@ import static bubble.model.bill.Promotion.SORT_PAYMENT_METHOD_CTIME; import static org.cobbzilla.util.daemon.ZillaRuntime.empty; import static org.cobbzilla.util.daemon.ZillaRuntime.shortError; import static org.cobbzilla.util.json.JsonUtil.json; -import static org.cobbzilla.wizard.resources.ResourceUtil.*; +import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; +import static org.cobbzilla.wizard.resources.ResourceUtil.notFoundEx; import static org.cobbzilla.wizard.server.RestServerBase.reportError; @Service @Slf4j @@ -43,6 +44,10 @@ public class PromotionService { @Autowired private BubbleConfiguration configuration; public void applyPromotions(Account account, String code, String currency) { + if (configuration.promoCodesDisabled()) { + log.info("applyPromotions: promotions disabled, not applying any"); + return; + } // apply promo code (or default) promotion final Set promos = new TreeSet<>(); ReferralCode referralCode = null; @@ -107,6 +112,9 @@ public class PromotionService { public ValidationResult validatePromotions(String code, String currency) { if (!empty(code)) { + if (configuration.promoCodesDisabled()) { + return new ValidationResult("err.promoCode.disabled"); + } Promotion promo = promotionDAO.findEnabledAndActiveWithCode(code, currency); if (promo == null) { // it might be a referral code @@ -148,6 +156,10 @@ public class PromotionService { PaymentServiceDriver paymentDriver, List promos, long chargeAmount) { + if (configuration.promoCodesDisabled()) { + log.warn("usePromotions: promo codes are disabled, not using"); + return chargeAmount; + } if (chargeAmount <= 0) { log.error("usePromotions: chargeAmount <= 0 : "+chargeAmount); return chargeAmount; diff --git a/bubble-server/src/main/resources/bubble-config.yml b/bubble-server/src/main/resources/bubble-config.yml index 0f02a66c..e5633465 100644 --- a/bubble-server/src/main/resources/bubble-config.yml +++ b/bubble-server/src/main/resources/bubble-config.yml @@ -75,6 +75,8 @@ localStorageDir: {{LOCALSTORAGE_BASE_DIR}} disallowedCountries: {{DISALLOWED_COUNTRIES}} +promoCodePolicy: {{#exists BUBBLE_PROMO_CODE_POLICY}}{{BUBBLE_PROMO_CODE_POLICY}}{{else}}disabled{{/exists}} + rateLimits: - { limit: 500, interval: 3s, block: 5m } - { limit: 2000, interval: 1m, block: 5m } diff --git a/bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties b/bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties index 14da6ef4..803a13c2 100644 --- a/bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties +++ b/bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties @@ -655,10 +655,6 @@ err.price.invalid=Price is invalid err.price.length=Price is too long err.privateKeyHash.length=Private key hash is too long err.privateKey.length=Private key is too long -err.promoCode.notFound=Promo code was not found -err.promoCode.notActive=Promotion is no longer available -err.promoCode.configurationError=Promotion was not set up correctly -err.promoCode.notApplied=The promotion code is not applicable to this purchase err.purchase.accountMismatch=Invalid payment err.purchase.amountMismatch=Payment amount does not match billed amount err.purchase.authNotFound=Payment authorization was not found diff --git a/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties b/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties index f84c6b6c..a368f8a4 100644 --- a/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties +++ b/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties @@ -239,6 +239,7 @@ field_label_unlock_key=Unlock Key form_title_register=Register field_label_contactType=Contact Type field_label_email=Email +field_label_promoCode=Promo Code field_label_sms=SMS Phone field_label_receiveInformationalMessages=Receive informational messages about your Bubble field_label_receivePromotionalMessages=Receive news about Bubble, including new releases and new features @@ -247,6 +248,14 @@ button_label_register=Register button_label_cancel=Cancel alert_registration_success=Registration successful +# Promo code errors +err.promoCode.required=Promo codes is required +err.promoCode.disabled=Promo codes are disabled +err.promoCode.notFound=Promo code was not found +err.promoCode.notActive=Promotion is no longer available +err.promoCode.configurationError=Promotion was not set up correctly +err.promoCode.notApplied=The promotion code is not applicable here + # Logout messages err.logout.noSession=Not logged in message_logging_out=Logging out... diff --git a/bubble-server/src/test/resources/test-bubble-config.yml b/bubble-server/src/test/resources/test-bubble-config.yml index 0ea53a51..a0cb5a5e 100644 --- a/bubble-server/src/test/resources/test-bubble-config.yml +++ b/bubble-server/src/test/resources/test-bubble-config.yml @@ -71,3 +71,5 @@ letsencryptEmail: {{LETSENCRYPT_EMAIL}} localStorageDir: {{LOCALSTORAGE_BASE_DIR}} disallowedCountries: {{DISALLOWED_COUNTRIES}} + +promoCodePolicy: {{#exists BUBBLE_PROMO_CODE_POLICY}}{{BUBBLE_PROMO_CODE_POLICY}}{{else}}optional{{/exists}} diff --git a/bubble-web b/bubble-web index 6f741c7c..a3713776 160000 --- a/bubble-web +++ b/bubble-web @@ -1 +1 @@ -Subproject commit 6f741c7c3905d9245692454d7bdd99ae6aabf667 +Subproject commit a3713776cbdb0ac9f3a82a62bf7ae7ee027e80a7