Преглед на файлове

add bill detail view, add currency checks to promotions

tags/v0.8.0
Jonathan Cobb преди 5 години
родител
ревизия
6adc073605
променени са 14 файла, в които са добавени 145 реда и са изтрити 37 реда
  1. +2
    -1
      bubble-server/src/main/java/bubble/cloud/payment/promo/PromotionalPaymentServiceDriver.java
  2. +7
    -0
      bubble-server/src/main/java/bubble/dao/bill/BubblePlanDAO.java
  3. +28
    -8
      bubble-server/src/main/java/bubble/dao/bill/PromotionDAO.java
  4. +1
    -0
      bubble-server/src/main/java/bubble/model/bill/AccountPayment.java
  5. +2
    -0
      bubble-server/src/main/java/bubble/model/bill/Bill.java
  6. +4
    -0
      bubble-server/src/main/java/bubble/model/bill/Promotion.java
  7. +12
    -2
      bubble-server/src/main/java/bubble/resources/account/AuthResource.java
  8. +4
    -3
      bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java
  9. +25
    -10
      bubble-server/src/main/java/bubble/resources/bill/BillsResource.java
  10. +6
    -1
      bubble-server/src/main/java/bubble/resources/bill/PromotionsResource.java
  11. +13
    -9
      bubble-server/src/main/java/bubble/service/bill/PromotionService.java
  12. +39
    -1
      bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties
  13. +1
    -1
      bubble-web
  14. +1
    -1
      utils/cobbzilla-utils

+ 2
- 1
bubble-server/src/main/java/bubble/cloud/payment/promo/PromotionalPaymentServiceDriver.java Целия файл

@@ -30,8 +30,9 @@ public interface PromotionalPaymentServiceDriver extends PaymentServiceDriver {
AccountPlan accountPlan,
AccountPaymentMethod paymentMethod) {
// do not use if deleted (should never happen)
// do not use if wrong currency (should never happen)
// do not use if other higher priority promotions are usable
return paymentMethod.notDeleted() && usable.isEmpty();
return paymentMethod.notDeleted() && promo.isCurrency(bill.getCurrency()) && usable.isEmpty();
}

}

+ 7
- 0
bubble-server/src/main/java/bubble/dao/bill/BubblePlanDAO.java Целия файл

@@ -8,6 +8,9 @@ import org.hibernate.criterion.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.Set;
import java.util.stream.Collectors;

@Repository
public class BubblePlanDAO extends AccountOwnedEntityDAO<BubblePlan> {

@@ -38,4 +41,8 @@ public class BubblePlanDAO extends AccountOwnedEntityDAO<BubblePlan> {
return findByName(id);
}

public Set<String> getSupportedCurrencies () {
return findAll().stream().map(BubblePlan::getCurrency).collect(Collectors.toSet());
}

}

+ 28
- 8
bubble-server/src/main/java/bubble/dao/bill/PromotionDAO.java Целия файл

@@ -25,27 +25,47 @@ public class PromotionDAO extends AbstractCRUDDAO<Promotion> {
return found != null ? found : findByName(id);
}

