From c841ed63269f78a2bc89b4c903d3bd03e8389756 Mon Sep 17 00:00:00 2001 From: Jonathan Cobb Date: Sat, 14 Dec 2019 16:05:42 -0500 Subject: [PATCH] WIP. free and code payment tests now passing --- .../src/main/java/bubble/ApiConstants.java | 1 + .../cloud/payment/PaymentDriverBase.java | 3 +- .../cloud/payment/code/CodePaymentDriver.java | 2 +- .../cloud/payment/code/CodePaymentToken.java | 2 +- .../bubble/dao/bill/AccountPaymentDAO.java | 4 ++ .../java/bubble/dao/bill/AccountPlanDAO.java | 9 ++++ .../bubble/resources/bill/BillsResource.java | 45 ++++++++++++++++++- .../post_auth/ResourceMessages.properties | 6 ++- .../models/tests/payment/pay_code.json | 37 +++++++++------ .../models/tests/payment/pay_credit.json | 6 +-- 10 files changed, 93 insertions(+), 22 deletions(-) diff --git a/bubble-server/src/main/java/bubble/ApiConstants.java b/bubble-server/src/main/java/bubble/ApiConstants.java index 8b99795e..5389647a 100644 --- a/bubble-server/src/main/java/bubble/ApiConstants.java +++ b/bubble-server/src/main/java/bubble/ApiConstants.java @@ -122,6 +122,7 @@ public class ApiConstants { public static final String EP_PAYMENT_METHODS = PAYMENT_METHODS_ENDPOINT; public static final String EP_PAYMENT = "/payment"; public static final String EP_PAYMENTS = "/payments"; + public static final String EP_PAY = "/pay"; public static final String EP_BILL = "/bill"; public static final String EP_BILLS = "/bills"; public static final String EP_CLOSEST = "/closest"; 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 d0bd1a2d..28ef7e46 100644 --- a/bubble-server/src/main/java/bubble/cloud/payment/PaymentDriverBase.java +++ b/bubble-server/src/main/java/bubble/cloud/payment/PaymentDriverBase.java @@ -95,6 +95,7 @@ public abstract class PaymentDriverBase extends CloudServiceDriverBase imp } catch (RuntimeException e) { // record failed payment, rethrow accountPaymentDAO.create(new AccountPayment() + .setType(AccountPaymentType.payment) .setAccount(accountPlan.getAccount()) .setPlan(accountPlan.getPlan()) .setAccountPlan(accountPlan.getUuid()) @@ -212,7 +213,7 @@ public abstract class PaymentDriverBase extends CloudServiceDriverBase imp String refundInfo) { // record the payment final AccountPayment accountPayment = accountPaymentDAO.create(new AccountPayment() - .setType(AccountPaymentType.payment) + .setType(AccountPaymentType.refund) .setAccount(accountPlan.getAccount()) .setPlan(accountPlan.getPlan()) .setAccountPlan(accountPlan.getUuid()) diff --git a/bubble-server/src/main/java/bubble/cloud/payment/code/CodePaymentDriver.java b/bubble-server/src/main/java/bubble/cloud/payment/code/CodePaymentDriver.java index 85f74f0b..e3ca32ea 100644 --- a/bubble-server/src/main/java/bubble/cloud/payment/code/CodePaymentDriver.java +++ b/bubble-server/src/main/java/bubble/cloud/payment/code/CodePaymentDriver.java @@ -151,7 +151,7 @@ public class CodePaymentDriver extends PaymentDriverBase expiration; } - public boolean hasPaymentMethod(String accountPlan) { + public boolean hasAccountPlan(String accountPlan) { return this.accountPlan != null && this.accountPlan.equals(accountPlan); } 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 d7d964a4..6889b2b5 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 org.hibernate.criterion.Order; import org.springframework.stereotype.Repository; import java.util.List; @@ -9,6 +10,9 @@ import java.util.List; @Repository public class AccountPaymentDAO extends AccountOwnedEntityDAO { + // newest first + @Override public Order getDefaultSortOrder() { return Order.desc("ctime"); } + public List findByAccountAndPlan(String accountUuid, String accountPlanUuid) { return findByFields("account", accountUuid, "plan", accountPlanUuid); } diff --git a/bubble-server/src/main/java/bubble/dao/bill/AccountPlanDAO.java b/bubble-server/src/main/java/bubble/dao/bill/AccountPlanDAO.java index 40f4e29f..83eca542 100644 --- a/bubble-server/src/main/java/bubble/dao/bill/AccountPlanDAO.java +++ b/bubble-server/src/main/java/bubble/dao/bill/AccountPlanDAO.java @@ -8,6 +8,7 @@ import bubble.model.bill.*; import bubble.model.cloud.BubbleNetwork; import bubble.model.cloud.BubbleNetworkState; import bubble.model.cloud.CloudService; +import bubble.notify.payment.PaymentValidationResult; import bubble.server.BubbleConfiguration; import bubble.service.bill.RefundService; import org.springframework.beans.factory.annotation.Autowired; @@ -47,10 +48,18 @@ public class AccountPlanDAO extends AccountOwnedEntityDAO { if (!accountPlan.hasPaymentMethodObject()) throw invalidEx("err.paymentMethod.required"); if (!accountPlan.getPaymentMethodObject().hasUuid()) throw invalidEx("err.paymentMethod.required"); + if (accountPlan.getPaymentMethod() == null) { + accountPlan.setPaymentMethod(accountPlan.getPaymentMethodObject().getUuid()); + } + final CloudService paymentService = cloudDAO.findByUuid(accountPlan.getPaymentMethodObject().getCloud()); if (paymentService == null) throw invalidEx("err.paymentService.notFound"); final PaymentServiceDriver paymentDriver = paymentService.getPaymentDriver(configuration); + if (paymentDriver.getPaymentMethodType().requiresClaim()) { + final PaymentValidationResult result = paymentDriver.claim(accountPlan); + if (result.hasErrors()) throw invalidEx(result.violationsList()); + } if (paymentDriver.getPaymentMethodType().requiresAuth()) { final BubblePlan plan = planDAO.findByUuid(accountPlan.getPlan()); paymentDriver.authorize(plan, accountPlan.getPaymentMethodObject()); 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 819497bc..5c27ba95 100644 --- a/bubble-server/src/main/java/bubble/resources/bill/BillsResource.java +++ b/bubble-server/src/main/java/bubble/resources/bill/BillsResource.java @@ -1,24 +1,37 @@ package bubble.resources.bill; +import bubble.cloud.payment.PaymentServiceDriver; +import bubble.dao.bill.AccountPaymentMethodDAO; import bubble.dao.bill.BillDAO; +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.cloud.CloudService; import bubble.resources.account.ReadOnlyAccountOwnedResource; import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.wizard.validation.ValidationResult; import org.glassfish.jersey.server.ContainerRequest; +import org.springframework.beans.factory.annotation.Autowired; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; import java.util.List; +import static bubble.ApiConstants.EP_PAY; import static bubble.ApiConstants.EP_PAYMENTS; -import static org.cobbzilla.wizard.resources.ResourceUtil.notFoundEx; +import static org.cobbzilla.wizard.resources.ResourceUtil.*; @Slf4j public class BillsResource extends ReadOnlyAccountOwnedResource { + @Autowired private AccountPaymentMethodDAO paymentMethodDAO; + @Autowired private CloudServiceDAO cloudDAO; + private AccountPlan accountPlan; public BillsResource(Account account) { super(account); } @@ -46,4 +59,34 @@ public class BillsResource extends ReadOnlyAccountOwnedResource { return configuration.subResource(AccountPaymentsResource.class, account, bill); } + @POST @Path("/{id}"+EP_PAY) + public Response payBill(@Context ContainerRequest ctx, + @PathParam("id") String id, + AccountPaymentMethod paymentMethod) { + final Bill bill = super.find(ctx, id); + if (bill == null) return notFound(id); + if (bill.paid()) return invalid("err.bill.alreadyPaid"); + + final AccountPaymentMethod payMethodToUse; + if (paymentMethod.hasUuid()) { + payMethodToUse = paymentMethodDAO.findByUuid(paymentMethod.getUuid()); + if (payMethodToUse == null) return invalid("err.paymentMethod.notFound"); + } else { + final ValidationResult result = new ValidationResult(); + paymentMethod.setAccount(getAccountUuid(ctx)).validate(result, configuration); + if (result.isInvalid()) return invalid(result); + payMethodToUse = paymentMethodDAO.create(paymentMethod); + } + final CloudService paymentCloud = cloudDAO.findByUuid(payMethodToUse.getCloud()); + if (paymentCloud == null) return invalid("err.paymentService.notFound"); + + final PaymentServiceDriver paymentDriver = paymentCloud.getPaymentDriver(configuration); + if (paymentDriver.purchase(bill.getAccountPlan(), payMethodToUse.getUuid(), bill.getUuid())) { + // re-lookup bill, should now be paid + return ok(getDao().findByUuid(bill.getUuid())); + } else { + return invalid("err.purchase.declined"); + } + } + } diff --git a/bubble-server/src/main/resources/message_templates/server/en_US/post_auth/ResourceMessages.properties b/bubble-server/src/main/resources/message_templates/server/en_US/post_auth/ResourceMessages.properties index 4024697a..e1ee75a0 100644 --- a/bubble-server/src/main/resources/message_templates/server/en_US/post_auth/ResourceMessages.properties +++ b/bubble-server/src/main/resources/message_templates/server/en_US/post_auth/ResourceMessages.properties @@ -82,6 +82,7 @@ err.authenticator.notConfigured=Authenticator has not been configured err.backup.cannotDelete=Cannot delete backup with its current status err.backupCleaner.didNotRun=Backup cleaner did not run err.backupCleaner.neverRun=Backup cleaner was never run +err.bill.alreadyPaid=Bill has already been paid err.cannotCreate=Create not allowed err.cannotUpdate=Update not allowed err.chargeName.required=Charge name is required @@ -159,13 +160,14 @@ err.node.running=Node must be stopped before deleting err.node.shutdownFailed=Node shutdown failed err.node.stop.error=Error stopping node err.oldPassword.invalid=Old password was invalid -err.paymentMethod.required=Payment method is required -err.paymentMethod.cannotDeleteInUse=Cannot delete payment method that is currently in use err.paymentInfo.invalid=Payment information is invalid err.paymentInfo.required=Payment information is required err.paymentInfo.processingError=Processing payment information failed err.paymentInfo.emailRequired=To use this payment method, please add an email address to your account err.paymentInfo.verifiedEmailRequired=To use this payment method, please verify your email address +err.paymentMethod.required=Payment method is required +err.paymentMethod.notFound=Payment method not found +err.paymentMethod.cannotDeleteInUse=Cannot delete payment method that is currently in use err.paymentMethodInfo.invalid=Payment method information is invalid err.paymentMethodType.required=Payment method type is required err.paymentMethodType.mismatch=Payment method type mismatch diff --git a/bubble-server/src/test/resources/models/tests/payment/pay_code.json b/bubble-server/src/test/resources/models/tests/payment/pay_code.json index b496edc7..9b39fd8d 100644 --- a/bubble-server/src/test/resources/models/tests/payment/pay_code.json +++ b/bubble-server/src/test/resources/models/tests/payment/pay_code.json @@ -89,6 +89,7 @@ }, { + "before": "sleep 15s", "comment": "verify account payment methods, should be one", "request": { "uri": "me/paymentMethods" }, "response": { @@ -148,18 +149,28 @@ }, { - "comment": "check payments for plan, expect one failed payment" - }, - - { - "comment": "pay for plan with a different code, succeeds" - }, - - { - "comment": "check payments for plan, expect two payments, one failed, the second successful" - }, - - { - "comment": "try to add plan with expired code, expect error" + "comment": "try to add plan with expired code, expect error", + "request": { + "uri": "me/plans", + "method": "put", + "entity": { + "name": "test-net3-{{rand 5}}", + "domain": "{{defaultDomain}}", + "locale": "en_US", + "timezone": "EST", + "plan": "{{plans.[0].name}}", + "footprint": "US", + "paymentMethodObject": { + "paymentMethodType": "code", + "paymentInfo": "expired_invite_token" + } + } + }, + "response": { + "status": 422, + "check": [ + {"condition": "json.has('err.purchase.tokenExpired')"} + ] + } } ] \ No newline at end of file diff --git a/bubble-server/src/test/resources/models/tests/payment/pay_credit.json b/bubble-server/src/test/resources/models/tests/payment/pay_credit.json index 55c28456..4da250b5 100644 --- a/bubble-server/src/test/resources/models/tests/payment/pay_credit.json +++ b/bubble-server/src/test/resources/models/tests/payment/pay_credit.json @@ -60,7 +60,7 @@ "timezone": "EST", "plan": "{{plans.[0].name}}", "footprint": "US", - "paymentMethod": { + "paymentMethodObject": { "paymentMethodType": "credit", "paymentInfo": "invalid_token" } @@ -86,7 +86,7 @@ "timezone": "EST", "plan": "{{plans.[0].name}}", "footprint": "US", - "paymentMethod": { + "paymentMethodObject": { "paymentMethodType": "credit", "paymentInfo": "{{stripeToken}}" } @@ -138,7 +138,7 @@ "timezone": "EST", "plan": "{{plans.[0].name}}", "footprint": "US", - "paymentMethod": { + "paymentMethodObject": { "paymentMethodType": "credit", "paymentInfo": "{{stripeToken}}" }