diff --git a/bubble-server/src/main/java/bubble/cloud/payment/PaymentDriverBase.java b/bubble-server/src/main/java/bubble/cloud/payment/PaymentDriverBase.java index 27352f5e..39ed1ac0 100644 --- a/bubble-server/src/main/java/bubble/cloud/payment/PaymentDriverBase.java +++ b/bubble-server/src/main/java/bubble/cloud/payment/PaymentDriverBase.java @@ -14,8 +14,8 @@ import java.util.Map; import java.util.TreeMap; import static bubble.model.bill.PaymentMethodType.promotional_credit; -import static org.cobbzilla.util.daemon.ZillaRuntime.*; -import static org.cobbzilla.wizard.model.IdentifiableBase.CTIME_ASC; +import static org.cobbzilla.util.daemon.ZillaRuntime.die; +import static org.cobbzilla.util.daemon.ZillaRuntime.shortError; import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; @Slf4j @@ -70,47 +70,6 @@ public abstract class PaymentDriverBase extends CloudServiceDriverBase imp return bill; } - protected long applyPromotions(String accountPlanUuid, AccountPaymentMethod paymentMethod, long price) { - // cannot apply a promotion to a promotion -- should never happen - if (getPaymentMethodType() == promotional_credit) { - log.warn("applyPromotions: cannot apply promotions to a promotion"); - return price; - } - - final List promos = paymentMethodDAO.findByAccountAndPromoAndNotDeleted(paymentMethod.getAccount()); - if (!empty(promos)) { - // sort oldest first, this ensures default promotions (like first month free) get applied before referral promotions - promos.sort(CTIME_ASC); - Promotion selectedPromo = null; - for (AccountPaymentMethod apm : promos) { - final Promotion promo = promotionDAO.findByUuid(apm.getPromotion()); - if (promo != null && promo.active()) { - - } - } - } - - - final Bill bill = billDAO.findOldestUnpaidBillByAccountPlan(accountPlanUuid); - final long creditApplied; - if (bill == null) { - log.warn("No unpaid bills for account "+paymentMethod.getAccount()+" and accountPlanUuid="+accountPlanUuid+", no credit applied"); - creditApplied = 0; - } else { - final AccountPayment promoPayment = accountPaymentDAO.findByAccountAndAccountPlanAndBillAndCreditAppliedSuccess(paymentMethod.getAccount(), accountPlanUuid, bill.getUuid()); - if (promoPayment != null) { - creditApplied = promoPayment.getAmount(); - } else { - creditApplied = 0; - } - } - if (creditApplied >= price) { - log.info("getChargeAmount: credit applied ("+creditApplied+") exceeds price "+price+", no charge due"); - return 0; - } - return price - creditApplied; - } - @Override public boolean authorize(BubblePlan plan, String accountPlanUuid, AccountPaymentMethod paymentMethod) { return true; } diff --git a/bubble-server/src/main/java/bubble/cloud/payment/firstMonthFree/FirstMonthFreePaymentDriver.java b/bubble-server/src/main/java/bubble/cloud/payment/firstMonthFree/FirstMonthFreePaymentDriver.java index 03536180..db3b3d2d 100644 --- a/bubble-server/src/main/java/bubble/cloud/payment/firstMonthFree/FirstMonthFreePaymentDriver.java +++ b/bubble-server/src/main/java/bubble/cloud/payment/firstMonthFree/FirstMonthFreePaymentDriver.java @@ -61,6 +61,7 @@ public class FirstMonthFreePaymentDriver extends PaymentDriverBase setupDone = new AtomicReference<>(null); @Override public void postSetup() { diff --git a/bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java b/bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java index d935195e..181f2c2c 100644 --- a/bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java +++ b/bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java @@ -224,7 +224,11 @@ public class AccountPlansResource extends AccountOwnedResource parts = splitAndTrim(before.substring(FAST_FORWARD_AND_BILL.length()), " "); final long delta = parseDuration(parts.get(0)); final long sleepTime = parts.size() > 1 ? parseDuration(parts.get(1)) : DEFAULT_BILLING_SLEEP; + configuration.autowire(new StripePaymentDriver()).flushCaches(); incrementSystemTimeOffset(delta); configuration.getBean(BillingService.class).processBilling(); sleep(sleepTime, "waiting for BillingService to complete"); diff --git a/bubble-server/src/test/resources/models/tests/promo/first_month_free.json b/bubble-server/src/test/resources/models/tests/promo/first_month_free.json index aefd9a51..cbd55334 100644 --- a/bubble-server/src/test/resources/models/tests/promo/first_month_free.json +++ b/bubble-server/src/test/resources/models/tests/promo/first_month_free.json @@ -98,6 +98,26 @@ { "before": "sleep 15s", + "comment": "start the network", + "request": { + "uri": "me/networks/{{accountPlan.network}}/actions/start?cloud=MockCompute®ion=nyc_mock", + "method": "post" + }, + "response": { + "store": "newNetworkNotification" + } + }, + + { + "before": "sleep 5s", + "comment": "verify the network is running", + "request": { "uri": "me/networks/{{accountPlan.network}}" }, + "response": { + "check": [ {"condition": "json.getState().name() == 'running'"} ] + } + }, + + { "comment": "list all account payment methods, should be two", "request": { "uri": "me/paymentMethods?all=true" }, "response": { @@ -203,5 +223,217 @@ {"condition": "json[0].getBillObject().getStatus().name() === 'paid'"} ] } + }, + + { + "comment": "add second plan, using same payment method, does NOT apply 1mo free promo because it's already been used", + "request": { + "uri": "me/plans", + "method": "put", + "entity": { + "name": "test-net2-{{rand 5}}", + "domain": "{{defaultDomain}}", + "locale": "en_US", + "timezone": "EST", + "plan": "{{plans.[0].name}}", + "footprint": "US", + "paymentMethodObject": { + "uuid": "{{find paymentMethods 'paymentMethodType' 'credit' 'uuid'}}" + } + } + }, + "response": { + "store": "accountPlan2" + } + }, + + { + "before": "sleep 15s", + "comment": "start the 2nd network", + "request": { + "uri": "me/networks/{{accountPlan2.network}}/actions/start?cloud=MockCompute®ion=nyc_mock", + "method": "post" + }, + "response": { + "store": "newNetworkNotification" + } + }, + + { + "before": "sleep 5s", + "comment": "verify the 2nd network is running", + "request": { "uri": "me/networks/{{accountPlan2.network}}" }, + "response": { + "check": [ {"condition": "json.getState().name() == 'running'"} ] + } + }, + + { + "comment": "list all account payment methods, should STILL be two", + "request": { "uri": "me/paymentMethods?all=true" }, + "response": { + "store": "paymentMethods", + "check": [ + {"condition": "json.length === 2"}, + {"condition": "_find(json, function(p) { return p.getPaymentMethodType().name() === 'credit'; }) !== null"}, + {"condition": "_find(json, function(p) { return p.getPaymentMethodType().name() === 'credit'; }).deleted() === false"}, + {"condition": "_find(json, function(p) { return p.getPaymentMethodType().name() === 'promotional_credit'; }) !== null"}, + {"condition": "_find(json, function(p) { return p.getPaymentMethodType().name() === 'promotional_credit'; }).deleted() === true"} + ] + } + }, + + { + "comment": "verify account plans, should now be two", + "request": { "uri": "me/plans" }, + "response": { + "check": [ + {"condition": "json.length === 2"}, + {"condition": "json[0].enabled()"}, + {"condition": "json[1].enabled()"} + ] + } + }, + + { + "comment": "verify 2nd account plan payment info", + "request": { "uri": "me/plans/{{accountPlan2.uuid}}/paymentMethod" }, + "response": { + "check": [ + {"condition": "json.getPaymentMethodType().name() === 'credit'"}, + {"condition": "json.getMaskedPaymentInfo() == 'XXXX-XXXX-XXXX-4242'"} + ] + } + }, + + { + "comment": "verify new bill exists for 2nd plan and was paid", + "request": { "uri": "me/bills" }, + "response": { + "check": [ + {"condition": "json.length === 2"}, + {"condition": "json[0].getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{accountPlan2.uuid}}'"}, + {"condition": "json[0].getQuantity() === 1"}, + {"condition": "json[0].getPrice() === {{plans.[0].price}}"}, + {"condition": "json[0].getTotal() === {{plans.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'paid'"} + ] + } + }, + + { + "comment": "verify bill exists for 2nd plan and is paid", + "request": { "uri": "me/plans/{{accountPlan2.uuid}}/bills" }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{accountPlan2.uuid}}'"}, + {"condition": "json[0].getQuantity() === 1"}, + {"condition": "json[0].getPrice() === {{plans.[0].price}}"}, + {"condition": "json[0].getTotal() === {{plans.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'paid'"} + ] + } + }, + + { + "comment": "verify payment for 2nd plan exists and is successful via credit card", + "request": { "uri": "me/payments" }, + "response": { + "check": [ + {"condition": "json.length === 2"}, + {"condition": "json[0].getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{accountPlan2.uuid}}'"}, + {"condition": "json[0].getAmount() === {{plans.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'success'"}, + {"condition": "json[0].getType().name() === 'payment'"}, + {"condition": "json[0].getPaymentMethod() === _find(paymentMethods, function(p) { return p.getPaymentMethodType().name() === 'credit'; }).getUuid()"} + ] + } + }, + + { + "comment": "verify payment for 2nd plan exists via plan and is successful via credit card", + "request": { "uri": "me/plans/{{accountPlan2.uuid}}/payments" }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{accountPlan2.uuid}}'"}, + {"condition": "json[0].getAmount() === {{plans.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'success'"}, + {"condition": "json[0].getType().name() === 'payment'"}, + {"condition": "json[0].getPaymentMethod() === _find(paymentMethods, function(p) { return p.getPaymentMethodType().name() === 'credit'; }).getUuid()"}, + {"condition": "json[0].getBillObject().getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getBillObject().getAccountPlan() === '{{accountPlan2.uuid}}'"}, + {"condition": "json[0].getBillObject().getQuantity() === 1"}, + {"condition": "json[0].getBillObject().getPrice() === {{plans.[0].price}}"}, + {"condition": "json[0].getBillObject().getTotal() === {{plans.[0].price}}"}, + {"condition": "json[0].getBillObject().getStatus().name() === 'paid'"} + ] + } + }, + + { + "before": "fast_forward_and_bill 31d 20s", + "comment": "fast-forward +31 days, verify a new bill exists for first accountPlan", + "request": { "uri": "me/plans/{{accountPlan.uuid}}/bills" }, + "response": { + "check": [ + {"condition": "json.length === 2"}, + {"condition": "json[0].getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{accountPlan.uuid}}'"}, + {"condition": "json[0].getQuantity() === 1"}, + {"condition": "json[0].getPrice() === {{plans.[0].price}}"}, + {"condition": "json[0].getTotal() === {{plans.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'paid'"} + ] + } + }, + + { + "comment": "Verify a successful payment for accountPlan has been made via credit card", + "request": { "uri": "me/plans/{{accountPlan.uuid}}/payments" }, + "response": { + "check": [ + {"condition": "json.length === 2"}, + {"condition": "json[0].getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{accountPlan.uuid}}'"}, + {"condition": "json[0].getAmount() === {{plans.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'success'"}, + {"condition": "json[0].getType().name() === 'payment'"}, + {"condition": "json[0].getPaymentMethod() === _find(paymentMethods, function(p) { return p.getPaymentMethodType().name() === 'credit'; }).getUuid()"}, + {"condition": "json[0].getBillObject().getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getBillObject().getAccountPlan() === '{{accountPlan.uuid}}'"}, + {"condition": "json[0].getBillObject().getQuantity() === 1"}, + {"condition": "json[0].getBillObject().getPrice() === {{plans.[0].price}}"}, + {"condition": "json[0].getBillObject().getTotal() === {{plans.[0].price}}"}, + {"condition": "json[0].getBillObject().getStatus().name() === 'paid'"} + ] + } + }, + + { + "comment": "Verify a successful payment for accountPlan2 has been made via credit card", + "request": { "uri": "me/plans/{{accountPlan2.uuid}}/payments" }, + "response": { + "check": [ + {"condition": "json.length === 2"}, + {"condition": "json[0].getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{accountPlan2.uuid}}'"}, + {"condition": "json[0].getAmount() === {{plans.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'success'"}, + {"condition": "json[0].getType().name() === 'payment'"}, + {"condition": "json[0].getPaymentMethod() === _find(paymentMethods, function(p) { return p.getPaymentMethodType().name() === 'credit'; }).getUuid()"}, + {"condition": "json[0].getBillObject().getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getBillObject().getAccountPlan() === '{{accountPlan2.uuid}}'"}, + {"condition": "json[0].getBillObject().getQuantity() === 1"}, + {"condition": "json[0].getBillObject().getPrice() === {{plans.[0].price}}"}, + {"condition": "json[0].getBillObject().getTotal() === {{plans.[0].price}}"}, + {"condition": "json[0].getBillObject().getStatus().name() === 'paid'"} + ] + } } ] \ No newline at end of file diff --git a/bubble-server/src/test/resources/test-bubble-config.yml b/bubble-server/src/test/resources/test-bubble-config.yml index 73b7c6d2..0ea53a51 100644 --- a/bubble-server/src/test/resources/test-bubble-config.yml +++ b/bubble-server/src/test/resources/test-bubble-config.yml @@ -60,7 +60,7 @@ jersey: redis: key: '{{#exists BUBBLE_REDIS_ENCRYPTION_KEY}}{{BUBBLE_REDIS_ENCRYPTION_KEY}}{{else}}{{key_file '.BUBBLE_REDIS_ENCRYPTION_KEY'}}{{/exists}}' - prefix: bubble_ + prefix: bubble errorApi: url: {{ERRBIT_URL}} diff --git a/utils/cobbzilla-wizard b/utils/cobbzilla-wizard index 0262f774..f7108ec2 160000 --- a/utils/cobbzilla-wizard +++ b/utils/cobbzilla-wizard @@ -1 +1 @@ -Subproject commit 0262f774014d5d6cea6451c0ad68dd697f8b9625 +Subproject commit f7108ec232850b7a6f1a0c81e93f16a44285b2dc