public Promotion findEnabledAndActiveWithCode(String code) {
return filterActive(findByUniqueFields("enabled", true, "code", code, "referral", false, "adminAssignOnly", false));
public Promotion findEnabledAndActiveWithCode(String code, String currency) {
return filterActive(findByUniqueFields(
"enabled", true,
"code", code,
"referral", false,
"currency", currency,
"adminAssignOnly", false));
}

public List<Promotion> findEnabledAndActiveWithNoCode() {
return filterActive(findByFields("enabled", true, "code", null, "referral", false, "adminAssignOnly", false));
public List<Promotion> findEnabledAndActiveWithNoCode(String currency) {
return filterActive(findByFields(
"enabled", true,
"code", null,
"referral", false,
"currency", currency,
"adminAssignOnly", false));
}

public List<Promotion> findVisibleAndEnabledAndActiveWithNoCodeOrWithCode(String code) {
public List<Promotion> findVisibleAndEnabledAndActiveWithNoCodeOrWithCode(String code, String currency) {
if (empty(code)) {
return filterActive(findByFields("enabled", true, "code", null, "visible", true, "adminAssignOnly", false));
return filterActive(findByFields(
"enabled", true,
"code", null,
"visible", true,
"currency", currency,
"adminAssignOnly", false));
} else {
return filterActive(list(criteria().add(and(
eq("enabled", true),
eq("visible", true),
eq("currency", currency),
or(isNull("code"), eq("code", code))))));
}
}

public List<Promotion> findEnabledAndActiveWithReferral() {
return filterActive(findByFields("enabled", true, "referral", true, "adminAssignOnly", false));
public List<Promotion> findEnabledAndActiveWithReferral(String currency) {
return filterActive(findByFields(
"enabled", true,
"referral", true,
"currency", currency,
"adminAssignOnly", false));
}

public Promotion filterActive(Promotion promo) { return promo != null && promo.active() ? promo : null; }


+ 1
- 0
bubble-server/src/main/java/bubble/model/bill/AccountPayment.java Целия файл

@@ -98,6 +98,7 @@ public class AccountPayment extends IdentifiableBase implements HasAccountNoName
}

@Transient @Getter @Setter private transient Bill billObject;
@Transient @Getter @Setter private transient AccountPaymentMethod paymentMethodObject;

public static int totalPayments (List<AccountPayment> payments) {
return empty(payments) ? 0 : payments.stream().mapToInt(AccountPayment::getAmountInt).sum();


+ 2
- 0
bubble-server/src/main/java/bubble/model/bill/Bill.java Целия файл

@@ -12,6 +12,7 @@ import org.cobbzilla.wizard.model.entityconfig.annotations.*;
import org.hibernate.annotations.Type;

import javax.persistence.*;
import java.util.List;

import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_LONG;
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_LONG;
@@ -74,5 +75,6 @@ public class Bill extends IdentifiableBase implements HasAccountNoName {
public boolean hasRefundedAmount () { return refundedAmount != null && refundedAmount > 0L; }

@Transient @Getter @Setter private transient BubblePlan planObject;
@Transient @Getter @Setter private transient List<AccountPayment> payments;

}

+ 4
- 0
bubble-server/src/main/java/bubble/model/bill/Promotion.java Целия файл

@@ -114,6 +114,10 @@ public class Promotion extends IdentifiableBase
@ECIndex @Column(nullable=false, updatable=false, length=10)
@Getter @Setter private String currency;

public boolean isCurrency(String currency) {
return currency != null && currency.equalsIgnoreCase(this.currency);
}

@ECSearchable @ECField(index=120)
@ECIndex @Column(nullable=false, updatable=false)
@Getter @Setter private Integer minValue = 100;


+ 12
- 2
bubble-server/src/main/java/bubble/resources/account/AuthResource.java Целия файл

@@ -4,6 +4,7 @@ import bubble.dao.SessionDAO;
import bubble.dao.account.AccountDAO;
import bubble.dao.account.AccountPolicyDAO;
import bubble.dao.account.message.AccountMessageDAO;
import bubble.dao.bill.BubblePlanDAO;
import bubble.dao.cloud.BubbleNodeDAO;
import bubble.model.CertType;
import bubble.model.account.*;
@@ -47,11 +48,13 @@ import static bubble.model.account.Account.validatePassword;
import static bubble.model.cloud.BubbleNetwork.TAG_ALLOW_REGISTRATION;
import static bubble.model.cloud.BubbleNetwork.TAG_PARENT_ACCOUNT;
import static bubble.model.cloud.notify.NotificationType.retrieve_backup;
import static bubble.server.BubbleConfiguration.getDEFAULT_LOCALE;
import static bubble.server.BubbleServer.getRestoreKey;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.cobbzilla.util.daemon.ZillaRuntime.*;
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON;
import static org.cobbzilla.util.http.HttpContentTypes.CONTENT_TYPE_ANY;
import static org.cobbzilla.util.string.LocaleUtil.currencyForLocale;
import static org.cobbzilla.util.system.Sleep.sleep;
import static org.cobbzilla.wizard.resources.ResourceUtil.*;

@@ -70,6 +73,7 @@ public class AuthResource {
@Autowired private ActivationService activationService;
@Autowired private AccountMessageDAO accountMessageDAO;
@Autowired private StandardAccountMessageService messageService;
@Autowired private BubblePlanDAO planDAO;
@Autowired private BubbleNodeDAO nodeDAO;
@Autowired private BubbleConfiguration configuration;
@Autowired private AuthenticatorService authenticatorService;
@@ -197,8 +201,14 @@ public class AuthResource {
request.getContact().validate(errors);
}

String currency = null;
if (configuration.paymentsEnabled()) {
errors.addAll(promoService.validatePromotions(request.getPromoCode()));
currency = currencyForLocale(request.getLocale(), getDEFAULT_LOCALE());
// do we have any plans with this currency?
if (!planDAO.getSupportedCurrencies().contains(currency)) {
currency = currencyForLocale(getDEFAULT_LOCALE());
}
errors.addAll(promoService.validatePromotions(request.getPromoCode(), currency));
}

if (errors.isInvalid()) return invalid(errors);
@@ -211,7 +221,7 @@ public class AuthResource {
SimpleViolationException promoEx = null;
if (configuration.paymentsEnabled()) {
try {
promoService.applyPromotions(account, request.getPromoCode());
promoService.applyPromotions(account, request.getPromoCode(), currency);
} catch (SimpleViolationException e) {
promoEx = e;
}


+ 4
- 3
bubble-server/src/main/java/bubble/resources/bill/AccountPlansResource.java Целия файл

@@ -6,14 +6,16 @@ import bubble.dao.account.AccountSshKeyDAO;
import bubble.dao.bill.AccountPaymentMethodDAO;
import bubble.dao.bill.AccountPlanDAO;
import bubble.dao.bill.BubblePlanDAO;
import bubble.dao.bill.PromotionDAO;
import bubble.dao.cloud.BubbleDomainDAO;
import bubble.dao.cloud.BubbleFootprintDAO;
import bubble.dao.cloud.BubbleNetworkDAO;
import bubble.dao.cloud.CloudServiceDAO;
import bubble.model.account.Account;
import bubble.model.account.AccountSshKey;
import bubble.model.bill.*;
import bubble.model.bill.AccountPaymentMethod;
import bubble.model.bill.AccountPlan;
import bubble.model.bill.BubblePlan;
import bubble.model.bill.PaymentMethodType;
import bubble.model.cloud.BubbleDomain;
import bubble.model.cloud.BubbleFootprint;
import bubble.model.cloud.BubbleNetwork;
@@ -56,7 +58,6 @@ public class AccountPlansResource extends AccountOwnedResource<AccountPlan, Acco
@Autowired private BubbleConfiguration configuration;
@Autowired private AuthenticatorService authenticatorService;
@Autowired private GeoService geoService;
@Autowired private PromotionDAO promotionDAO;

public AccountPlansResource(Account account) { super(account); }



+ 25
- 10
bubble-server/src/main/java/bubble/resources/bill/BillsResource.java Целия файл

@@ -1,16 +1,10 @@
package bubble.resources.bill;

import bubble.cloud.payment.PaymentServiceDriver;
import bubble.dao.bill.AccountPaymentMethodDAO;
import bubble.dao.bill.AccountPlanDAO;
import bubble.dao.bill.BillDAO;
import bubble.dao.bill.BubblePlanDAO;
import bubble.dao.bill.*;
import bubble.dao.cloud.CloudServiceDAO;
import bubble.model.account.Account;
import bubble.model.bill.AccountPaymentMethod;
import bubble.model.bill.AccountPlan;
import bubble.model.bill.Bill;
import bubble.model.bill.BubblePlan;
import bubble.model.bill.*;
import bubble.model.cloud.CloudService;
import bubble.resources.account.ReadOnlyAccountOwnedResource;
import lombok.extern.slf4j.Slf4j;
@@ -30,14 +24,18 @@ import java.util.Map;

import static bubble.ApiConstants.EP_PAY;
import static bubble.ApiConstants.EP_PAYMENTS;
import static org.cobbzilla.util.http.URIUtil.queryParams;
import static org.cobbzilla.wizard.resources.ResourceUtil.*;

@Slf4j
public class BillsResource extends ReadOnlyAccountOwnedResource<Bill, BillDAO> {

public static final String PARAM_PAYMENTS = "payments";

@Autowired private BubblePlanDAO planDAO;
@Autowired private AccountPlanDAO accountPlanDAO;
@Autowired private AccountPaymentMethodDAO paymentMethodDAO;
@Autowired private AccountPaymentDAO paymentDAO;
@Autowired private CloudServiceDAO cloudDAO;

private AccountPlan accountPlan;
@@ -51,7 +49,24 @@ public class BillsResource extends ReadOnlyAccountOwnedResource<Bill, BillDAO> {

@Override protected Bill find(ContainerRequest ctx, String id) {
final Bill bill = super.find(ctx, id);
return bill == null || (accountPlan != null && !bill.getAccountPlan().equals(accountPlan.getUuid())) ? null : bill;
if (bill == null || (accountPlan != null && !bill.getAccountPlan().equals(accountPlan.getUuid()))) return null;

final Map<String, String> params = queryParams(ctx.getRequestUri().getQuery());
if (Boolean.parseBoolean(params.get(PARAM_PAYMENTS))) {
final List<AccountPayment> payments = paymentDAO.findByAccountAndAccountPlanAndBill(bill.getAccount(), bill.getAccountPlan(), bill.getUuid());
for (AccountPayment payment : payments) {
final String paymentMethodUuid = payment.getPaymentMethod();
payment.setPaymentMethodObject(findPaymentMethod(paymentMethodUuid));
}
return bill.setPayments(payments);
}

return bill;
}

private Map<String, AccountPaymentMethod> paymentMethodCache = new ExpirationMap<>(ExpirationEvictionPolicy.atime);
private AccountPaymentMethod findPaymentMethod(String paymentMethodUuid) {
return paymentMethodCache.computeIfAbsent(paymentMethodUuid, k -> paymentMethodDAO.findByUuid(k));
}

@Override protected List<Bill> list(ContainerRequest ctx) {
@@ -64,7 +79,7 @@ public class BillsResource extends ReadOnlyAccountOwnedResource<Bill, BillDAO> {
}

private Map<String, BubblePlan> planCache = new ExpirationMap<>(ExpirationEvictionPolicy.atime);
private BubblePlan findPlan(String planUuid) { return planCache.computeIfAbsent(planUuid, k -> planDAO.findByUuid(planUuid)); }
private BubblePlan findPlan(String planUuid) { return planCache.computeIfAbsent(planUuid, k -> planDAO.findByUuid(k)); }

@Path("/{id}"+EP_PAYMENTS)
public AccountPaymentsResource getPayments(@Context ContainerRequest ctx,


+ 6
- 1
bubble-server/src/main/java/bubble/resources/bill/PromotionsResource.java Целия файл

@@ -18,7 +18,10 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

import static bubble.ApiConstants.PROMOTIONS_ENDPOINT;
import static bubble.server.BubbleConfiguration.getDEFAULT_LOCALE;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON;
import static org.cobbzilla.util.string.LocaleUtil.currencyForLocale;
import static org.cobbzilla.wizard.resources.ResourceUtil.*;

@Consumes(APPLICATION_JSON)
@@ -35,12 +38,14 @@ public class PromotionsResource {

@GET
public Response listPromos(@Context ContainerRequest ctx,
@QueryParam("currency") String currency,
@QueryParam("code") String code) {
if (empty(currency)) currency = currencyForLocale(getDEFAULT_LOCALE());
final Account caller = optionalUserPrincipal(ctx);
if (caller != null && caller.admin()) {
return ok(promotionDAO.findAll());
}
return ok(promotionDAO.findVisibleAndEnabledAndActiveWithNoCodeOrWithCode(code));
return ok(promotionDAO.findVisibleAndEnabledAndActiveWithNoCodeOrWithCode(code, currency));
}

@GET @Path("/{id}")


+ 13
- 9
bubble-server/src/main/java/bubble/service/bill/PromotionService.java Целия файл

@@ -42,19 +42,19 @@ public class PromotionService {
@Autowired protected AccountPaymentMethodDAO accountPaymentMethodDAO;
@Autowired private BubbleConfiguration configuration;

public void applyPromotions(Account account, String code) {
public void applyPromotions(Account account, String code, String currency) {
// apply promo code (or default) promotion
final Set<Promotion> promos = new TreeSet<>();
ReferralCode referralCode = null;
if (!empty(code)) {
Promotion promo = promotionDAO.findEnabledAndActiveWithCode(code);
Promotion promo = promotionDAO.findEnabledAndActiveWithCode(code, currency);
if (promo == null) {
// check referral codes
// it might be a referral code
referralCode = referralCodeDAO.findByName(code);
if (referralCode != null && !referralCode.claimed()) {
// is there a referral promotion we can use?
for (Promotion p : promotionDAO.findEnabledAndActiveWithReferral()) {
for (Promotion p : promotionDAO.findEnabledAndActiveWithReferral(currency)) {
promos.add(p);
break;
}
@@ -66,7 +66,7 @@ public class PromotionService {
}

// everyone gets the highest-priority default promotion, if there are any enabled and active
for (Promotion p : promotionDAO.findEnabledAndActiveWithNoCode()) {
for (Promotion p : promotionDAO.findEnabledAndActiveWithNoCode(currency)) {
promos.add(p);
break;
}
@@ -105,9 +105,9 @@ public class PromotionService {
}
}

public ValidationResult validatePromotions(String code) {
public ValidationResult validatePromotions(String code, String currency) {
if (!empty(code)) {
Promotion promo = promotionDAO.findEnabledAndActiveWithCode(code);
Promotion promo = promotionDAO.findEnabledAndActiveWithCode(code, currency);
if (promo == null) {
// it might be a referral code
final ReferralCode referralCode = referralCodeDAO.findByName(code);
@@ -116,7 +116,7 @@ public class PromotionService {
if (referer == null || referer.deleted()) return new ValidationResult("err.promoCode.notFound");

// is there a referral promotion we can use?
for (Promotion p : promotionDAO.findEnabledAndActiveWithReferral()) {
for (Promotion p : promotionDAO.findEnabledAndActiveWithReferral(currency)) {
// todo: add JS check?
promo = p;
break;
@@ -167,11 +167,15 @@ public class PromotionService {
final CloudService promoCloud = cloudDAO.findByUuid(promo.getCloud());
final String prefix = getClass().getSimpleName()+": ";
if (promoCloud == null) {
reportError(prefix+"purchase: cloud "+promo.getCloud()+" not found for promotion "+promo.getUuid());
reportError(prefix+"purchase: cloud "+promo.getCloud()+" not found for promotion "+promo.getName());
continue;
}
if (promoCloud.getType() != CloudServiceType.payment) {
reportError(prefix+"purchase: cloud "+promo.getCloud()+" for promotion "+promo.getUuid()+" has wrong type (expected 'payment'): "+promoCloud.getType());
reportError(prefix+"purchase: cloud "+promo.getCloud()+" for promotion "+promo.getName()+" has wrong type (expected 'payment'): "+promoCloud.getType());
continue;
}
if (!promo.getCurrency().equals(plan.getCurrency())) {
reportError(prefix+"purchase: promotion "+promo.getName()+" has wrong currency (expected "+plan.getCurrency()+" for plan "+plan.getName()+"): "+promoCloud.getType());
continue;
}
log.info("purchase: using Promotion: "+promo.getName());


+ 39
- 1
bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties Целия файл

@@ -242,10 +242,48 @@ label_bill_status=Status
bill_status_paid=Paid
bill_status_unpaid=Unpaid
bill_status_partial_payment=Partial payment
label_bill_period=Period
bill_status_undefined=Unknown
bill_status_null=Unknown
bill_status_=Unknown
label_bill_period=Date
label_bill_period_start=From
label_bill_period_end=To
label_bill_total=Amount
label_bill_total_format={{messages['currency_symbol_'+currency.toUpperCase()]}}{{totalMajorUnits}}{{totalMinorUnits === 0 ? '' : totalMinorUnits < 10 ? '.0'+totalMinorUnits : '.'+totalMinorUnits}}
label_bill_refunded=refunded
label_payment_type=Type
label_payment_method=Paid By
label_payment_status=Status
label_payment_amount=Amount
label_payment_amount_format={{messages['currency_symbol_'+currency.toUpperCase()]}}{{amountMajorUnits}}{{amountMinorUnits === 0 ? '' : amountMinorUnits < 10 ? '.0'+amountMinorUnits : '.'+amountMinorUnits}}
label_payment_action=Action
button_label_close_bill_detail=Close

payment_method_credit=Credit/Debit Card
payment_method_code=Invitation Code
payment_method_free=Free!
payment_method_promotional_credit=Promotion
payment_method_undefined=
payment_method_null=
payment_method_=

payment_status_init=Created
payment_status_success=Success
payment_status_failure=Failure
payment_status_unknown=Unknown
payment_status_undefined=Unknown
payment_status_null=Unknown
payment_status_=Unknown

payment_type_payment=payment
payment_type_credit_applied=credit applied
payment_type_refund=refund

label_promotion_FirstMonthFree=First Month Free
label_promotion_ReferralMonthFree=Referral Bonus
label_promotion_AccountCredit1=$1 Bonus
label_promotion_AccountCredit5=$5 Bonus
label_promotion_AccountCreditBill=Full Bill Bonus ($100 max value)

# Bubble Plans
plan_name_bubble=Bubble Standard


+ 1
- 1
bubble-web

@@ -1 +1 @@
Subproject commit c194f2c575af7d7735759759ee3dbfda5c768a15
Subproject commit ddbe7e69b39138c458766c82ee616c31fa7980d1

+ 1
- 1
utils/cobbzilla-utils

@@ -1 +1 @@
Subproject commit d1d485b1a8dcd51da565ca21886a95a728f3a832
Subproject commit 77831c8f23574ebdc8476dd835f8bbfbd8404338

Зареждане…
Отказ
Запис