diff --git a/bubble-server/src/main/java/bubble/dao/bill/AccountPaymentMethodDAO.java b/bubble-server/src/main/java/bubble/dao/bill/AccountPaymentMethodDAO.java index 3010c11e..e55925b8 100644 --- a/bubble-server/src/main/java/bubble/dao/bill/AccountPaymentMethodDAO.java +++ b/bubble-server/src/main/java/bubble/dao/bill/AccountPaymentMethodDAO.java @@ -9,6 +9,7 @@ import bubble.model.bill.AccountPlan; import bubble.model.cloud.CloudService; import bubble.server.BubbleConfiguration; import lombok.extern.slf4j.Slf4j; +import org.hibernate.criterion.Order; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @@ -24,6 +25,8 @@ public class AccountPaymentMethodDAO extends AccountOwnedEntityDAO apm.getPaymentInfo().equals(paymentInfo)) diff --git a/bubble-server/src/main/java/bubble/dao/bill/PromotionDAO.java b/bubble-server/src/main/java/bubble/dao/bill/PromotionDAO.java index 114c85b9..91bac3a2 100644 --- a/bubble-server/src/main/java/bubble/dao/bill/PromotionDAO.java +++ b/bubble-server/src/main/java/bubble/dao/bill/PromotionDAO.java @@ -26,16 +26,16 @@ public class PromotionDAO extends AbstractCRUDDAO { } public Promotion findEnabledAndActiveWithCode(String code) { - return filterActive(findByUniqueFields("enabled", true, "code", code, "referral", false)); + return filterActive(findByUniqueFields("enabled", true, "code", code, "referral", false, "adminAssignOnly", false)); } public List findEnabledAndActiveWithNoCode() { - return filterActive(findByFields("enabled", true, "code", null, "referral", false)); + return filterActive(findByFields("enabled", true, "code", null, "referral", false, "adminAssignOnly", false)); } public List findVisibleAndEnabledAndActiveWithNoCodeOrWithCode(String code) { if (empty(code)) { - return filterActive(findByFields("enabled", true, "code", null, "visible", true)); + return filterActive(findByFields("enabled", true, "code", null, "visible", true, "adminAssignOnly", false)); } else { return filterActive(list(criteria().add(and( eq("enabled", true), @@ -45,7 +45,7 @@ public class PromotionDAO extends AbstractCRUDDAO { } public List findEnabledAndActiveWithReferral() { - return filterActive(findByFields("enabled", true, "referral", true)); + return filterActive(findByFields("enabled", true, "referral", true, "adminAssignOnly", false)); } public Promotion filterActive(Promotion promo) { return promo != null && promo.active() ? promo : null; } diff --git a/bubble-server/src/main/java/bubble/model/bill/Promotion.java b/bubble-server/src/main/java/bubble/model/bill/Promotion.java index 04b90cd9..84f78591 100644 --- a/bubble-server/src/main/java/bubble/model/bill/Promotion.java +++ b/bubble-server/src/main/java/bubble/model/bill/Promotion.java @@ -30,7 +30,9 @@ import static org.cobbzilla.util.reflect.ReflectionUtil.copy; public class Promotion extends IdentifiableBase implements NamedEntity, HasPriority, SqlViewSearchResult, Comparable { - public static final String[] UPDATE_FIELDS = {"priority", "enabled", "validFrom", "validTo"}; + public static final String[] UPDATE_FIELDS = { + "priority", "enabled", "visible", "adminAssignOnly", "validFrom", "validTo" + }; public static final String[] CREATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, "name", "code", "referral", "currency", "maxValue"); @@ -87,30 +89,36 @@ public class Promotion extends IdentifiableBase public boolean invisible() { return !visible(); } @ECSearchable @ECField(index=70) + @ECIndex @Column(nullable=false) + @Getter @Setter private Boolean adminAssignOnly = false; + public boolean adminAssignOnly() { return adminAssignOnly != null && adminAssignOnly; } + public boolean notAdminAssignOnly() { return !adminAssignOnly(); } + + @ECSearchable @ECField(index=80) @ECIndex @Getter @Setter private Long validFrom; public boolean hasStarted () { return validFrom == null || validFrom > now(); } - @ECSearchable @ECField(index=80) + @ECSearchable @ECField(index=90) @ECIndex @Getter @Setter private Long validTo; public boolean hasEnded () { return validTo != null && validTo > now(); } public boolean active () { return enabled() && hasStarted() && !hasEnded(); } public boolean inactive () { return !active(); } - @ECSearchable @ECField(index=90) + @ECSearchable @ECField(index=100) @ECIndex @Column(nullable=false) @Getter @Setter private Boolean referral = false; public boolean referral () { return referral != null && referral; } - @ECSearchable @ECField(index=100) + @ECSearchable @ECField(index=110) @ECIndex @Column(nullable=false, updatable=false, length=10) @Getter @Setter private String currency; - @ECSearchable @ECField(index=110) + @ECSearchable @ECField(index=120) @ECIndex @Column(nullable=false, updatable=false) @Getter @Setter private Integer minValue = 100; - @ECSearchable @ECField(index=120) + @ECSearchable @ECField(index=130) @ECIndex @Column(nullable=false, updatable=false) @Getter @Setter private Integer maxValue; 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 07bb735c..3eeb027f 100644 --- a/bubble-server/src/main/java/bubble/service/bill/PromotionService.java +++ b/bubble-server/src/main/java/bubble/service/bill/PromotionService.java @@ -157,8 +157,11 @@ public class PromotionService { return chargeAmount; } + promos.sort(Promotion::compareTo); + // find the payment cloud associated with the promo, defer to that final String accountPlanUuid = accountPlan.getUuid(); + final Set used = new HashSet<>(); for (Promotion promo : promos) { final AccountPaymentMethod apm = promo.getPaymentMethod(); final CloudService promoCloud = cloudDAO.findByUuid(promo.getCloud()); @@ -175,19 +178,18 @@ public class PromotionService { try { final PaymentServiceDriver promoPaymentDriver = promoCloud.getPaymentDriver(configuration); final PromotionalPaymentServiceDriver promoDriver = (PromotionalPaymentServiceDriver) promoPaymentDriver; - final Set usable = new HashSet<>(); - if (!promoDriver.canUseNow(bill, promo, promoDriver, promos, usable, accountPlan, paymentMethod)) { + if (!promoDriver.canUseNow(bill, promo, promoDriver, promos, used, accountPlan, paymentMethod)) { log.warn("purchase: Promotion "+promo.getName()+" cannot currently be used for accountPlan "+ accountPlanUuid); continue; } - usable.add(promo); promoDriver.purchase(accountPlanUuid, apm.getUuid(), bill.getUuid()); + used.add(promo); // verify AccountPayments exists for new payment with promo final List creditsApplied = accountPaymentDAO.findByAccountAndAccountPlanAndBillAndCreditAppliedSuccess(accountPlan.getAccount(), accountPlanUuid, bill.getUuid()); final List creditsByThisPromo = creditsApplied.stream() .filter(c -> c.getPaymentMethod().equals(apm.getUuid())) - .collect(Collectors.toList());; + .collect(Collectors.toList()); if (empty(creditsByThisPromo)) { log.warn("purchase: applying promotion did not result in an AccountPayment to Bill "+bill.getUuid()); continue; diff --git a/bubble-server/src/test/java/bubble/test/promo/FirstMonthAndReferralMonthPromotionTest.java b/bubble-server/src/test/java/bubble/test/promo/FirstMonthAndReferralMonthPromotionTest.java deleted file mode 100644 index d0245b90..00000000 --- a/bubble-server/src/test/java/bubble/test/promo/FirstMonthAndReferralMonthPromotionTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package bubble.test.promo; - -import bubble.test.payment.PaymentTestBase; -import lombok.extern.slf4j.Slf4j; -import org.junit.Test; - -@Slf4j -public class FirstMonthAndReferralMonthPromotionTest extends PaymentTestBase { - - @Override protected String getManifest() { return "promo/1mo_and_referral/manifest_1mo_and_referral"; } - - @Test public void testFirstMonthAndMultipleReferralMonthsFree () throws Exception { - modelTest("promo/first_month_and_multiple_referral_months_free"); - } - -} diff --git a/bubble-server/src/test/java/bubble/test/promo/MultiplePromotionsTest.java b/bubble-server/src/test/java/bubble/test/promo/MultiplePromotionsTest.java new file mode 100644 index 00000000..1a6bd6f0 --- /dev/null +++ b/bubble-server/src/test/java/bubble/test/promo/MultiplePromotionsTest.java @@ -0,0 +1,14 @@ +package bubble.test.promo; + +import bubble.test.payment.PaymentTestBase; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; + +@Slf4j +public class MultiplePromotionsTest extends PaymentTestBase { + + @Override protected String getManifest() { return "promo/multi/manifest_multi"; } + + @Test public void testMultiplePromotions() throws Exception { modelTest("promo/multi_promo"); } + +} diff --git a/bubble-server/src/test/resources/models/include/referral_signup.json b/bubble-server/src/test/resources/models/include/referral_signup.json new file mode 100644 index 00000000..f79750f1 --- /dev/null +++ b/bubble-server/src/test/resources/models/include/referral_signup.json @@ -0,0 +1,228 @@ +[ + { + "comment": "declare defaults for referral_signup test part", + "include": "_defaults", + "params": { + "referralCode": "_required", + "referredName": "_required", + "referredUserSessionName": "referredUserSession", + "referredUserVar": "referredUser", + "referredPaymentMethodsVar": "referredPaymentMethods", + "referredAccountPlanVar": "referredAccountPlan", + "rootSessionName": "rootSession", + "plansVar": "plans" + } + }, + { + "comment": "<>: register an account for the referred user, using one of the referral codes", + "request": { + "session": "new", + "uri": "auth/register", + "entity": { + "name": "<>", + "password": "password1!", + "contact": {"type": "email", "info": "<>@example.com"}, + "promoCode": "<>" + } + }, + "response": { + "store": "<>", + "sessionName": "<>", + "session": "token" + } + }, + + { + "before": "sleep 5s", // wait for account objects to be created + "comment": "as root, check email inbox for verification message", + "request": { + "session": "<>", + "uri": "debug/inbox/email/<>@example.com?type=request&action=verify&target=account" + }, + "response": { + "store": "emailInbox", + "check": [ + {"condition": "'{{json.[0].ctx.message.messageType}}' == 'request'"}, + {"condition": "'{{json.[0].ctx.message.action}}' == 'verify'"}, + {"condition": "'{{json.[0].ctx.message.target}}' == 'account'"} + ] + } + }, + + { + "comment": "<>: approve email verification request", + "request": { + "session": "<>", + "uri": "auth/approve/{{emailInbox.[0].ctx.confirmationToken}}", + "method": "post" + } + }, + + { + "comment": "<>: lookup payment methods, ensure both FirstMonthFree and ReferralMonthFree are present, tokenize a credit card", + "request": { "uri": "me/paymentMethods" }, + "response": { + "store": "<>", + "check": [ + {"condition": "json.length === 2"}, + {"condition": "json[0].getPaymentMethodType().name() === 'promotional_credit'"}, + {"condition": "json[0].deleted() === false"}, + {"condition": "json[1].getPaymentMethodType().name() === 'promotional_credit'"}, + {"condition": "json[1].deleted() === false"}, + {"condition": "_find(json, function (p) { return p.getMaskedPaymentInfo() === 'FirstMonthFree'; }) !== null"}, + {"condition": "_find(json, function (p) { return p.getMaskedPaymentInfo() === 'ReferralMonthFree'; }) !== null"} + ] + }, + "after": "stripe_tokenize_card" + }, + + { + "comment": "<>: add plan, using 'credit' payment method, applied first-month free promo", + "request": { + "uri": "me/plans", + "method": "put", + "entity": { + "name": "test-net-{{rand 5}}", + "domain": "{{defaultDomain}}", + "locale": "en_US", + "timezone": "EST", + "plan": "{{<>.[0].name}}", + "footprint": "US", + "paymentMethodObject": { + "paymentMethodType": "credit", + "paymentInfo": "{{stripeToken}}" + } + } + }, + "response": { + "store": "<>" + } + }, + + { + "before": "sleep 10s", + "comment": "<>: start the network", + "request": { + "uri": "me/networks/{{<>.network}}/actions/start?cloud=MockCompute®ion=nyc_mock", + "method": "post" + } + }, + + { + "before": "sleep 3s", + "comment": "<>: verify the network is running", + "request": { "uri": "me/networks/{{<>.network}}" }, + "response": { + "check": [ {"condition": "json.getState().name() == 'running'"} ] + } + }, + + { + "comment": "<>: list all account payment methods, should be three, with the FirstMonthFree credit used up", + "request": { "uri": "me/paymentMethods?all=true" }, + "response": { + "store": "<>", + "check": [ + {"condition": "json.length === 3"}, + {"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.getMaskedPaymentInfo() === 'ReferralMonthFree'; }).deleted() === false"}, + {"condition": "_find(json, function(p) { return p.getMaskedPaymentInfo() === 'FirstMonthFree'; }).deleted() === true"} + ] + } + }, + + { + "comment": "<>: verify account plans, should be one, verify enabled", + "request": { "uri": "me/plans" }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getName() === <>.getName()"}, + {"condition": "json[0].enabled()"} + ] + } + }, + + { + "comment": "<>: verify account plan payment info", + "request": { "uri": "me/plans/{{<>.uuid}}/paymentMethod" }, + "response": { + "store": "referredCreditPaymentMethod", + "check": [ + {"condition": "json.getPaymentMethodType().name() === 'credit'"}, + {"condition": "json.getMaskedPaymentInfo() == 'XXXX-XXXX-XXXX-4242'"} + ] + } + }, + + { + "comment": "<>: verify bill exists and was paid", + "request": { "uri": "me/bills" }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getPlan() === '{{<>.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{<>.uuid}}'"}, + {"condition": "json[0].getQuantity() === 1"}, + {"condition": "json[0].getPrice() === {{<>.[0].price}}"}, + {"condition": "json[0].getTotal() === {{<>.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'paid'"} + ] + } + }, + + { + "comment": "<>: verify bill exists via plan and is paid", + "request": { "uri": "me/plans/{{<>.uuid}}/bills" }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getPlan() === '{{<>.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{<>.uuid}}'"}, + {"condition": "json[0].getQuantity() === 1"}, + {"condition": "json[0].getPrice() === {{<>.[0].price}}"}, + {"condition": "json[0].getTotal() === {{<>.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'paid'"} + ] + } + }, + + { + "comment": "<>: verify payment exists and is successful and used a promotion", + "request": { "uri": "me/payments" }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getPlan() === '{{<>.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{<>.uuid}}'"}, + {"condition": "json[0].getAmount() === {{<>.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'success'"}, + {"condition": "json[0].getType().name() === 'credit_applied'"}, + {"condition": "json[0].getPaymentMethod() === _find(<>, function(p) { return p.getMaskedPaymentInfo() === 'FirstMonthFree'; }).getUuid()"} + ] + } + }, + + { + "comment": "<>: verify payment exists via plan and is successful and used a promotion", + "request": { "uri": "me/plans/{{<>.uuid}}/payments" }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getPlan() === '{{<>.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{<>.uuid}}'"}, + {"condition": "json[0].getAmount() === {{<>.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'success'"}, + {"condition": "json[0].getType().name() === 'credit_applied'"}, + {"condition": "json[0].getPaymentMethod() === _find(<>, function(p) { return p.getMaskedPaymentInfo() === 'FirstMonthFree'; }).getUuid()"}, + {"condition": "json[0].getBillObject().getPlan() === '{{<>.[0].uuid}}'"}, + {"condition": "json[0].getBillObject().getAccountPlan() === '{{<>.uuid}}'"}, + {"condition": "json[0].getBillObject().getQuantity() === 1"}, + {"condition": "json[0].getBillObject().getPrice() === {{<>.[0].price}}"}, + {"condition": "json[0].getBillObject().getTotal() === {{<>.[0].price}}"}, + {"condition": "json[0].getBillObject().getStatus().name() === 'paid'"} + ] + } + } +] \ No newline at end of file diff --git a/bubble-server/src/test/resources/models/promo/1mo/promotion_1mo.json b/bubble-server/src/test/resources/models/promo/1mo/promotion_1mo.json index c0c842d8..7a3d7d6f 100644 --- a/bubble-server/src/test/resources/models/promo/1mo/promotion_1mo.json +++ b/bubble-server/src/test/resources/models/promo/1mo/promotion_1mo.json @@ -2,7 +2,7 @@ { "name": "FirstMonthFree", "cloud": "FirstMonthFree", - "priority": 1, + "priority": -10000, "currency": "USD", "maxValue": 1200 } diff --git a/bubble-server/src/test/resources/models/promo/1mo_and_referral/manifest_1mo_and_referral.json b/bubble-server/src/test/resources/models/promo/1mo_and_referral/manifest_1mo_and_referral.json deleted file mode 100644 index bccf3d5c..00000000 --- a/bubble-server/src/test/resources/models/promo/1mo_and_referral/manifest_1mo_and_referral.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - "manifest-test", - "promo/1mo/manifest_1mo", - "promo/referral/manifest_referral" -] \ No newline at end of file diff --git a/bubble-server/src/test/resources/models/promo/credit/promotion_credit.json b/bubble-server/src/test/resources/models/promo/credit/promotion_credit.json index d66432c2..fbca5f83 100644 --- a/bubble-server/src/test/resources/models/promo/credit/promotion_credit.json +++ b/bubble-server/src/test/resources/models/promo/credit/promotion_credit.json @@ -2,25 +2,28 @@ { "name": "AccountCredit1", "cloud": "AccountCredit1", - "priority": 1, + "priority": 10000, "currency": "USD", "maxValue": 100, - "visible": false + "visible": false, + "adminAssignOnly": true }, { "name": "AccountCredit5", "cloud": "AccountCredit5", - "priority": 1, + "priority": 10000, "currency": "USD", "maxValue": 500, - "visible": false + "visible": false, + "adminAssignOnly": true }, { "name": "AccountCreditBill", "cloud": "AccountCreditBill", - "priority": 1, + "priority": 10000, "currency": "USD", "maxValue": 10000, - "visible": false + "visible": false, + "adminAssignOnly": true } ] \ No newline at end of file diff --git a/bubble-server/src/test/resources/models/promo/multi/manifest_multi.json b/bubble-server/src/test/resources/models/promo/multi/manifest_multi.json new file mode 100644 index 00000000..88d03402 --- /dev/null +++ b/bubble-server/src/test/resources/models/promo/multi/manifest_multi.json @@ -0,0 +1,6 @@ +[ + "manifest-test", + "promo/1mo/manifest_1mo", + "promo/referral/manifest_referral", + "promo/credit/manifest_credit" +] \ No newline at end of file diff --git a/bubble-server/src/test/resources/models/promo/referral/promotion_referral.json b/bubble-server/src/test/resources/models/promo/referral/promotion_referral.json index 4672f1d6..04df1267 100644 --- a/bubble-server/src/test/resources/models/promo/referral/promotion_referral.json +++ b/bubble-server/src/test/resources/models/promo/referral/promotion_referral.json @@ -2,9 +2,9 @@ { "name": "ReferralMonthFree", "cloud": "ReferralMonthFree", - "priority": 1, + "priority": -1000, "currency": "USD", - "maxValue": 1200, + "maxValue": 2500, "referral": true } ] \ No newline at end of file diff --git a/bubble-server/src/test/resources/models/tests/promo/first_month_and_multiple_referral_months_free.json b/bubble-server/src/test/resources/models/tests/promo/first_month_and_multiple_referral_months_free.json deleted file mode 100644 index 0637a088..00000000 --- a/bubble-server/src/test/resources/models/tests/promo/first_month_and_multiple_referral_months_free.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/bubble-server/src/test/resources/models/tests/promo/multi_promo.json b/bubble-server/src/test/resources/models/tests/promo/multi_promo.json new file mode 100644 index 00000000..718ce71d --- /dev/null +++ b/bubble-server/src/test/resources/models/tests/promo/multi_promo.json @@ -0,0 +1,347 @@ +[ + { + "comment": "root: create a user account for the referring user", + "request": { + "session": "new", + "uri": "auth/register", + "entity": { + "name": "test_user_referring_multi", + "password": "password1!", + "contact": {"type": "email", "info": "test_user_referring_multi@example.com"} + } + }, + "response": { + "store": "referringUser", + "sessionName": "referringUserSession", + "session": "token" + } + }, + + { + "before": "sleep 5s", + "comment": "referring: lookup payment methods, should have 1mo free", + "request": { + "session": "referringUserSession", + "uri": "me/paymentMethods" + }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].hasPromotion()"}, + {"condition": "json[0].getMaskedPaymentInfo() === 'FirstMonthFree'"} + ] + } + }, + + { + "comment": "referring: list promos, should have 1mo free", + "request": { + "uri": "me/promos" + }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getName() === 'FirstMonthFree'"} + ] + } + }, + + { + "comment": "root: check email inbox for verification message for referring user", + "request": { + "session": "rootSession", + "uri": "debug/inbox/email/test_user_referring_multi@example.com?type=request&action=verify&target=account" + }, + "response": { + "store": "emailInbox", + "check": [ + {"condition": "'{{json.[0].ctx.message.messageType}}' == 'request'"}, + {"condition": "'{{json.[0].ctx.message.action}}' == 'verify'"}, + {"condition": "'{{json.[0].ctx.message.target}}' == 'account'"} + ] + } + }, + + { + "comment": "as root, grant some referral codes to the referring user", + "request": { + "uri": "users/test_user_referring_multi/referralCodes", + "method": "put", + "entity": { "count": 3 } + }, + "response": { + "store": "referralCodes", + "check": [ {"condition": "json.length === 3"} ] + } + }, + + { + "comment": "referring: approve email verification request for referring user", + "request": { + "session": "referringUserSession", + "uri": "auth/approve/{{emailInbox.[0].ctx.confirmationToken}}", + "method": "post" + } + }, + + { + "comment": "referring: list referral codes, verify all codes are unused", + "request": { "uri": "me/referralCodes" }, + "response": { + "check": [ + {"condition": "json.length === 3"}, + {"condition": "json[0].getClaimedBy() === null"}, + {"condition": "json[1].getClaimedBy() === null"}, + {"condition": "json[2].getClaimedBy() === null"}, + {"condition": "json[0].getClaimedByUuid() === null"}, + {"condition": "json[1].getClaimedByUuid() === null"}, + {"condition": "json[2].getClaimedByUuid() === null"} + ] + } + }, + + { + "comment": "referring: get plans", + "request": { "uri": "plans" }, + "response": { + "store": "plans", + "check": [{"condition": "json.length >= 1"}] + } + }, + + { + "comment": "sign up as first referred user", + "include": "referral_signup", + "params": { + "referralCode": "{{referralCodes.[0].name}}", + "referredName": "referred_multi_1" + } + }, + + { + "comment": "sign up as second referred user", + "include": "referral_signup", + "params": { + "referralCode": "{{referralCodes.[1].name}}", + "referredName": "referred_multi_2" + } + }, + + { + "comment": "sign up as third referred user", + "include": "referral_signup", + "params": { + "referralCode": "{{referralCodes.[2].name}}", + "referredName": "referred_multi_3" + } + }, + + { + "comment": "referring: list referral codes after 3 referrals, verify all codes have been claimed, tokenize a card", + "request": { + "session": "referringUserSession", + "uri": "me/referralCodes?show=all" + }, + "response": { + "check": [ + {"condition": "json.length === 3"}, + {"condition": "json[0].getClaimedBy() !== null"}, + {"condition": "json[1].getClaimedBy() !== null"}, + {"condition": "json[2].getClaimedBy() !== null"}, + {"condition": "json[0].getClaimedByUuid() !== null"}, + {"condition": "json[1].getClaimedByUuid() !== null"}, + {"condition": "json[2].getClaimedByUuid() !== null"} + ] + }, + "after": "stripe_tokenize_card" + }, + + { + "comment": "referring: add plan, using 'credit' payment method, applies 1mo promo", + "request": { + "uri": "me/plans", + "method": "put", + "entity": { + "name": "test-net-{{rand 5}}", + "domain": "{{defaultDomain}}", + "locale": "en_US", + "timezone": "EST", + "plan": "{{plans.[0].name}}", + "footprint": "US", + "paymentMethodObject": { + "paymentMethodType": "credit", + "paymentInfo": "{{stripeToken}}" + } + } + }, + "response": { + "store": "referringAccountPlan" + } + }, + + { + "before": "sleep 15s", + "comment": "referring: start the network", + "request": { + "uri": "me/networks/{{referringAccountPlan.network}}/actions/start?cloud=MockCompute®ion=nyc_mock", + "method": "post" + } + }, + + { + "before": "sleep 5s", + "comment": "referring: verify the network is running", + "request": { "uri": "me/networks/{{referringAccountPlan.network}}" }, + "response": { + "check": [ {"condition": "json.getState().name() == 'running'"} ] + } + }, + + { + "comment": "referring: list active account payment methods, should be five, FirstMonthFree used, ReferralMonthFree promos all unused", + "request": { "uri": "me/paymentMethods?all=true" }, + "response": { + "store": "referringPaymentMethods", + "check": [ + {"condition": "json.length === 5"}, + {"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.getMaskedPaymentInfo() === 'ReferralMonthFree' && p.deleted(); }) === null"}, + {"condition": "_find(json, function(p) { return p.getMaskedPaymentInfo() === 'FirstMonthFree' && p.deleted(); }) !== null"} + ] + } + }, + + { + "comment": "referring: verify account plans, should be one, verify enabled", + "request": { "uri": "me/plans" }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getName() === referringAccountPlan.getName()"}, + {"condition": "json[0].enabled()"} + ] + } + }, + + { + "comment": "referring: verify account plan payment info", + "request": { "uri": "me/plans/{{referringAccountPlan.uuid}}/paymentMethod" }, + "response": { + "store": "referringCreditPaymentMethod", + "check": [ + {"condition": "json.getPaymentMethodType().name() === 'credit'"}, + {"condition": "json.getMaskedPaymentInfo() == 'XXXX-XXXX-XXXX-4242'"} + ] + } + }, + + { + "comment": "referring: verify bill exists and was paid", + "request": { "uri": "me/bills" }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{referringAccountPlan.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": "referring: verify bill exists via plan and is paid", + "request": { "uri": "me/plans/{{referringAccountPlan.uuid}}/bills" }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{referringAccountPlan.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": "referring: verify payment exists and is successful via 1mo free", + "request": { "uri": "me/payments" }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{referringAccountPlan.uuid}}'"}, + {"condition": "json[0].getAmount() === {{plans.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'success'"}, + {"condition": "json[0].getType().name() === 'credit_applied'"}, + {"condition": "_find(referringPaymentMethods, function(p) { return p.getUuid() === json[0].getPaymentMethod() && p.getMaskedPaymentInfo() === 'FirstMonthFree' }) !== null"} + ] + } + }, + + { + "comment": "verify payment exists via plan and is successful via 1mo free", + "request": { "uri": "me/plans/{{referringAccountPlan.uuid}}/payments" }, + "response": { + "check": [ + {"condition": "json.length === 1"}, + {"condition": "json[0].getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getAccountPlan() === '{{referringAccountPlan.uuid}}'"}, + {"condition": "json[0].getAmount() === {{plans.[0].price}}"}, + {"condition": "json[0].getStatus().name() === 'success'"}, + {"condition": "json[0].getType().name() === 'credit_applied'"}, + {"condition": "_find(referringPaymentMethods, function(p) { return p.getUuid() === json[0].getPaymentMethod() && p.getMaskedPaymentInfo() === 'FirstMonthFree' }) !== null"}, + {"condition": "json[0].getBillObject().getPlan() === '{{plans.[0].uuid}}'"}, + {"condition": "json[0].getBillObject().getAccountPlan() === '{{referringAccountPlan.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'"} + ] + } + } + + // todo: fast-forward 1month + + // referred accounts have all paid via referral credit + + // referring account paid via credit card + + // todo: ff 1 month + + // referred accounts have paid via credit card + + // referring account used first referral credit + + // root: apply credit to referring account + + // ff 1 month + + // referred accounts have paid via credit card + + // referring account used second referral credit + + // ff 1 month + + // referred accounts have paid via credit card + + // referring account used third referral credit + + // ff 1 month + + // referred accounts have paid via credit card + + // referring account used partial regular account credit + + // ff 1 month + + // referred accounts have paid via credit card + + // referring account paid via credit card + +] \ No newline at end of file diff --git a/pom.xml b/pom.xml index d08d5741..15571904 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ This code is available under the GNU Affero General Public License, version 3: h bubble.test.promo.FirstMonthFreePromotionTest bubble.test.promo.ReferralMonthFreePromotionTest bubble.test.promo.AccountCreditTest - bubble.test.promo.FirstMonthAndReferralMonthPromotionTest + bubble.test.promo.MultiplePromotionsTest bubble.test.system.DriverTest bubble.test.filter.ProxyTest bubble.test.filter.TrafficAnalyticsTest