@@ -1,4 +1,4 @@ | |||||
package bubble.mock; | |||||
package bubble.cloud.payment.stripe.mock; | |||||
import bubble.cloud.payment.ChargeResult; | import bubble.cloud.payment.ChargeResult; | ||||
import bubble.cloud.payment.stripe.StripePaymentDriver; | import bubble.cloud.payment.stripe.StripePaymentDriver; | ||||
@@ -55,13 +55,11 @@ public class BillDAO extends AccountOwnedEntityDAO<Bill> { | |||||
.setAccount(accountPlan.getAccount()) | .setAccount(accountPlan.getAccount()) | ||||
.setPlan(plan.getUuid()) | .setPlan(plan.getUuid()) | ||||
.setAccountPlan(accountPlan.getUuid()) | .setAccountPlan(accountPlan.getUuid()) | ||||
.setPrice(plan.getPrice()) | |||||
.setTotal(plan.getPrice()) | |||||
.setCurrency(plan.getCurrency()) | .setCurrency(plan.getCurrency()) | ||||
.setPeriodLabel(period.periodLabel(periodStartMillis)) | .setPeriodLabel(period.periodLabel(periodStartMillis)) | ||||
.setPeriodStart(period.periodStart(periodStartMillis)) | .setPeriodStart(period.periodStart(periodStartMillis)) | ||||
.setPeriodEnd(period.periodEnd(periodStartMillis)) | .setPeriodEnd(period.periodEnd(periodStartMillis)) | ||||
.setQuantity(1L) | |||||
.setType(BillItemType.compute) | |||||
.setStatus(BillStatus.unpaid); | .setStatus(BillStatus.unpaid); | ||||
} | } | ||||
@@ -2,7 +2,6 @@ package bubble.model.bill; | |||||
import bubble.model.account.Account; | import bubble.model.account.Account; | ||||
import bubble.model.account.HasAccountNoName; | import bubble.model.account.HasAccountNoName; | ||||
import com.fasterxml.jackson.annotation.JsonIgnore; | |||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
@@ -14,8 +13,8 @@ import org.hibernate.annotations.Type; | |||||
import javax.persistence.*; | 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") | @ECType(root=true) @ECTypeCreate(method="DISABLED") | ||||
@ECTypeURIs(listFields={"name", "status", "type", "quantity", "price", "periodStart"}) | @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 paid() { return status == BillStatus.paid; } | ||||
public boolean unpaid() { return !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) | @Column(nullable=false, updatable=false, length=20) | ||||
@ECIndex @Getter @Setter private String periodLabel; | @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) | @Column(nullable=false, updatable=false, length=20) | ||||
@Getter @Setter private String periodStart; | @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) | @Column(nullable=false, updatable=false, length=20) | ||||
@Getter @Setter private String periodEnd; | @Getter @Setter private String periodEnd; | ||||
public int daysInPeriod () { return BillPeriod.daysInPeriod(periodStart, 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") | @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) | @ECIndex @Column(nullable=false, updatable=false, length=10) | ||||
@Getter @Setter private String currency; | @Getter @Setter private String currency; | ||||
@ECSearchable @ECField(index=120) | |||||
@ECSearchable @ECField(index=100) | |||||
@Type(type=ENCRYPTED_LONG) @Column(columnDefinition="varchar("+(ENC_LONG)+")") | @Type(type=ENCRYPTED_LONG) @Column(columnDefinition="varchar("+(ENC_LONG)+")") | ||||
@Getter @Setter private Long refundedAmount = 0L; | @Getter @Setter private Long refundedAmount = 0L; | ||||
public boolean hasRefundedAmount () { return refundedAmount != null && 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; | |||||
} | } |
@@ -4,14 +4,18 @@ import bubble.cloud.payment.PaymentServiceDriver; | |||||
import bubble.dao.bill.AccountPaymentMethodDAO; | import bubble.dao.bill.AccountPaymentMethodDAO; | ||||
import bubble.dao.bill.AccountPlanDAO; | import bubble.dao.bill.AccountPlanDAO; | ||||
import bubble.dao.bill.BillDAO; | import bubble.dao.bill.BillDAO; | ||||
import bubble.dao.bill.BubblePlanDAO; | |||||
import bubble.dao.cloud.CloudServiceDAO; | import bubble.dao.cloud.CloudServiceDAO; | ||||
import bubble.model.account.Account; | import bubble.model.account.Account; | ||||
import bubble.model.bill.AccountPaymentMethod; | import bubble.model.bill.AccountPaymentMethod; | ||||
import bubble.model.bill.AccountPlan; | import bubble.model.bill.AccountPlan; | ||||
import bubble.model.bill.Bill; | import bubble.model.bill.Bill; | ||||
import bubble.model.bill.BubblePlan; | |||||
import bubble.model.cloud.CloudService; | import bubble.model.cloud.CloudService; | ||||
import bubble.resources.account.ReadOnlyAccountOwnedResource; | import bubble.resources.account.ReadOnlyAccountOwnedResource; | ||||
import lombok.extern.slf4j.Slf4j; | 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.cobbzilla.wizard.validation.ValidationResult; | ||||
import org.glassfish.jersey.server.ContainerRequest; | import org.glassfish.jersey.server.ContainerRequest; | ||||
import org.springframework.beans.factory.annotation.Autowired; | 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.Context; | ||||
import javax.ws.rs.core.Response; | import javax.ws.rs.core.Response; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | |||||
import static bubble.ApiConstants.EP_PAY; | import static bubble.ApiConstants.EP_PAY; | ||||
import static bubble.ApiConstants.EP_PAYMENTS; | import static bubble.ApiConstants.EP_PAYMENTS; | ||||
@@ -30,6 +35,7 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.*; | |||||
@Slf4j | @Slf4j | ||||
public class BillsResource extends ReadOnlyAccountOwnedResource<Bill, BillDAO> { | public class BillsResource extends ReadOnlyAccountOwnedResource<Bill, BillDAO> { | ||||
@Autowired private BubblePlanDAO planDAO; | |||||
@Autowired private AccountPlanDAO accountPlanDAO; | @Autowired private AccountPlanDAO accountPlanDAO; | ||||
@Autowired private AccountPaymentMethodDAO paymentMethodDAO; | @Autowired private AccountPaymentMethodDAO paymentMethodDAO; | ||||
@Autowired private CloudServiceDAO cloudDAO; | @Autowired private CloudServiceDAO cloudDAO; | ||||
@@ -53,6 +59,13 @@ public class BillsResource extends ReadOnlyAccountOwnedResource<Bill, BillDAO> { | |||||
return getDao().findByAccountAndAccountPlan(getAccountUuid(ctx), accountPlan.getUuid()); | 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<String, BubblePlan> planCache = new ExpirationMap<>(ExpirationEvictionPolicy.atime); | |||||
private BubblePlan findPlan(String planUuid) { return planCache.computeIfAbsent(planUuid, k -> planDAO.findByUuid(planUuid)); } | |||||
@Path("/{id}"+EP_PAYMENTS) | @Path("/{id}"+EP_PAYMENTS) | ||||
public AccountPaymentsResource getPayments(@Context ContainerRequest ctx, | public AccountPaymentsResource getPayments(@Context ContainerRequest ctx, | ||||
@PathParam("id") String id) { | @PathParam("id") String id) { | ||||
@@ -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=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. | 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 | # Bubble Plans | ||||
plan_name_bubble=Bubble Standard | plan_name_bubble=Bubble Standard | ||||
plan_description_bubble=Try this one first. Most users probably don't need anything more | plan_description_bubble=Try this one first. Most users probably don't need anything more | ||||
@@ -133,7 +133,10 @@ price_period_monthly_name=monthly | |||||
price_period_monthly_unit=month | price_period_monthly_unit=month | ||||
price_period_yearly_name=annually | price_period_yearly_name=annually | ||||
price_period_yearly_unit=year | 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 | # Token errors | ||||
err.approvalToken.invalid=Code is incorrect or no longer valid | err.approvalToken.invalid=Code is incorrect or no longer valid | ||||
@@ -6,7 +6,7 @@ import bubble.cloud.payment.stripe.StripePaymentDriver; | |||||
import bubble.dao.account.AccountDAO; | import bubble.dao.account.AccountDAO; | ||||
import bubble.dao.cloud.BubbleDomainDAO; | import bubble.dao.cloud.BubbleDomainDAO; | ||||
import bubble.dao.cloud.CloudServiceDAO; | import bubble.dao.cloud.CloudServiceDAO; | ||||
import bubble.mock.MockStripePaymentDriver; | |||||
import bubble.cloud.payment.stripe.mock.MockStripePaymentDriver; | |||||
import bubble.model.account.Account; | import bubble.model.account.Account; | ||||
import bubble.model.cloud.CloudService; | import bubble.model.cloud.CloudService; | ||||
import bubble.server.BubbleConfiguration; | import bubble.server.BubbleConfiguration; | ||||
@@ -78,7 +78,7 @@ | |||||
"_subst": true, | "_subst": true, | ||||
"name": "StripePayments", | "name": "StripePayments", | ||||
"type": "payment", | "type": "payment", | ||||
"driverClass": "bubble.mock.MockStripePaymentDriver", | |||||
"driverClass": "bubble.cloud.payment.stripe.mock.MockStripePaymentDriver", | |||||
"driverConfig": { | "driverConfig": { | ||||
"publicApiKey": "{{STRIPE_PUBLIC_API_KEY}}" | "publicApiKey": "{{STRIPE_PUBLIC_API_KEY}}" | ||||
}, | }, | ||||
@@ -1 +1 @@ | |||||
Subproject commit a589fe857769f04f0944d0b5ea0dabb4c14a426c | |||||
Subproject commit c194f2c575af7d7735759759ee3dbfda5c768a15 |