@@ -14,8 +14,8 @@ import java.util.Map; | |||||
import java.util.TreeMap; | import java.util.TreeMap; | ||||
import static bubble.model.bill.PaymentMethodType.promotional_credit; | 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; | import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | ||||
@Slf4j | @Slf4j | ||||
@@ -70,47 +70,6 @@ public abstract class PaymentDriverBase<T> extends CloudServiceDriverBase<T> imp | |||||
return bill; | 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<AccountPaymentMethod> 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) { | @Override public boolean authorize(BubblePlan plan, String accountPlanUuid, AccountPaymentMethod paymentMethod) { | ||||
return true; | return true; | ||||
} | } | ||||
@@ -61,6 +61,7 @@ public class FirstMonthFreePaymentDriver extends PaymentDriverBase<FirstMonthPay | |||||
Bill bill, | Bill bill, | ||||
long chargeAmount) { | long chargeAmount) { | ||||
// mark deleted so it will not be found/applied for future transactions | // mark deleted so it will not be found/applied for future transactions | ||||
log.info("charge: applying promotion: "+paymentMethod.getPromotion()+" via AccountPaymentMethod: "+paymentMethod.getUuid()); | |||||
paymentMethodDAO.update(paymentMethod.setDeleted()); | paymentMethodDAO.update(paymentMethod.setDeleted()); | ||||
return FIRST_MONTH_FREE_INFO; | return FIRST_MONTH_FREE_INFO; | ||||
} | } | ||||
@@ -49,6 +49,12 @@ public class StripePaymentDriver extends PaymentDriverBase<StripePaymentDriverCo | |||||
@Getter(lazy=true) private final RedisService chargeCache = redisService.prefixNamespace(SIMPLE_NAME +"_charge"); | @Getter(lazy=true) private final RedisService chargeCache = redisService.prefixNamespace(SIMPLE_NAME +"_charge"); | ||||
@Getter(lazy=true) private final RedisService refundCache = redisService.prefixNamespace(SIMPLE_NAME +"_refund"); | @Getter(lazy=true) private final RedisService refundCache = redisService.prefixNamespace(SIMPLE_NAME +"_refund"); | ||||
public void flushCaches () { | |||||
getAuthCache().flush(); | |||||
getChargeCache().flush(); | |||||
getRefundCache().flush(); | |||||
} | |||||
private static final AtomicReference<String> setupDone = new AtomicReference<>(null); | private static final AtomicReference<String> setupDone = new AtomicReference<>(null); | ||||
@Override public void postSetup() { | @Override public void postSetup() { | ||||
@@ -224,7 +224,11 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco | |||||
} else { | } else { | ||||
final PromotionalPaymentServiceDriver promoPaymentDriver = (PromotionalPaymentServiceDriver) promoDriver; | final PromotionalPaymentServiceDriver promoPaymentDriver = (PromotionalPaymentServiceDriver) promoDriver; | ||||
if (!promoPaymentDriver.applyPromo(promo, caller)) { | if (!promoPaymentDriver.applyPromo(promo, caller)) { | ||||
errors.addViolation("err.promoCode.notApplied"); | |||||
if (request.hasPromoCode()) { | |||||
errors.addViolation("err.promoCode.notApplied"); | |||||
} else { | |||||
log.warn("setReferences: promo not applied: "+promo.getName()); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -29,6 +29,7 @@ import java.util.List; | |||||
import static java.util.concurrent.TimeUnit.HOURS; | import static java.util.concurrent.TimeUnit.HOURS; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | import static org.cobbzilla.util.daemon.ZillaRuntime.die; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.now; | import static org.cobbzilla.util.daemon.ZillaRuntime.now; | ||||
import static org.cobbzilla.wizard.server.RestServerBase.reportError; | |||||
@Service @Slf4j | @Service @Slf4j | ||||
public class BillingService extends SimpleDaemon { | public class BillingService extends SimpleDaemon { | ||||
@@ -58,8 +59,9 @@ public class BillingService extends SimpleDaemon { | |||||
final BubblePlan plan = planDAO.findByUuid(accountPlan.getPlan()); | final BubblePlan plan = planDAO.findByUuid(accountPlan.getPlan()); | ||||
if (plan == null) { | if (plan == null) { | ||||
// todo: this is really bad -- notify admin | |||||
log.error("billPlan: plan not found ("+accountPlan.getPlan()+") for accountPlan: "+accountPlan.getUuid()); | |||||
final String msg = "process: plan not found (" + accountPlan.getPlan() + ") for accountPlan: " + accountPlan.getUuid(); | |||||
log.error(msg); | |||||
reportError("BillingService: "+msg); | |||||
continue; | continue; | ||||
} | } | ||||
@@ -97,8 +99,9 @@ public class BillingService extends SimpleDaemon { | |||||
try { | try { | ||||
networkService.stopNetwork(network); | networkService.stopNetwork(network); | ||||
} catch (Exception e) { | } catch (Exception e) { | ||||
// todo: notify admin, requires intervention | |||||
log.error("process: error stopping network due to non-payment: "+network.getUuid()); | |||||
final String msg = "process: error stopping network due to non-payment: " + network.getUuid(); | |||||
log.error(msg); | |||||
reportError("BillingService: "+msg); | |||||
continue; | continue; | ||||
} | } | ||||
messageDAO.create(new AccountMessage() | messageDAO.create(new AccountMessage() | ||||
@@ -10,8 +10,10 @@ import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||
import static bubble.ApiConstants.ROOT_NETWORK_UUID; | import static bubble.ApiConstants.ROOT_NETWORK_UUID; | ||||
import static java.util.concurrent.TimeUnit.HOURS; | |||||
import static java.util.concurrent.TimeUnit.MINUTES; | import static java.util.concurrent.TimeUnit.MINUTES; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.shortError; | import static org.cobbzilla.util.daemon.ZillaRuntime.shortError; | ||||
import static org.cobbzilla.util.time.TimeUtil.formatDuration; | |||||
import static org.cobbzilla.wizard.server.RestServerBase.reportError; | import static org.cobbzilla.wizard.server.RestServerBase.reportError; | ||||
@Service @Slf4j | @Service @Slf4j | ||||
@@ -19,6 +21,7 @@ public class NetworkMonitorService extends SimpleDaemon { | |||||
private static final long STARTUP_DELAY = MINUTES.toMillis(1); | private static final long STARTUP_DELAY = MINUTES.toMillis(1); | ||||
private static final long CHECK_INTERVAL = MINUTES.toMillis(30); | private static final long CHECK_INTERVAL = MINUTES.toMillis(30); | ||||
private static final long NO_NODES_GRACE_PERIOD = HOURS.toMillis(1); | |||||
@Override protected long getStartupDelay() { return STARTUP_DELAY; } | @Override protected long getStartupDelay() { return STARTUP_DELAY; } | ||||
@Override protected long getSleepTime() { return CHECK_INTERVAL; } | @Override protected long getSleepTime() { return CHECK_INTERVAL; } | ||||
@@ -52,8 +55,12 @@ public class NetworkMonitorService extends SimpleDaemon { | |||||
} | } | ||||
} else { | } else { | ||||
if (network.getState() != BubbleNetworkState.stopped && network.getState() != BubbleNetworkState.error_stopping) { | if (network.getState() != BubbleNetworkState.stopped && network.getState() != BubbleNetworkState.error_stopping) { | ||||
reportError(getName() + ": network " + network.getNetworkDomain() + " does NOT have nodes running but state is " + network.getState()+", marking it 'error_stopping'"); | |||||
networkDAO.update(network.setState(BubbleNetworkState.error_stopping)); | |||||
if (network.getCtimeAge() < NO_NODES_GRACE_PERIOD) { | |||||
log.warn(getName() + ": network " + network.getNetworkDomain() + " does NOT have nodes running but state is " + network.getState() + ", we would normally mark it 'error_stopping' but it is less than "+formatDuration(NO_NODES_GRACE_PERIOD)+" old"); | |||||
} else { | |||||
reportError(getName() + ": network " + network.getNetworkDomain() + " does NOT have nodes running but state is " + network.getState() + ", marking it 'error_stopping'"); | |||||
networkDAO.update(network.setState(BubbleNetworkState.error_stopping)); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -61,7 +61,7 @@ jersey: | |||||
redis: | redis: | ||||
key: '{{#exists BUBBLE_REDIS_ENCRYPTION_KEY}}{{BUBBLE_REDIS_ENCRYPTION_KEY}}{{else}}{{key_file '.BUBBLE_REDIS_ENCRYPTION_KEY'}}{{/exists}}' | key: '{{#exists BUBBLE_REDIS_ENCRYPTION_KEY}}{{BUBBLE_REDIS_ENCRYPTION_KEY}}{{else}}{{key_file '.BUBBLE_REDIS_ENCRYPTION_KEY'}}{{/exists}}' | ||||
prefix: bubble_ | |||||
prefix: bubble | |||||
errorApi: | errorApi: | ||||
url: {{ERRBIT_URL}} | url: {{ERRBIT_URL}} | ||||
@@ -52,6 +52,7 @@ public class BubbleApiRunnerListener extends SimpleApiRunnerListener { | |||||
final List<String> parts = splitAndTrim(before.substring(FAST_FORWARD_AND_BILL.length()), " "); | final List<String> parts = splitAndTrim(before.substring(FAST_FORWARD_AND_BILL.length()), " "); | ||||
final long delta = parseDuration(parts.get(0)); | final long delta = parseDuration(parts.get(0)); | ||||
final long sleepTime = parts.size() > 1 ? parseDuration(parts.get(1)) : DEFAULT_BILLING_SLEEP; | final long sleepTime = parts.size() > 1 ? parseDuration(parts.get(1)) : DEFAULT_BILLING_SLEEP; | ||||
configuration.autowire(new StripePaymentDriver()).flushCaches(); | |||||
incrementSystemTimeOffset(delta); | incrementSystemTimeOffset(delta); | ||||
configuration.getBean(BillingService.class).processBilling(); | configuration.getBean(BillingService.class).processBilling(); | ||||
sleep(sleepTime, "waiting for BillingService to complete"); | sleep(sleepTime, "waiting for BillingService to complete"); | ||||
@@ -98,6 +98,26 @@ | |||||
{ | { | ||||
"before": "sleep 15s", | "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", | "comment": "list all account payment methods, should be two", | ||||
"request": { "uri": "me/paymentMethods?all=true" }, | "request": { "uri": "me/paymentMethods?all=true" }, | ||||
"response": { | "response": { | ||||
@@ -203,5 +223,217 @@ | |||||
{"condition": "json[0].getBillObject().getStatus().name() === 'paid'"} | {"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'"} | |||||
] | |||||
} | |||||
} | } | ||||
] | ] |
@@ -60,7 +60,7 @@ jersey: | |||||
redis: | redis: | ||||
key: '{{#exists BUBBLE_REDIS_ENCRYPTION_KEY}}{{BUBBLE_REDIS_ENCRYPTION_KEY}}{{else}}{{key_file '.BUBBLE_REDIS_ENCRYPTION_KEY'}}{{/exists}}' | key: '{{#exists BUBBLE_REDIS_ENCRYPTION_KEY}}{{BUBBLE_REDIS_ENCRYPTION_KEY}}{{else}}{{key_file '.BUBBLE_REDIS_ENCRYPTION_KEY'}}{{/exists}}' | ||||
prefix: bubble_ | |||||
prefix: bubble | |||||
errorApi: | errorApi: | ||||
url: {{ERRBIT_URL}} | url: {{ERRBIT_URL}} | ||||
@@ -1 +1 @@ | |||||
Subproject commit 0262f774014d5d6cea6451c0ad68dd697f8b9625 | |||||
Subproject commit f7108ec232850b7a6f1a0c81e93f16a44285b2dc |