Bläddra i källkod

WIP. adding cancelAuthorization to payment drivers, add single-item get to PromotionsResource, add test stubs

tags/v0.7.2
Jonathan Cobb 4 år sedan
förälder
incheckning
ab006a88b7
20 ändrade filer med 219 tillägg och 9 borttagningar
  1. +8
    -0
      bubble-server/src/main/java/bubble/cloud/payment/PaymentDriverBase.java
  2. +2
    -0
      bubble-server/src/main/java/bubble/cloud/payment/PaymentServiceDriver.java
  3. +11
    -0
      bubble-server/src/main/java/bubble/cloud/payment/delegate/DelegatedPaymentDriver.java
  4. +71
    -0
      bubble-server/src/main/java/bubble/cloud/payment/stripe/StripePaymentDriver.java
  5. +4
    -1
      bubble-server/src/main/java/bubble/model/bill/AccountPayment.java
  6. +7
    -7
      bubble-server/src/main/java/bubble/model/bill/Promotion.java
  7. +1
    -0
      bubble-server/src/main/java/bubble/model/cloud/notify/NotificationType.java
  8. +23
    -0
      bubble-server/src/main/java/bubble/notify/payment/NotificationHandler_payment_driver_cancel_authorization.java
  9. +14
    -1
      bubble-server/src/main/java/bubble/resources/bill/PromotionsResource.java
  10. +9
    -0
      bubble-server/src/test/java/bubble/mock/MockStripePaymentDriver.java
  11. +13
    -0
      bubble-server/src/test/java/bubble/test/FirstMonthAndReferralMonthPromotionTest.java
  12. +15
    -0
      bubble-server/src/test/java/bubble/test/FirstMonthFreePromotionTest.java
  13. +13
    -0
      bubble-server/src/test/java/bubble/test/ReferralMonthFreePromotionTest.java
  14. +5
    -0
      bubble-server/src/test/resources/models/manifest-1mo-promo.json
  15. +10
    -0
      bubble-server/src/test/resources/models/system/cloudService_1mo_free.json
  16. +7
    -0
      bubble-server/src/test/resources/models/system/promotion_1mo_free.json
  17. +1
    -0
      bubble-server/src/test/resources/models/tests/promo/first_month_and_multiple_referral_months_free.json
  18. +1
    -0
      bubble-server/src/test/resources/models/tests/promo/first_month_free.json
  19. +1
    -0
      bubble-server/src/test/resources/models/tests/promo/referral_month_free.json
  20. +3
    -0
      pom.xml

+ 8
- 0
bubble-server/src/main/java/bubble/cloud/payment/PaymentDriverBase.java Visa fil

@@ -63,6 +63,10 @@ public abstract class PaymentDriverBase<T> extends CloudServiceDriverBase<T> imp
return true;
}

@Override public boolean cancelAuthorization(BubblePlan plan, String accountPlanUuid, AccountPaymentMethod paymentMethod) {
return true;
}

@Override public boolean purchase(String accountPlanUuid, String paymentMethodUuid, String billUuid) {

final AccountPlan accountPlan = getAccountPlan(accountPlanUuid);
@@ -87,6 +91,10 @@ public abstract class PaymentDriverBase<T> extends CloudServiceDriverBase<T> imp
return true;
}

// If the current PaymentDriver is not for a promotional credit,
// then check for AccountPaymentMethods associated with promotional credits
// If we have one, use that payment driver instead. It may apply a partial payment.

final String chargeInfo;
try {
chargeInfo = charge(plan, accountPlan, paymentMethod, bill);


+ 2
- 0
bubble-server/src/main/java/bubble/cloud/payment/PaymentServiceDriver.java Visa fil

@@ -20,6 +20,8 @@ public interface PaymentServiceDriver extends CloudServiceDriver {

boolean authorize(BubblePlan plan, String accountPlanUuid, AccountPaymentMethod paymentMethod);

boolean cancelAuthorization(BubblePlan plan, String accountPlanUuid, AccountPaymentMethod paymentMethod);

boolean purchase(String accountPlanUuid, String paymentMethodUuid, String billUuid);

boolean refund(String accountPlanUuid);


+ 11
- 0
bubble-server/src/main/java/bubble/cloud/payment/delegate/DelegatedPaymentDriver.java Visa fil

@@ -67,6 +67,17 @@ public class DelegatedPaymentDriver extends DelegatedCloudServiceDriverBase impl
return processResult(result);
}

@Override public boolean cancelAuthorization(BubblePlan plan, String accountPlanUuid, AccountPaymentMethod paymentMethod) {
final BubbleNode delegate = getDelegateNode();
final PaymentResult result = notificationService.notifySync(delegate, payment_driver_cancel_authorization,
new PaymentNotification()
.setCloud(cloud.getName())
.setPlanUuid(plan.getUuid())
.setAccountPlanUuid(accountPlanUuid)
.setPaymentMethodUuid(paymentMethod.getUuid()));
return processResult(result);
}

@Override public boolean purchase(String accountPlanUuid,
String paymentMethodUuid,
String billUuid) {


+ 71
- 0
bubble-server/src/main/java/bubble/cloud/payment/stripe/StripePaymentDriver.java Visa fil

@@ -212,6 +212,77 @@ public class StripePaymentDriver extends PaymentDriverBase<StripePaymentDriverCo
}
}

