diff --git a/bubble-server/src/main/java/bubble/cloud/payment/stripe/mock/MockStripePaymentDriver.java b/bubble-server/src/main/java/bubble/cloud/payment/stripe/mock/MockStripePaymentDriver.java index d1667a59..ef3693df 100644 --- a/bubble-server/src/main/java/bubble/cloud/payment/stripe/mock/MockStripePaymentDriver.java +++ b/bubble-server/src/main/java/bubble/cloud/payment/stripe/mock/MockStripePaymentDriver.java @@ -1,4 +1,4 @@ -package bubble.mock; +package bubble.cloud.payment.stripe.mock; import bubble.cloud.payment.ChargeResult; import bubble.cloud.payment.stripe.StripePaymentDriver; diff --git a/bubble-server/src/main/java/bubble/dao/bill/BillDAO.java b/bubble-server/src/main/java/bubble/dao/bill/BillDAO.java index f104293b..23b2afd7 100644 --- a/bubble-server/src/main/java/bubble/dao/bill/BillDAO.java +++ b/bubble-server/src/main/java/bubble/dao/bill/BillDAO.java @@ -55,13 +55,11 @@ public class BillDAO extends AccountOwnedEntityDAO { .setAccount(accountPlan.getAccount()) .setPlan(plan.getUuid()) .setAccountPlan(accountPlan.getUuid()) - .setPrice(plan.getPrice()) + .setTotal(plan.getPrice()) .setCurrency(plan.getCurrency()) .setPeriodLabel(period.periodLabel(periodStartMillis)) .setPeriodStart(period.periodStart(periodStartMillis)) .setPeriodEnd(period.periodEnd(periodStartMillis)) - .setQuantity(1L) - .setType(BillItemType.compute) .setStatus(BillStatus.unpaid); } diff --git a/bubble-server/src/main/java/bubble/model/bill/Bill.java b/bubble-server/src/main/java/bubble/model/bill/Bill.java index 89237454..ffac3185 100644 --- a/bubble-server/src/main/java/bubble/model/bill/Bill.java +++ b/bubble-server/src/main/java/bubble/model/bill/Bill.java @@ -2,7 +2,6 @@ package bubble.model.bill; import bubble.model.account.Account; import bubble.model.account.HasAccountNoName; -import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -14,8 +13,8 @@ import org.hibernate.annotations.Type; import javax.persistence.*; -import static org.cobbzilla.util.daemon.ZillaRuntime.big; -import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.*; +import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_LONG; +import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_LONG; @ECType(root=true) @ECTypeCreate(method="DISABLED") @ECTypeURIs(listFields={"name", "status", "type", "quantity", "price", "periodStart"}) @@ -47,42 +46,33 @@ public class Bill extends IdentifiableBase implements HasAccountNoName { public boolean paid() { return status == BillStatus.paid; } public boolean unpaid() { return !paid(); } - @ECSearchable @ECField(index=50) - @ECIndex @Enumerated(EnumType.STRING) - @Column(nullable=false, updatable=false, length=20) - @Getter @Setter private BillItemType type; - - @ECSearchable @ECField(index=60, type=EntityFieldType.opaque_string) + @ECSearchable @ECField(index=50, type=EntityFieldType.opaque_string) @Column(nullable=false, updatable=false, length=20) @ECIndex @Getter @Setter private String periodLabel; - @ECSearchable @ECField(index=70, type=EntityFieldType.opaque_string) + @ECSearchable @ECField(index=60, type=EntityFieldType.opaque_string) @Column(nullable=false, updatable=false, length=20) @Getter @Setter private String periodStart; - @ECSearchable @ECField(index=80, type=EntityFieldType.opaque_string) + @ECSearchable @ECField(index=70, type=EntityFieldType.opaque_string) @Column(nullable=false, updatable=false, length=20) @Getter @Setter private String periodEnd; public int daysInPeriod () { return BillPeriod.daysInPeriod(periodStart, periodEnd); } - @ECSearchable @ECField(index=90) - @Type(type=ENCRYPTED_LONG) @Column(updatable=false, columnDefinition="varchar("+(ENC_LONG)+") NOT NULL") - @Getter @Setter private Long quantity = 0L; - - @ECSearchable @ECField(index=100) + @ECSearchable @ECField(index=80) @Type(type=ENCRYPTED_LONG) @Column(updatable=false, columnDefinition="varchar("+(ENC_LONG)+") NOT NULL") - @Getter @Setter private Long price = 0L; + @Getter @Setter private Long total = 0L; - @ECSearchable @ECField(index=110) + @ECSearchable @ECField(index=90) @ECIndex @Column(nullable=false, updatable=false, length=10) @Getter @Setter private String currency; - @ECSearchable @ECField(index=120) + @ECSearchable @ECField(index=100) @Type(type=ENCRYPTED_LONG) @Column(columnDefinition="varchar("+(ENC_LONG)+")") @Getter @Setter private Long refundedAmount = 0L; public boolean hasRefundedAmount () { return refundedAmount != null && refundedAmount > 0L; } - @JsonIgnore @Transient public long getTotal() { return big(quantity).multiply(big(price)).longValue(); } + @Transient @Getter @Setter private transient BubblePlan planObject; } diff --git a/bubble-server/src/main/java/bubble/resources/bill/BillsResource.java b/bubble-server/src/main/java/bubble/resources/bill/BillsResource.java index 3a2bd977..bb822c81 100644 --- a/bubble-server/src/main/java/bubble/resources/bill/BillsResource.java +++ b/bubble-server/src/main/java/bubble/resources/bill/BillsResource.java @@ -4,14 +4,18 @@ 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.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.cloud.CloudService; import bubble.resources.account.ReadOnlyAccountOwnedResource; import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.util.collection.ExpirationEvictionPolicy; +import org.cobbzilla.util.collection.ExpirationMap; import org.cobbzilla.wizard.validation.ValidationResult; import org.glassfish.jersey.server.ContainerRequest; import org.springframework.beans.factory.annotation.Autowired; @@ -22,6 +26,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import java.util.List; +import java.util.Map; import static bubble.ApiConstants.EP_PAY; import static bubble.ApiConstants.EP_PAYMENTS; @@ -30,6 +35,7 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.*; @Slf4j public class BillsResource extends ReadOnlyAccountOwnedResource { + @Autowired private BubblePlanDAO planDAO; @Autowired private AccountPlanDAO accountPlanDAO; @Autowired private AccountPaymentMethodDAO paymentMethodDAO; @Autowired private CloudServiceDAO cloudDAO; @@ -53,6 +59,13 @@ public class BillsResource extends ReadOnlyAccountOwnedResource { return getDao().findByAccountAndAccountPlan(getAccountUuid(ctx), accountPlan.getUuid()); } + @Override protected Bill populate(ContainerRequest ctx, Bill bill) { + return super.populate(ctx, bill.setPlanObject(findPlan(bill.getPlan()))); + } + + private Map planCache = new ExpirationMap<>(ExpirationEvictionPolicy.atime); + private BubblePlan findPlan(String planUuid) { return planCache.computeIfAbsent(planUuid, k -> planDAO.findByUuid(planUuid)); } + @Path("/{id}"+EP_PAYMENTS) public AccountPaymentsResource getPayments(@Context ContainerRequest ctx, @PathParam("id") String id) { 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 40bb051d..c3ed0755 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 @@ -235,6 +235,18 @@ link_network_action_stop_description=Stop this Bubble. If you have downloaded yo link_network_action_delete=Delete link_network_action_delete_description=Delete this Bubble and all backups. You will not be able to restore this Bubble. This action cannot be undone. +# Billing and Payment +title_bills=Bills +label_bill_plan=Plan +label_bill_status=Status +bill_status_paid=Paid +bill_status_unpaid=Unpaid +bill_status_partial_payment=Partial payment +label_bill_period=Period +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 + # Bubble Plans plan_name_bubble=Bubble Standard plan_description_bubble=Try this one first. Most users probably don't need anything more 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 8a3fac2e..f84c6b6c 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 @@ -133,7 +133,10 @@ price_period_monthly_name=monthly price_period_monthly_unit=month price_period_yearly_name=annually price_period_yearly_unit=year -price_format=${{priceMajorUnits}}{{priceMinorUnits === 0 ? '' : priceMinorUnits < 10 ? '.0'+priceMinorUnits : '.'+priceMinorUnits}} {{messages['price_period_'+period+'_name']}} +price_format={{messages['currency_symbol_'+currency.toUpperCase()]}}{{priceMajorUnits}}{{priceMinorUnits === 0 ? '' : priceMinorUnits < 10 ? '.0'+priceMinorUnits : '.'+priceMinorUnits}} {{messages['price_period_'+period+'_name']}} + +currency_symbol_= +currency_symbol_USD=$ # Token errors err.approvalToken.invalid=Code is incorrect or no longer valid diff --git a/bubble-server/src/test/java/bubble/test/BubbleApiRunnerListener.java b/bubble-server/src/test/java/bubble/test/BubbleApiRunnerListener.java index 2821cea9..e0a795db 100644 --- a/bubble-server/src/test/java/bubble/test/BubbleApiRunnerListener.java +++ b/bubble-server/src/test/java/bubble/test/BubbleApiRunnerListener.java @@ -6,7 +6,7 @@ import bubble.cloud.payment.stripe.StripePaymentDriver; import bubble.dao.account.AccountDAO; import bubble.dao.cloud.BubbleDomainDAO; import bubble.dao.cloud.CloudServiceDAO; -import bubble.mock.MockStripePaymentDriver; +import bubble.cloud.payment.stripe.mock.MockStripePaymentDriver; import bubble.model.account.Account; import bubble.model.cloud.CloudService; import bubble.server.BubbleConfiguration; diff --git a/bubble-server/src/test/resources/models/system/cloudService_test.json b/bubble-server/src/test/resources/models/system/cloudService_test.json index b8c5f606..91f0b12a 100644 --- a/bubble-server/src/test/resources/models/system/cloudService_test.json +++ b/bubble-server/src/test/resources/models/system/cloudService_test.json @@ -78,7 +78,7 @@ "_subst": true, "name": "StripePayments", "type": "payment", - "driverClass": "bubble.mock.MockStripePaymentDriver", + "driverClass": "bubble.cloud.payment.stripe.mock.MockStripePaymentDriver", "driverConfig": { "publicApiKey": "{{STRIPE_PUBLIC_API_KEY}}" }, diff --git a/bubble-web b/bubble-web index a589fe85..c194f2c5 160000 --- a/bubble-web +++ b/bubble-web @@ -1 +1 @@ -Subproject commit a589fe857769f04f0944d0b5ea0dabb4c14a426c +Subproject commit c194f2c575af7d7735759759ee3dbfda5c768a15