diff --git a/bubble-server/src/main/java/bubble/cloud/payment/PaymentDriverBase.java b/bubble-server/src/main/java/bubble/cloud/payment/PaymentDriverBase.java index 68086ce4..6be55b7b 100644 --- a/bubble-server/src/main/java/bubble/cloud/payment/PaymentDriverBase.java +++ b/bubble-server/src/main/java/bubble/cloud/payment/PaymentDriverBase.java @@ -83,12 +83,11 @@ public abstract class PaymentDriverBase extends CloudServiceDriverBase imp return true; } - if (bill.hasPayment()) { - final AccountPayment existing = accountPaymentDAO.findByUuid(bill.getPayment()); - if (existing != null) { - log.warn("purchase: existing AccountPayment found (returning true): " + existing.getUuid()); - return true; - } + final AccountPayment successfulPayment = accountPaymentDAO.findByAccountAndAccountPlanAndBillAndSuccess(accountPlan.getAccount(), accountPlanUuid, bill.getUuid()); + if (successfulPayment != null) { + log.warn("purchase: successful AccountPayment found (marking Bill "+bill.getUuid()+" as paid and returning true): " + successfulPayment.getUuid()); + billDAO.update(bill.setStatus(BillStatus.paid)); + return true; } final String chargeInfo; @@ -132,7 +131,7 @@ public abstract class PaymentDriverBase extends CloudServiceDriverBase imp .setInfo(chargeInfo)); // mark the bill as paid, enable the plan - billDAO.update(bill.setPayment(accountPayment.getUuid()).setStatus(BillStatus.paid)); + billDAO.update(bill.setStatus(BillStatus.paid)); // if there are no unpaid bills, we can (re-)enable the plan final List unpaidBills = billDAO.findUnpaidByAccountPlan(accountPlan.getUuid()); @@ -159,8 +158,9 @@ public abstract class PaymentDriverBase extends CloudServiceDriverBase imp } // Was the recent bill paid? + final AccountPayment successfulPayment = accountPaymentDAO.findByAccountAndAccountPlanAndBillAndSuccess(accountPlan.getAccount(), accountPlanUuid, bill.getUuid()); if (bill.unpaid()) { - if (bill.hasPayment()) { + if (successfulPayment != null) { // should never happen throw invalidEx("err.refund.unpaidBillHasPayment"); } @@ -169,14 +169,13 @@ public abstract class PaymentDriverBase extends CloudServiceDriverBase imp } // What payment was used to pay the bill? - final AccountPayment payment = accountPaymentDAO.findByUuid(bill.getPayment()); - if (payment == null) { + if (successfulPayment == null) { log.warn("refund: AccountPlanPayment not found for paid bill ("+bill.getUuid()+") accountPlan: "+accountPlanUuid); throw invalidEx("err.refund.paymentNotFound"); } // Is the payment method associated with the bill still active? - final AccountPaymentMethod paymentMethod = paymentMethodDAO.findByUuid(payment.getPaymentMethod()); + final AccountPaymentMethod paymentMethod = paymentMethodDAO.findByUuid(successfulPayment.getPaymentMethod()); if (paymentMethod == null || paymentMethod.deleted()) { log.warn("refund: cannot refund: AccountPaymentMethod not found or deleted for paid bill ("+bill.getUuid()+") accountPlan: "+accountPlanUuid); throw invalidEx("err.refund.paymentMethodNotFound"); @@ -198,7 +197,7 @@ public abstract class PaymentDriverBase extends CloudServiceDriverBase imp final String refundInfo; try { - refundInfo = refund(accountPlan, payment, paymentMethod, bill, refundAmount); + refundInfo = refund(accountPlan, successfulPayment, paymentMethod, bill, refundAmount); } catch (RuntimeException e) { // record failed payment, rethrow accountPaymentDAO.create(new AccountPayment() diff --git a/bubble-server/src/main/java/bubble/dao/bill/AccountPaymentDAO.java b/bubble-server/src/main/java/bubble/dao/bill/AccountPaymentDAO.java index 6889b2b5..a435294f 100644 --- a/bubble-server/src/main/java/bubble/dao/bill/AccountPaymentDAO.java +++ b/bubble-server/src/main/java/bubble/dao/bill/AccountPaymentDAO.java @@ -2,6 +2,7 @@ package bubble.dao.bill; import bubble.dao.account.AccountOwnedEntityDAO; import bubble.model.bill.AccountPayment; +import bubble.model.bill.AccountPaymentStatus; import org.hibernate.criterion.Order; import org.springframework.stereotype.Repository; @@ -29,4 +30,8 @@ public class AccountPaymentDAO extends AccountOwnedEntityDAO { return findByFields("account", accountUuid, "accountPlan", accountPlanUuid, "bill", billUuid); } + public AccountPayment findByAccountAndAccountPlanAndBillAndSuccess(String accountUuid, String accountPlanUuid, String billUuid) { + return findByUniqueFields("account", accountUuid, "accountPlan", accountPlanUuid, "bill", billUuid, "status", AccountPaymentStatus.success); + } + } diff --git a/bubble-server/src/main/java/bubble/model/bill/AccountPayment.java b/bubble-server/src/main/java/bubble/model/bill/AccountPayment.java index 4005d95c..a7e4d220 100644 --- a/bubble-server/src/main/java/bubble/model/bill/AccountPayment.java +++ b/bubble-server/src/main/java/bubble/model/bill/AccountPayment.java @@ -20,6 +20,9 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.*; @ECType(root=true) @ECTypeURIs(listFields={"account", "paymentMethod", "amount"}) @ECTypeFields(list={"account", "paymentMethod", "amount"}) +@ECIndexes({ + @ECIndex(name="account_payment_uniq_bill_success", unique=true, of={"bill"}, where="status = 'success'") +}) @Entity @NoArgsConstructor @Accessors(chain=true) public class AccountPayment extends IdentifiableBase implements HasAccountNoName { 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 32dba105..e7b2867e 100644 --- a/bubble-server/src/main/java/bubble/model/bill/Bill.java +++ b/bubble-server/src/main/java/bubble/model/bill/Bill.java @@ -16,11 +16,11 @@ import javax.persistence.*; import static org.cobbzilla.util.daemon.ZillaRuntime.big; import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.*; -@ECType(root=true) @ECTypeURIs(listFields={"name", "status", "type", "quantity", "price", "period"}) -@ECTypeFields(list={"name", "Status", "type", "quantity", "price", "period"}) +@ECType(root=true) @ECTypeURIs(listFields={"name", "status", "type", "quantity", "price", "periodStart"}) +@ECTypeFields(list={"name", "status", "type", "quantity", "price", "periodStart"}) @Entity @NoArgsConstructor @Accessors(chain=true) @ECIndexes({ - @ECIndex(unique=true, of={"account", "plan", "type", "period"}) + @ECIndex(unique=true, of={"account", "plan", "type", "periodStart"}) }) public class Bill extends IdentifiableBase implements HasAccountNoName { @@ -42,11 +42,6 @@ public class Bill extends IdentifiableBase implements HasAccountNoName { public boolean paid() { return status == BillStatus.paid; } public boolean unpaid() { return !paid(); } - @ECForeignKey(entity=AccountPayment.class, cascade=false) - @Column(length=UUID_MAXLEN) - @Getter @Setter private String payment; - public boolean hasPayment () { return payment != null; } - @ECIndex @Enumerated(EnumType.STRING) @Column(nullable=false, updatable=false, length=20) @Getter @Setter private BillItemType type; diff --git a/bubble-server/src/main/java/bubble/model/cloud/CloudServiceData.java b/bubble-server/src/main/java/bubble/model/cloud/CloudServiceData.java index af82c7e7..e5e30388 100644 --- a/bubble-server/src/main/java/bubble/model/cloud/CloudServiceData.java +++ b/bubble-server/src/main/java/bubble/model/cloud/CloudServiceData.java @@ -28,7 +28,7 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; @ECTypeURIs(baseURI=EP_DATA, listFields={"key", "data", "expiration"}) @ECTypeFields(list={"key", "data", "expiration"}) @Entity @NoArgsConstructor @Accessors(chain=true) -@ECIndexes({ @ECIndex(unique=true, of={"cloudService", "key"}) }) +@ECIndexes({ @ECIndex(unique=true, of={"cloud", "key"}) }) public class CloudServiceData extends IdentifiableBase implements HasAccount { public static final String[] CREATE_FIELDS = {"account", "key", "data", "expiration"}; diff --git a/bubble-server/src/test/java/bubble/test/BubbleCoreSuite.java b/bubble-server/src/test/java/bubble/test/BubbleCoreSuite.java index f23ba6bf..200951fe 100644 --- a/bubble-server/src/test/java/bubble/test/BubbleCoreSuite.java +++ b/bubble-server/src/test/java/bubble/test/BubbleCoreSuite.java @@ -5,6 +5,7 @@ import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ + DbInit.class, AuthTest.class, PaymentTest.class, S3StorageTest.class, diff --git a/utils/cobbzilla-wizard b/utils/cobbzilla-wizard index ea4b74c9..74af88a8 160000 --- a/utils/cobbzilla-wizard +++ b/utils/cobbzilla-wizard @@ -1 +1 @@ -Subproject commit ea4b74c95d3f0d20bff6639fb75884ed88f8dedb +Subproject commit 74af88a87447e0a6661c8e5940f08a43d1fdf042