@Override public boolean cancelAuthorization(BubblePlan plan, String accountPlanUuid, AccountPaymentMethod paymentMethod) {
final String paymentMethodUuid = paymentMethod.getUuid();

final RedisService authCache = getAuthCache();
final String authCacheKey = getAuthCacheKey(accountPlanUuid, paymentMethodUuid);

final Map<String, Object> refundParams = new LinkedHashMap<>();;
final Refund refund;
final RedisService refundCache = getRefundCache();
try {
final Long price = plan.getPrice();
if (price <= 0) throw invalidEx("err.purchase.priceInvalid");

final String chargeId = authCache.get(authCacheKey);
if (chargeId == null) throw invalidEx("err.purchase.authNotFound");

final String refunded = refundCache.get(chargeId);
if (refunded != null) {
// already refunded, nothing to do
log.info("refund: already refunded: "+refunded);
return true;
}

refundParams.put("charge", chargeId);
refundParams.put("amount", price);
refund = Refund.create(refundParams);
if (refund.getStatus() == null) {
final String msg = "cancelAuthorization: no status returned for Charge, accountPlan=" + accountPlanUuid + " with paymentMethod=" + paymentMethodUuid;
log.error(msg);
throw invalidEx("err.refund.unknownError", msg);
} else {
final String msg;
switch (refund.getStatus()) {
case "succeeded":
log.info("cancelAuthorization: authorization of "+price+" successful cancelled");
refundCache.set(chargeId, refund.getId(), EX, REFUND_CACHE_DURATION);
return true;

case "pending":
msg = "cancelAuthorization: status='pending' (expected 'succeeded'), accountPlan=" + accountPlanUuid + " with paymentMethod=" + paymentMethodUuid;
log.error(msg);
throw invalidEx("err.refund.refundPendingError", msg);

default:
msg = "cancelAuthorization: status='"+refund.getStatus()+"' (expected 'succeeded'), accountPlan=" + accountPlanUuid + " with paymentMethod=" + paymentMethodUuid;
log.error(msg);
throw invalidEx("err.refund.refundFailedError", msg);
}
}

} catch (CardException e) {
// The card has been declined
final String msg = "cancelAuthorization: CardException for accountPlan=" + accountPlanUuid + " with paymentMethod=" + paymentMethodUuid + ": requestId=" + e.getRequestId() + ", code="+e.getCode()+", declineCode="+e.getDeclineCode()+", error=" + e.toString();
log.error(msg);
throw invalidEx("err.purchase.cardError", msg);

} catch (SimpleViolationException e) {
throw e;

} catch (StripeException e) {
final String msg = "cancelAuthorization: "+e.getClass().getSimpleName()+" for accountPlan=" + accountPlanUuid + " with paymentMethod=" + paymentMethodUuid + ": requestId=" + e.getRequestId() + ", code="+e.getCode()+", error=" + e.toString();
log.error(msg);
throw invalidEx("err.purchase.cardProcessingError", msg);

} catch (Exception e) {
final String msg = "cancelAuthorization: "+e.getClass().getSimpleName()+" for accountPlan=" + accountPlanUuid + " with paymentMethod=" + paymentMethodUuid + ": error=" + e.toString();
log.error(msg);
throw invalidEx("err.purchase.cardUnknownError", msg);
}
}

@Override protected String charge(BubblePlan plan,
AccountPlan accountPlan,
AccountPaymentMethod paymentMethod,


+ 4
- 1
bubble-server/src/main/java/bubble/model/bill/AccountPayment.java Visa fil

@@ -22,7 +22,10 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.*;
@ECType(root=true) @ECTypeCreate(method="DISABLED")
@ECTypeURIs(listFields={"account", "paymentMethod", "amount"})
@ECIndexes({
@ECIndex(name="account_payment_uniq_bill_type_success", unique=true, of={"bill", "type"}, where="status = 'success'")
@ECIndex(name="account_payment_uniq_bill_type_payment_method_success",
unique=true,
of={"bill", "type", "paymentMethod"},
where="status = 'success'")
})
@Entity @NoArgsConstructor @Accessors(chain=true)
public class AccountPayment extends IdentifiableBase implements HasAccountNoName {


+ 7
- 7
bubble-server/src/main/java/bubble/model/bill/Promotion.java Visa fil

@@ -21,12 +21,12 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.now;
import static org.cobbzilla.util.reflect.ReflectionUtil.copy;

@ECType(root=true)
@ECTypeURIs(baseURI=PROMOTIONS_ENDPOINT, listFields={"name", "enabled", "start", "end"})
@ECTypeURIs(baseURI=PROMOTIONS_ENDPOINT, listFields={"name", "priority", "enabled", "validFrom", "validTo", "code", "referral"})
@Entity @NoArgsConstructor @Accessors(chain=true)
public class Promotion extends IdentifiableBase implements NamedEntity, HasPriority {

public static final String[] UPDATE_FIELDS = {"enabled", "start", "end"};
public static final String[] CREATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, "name", "referral");
public static final String[] UPDATE_FIELDS = {"priority", "enabled", "validFrom", "validTo"};
public static final String[] CREATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, "name", "code", "referral");

public Promotion (Promotion other) { copy(this, other, CREATE_FIELDS); }

@@ -55,12 +55,12 @@ public class Promotion extends IdentifiableBase implements NamedEntity, HasPrior
public boolean enabled () { return enabled == null || enabled; }

@ECSearchable @ECField(index=60)
@ECIndex @Getter @Setter private Long start;
public boolean hasStarted () { return start == null || start > now(); }
@ECIndex @Getter @Setter private Long validFrom;
public boolean hasStarted () { return validFrom == null || validFrom > now(); }

@ECSearchable @ECField(index=70)
@ECIndex @Getter @Setter private Long end;
public boolean hasEnded () { return end != null && end > now(); }
@ECIndex @Getter @Setter private Long validTo;
public boolean hasEnded () { return validTo != null && validTo > now(); }

public boolean active () { return enabled() && hasStarted() && !hasEnded(); }
public boolean inactive () { return !active(); }


+ 1
- 0
bubble-server/src/main/java/bubble/model/cloud/notify/NotificationType.java Visa fil

@@ -92,6 +92,7 @@ public enum NotificationType {
payment_driver_validate (PaymentValidationResult.class),
payment_driver_claim (PaymentValidationResult.class),
payment_driver_authorize (PaymentResult.class),
payment_driver_cancel_authorization (PaymentResult.class),
payment_driver_purchase (PaymentResult.class),
payment_driver_refund (PaymentResult.class),
payment_driver_response (true);


+ 23
- 0
bubble-server/src/main/java/bubble/notify/payment/NotificationHandler_payment_driver_cancel_authorization.java Visa fil

@@ -0,0 +1,23 @@
package bubble.notify.payment;

import bubble.cloud.payment.PaymentServiceDriver;
import bubble.dao.bill.AccountPaymentMethodDAO;
import bubble.dao.bill.BubblePlanDAO;
import bubble.model.bill.AccountPaymentMethod;
import bubble.model.bill.BubblePlan;
import bubble.model.cloud.CloudService;
import org.springframework.beans.factory.annotation.Autowired;

public class NotificationHandler_payment_driver_cancel_authorization extends NotificationHandler_payment_driver {

@Autowired private BubblePlanDAO planDAO;
@Autowired private AccountPaymentMethodDAO paymentMethodDAO;

@Override public boolean handlePaymentRequest(PaymentNotification paymentNotification, CloudService paymentService) {
final BubblePlan plan = planDAO.findByUuid(paymentNotification.getPlanUuid());
final AccountPaymentMethod paymentMethod = paymentMethodDAO.findByUuid(paymentNotification.getPaymentMethodUuid());
final PaymentServiceDriver paymentDriver = paymentService.getPaymentDriver(configuration);
return paymentDriver.cancelAuthorization(plan, paymentNotification.getAccountPlanUuid(), paymentMethod);
}

}

+ 14
- 1
bubble-server/src/main/java/bubble/resources/bill/PromotionsResource.java Visa fil

@@ -11,17 +11,20 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.glassfish.jersey.server.ContainerRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

import static bubble.ApiConstants.PROMOTIONS_ENDPOINT;
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON;
import static org.cobbzilla.wizard.resources.ResourceUtil.*;

@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@Slf4j
@Path(PROMOTIONS_ENDPOINT)
@Service @Slf4j
public class PromotionsResource {

@Autowired private CloudServiceDAO cloudDAO;
@@ -37,6 +40,16 @@ public class PromotionsResource {
return ok(promotionDAO.findEnabledAndNoCodeOrWithCode(code));
}

@GET @Path("/{id}")
public Response findPromo(@Context ContainerRequest ctx,
@PathParam("id") String id) {
final Account caller = userPrincipal(ctx);
if (!caller.admin()) return forbidden();
if (!caller.getUuid().equals(getFirstAdmin().getUuid())) return forbidden();

return ok(promotionDAO.findById(id));
}

@PUT
public Response createPromo(@Context ContainerRequest ctx,
Promotion request) {


+ 9
- 0
bubble-server/src/test/java/bubble/mock/MockStripePaymentDriver.java Visa fil

@@ -29,6 +29,15 @@ public class MockStripePaymentDriver extends StripePaymentDriver {
}
}

@Override public boolean cancelAuthorization(BubblePlan plan, String accountPlanUuid, AccountPaymentMethod paymentMethod) {
final String err = error.get();
if (err != null && (err.equals("cancelAuthorization") || err.equals("all"))) {
throw invalidEx("err.purchase.authNotFound", "mock: error flag="+err);
} else {
return super.cancelAuthorization(plan, accountPlanUuid, paymentMethod);
}
}

@Override protected String charge(BubblePlan plan, AccountPlan accountPlan, AccountPaymentMethod paymentMethod, Bill bill) {
final String err = error.get();
if (err != null && (err.equals("charge") || err.equals("all"))) {


+ 13
- 0
bubble-server/src/test/java/bubble/test/FirstMonthAndReferralMonthPromotionTest.java Visa fil

@@ -0,0 +1,13 @@
package bubble.test;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

@Slf4j
public class FirstMonthAndReferralMonthPromotionTest extends PaymentTestBase {

@Test public void testFirstMonthAndMultipleReferralMonthsFree () throws Exception {
modelTest("promo/first_month_and_multiple_referral_months_free");
}

}

+ 15
- 0
bubble-server/src/test/java/bubble/test/FirstMonthFreePromotionTest.java Visa fil

@@ -0,0 +1,15 @@
package bubble.test;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

@Slf4j
public class FirstMonthFreePromotionTest extends PaymentTestBase {

@Override protected String getManifest() { return "manifest-1mo-promo"; }

@Test public void testFirstMonthFree () throws Exception {
modelTest("promo/first_month_free");
}

}

+ 13
- 0
bubble-server/src/test/java/bubble/test/ReferralMonthFreePromotionTest.java Visa fil

@@ -0,0 +1,13 @@
package bubble.test;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

@Slf4j
public class ReferralMonthFreePromotionTest extends PaymentTestBase {

@Test public void testReferralMonthFree () throws Exception {
modelTest("promo/referral_month_free");
}

}

+ 5
- 0
bubble-server/src/test/resources/models/manifest-1mo-promo.json Visa fil

@@ -0,0 +1,5 @@
[
"manifest-test",
"system/cloudService_1mo_free",
"system/promotion_1mo_free"
]

+ 10
- 0
bubble-server/src/test/resources/models/system/cloudService_1mo_free.json Visa fil

@@ -0,0 +1,10 @@
[
{
"name": "FirstMonthFree",
"type": "payment",
"driverClass": "bubble.cloud.payment.firstMonthFree.FirstMonthFreePaymentDriver",
"driverConfig": {},
"credentials": {},
"template": true
}
]

+ 7
- 0
bubble-server/src/test/resources/models/system/promotion_1mo_free.json Visa fil

@@ -0,0 +1,7 @@
[
{
"name": "FirstMonthFree",
"cloud": "FirstMonthFree",
"priority": 1
}
]

+ 1
- 0
bubble-server/src/test/resources/models/tests/promo/first_month_and_multiple_referral_months_free.json Visa fil

@@ -0,0 +1 @@
[]

+ 1
- 0
bubble-server/src/test/resources/models/tests/promo/first_month_free.json Visa fil

@@ -0,0 +1 @@
[]

+ 1
- 0
bubble-server/src/test/resources/models/tests/promo/referral_month_free.json Visa fil

@@ -0,0 +1 @@
[]

+ 3
- 0
pom.xml Visa fil

@@ -69,6 +69,9 @@ This code is available under the GNU Affero General Public License, version 3: h
<include>bubble.test.AuthTest</include>
<include>bubble.test.PaymentTest</include>
<include>bubble.test.RecurringBillingTest</include>
<include>bubble.test.FirstMonthFreePromotionTest</include>
<include>bubble.test.ReferralMonthFreePromotionTest</include>
<include>bubble.test.FirstMonthAndReferralMonthPromotionTest</include>
<include>bubble.test.DriverTest</include>
<include>bubble.test.ProxyTest</include>
<include>bubble.test.TrafficAnalyticsTest</include>


Laddar…
Avbryt
Spara