Payment: Refactor and update to support multiple subscriptions for a user at a time

This commit is contained in:
Tu Tran committed 2020-07-28 18:12:09 +07:00
1 parent bad67dd2fe
commit 436c90eba8
27 files changed
+641 -462

No files matched your search

@@ -0,0 +1,148 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.subscriptiontab;
import java.util.Date;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.ButtonElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.uibinder.client.UiRenderer;
import com.sap.sailing.gwt.home.shared.places.user.profile.subscription.UserSubscriptionView;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.subscription.chargebee.SubscriptionItem;
import com.sap.sse.security.shared.SubscriptionPlan;
import com.sap.sse.security.shared.SubscriptionPlanHolder;
public class SubscriptionCell extends AbstractCell<SubscriptionItem> {
interface Style extends CssResource {
String subscriptionContainer();
String textRow();
String trialText();
String errorText();
String blueText();
String hide();
String cancelButton();
}
interface MyUiRenderer extends UiRenderer {
void render(SafeHtmlBuilder sb, String planName, String subscriptionStatus, String trial, String paymentStatus,
String subscriptionStatusCssClass, String paymentStatusCssClass, String trialCssClass);
void onBrowserEvent(SubscriptionCell cell, NativeEvent event, Element parent, SubscriptionItem value);
Style getStyle();
ButtonElement getCancelButton(Element parent);
}
private static MyUiRenderer renderer = GWT.create(MyUiRenderer.class);
private UserSubscriptionView.Presenter presenter;
public SubscriptionCell(UserSubscriptionView.Presenter presenter) {
super("click");
this.presenter = presenter;
}
@Override
public void render(Context context, SubscriptionItem subscription, SafeHtmlBuilder sb) {
SubscriptionPlan plan = getSubscriptionPlan(subscription);
if (plan != null) {
String planName = plan.getName();
String subscriptionStatus = subscription.getSubscriptionStatusLabel();
String subscriptionStatusCssClass = "";
String paymentStatus = "";
String paymentStatusCssClass = "";
String trial = "";
String trialCssClass = "";
if (subscription.isActive()) {
subscriptionStatusCssClass = renderer.getStyle().blueText();
paymentStatus = subscription.getPaymentStatusLabel();
paymentStatusCssClass = subscription.isPaymentSuccess() ? renderer.getStyle().blueText()
: renderer.getStyle().errorText();
} else {
paymentStatusCssClass = renderer.getStyle().hide();
if (subscription.isInTrial()) {
trial = buildTrialText(subscription);
} else {
trialCssClass = renderer.getStyle().hide();
}
}
renderer.render(sb, planName, subscriptionStatus, trial, paymentStatus, subscriptionStatusCssClass,
paymentStatusCssClass, trialCssClass);
}
}
@Override
public void onBrowserEvent(Context context, Element parent, SubscriptionItem value, NativeEvent event,
ValueUpdater<SubscriptionItem> valueUpdater) {
renderer.onBrowserEvent(this, event, parent, value);
}
@UiHandler({ "cancelButton" })
void onCancelButtonPressed(ClickEvent event, Element parent, SubscriptionItem subscription) {
renderer.getCancelButton(parent).setDisabled(true);
presenter.cancelSubscription(subscription.getPlanId());
}
private SubscriptionPlan getSubscriptionPlan(SubscriptionItem subscription) {
return subscription != null ? SubscriptionPlanHolder.getInstance().getPlan(subscription.getPlanId()) : null;
}
private String buildTrialText(SubscriptionItem subscription) {
return StringMessages.INSTANCE.trialText(getTrialRemainingText(subscription),
DateTimeFormat.getFormat("yyyy-MM-dd HH:mm").format(new Date(subscription.getTrialEnd() * 1000)));
}
private String getTrialRemainingText(SubscriptionItem subscription) {
long now = Math.round(Duration.currentTimeMillis() / 1000);
long remain = subscription.getTrialEnd() - now;
StringBuilder remainText = new StringBuilder();
if (remain <= 0) {
remainText.append(StringMessages.INSTANCE.numHours(0));
} else {
int days = (int) (remain / 86400);
if (days > 0) {
remainText.append(StringMessages.INSTANCE.numDays(days));
}
remain = remain % 86400;
int hours = (int) (remain / 3600);
if (hours > 0) {
if (remainText.length() > 0) {
remainText.append(" ");
}
remainText.append(StringMessages.INSTANCE.numHours(hours));
}
if (days == 0) {
remain = remain % 3600;
int mins = (int) (remain / 60);
if (mins > 0) {
if (remainText.length() > 0) {
remainText.append(" ");
}
remainText.append(StringMessages.INSTANCE.numMinutes(mins));
}
}
}
if (remainText.length() == 0) {
remainText.append(StringMessages.INSTANCE.numHours(0));
}
return remainText.toString();
}
}
@@ -0,0 +1,50 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:um="urn:import:com.sap.sailing.gwt.home.shared.usermanagement.decorator"
xmlns:c="urn:import:com.google.gwt.user.cellview.client">
<ui:with field="i18n" type="com.sap.sailing.gwt.ui.client.StringMessages" />
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:with field="planName" type="java.lang.String" />
<ui:with field="subscriptionStatus" type="java.lang.String" />
<ui:with field="trial" type="java.lang.String" />
<ui:with field="paymentStatus" type="java.lang.String" />
<ui:with field="subscriptionStatusCssClass" type="java.lang.String" />
<ui:with field="paymentStatusCssClass" type="java.lang.String" />
<ui:with field="trialCssClass" type="java.lang.String" />
<ui:style field="style" type="com.sap.sailing.gwt.home.desktop.places.user.profile.subscriptiontab.SubscriptionCell.Style">
.subscriptionContainer {
margin-bottom: 25px;
}
.textRow {
margin-top: 5px;
}
.trialText {
color: #ff0000;
}
.errorText{
color: #ff0000;
}
.blueText {
color: #0000ff;
}
.hide {
display: none;
}
.cancelButton {
margin-top: 10px;
}
</ui:style>
<div class="{style.subscriptionContainer}">
<div>Subscription plan: <span><ui:text from="{planName}" /></span></div>
<div class="{style.textRow} {subscriptionStatusCssClass}">
Subscription status: <span><ui:text from="{subscriptionStatus}" /></span>
</div>
<div ui:field="trialDivUi" class="{style.textRow} {style.trialText} {trialCssClass}"><ui:text from="{trial}" /></div>
<div ui:field="paymentStatusDivUi" class="{style.textRow} {paymentStatusCssClass}">
Payment charge status: <span><ui:text from="{paymentStatus}" /></span>
</div>
<button ui:field="cancelButton" class="{res.mainCss.button} {res.mainCss.buttonprimary} {style.cancelButton}">
<ui:text from="{i18n.cancelSubscription}" />
</button>
</div>
</ui:UiBinder>
@@ -15,17 +15,11 @@ public interface SubscriptionProfileDesktopResources extends ClientBundle {
SubscriptionProfileCss css();
public interface SubscriptionProfileCss extends CssResource {
String emptySubscriptionsLabel();
String bottomButton();
String trialText();
String textRow();
String errorText();
String blueText();
String plansLabel();
String sectionLabel();
String plansInput();
@@ -1,27 +1,20 @@
.emptySubscriptionsLabel {
color: #777;
}
.bottomButton {
margin-top: 20px;
}
.textRow {
margin-top: 5px;
}
.trialText {
color: #ff0000;
}
.errorText{
color: #ff0000;
}
.blueText {
color: #0000ff;
}
.planInputContainer {
margin-top: 10px;
}
.plansLabel {
.sectionLabel {
display: block;
width: 100%;
font-weight: 600;
margin-bottom: 0.4em;
margin-bottom: 0.8em;
margin-top: 1.5em;
padding: 0;
font-size: 1.5rem;
}
.plansInput {
display: block;
@@ -1,25 +1,26 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.subscriptiontab;
import java.util.Date;
import java.util.ArrayList;
import java.util.Arrays;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.cellview.client.CellList;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.shared.places.user.profile.subscription.UserSubscriptionView;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.subscription.chargebee.SubscriptionDTO;
import com.sap.sailing.gwt.ui.shared.subscription.chargebee.SubscriptionItem;
import com.sap.sse.gwt.client.Notification;
import com.sap.sse.gwt.client.Notification.NotificationType;
import com.sap.sse.security.shared.SubscriptionPlan;
@@ -39,45 +40,28 @@ public class UserSubscription extends Composite implements UserSubscriptionView
@UiField
DivElement rootUi;
@UiField
Button updateSubscriptionButtonUi;
@UiField
Button cancelSubscriptionButtonUi;
@UiField
SpanElement planNameSpanUi;
@UiField
DivElement trialDivUi;
@UiField
DivElement subscriptionGroupUi;
@UiField
SpanElement subscriptionStatusSpanUi;
@UiField
SpanElement paymentStatusSpanUi;
@UiField
DivElement paymentStatusDivUi;
Button subscribeButtonUi;
@UiField
ListBox planListUi;
@UiField(provided = true)
CellList<SubscriptionItem> subscriptionListUi;
private Presenter presenter;
public UserSubscription(UserSubscriptionView.Presenter presenter) {
initSubscriptionCellList(presenter);
initWidget(uiBinder.createAndBindUi(this));
presenter.setView(this);
this.presenter = presenter;
}
@UiHandler("updateSubscriptionButtonUi")
@UiHandler("subscribeButtonUi")
public void handleUpdateSubscriptionClick(ClickEvent e) {
updateSubscriptionButtonUi.setEnabled(false);
subscribeButtonUi.setEnabled(false);
presenter.openCheckout(planListUi.getSelectedValue());
}
@UiHandler("cancelSubscriptionButtonUi")
public void handleCancelSubscriptionClick(ClickEvent e) {
cancelSubscriptionButtonUi.setEnabled(false);
presenter.cancelSubscription();
}
@Override
protected void onLoad() {
super.onLoad();
@@ -91,136 +75,46 @@ public class UserSubscription extends Composite implements UserSubscriptionView
@Override
public void onOpenCheckoutError(String error) {
updateSubscriptionButtonUi.setEnabled(true);
subscribeButtonUi.setEnabled(true);
Notification.notify(error, NotificationType.ERROR);
}
@Override
public void onCloseCheckoutModal() {
updateSubscriptionButtonUi.setEnabled(true);
subscribeButtonUi.setEnabled(true);
}
@Override
public void updateView(SubscriptionDTO subscription) {
resetElementsVisibleState();
updatePlanList(subscription);
updateSubscriptionButtonUi.setEnabled(true);
SubscriptionPlan plan = getSubscriptionPlan(subscription);
if (subscription == null || plan == null) {
updateViewWithFreePlan();
subscribeButtonUi.setEnabled(true);
if (subscription == null) {
subscriptionListUi.setRowData(new ArrayList<SubscriptionItem>());
} else {
updateViewWithPlan(subscription, plan);
subscriptionListUi.setRowData(Arrays.asList(subscription.getSubscriptionItems()));
}
subscriptionListUi.redraw();
show();
}
private SubscriptionPlan getSubscriptionPlan(SubscriptionDTO subscription) {
return subscription != null ? SubscriptionPlanHolder.getInstance().getPlan(subscription.getPlanId()) : null;
}
private void updateViewWithFreePlan() {
planNameSpanUi.setInnerText(StringMessages.INSTANCE.freePlan());
hideElement(subscriptionGroupUi);
cancelSubscriptionButtonUi.setVisible(false);
}
private void updateViewWithPlan(SubscriptionDTO subscription, SubscriptionPlan plan) {
planNameSpanUi.setInnerText(plan.getName());
subscriptionStatusSpanUi.setInnerText(subscription.getSubscriptionStatusLabel());
if (subscription.isActive()) {
subscriptionStatusSpanUi.addClassName(SubscriptionProfileDesktopResources.INSTANCE.css().blueText());
paymentStatusSpanUi.setInnerText(subscription.getPaymentStatusLabel());
paymentStatusSpanUi.addClassName(
subscription.isPaymentSuccess() ? SubscriptionProfileDesktopResources.INSTANCE.css().blueText()
: SubscriptionProfileDesktopResources.INSTANCE.css().errorText());
} else {
hideElement(paymentStatusDivUi);
if (subscription.isInTrial()) {
trialDivUi.setInnerText(buildTrialText(subscription));
} else {
hideElement(trialDivUi);
}
}
private void initSubscriptionCellList(Presenter presenter) {
subscriptionListUi = new CellList<SubscriptionItem>(new SubscriptionCell(presenter));
Label emptyWidget = new Label(StringMessages.INSTANCE.noSubscriptions());
emptyWidget.setStyleName(SubscriptionProfileDesktopResources.INSTANCE.css().emptySubscriptionsLabel());
subscriptionListUi.setEmptyListWidget(emptyWidget);
subscriptionListUi.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
}
private void updatePlanList(SubscriptionDTO subscription) {
planListUi.clear();
if (subscription == null) {
planListUi.addItem("", "");
planListUi.setSelectedIndex(0);
}
SubscriptionPlan[] planList = SubscriptionPlanHolder.getInstance().getPlanList();
int i = 0;
for (SubscriptionPlan plan : planList) {
planListUi.addItem(plan.getName(), plan.getId());
if (subscription != null && subscription.getPlanId().equals(plan.getId())) {
planListUi.setSelectedIndex(i);
}
i++;
}
}
private String buildTrialText(SubscriptionDTO subscription) {
return StringMessages.INSTANCE.trialText(getTrialRemainingText(subscription),
DateTimeFormat.getFormat("yyyy-MM-dd HH:mm").format(new Date(subscription.getTrialEnd() * 1000)));
}
private String getTrialRemainingText(SubscriptionDTO subscription) {
long now = Math.round(Duration.currentTimeMillis() / 1000);
long remain = subscription.getTrialEnd() - now;
StringBuilder remainText = new StringBuilder();
if (remain <= 0) {
remainText.append(StringMessages.INSTANCE.numHours(0));
} else {
int days = (int) (remain / 86400);
if (days > 0) {
remainText.append(StringMessages.INSTANCE.numDays(days));
}
remain = remain % 86400;
int hours = (int) (remain / 3600);
if (hours > 0) {
if (remainText.length() > 0) {
remainText.append(" ");
}
remainText.append(StringMessages.INSTANCE.numHours(hours));
}
if (days == 0) {
remain = remain % 3600;
int mins = (int) (remain / 60);
if (mins > 0) {
if (remainText.length() > 0) {
remainText.append(" ");
}
remainText.append(StringMessages.INSTANCE.numMinutes(mins));
}
}
}
if (remainText.length() == 0) {
remainText.append(StringMessages.INSTANCE.numHours(0));
}
return remainText.toString();
}
private void resetElementsVisibleState() {
showElement(subscriptionGroupUi);
cancelSubscriptionButtonUi.setVisible(true);
showElement(paymentStatusDivUi);
showElement(trialDivUi);
}
private void hideElement(Element element) {
element.getStyle().setDisplay(Display.NONE);
}
private void showElement(Element element) {
element.getStyle().setDisplay(Display.BLOCK);
planListUi.setSelectedIndex(0);
}
private void hide() {
@@ -8,23 +8,16 @@
type="com.sap.sailing.gwt.home.desktop.places.user.profile.subscriptiontab.SubscriptionProfileDesktopResources" />
<g:HTMLPanel addStyleNames="{res.mediaCss.grid} {res.mainCss.spacermarginbottommediumsmall}">
<div ui:field="rootUi" class="{res.mediaCss.column} {res.mediaCss.small12} {res.mainCss.spacermargintopmedium}">
<div>Subscription plan: <span ui:field="planNameSpanUi"></span></div>
<div ui:field="subscriptionGroupUi">
<div class="{local_res.css.textRow}">Subscription status: <span ui:field="subscriptionStatusSpanUi"></span></div>
<div ui:field="trialDivUi" class="{local_res.css.textRow} {local_res.css.trialText}"></div>
<div ui:field="paymentStatusDivUi" class="{local_res.css.textRow}">Payment charge status: <span ui:field="paymentStatusSpanUi"></span></div>
</div>
<div class="{local_res.css.sectionLabel}">Your subscriptions</div>
<c:CellList ui:field="subscriptionListUi"></c:CellList>
<div class="{res.mediaCss.medium4} {res.mediaCss.small12} {local_res.css.planInputContainer}">
<label class="{local_res.css.plansLabel}">
<label class="{local_res.css.sectionLabel}">
<ui:text from="{i18n.plans}" />
</label>
<g:ListBox ui:field="planListUi" addStyleNames="{local_res.css.plansInput}" />
</div>
<g:Button ui:field="updateSubscriptionButtonUi" addStyleNames="{res.mainCss.button} {res.mainCss.buttonprimary} {local_res.css.bottomButton}">
<ui:text from="{i18n.updateSubscription}" />
</g:Button>
<g:Button ui:field="cancelSubscriptionButtonUi" addStyleNames="{res.mainCss.button} {res.mainCss.buttonprimary} {local_res.css.bottomButton}">
<ui:text from="{i18n.cancelSubscription}" />
<g:Button ui:field="subscribeButtonUi" addStyleNames="{res.mainCss.button} {res.mainCss.buttonprimary} {local_res.css.bottomButton}">
<ui:text from="{i18n.subscribe}" />
</g:Button>
</div>
</g:HTMLPanel>
@@ -71,24 +71,7 @@ public class UserSubscriptionPresenter<C extends ClientFactoryWithDispatch & Err
@Override
public void loadSubscription() {
view.onStartLoadSubscription();
clientFactory.getSubscriptionService().getSubscription(new AsyncCallback<SubscriptionDTO>() {
@Override
public void onSuccess(SubscriptionDTO result) {
if (result != null && result.getError() != null && !result.getError().isEmpty()) {
showError(StringMessages.INSTANCE.errorLoadingUserSubscription(result.getError()));
return;
}
view.updateView(result);
}
@Override
public void onFailure(Throwable caught) {
showError(StringMessages.INSTANCE.errorLoadingUserSubscription(caught.getMessage()));
}
});
fetchSubscription();
}
@Override
@@ -125,8 +108,8 @@ public class UserSubscriptionPresenter<C extends ClientFactoryWithDispatch & Err
}
@Override
public void cancelSubscription() {
clientFactory.getSubscriptionService().cancelSubscription(new AsyncCallback<Boolean>() {
public void cancelSubscription(String planId) {
clientFactory.getSubscriptionService().cancelSubscription(planId, new AsyncCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
@@ -134,8 +117,7 @@ public class UserSubscriptionPresenter<C extends ClientFactoryWithDispatch & Err
showError(StringMessages.INSTANCE.failedCancelSubscription());
return;
}
view.updateView(null);
fetchSubscription();
}
@Override
@@ -145,6 +127,26 @@ public class UserSubscriptionPresenter<C extends ClientFactoryWithDispatch & Err
});
}
private void fetchSubscription() {
clientFactory.getSubscriptionService().getSubscription(new AsyncCallback<SubscriptionDTO>() {
@Override
public void onSuccess(SubscriptionDTO result) {
if (result != null && result.getError() != null && !result.getError().isEmpty()) {
showError(StringMessages.INSTANCE.errorLoadingUserSubscription(result.getError()));
return;
}
view.updateView(result);
}
@Override
public void onFailure(Throwable caught) {
showError(StringMessages.INSTANCE.errorLoadingUserSubscription(caught.getMessage()));
}
});
}
private void requestFinishingPlanUpdating(String hostedPageId) {
clientFactory.getSubscriptionService().updatePlanSuccess(hostedPageId, new AsyncCallback<SubscriptionDTO>() {
@@ -62,7 +62,10 @@ public interface UserSubscriptionView extends IsWidget {
/**
* Request to cancel current user's subscription
*
* @param planId
* Id of plan to cancel
*/
public void cancelSubscription();
public void cancelSubscription(String planId);
}
}
@@ -2386,7 +2386,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String whitelabelFooterLanguage();
String whitelabelSailing();
String subscription();
String updateSubscription();
String noSubscriptions();
String subscribe();
String cancelSubscription();
String openPortal();
String plans();
@@ -2391,7 +2391,8 @@ autoRestartTrackingUponCompetitorSetChange=Restart tracking of races automatical
whitelabelFooterLanguage=Sailing in
whitelabelSailing=Sailing
subscription=Subscription
updateSubscription=Update Subscription
noSubscriptions=No subscriptions
subscribe=Subscribe
cancelSubscription=Cancel Subscription
openPortal=Open Portal
plans=Plans
@@ -37,6 +37,8 @@ public interface SubscriptionService extends RemoteService {
/**
* Cancel current user subscription
*
* @param planId id of canceled plan
*/
public boolean cancelSubscription();
public boolean cancelSubscription(String planId);
}
@@ -16,5 +16,5 @@ public interface SubscriptionServiceAsync {
public void getSubscription(AsyncCallback<SubscriptionDTO> callback);
void cancelSubscription(AsyncCallback<Boolean> callback);
void cancelSubscription(String planId, AsyncCallback<Boolean> callback);
}
@@ -1,6 +1,8 @@
package com.sap.sailing.gwt.ui.server.subscription.chargebee;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
@@ -22,6 +24,7 @@ import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.sap.sailing.gwt.ui.client.subscription.chargebee.SubscriptionService;
import com.sap.sailing.gwt.ui.shared.subscription.chargebee.HostedPageResultDTO;
import com.sap.sailing.gwt.ui.shared.subscription.chargebee.SubscriptionDTO;
import com.sap.sailing.gwt.ui.shared.subscription.chargebee.SubscriptionItem;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.security.SecurityService;
import com.sap.sse.security.shared.Subscription;
@@ -65,30 +68,17 @@ public class SubscriptionServiceImpl extends RemoteServiceServlet implements Sub
try {
User user = getCurrentUser();
if (isUserSubscribedToPlan(user.getSubscription(), planId)) {
if (isUserSubscribedToPlan(user, planId)) {
response.setError("User has already subscribed to "
+ SubscriptionPlanHolder.getInstance().getPlan(planId).getName() + " plan");
return response;
}
Result result;
if (!hasUserSubscription(user.getSubscription())) {
Pair<String, String> usernames = getUserFirstAndLastName(user.getFullName());
String locale = user.getLocaleOrDefault().getLanguage();
// Make a checkout-new request
result = HostedPage.checkoutNew().customerId(user.getName()).customerEmail(user.getEmail())
.customerFirstName(usernames.getA()).customerLastName(usernames.getB()).customerLocale(locale)
.subscriptionPlanId(planId).billingAddressFirstName(usernames.getA())
.billingAddressLastName(usernames.getB()).billingAddressCountry("US").request();
} else {
// Make a checkout-existing request
result = HostedPage.checkoutExisting().subscriptionId(user.getSubscription().getSubscriptionId())
.subscriptionPlanId(planId).request();
}
Pair<String, String> usernames = getUserFirstAndLastName(user.getFullName());
String locale = user.getLocaleOrDefault().getLanguage();
Result result = HostedPage.checkoutNew().customerId(user.getName()).customerEmail(user.getEmail())
.customerFirstName(usernames.getA()).customerLastName(usernames.getB()).customerLocale(locale)
.subscriptionPlanId(planId).billingAddressFirstName(usernames.getA())
.billingAddressLastName(usernames.getB()).billingAddressCountry("US").request();
response.setHostedPageJSONString(result.hostedPage().toJson());
} catch (Exception e) {
logger.log(Level.SEVERE, "Error in generating Chargebee hosted page data ", e);
@@ -100,7 +90,7 @@ public class SubscriptionServiceImpl extends RemoteServiceServlet implements Sub
@Override
public SubscriptionDTO updatePlanSuccess(String hostedPageId) {
SubscriptionDTO subscriptionDto = new SubscriptionDTO();
SubscriptionDTO subscriptionDto;
try {
User user = getCurrentUser();
@@ -118,15 +108,10 @@ public class SubscriptionServiceImpl extends RemoteServiceServlet implements Sub
getSecurityService().updateUserSubscription(user.getName(), subscription);
subscriptionDto.setPlanId(subscription.getPlanId());
subscriptionDto.setTrialStart(subscription.getTrialStart());
subscriptionDto.setTrialEnd(subscription.getTrialEnd());
subscriptionDto.setSubscriptionStatus(subscription.getSubscriptionStatus());
subscriptionDto.setPaymentStatus(subscription.getPaymentStatus());
subscriptionDto = getSubscription();
} catch (Exception e) {
logger.log(Level.SEVERE, "Error in saving subscription ", e);
subscriptionDto.setError(e.getMessage());
subscriptionDto = new SubscriptionDTO(null, e.getMessage());
}
return subscriptionDto;
@@ -134,47 +119,48 @@ public class SubscriptionServiceImpl extends RemoteServiceServlet implements Sub
@Override
public SubscriptionDTO getSubscription() {
SubscriptionDTO subscriptionDto = new SubscriptionDTO();
SubscriptionDTO subscriptionDto = null;
try {
final User user = getCurrentUser();
final Subscription subscription = user.getSubscription();
if (subscription == null || subscription.getPlanId() == null || subscription.getPlanId().isEmpty()) {
return null;
final Subscription[] subscriptions = user.getSubscriptions();
if (subscriptions != null && subscriptions.length > 0) {
List<SubscriptionItem> itemList = new ArrayList<SubscriptionItem>();
for (Subscription subscription : subscriptions) {
if (StringUtils.isNotEmpty(subscription.getSubscriptionId())) {
itemList.add(new SubscriptionItem(subscription.getPlanId(), subscription.getTrialStart(),
subscription.getTrialEnd(), subscription.getSubscriptionStatus(),
subscription.getPaymentStatus()));
}
}
if (!itemList.isEmpty()) {
subscriptionDto = new SubscriptionDTO(itemList.toArray(new SubscriptionItem[0]), null);
}
}
subscriptionDto.setPlanId(subscription.getPlanId());
subscriptionDto.setSubscriptionStatus(subscription.getSubscriptionStatus());
subscriptionDto.setPaymentStatus(subscription.getPaymentStatus());
subscriptionDto.setTrialStart(subscription.getTrialStart());
subscriptionDto.setTrialEnd(subscription.getTrialEnd());
} catch (Exception e) {
logger.log(Level.SEVERE, "Error in getting subscription ", e);
subscriptionDto.setError(e.getMessage());
subscriptionDto = new SubscriptionDTO(null, e.getMessage());
}
return subscriptionDto;
}
@Override
public boolean cancelSubscription() {
public boolean cancelSubscription(String planId) {
try {
User user = getCurrentUser();
Subscription subscription = user.getSubscription();
if (subscription == null) {
Subscription subscription = user.getSubscriptionByPlan(planId);
if (!isValidSubscription(subscription)) {
return true;
}
String subscriptionId = subscription.getSubscriptionId();
if (subscriptionId != null && !subscriptionId.isEmpty()) {
Result result = cancel(subscriptionId).request();
if (!result.subscription().status().name().toLowerCase()
.equals(ChargebeeSubscription.SUBSCRIPTION_STATUS_CANCELLED)) {
return false;
}
Result result = cancel(subscription.getSubscriptionId()).request();
if (!result.subscription().status().name().toLowerCase()
.equals(ChargebeeSubscription.SUBSCRIPTION_STATUS_CANCELLED)) {
return false;
}
Subscription newSubscription = ChargebeeSubscription
.createEmptySubscription(subscription.getLatestEventTime(), System.currentTimeMillis() / 1000);
Subscription newSubscription = ChargebeeSubscription.createEmptySubscription(subscription.getPlanId(),
subscription.getLatestEventTime(), System.currentTimeMillis() / 1000);
getSecurityService().updateUserSubscription(user.getName(), newSubscription);
return true;
} catch (Exception e) {
@@ -212,13 +198,13 @@ public class SubscriptionServiceImpl extends RemoteServiceServlet implements Sub
return StringUtils.isNotEmpty(planId) && SubscriptionPlanHolder.getInstance().getPlan(planId) != null;
}
private boolean isUserSubscribedToPlan(Subscription userSubscription, String planId) {
return userSubscription != null && userSubscription.getPlanId() != null
&& userSubscription.getPlanId().equals(planId);
private boolean isUserSubscribedToPlan(User user, String planId) {
Subscription subscription = user.getSubscriptionByPlan(planId);
return isValidSubscription(subscription);
}
private boolean hasUserSubscription(Subscription userSubscription) {
return userSubscription != null && userSubscription.getPlanId() != null;
private boolean isValidSubscription(Subscription subscription) {
return subscription != null && StringUtils.isNotEmpty(subscription.getSubscriptionId());
}
private Pair<String, String> getUserFirstAndLastName(String fullName) {
@@ -1,7 +1,6 @@
package com.sap.sailing.gwt.ui.shared.subscription.chargebee;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.subscription.chargebee.SubscriptionService;
/**
@@ -10,141 +9,37 @@ import com.sap.sailing.gwt.ui.client.subscription.chargebee.SubscriptionService;
* @author Tu Tran
*/
public class SubscriptionDTO implements IsSerializable {
public static String PAYMENT_STATUS_SUCCESS = "success";
public static String PAYMENT_STATUS_NO_SUCCESS = "no_success";
public static String SUBSCRIPTION_STATUS_TRIAL = "in_trial";
public static String SUBSCRIPTION_STATUS_ACTIVE = "active";
/**
* User current subscription plan id
* User's subscriptions
*/
private String planId;
/**
* Trial start timestamp
*/
private long trialStart;
/**
* Trial end timestamp
*/
private long trialEnd;
/**
* Subscription status: active or in_trial
*/
private String subscriptionStatus;
/**
* Subscription payment status: success or no_success
*/
private String paymentStatus;
private SubscriptionItem[] subscriptionItems;
/**
* Error message
*/
private String error;
/**
* Check if subscription is in trial status
*
* @return
*/
public boolean isInTrial() {
return subscriptionStatus != null && subscriptionStatus.equals(SUBSCRIPTION_STATUS_TRIAL);
public SubscriptionDTO() {
}
/**
* Check if subscription is in active status
*
* @return
*/
public boolean isActive() {
return subscriptionStatus != null && subscriptionStatus.equals(SUBSCRIPTION_STATUS_ACTIVE);
public SubscriptionDTO(SubscriptionItem[] subscriptionItems, String error) {
this.subscriptionItems = subscriptionItems;
this.error = error;
}
public boolean isPaymentSuccess() {
return paymentStatus != null && paymentStatus.equals(PAYMENT_STATUS_SUCCESS);
}
/**
* Get subscription status i18n label
*
* @return
*/
public String getSubscriptionStatusLabel() {
if (subscriptionStatus != null) {
if (subscriptionStatus.equals(SUBSCRIPTION_STATUS_TRIAL)) {
return StringMessages.INSTANCE.inTrial();
} else if (subscriptionStatus.equals(SUBSCRIPTION_STATUS_ACTIVE)) {
return StringMessages.INSTANCE.active();
}
}
return "";
}
/**
* Get subscription payment status i18n label
*
* @return
*/
public String getPaymentStatusLabel() {
if (paymentStatus != null) {
if (paymentStatus.equals(PAYMENT_STATUS_SUCCESS)) {
return StringMessages.INSTANCE.paymentStatusSuccess();
} else {
return StringMessages.INSTANCE.paymentStatusNoSuccess();
}
}
return "";
}
public String getPlanId() {
return planId;
}
public void setPlanId(String planId) {
this.planId = planId;
}
public long getTrialStart() {
return trialStart;
}
public void setTrialStart(long trialStart) {
this.trialStart = trialStart;
}
public long getTrialEnd() {
return trialEnd;
}
public void setTrialEnd(long trialEnd) {
this.trialEnd = trialEnd;
}
public String getSubscriptionStatus() {
return subscriptionStatus;
}
public void setSubscriptionStatus(String subscriptionStatus) {
this.subscriptionStatus = subscriptionStatus;
}
public String getPaymentStatus() {
return paymentStatus;
}
public void setPaymentStatus(String paymentStatus) {
this.paymentStatus = paymentStatus;
public SubscriptionItem[] getSubscriptionItems() {
return subscriptionItems;
}
public String getError() {
return error;
}
public void setSubscriptionItems(SubscriptionItem[] subscriptionItems) {
this.subscriptionItems = subscriptionItems;
}
public void setError(String error) {
this.error = error;
}
@@ -0,0 +1,144 @@
package com.sap.sailing.gwt.ui.shared.subscription.chargebee;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.gwt.ui.client.StringMessages;
public class SubscriptionItem implements IsSerializable {
public static String PAYMENT_STATUS_SUCCESS = "success";
public static String PAYMENT_STATUS_NO_SUCCESS = "no_success";
public static String SUBSCRIPTION_STATUS_TRIAL = "in_trial";
public static String SUBSCRIPTION_STATUS_ACTIVE = "active";
/**
* User current subscription plan id
*/
private String planId;
/**
* Trial start timestamp
*/
private long trialStart;
/**
* Trial end timestamp
*/
private long trialEnd;
/**
* Subscription status: active or in_trial
*/
private String subscriptionStatus;
/**
* Subscription payment status: success or no_success
*/
private String paymentStatus;
public SubscriptionItem() {
}
public SubscriptionItem(String planId, long trialStart, long trialEnd, String subscriptionStatus,
String paymentStatus) {
this.planId = planId;
this.trialStart = trialStart;
this.trialEnd = trialEnd;
this.subscriptionStatus = subscriptionStatus;
this.paymentStatus = paymentStatus;
}
/**
* Check if subscription is in trial status
*
* @return
*/
public boolean isInTrial() {
return subscriptionStatus != null && subscriptionStatus.equals(SUBSCRIPTION_STATUS_TRIAL);
}
/**
* Check if subscription is in active status
*
* @return
*/
public boolean isActive() {
return subscriptionStatus != null && subscriptionStatus.equals(SUBSCRIPTION_STATUS_ACTIVE);
}
public boolean isPaymentSuccess() {
return paymentStatus != null && paymentStatus.equals(PAYMENT_STATUS_SUCCESS);
}
/**
* Get subscription status i18n label
*
* @return
*/
public String getSubscriptionStatusLabel() {
if (subscriptionStatus != null) {
if (subscriptionStatus.equals(SUBSCRIPTION_STATUS_TRIAL)) {
return StringMessages.INSTANCE.inTrial();
} else if (subscriptionStatus.equals(SUBSCRIPTION_STATUS_ACTIVE)) {
return StringMessages.INSTANCE.active();
}
}
return "";
}
/**
* Get subscription payment status i18n label
*
* @return
*/
public String getPaymentStatusLabel() {
if (paymentStatus != null) {
if (paymentStatus.equals(PAYMENT_STATUS_SUCCESS)) {
return StringMessages.INSTANCE.paymentStatusSuccess();
} else {
return StringMessages.INSTANCE.paymentStatusNoSuccess();
}
}
return "";
}
public String getPlanId() {
return planId;
}
public long getTrialStart() {
return trialStart;
}
public long getTrialEnd() {
return trialEnd;
}
public String getSubscriptionStatus() {
return subscriptionStatus;
}
public String getPaymentStatus() {
return paymentStatus;
}
public void setPlanId(String planId) {
this.planId = planId;
}
public void setTrialStart(long trialStart) {
this.trialStart = trialStart;
}
public void setTrialEnd(long trialEnd) {
this.trialEnd = trialEnd;
}
public void setSubscriptionStatus(String subscriptionStatus) {
this.subscriptionStatus = subscriptionStatus;
}
public void setPaymentStatus(String paymentStatus) {
this.paymentStatus = paymentStatus;
}
}
@@ -20,10 +20,12 @@ public class ChargebeeSubscriptionPermissionsAuthorizationFilter extends Subscri
try {
final Object requestBody = JSONValue.parseWithException(request.getReader());
final JSONObject requestObject = Helpers.toJSONObjectSafe(requestBody);
logger.log(Level.INFO, "Payment service webhook data: " + requestObject.toJSONString());
final SubscriptionWebHookEvent event = new SubscriptionWebHookEvent(requestObject);
if (!event.isValidEvent()) {
throw new IllegalArgumentException("Invalid webhook event");
}
request.setAttribute("event", event);
return event.getCustomerId();
} catch (Exception e) {
logger.log(Level.SEVERE, "Subscription permission checking error", e);
@@ -6,13 +6,8 @@ import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import org.apache.commons.lang.StringUtils;
import com.sap.sailing.server.gateway.deserialization.impl.Helpers;
import com.sap.sailing.server.gateway.subscription.SubscriptionWebHookHandler;
import com.sap.sailing.server.gateway.subscription.SubscriptionWebHookServlet;
import com.sap.sse.security.shared.Subscription;
@@ -37,38 +32,43 @@ public class ChargebeeWebHookHandler extends SubscriptionWebHookHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response) {
try {
final Object requestBody = JSONValue.parseWithException(request.getReader());
final JSONObject requestObject = Helpers.toJSONObjectSafe(requestBody);
logger.log(Level.INFO, "Payment service webhook data: " + requestObject.toJSONString());
final SubscriptionWebHookEvent event = new SubscriptionWebHookEvent(requestObject);
if (!event.isValidEvent()) {
throw new IllegalArgumentException("Invalid webhook event");
}
final SubscriptionWebHookEvent event = (SubscriptionWebHookEvent) request.getAttribute("event");
final User user = getUser(event.getCustomerId());
if (user != null && !isOutdatedEvent(event, user.getSubscription())) {
if (user != null && !isOutdatedEvent(event, user)) {
processEvent(event, user);
}
sendSuccess(response);
} catch (ParseException e) {
logger.log(Level.SEVERE, "Failed to parse subscription webhook event data", e);
sendFail(response);
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed to proccess subscription webhook event", e);
sendFail(response);
}
}
private boolean isOutdatedEvent(SubscriptionWebHookEvent event, Subscription userSubscription) {
private boolean isOutdatedEvent(SubscriptionWebHookEvent event, User user) {
final long occuredAt = event.getEventOccurredAt();
return userSubscription != null && (occuredAt < userSubscription.getLatestEventTime()
|| occuredAt < userSubscription.getManuallyUpdatedAt());
final String planId = event.getPlanId();
if (StringUtils.isNotEmpty(planId)) {
Subscription subscription = user.getSubscriptionByPlan(planId);
return subscription != null && (occuredAt < subscription.getLatestEventTime()
|| occuredAt < subscription.getManuallyUpdatedAt());
} else {
Subscription[] subscriptions = user.getSubscriptions();
if (subscriptions != null && subscriptions.length > 0) {
for (Subscription subscription : subscriptions) {
if (occuredAt < subscription.getLatestEventTime()
|| occuredAt < subscription.getManuallyUpdatedAt()) {
return true;
}
}
}
return false;
}
}
private void processEvent(SubscriptionWebHookEvent event, User user) throws UserManagementException {
final SubscriptionWebHookEventType eventType = event.getEventType();
if (eventType != null) {
final Subscription userSubscription = user.getSubscription();
final Subscription userSubscription = user.getSubscriptionByPlan(event.getPlanId());
switch (eventType) {
case CUSTOMER_DELETED:
updateUserSubscription(user, buildEmptySubscription(userSubscription, event));
@@ -92,7 +92,7 @@ public class ChargebeeWebHookHandler extends SubscriptionWebHookHandler {
}
private Subscription buildEmptySubscription(Subscription currentSubscription, SubscriptionWebHookEvent event) {
return ChargebeeSubscription.createEmptySubscription(event.getEventOccurredAt(),
return ChargebeeSubscription.createEmptySubscription(event.getPlanId(), event.getEventOccurredAt(),
currentSubscription != null ? currentSubscription.getManuallyUpdatedAt() : 0);
}
@@ -29,7 +29,7 @@ public class SubscriptionWebHookEvent {
}
public boolean isValidEvent() {
return eventId != null && eventType != null;
return eventId != null;
}
public String getEventId() {
@@ -41,7 +41,8 @@ public class SubscriptionWebHookEvent {
try {
return SubscriptionWebHookEventType.valueOf(eventTypeAsUppercaseString);
} catch (NullPointerException | IllegalArgumentException e) {
logger.warning("Exception trying to extract subscription event type "+eventTypeAsUppercaseString+": "+e.getMessage());
logger.warning("Exception trying to extract subscription event type " + eventTypeAsUppercaseString + ": "
+ e.getMessage());
return null;
}
}
@@ -130,7 +131,7 @@ public class SubscriptionWebHookEvent {
private String toLowerCase(String str) {
return str != null ? str.toLowerCase() : null;
}
private String toUpperCase(String str) {
return str != null ? str.toUpperCase() : null;
}
@@ -9,8 +9,8 @@ public class ChargebeeSubscription extends Subscription {
private static final long serialVersionUID = -3682427457347116687L;
public static Subscription createEmptySubscription(long latestEventTime, long manualUpdatedAt) {
return new ChargebeeSubscription(null, null, null, 0, 0, null, null, 0, 0, latestEventTime, manualUpdatedAt);
public static Subscription createEmptySubscription(String planId, long latestEventTime, long manualUpdatedAt) {
return new ChargebeeSubscription(null, planId, null, 0, 0, null, null, 0, 0, latestEventTime, manualUpdatedAt);
}
public ChargebeeSubscription(String subscriptionId, String planId, String customerId, long trialStart,
@@ -113,7 +113,9 @@ public interface User extends SecurityUser<RoleDefinition, Role, UserGroup> {
String createRandomSecret();
void setSubscription(Subscription subscription);
Subscription getSubscription();
Subscription[] getSubscriptions();
void setSubscriptions(Subscription[] subscriptions);
Subscription getSubscriptionByPlan(String planId);
}
@@ -89,7 +89,7 @@ public class UserImpl extends SecurityUserImpl<RoleDefinition, Role, UserGroup,
private List<Role> roleListForSerialization;
private Subscription subscription;
private Subscription[] subscriptions;
public UserImpl(String name, String email, Map<String, UserGroup> defaultTenantForServer,
UserGroupProvider userGroupProvider, Account... accounts) {
@@ -380,12 +380,24 @@ public class UserImpl extends SecurityUserImpl<RoleDefinition, Role, UserGroup,
}
@Override
public Subscription getSubscription() {
return subscription;
public Subscription[] getSubscriptions() {
return subscriptions;
}
@Override
public void setSubscription(Subscription subscription) {
this.subscription = subscription;
public void setSubscriptions(Subscription[] subscriptions) {
this.subscriptions = subscriptions;
}
@Override
public Subscription getSubscriptionByPlan(String planId) {
if (planId != null && !planId.equals("") && subscriptions != null && subscriptions.length > 0) {
for (Subscription subscription : subscriptions) {
if (subscription.getPlanId() != null && subscription.getPlanId().equals(planId)) {
return subscription;
}
}
}
return null;
}
}
@@ -360,8 +360,8 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
// is also only interested in the object's ID
new MongoObjectFactoryImpl(db).storeUser(result);
}
final Document subscriptionDoc = (Document) userDBObject.get(FieldNames.User.SUBSCRIPTION.name());
result.setSubscription(loadSubscription(subscriptionDoc));
final List<?> subscriptionDocs = (List<?>) userDBObject.get(FieldNames.User.SUBSCRIPTIONS.name());
result.setSubscriptions(loadSubscriptions(subscriptionDocs));
return result;
}
@@ -507,28 +507,31 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return result;
}
private Subscription loadSubscription(Document subscriptionDoc) {
final Subscription result;
if (subscriptionDoc != null) {
String customerId = subscriptionDoc.getString(FieldNames.Subscription.CUSTOMER_ID.name());
String planId = subscriptionDoc.getString(FieldNames.Subscription.PLAN_ID.name());
String subscriptionStatus = subscriptionDoc.getString(FieldNames.Subscription.SUBSCRIPTION_STATUS.name());
String subscriptionId = subscriptionDoc.getString(FieldNames.Subscription.SUBSCRIPTION_ID.name());
long trialStart = subscriptionDoc.getLong(FieldNames.Subscription.TRIAL_START.name());
long trialEnd = subscriptionDoc.getLong(FieldNames.Subscription.TRIAL_END.name());
String paymentStatus = subscriptionDoc.getString(FieldNames.Subscription.PAYMENT_STATUS.name());
long subscriptionCreatedAt = subscriptionDoc
.getLong(FieldNames.Subscription.SUBSCRIPTION_CREATED_AT.name());
long subscriptionUpdatedAt = subscriptionDoc
.getLong(FieldNames.Subscription.SUBSCRIPTION_UPDATED_AT.name());
long latestEventTime = subscriptionDoc.getLong(FieldNames.Subscription.LATEST_EVENT_TIME.name());
long manualUpdatedAt = subscriptionDoc.getLong(FieldNames.Subscription.MANUAL_UPDATED_AT.name());
result = new ChargebeeSubscription(subscriptionId, planId, customerId, trialStart, trialEnd,
subscriptionStatus, paymentStatus, subscriptionCreatedAt, subscriptionUpdatedAt, latestEventTime,
manualUpdatedAt);
private Subscription[] loadSubscriptions(List<?> subscriptionsDoc) {
final Subscription[] subscriptions;
if (subscriptionsDoc != null) {
subscriptions = new Subscription[subscriptionsDoc.size()];
int i = 0;
for (Object o : subscriptionsDoc) {
final Document doc = (Document) o;
final String customerId = doc.getString(FieldNames.Subscription.CUSTOMER_ID.name());
final String planId = doc.getString(FieldNames.Subscription.PLAN_ID.name());
final String subscriptionStatus = doc.getString(FieldNames.Subscription.SUBSCRIPTION_STATUS.name());
final String subscriptionId = doc.getString(FieldNames.Subscription.SUBSCRIPTION_ID.name());
final long trialStart = doc.getLong(FieldNames.Subscription.TRIAL_START.name());
final long trialEnd = doc.getLong(FieldNames.Subscription.TRIAL_END.name());
final String paymentStatus = doc.getString(FieldNames.Subscription.PAYMENT_STATUS.name());
final long subscriptionCreatedAt = doc.getLong(FieldNames.Subscription.SUBSCRIPTION_CREATED_AT.name());
final long subscriptionUpdatedAt = doc.getLong(FieldNames.Subscription.SUBSCRIPTION_UPDATED_AT.name());
final long latestEventTime = doc.getLong(FieldNames.Subscription.LATEST_EVENT_TIME.name());
final long manualUpdatedAt = doc.getLong(FieldNames.Subscription.MANUAL_UPDATED_AT.name());
subscriptions[i++] = new ChargebeeSubscription(subscriptionId, planId, customerId, trialStart, trialEnd,
subscriptionStatus, paymentStatus, subscriptionCreatedAt, subscriptionUpdatedAt,
latestEventTime, manualUpdatedAt);
}
} else {
result = null;
subscriptions = null;
}
return result;
return subscriptions;
}
}
@@ -56,7 +56,7 @@ public class FieldNames {
VALIDATION_SECRET,
DEFAULT_TENANT_SERVER,
DEFAULT_TENANT_GROUP,
SUBSCRIPTION;
SUBSCRIPTIONS;
}
public static enum Settings {
@@ -230,7 +230,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
defaultTennants.add(tenant);
}
dbUser.put(FieldNames.User.DEFAULT_TENANT_IDS.name(), defaultTennants);
dbUser.put(FieldNames.User.SUBSCRIPTION.name(), createSubscriptionObject(user.getSubscription()));
dbUser.put(FieldNames.User.SUBSCRIPTIONS.name(), createSubscriptions(user.getSubscriptions()));
usersCollection.withWriteConcern(WriteConcern.ACKNOWLEDGED).replaceOne(query, dbUser, new UpdateOptions().upsert(true));
}
@@ -334,21 +334,27 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
return dbSettingTypes;
}
private Document createSubscriptionObject(Subscription subscription) {
final Document result;
if (subscription != null) {
result = new Document();
result.put(FieldNames.Subscription.SUBSCRIPTION_ID.name(), subscription.getSubscriptionId());
result.put(FieldNames.Subscription.PLAN_ID.name(), subscription.getPlanId());
result.put(FieldNames.Subscription.CUSTOMER_ID.name(), subscription.getCustomerId());
result.put(FieldNames.Subscription.TRIAL_START.name(), subscription.getTrialStart());
result.put(FieldNames.Subscription.TRIAL_END.name(), subscription.getTrialEnd());
result.put(FieldNames.Subscription.SUBSCRIPTION_STATUS.name(), subscription.getSubscriptionStatus());
result.put(FieldNames.Subscription.PAYMENT_STATUS.name(), subscription.getPaymentStatus());
result.put(FieldNames.Subscription.SUBSCRIPTION_CREATED_AT.name(), subscription.getSubscriptionCreatedAt());
result.put(FieldNames.Subscription.SUBSCRIPTION_UPDATED_AT.name(), subscription.getSubscriptionUpdatedAt());
result.put(FieldNames.Subscription.LATEST_EVENT_TIME.name(), subscription.getLatestEventTime());
result.put(FieldNames.Subscription.MANUAL_UPDATED_AT.name(), subscription.getManuallyUpdatedAt());
private BasicDBList createSubscriptions(Subscription[] subscriptions) {
final BasicDBList result;
if (subscriptions != null) {
result = new BasicDBList();
for (Subscription subscription : subscriptions) {
final Document doc = new Document();
doc.put(FieldNames.Subscription.SUBSCRIPTION_ID.name(), subscription.getSubscriptionId());
doc.put(FieldNames.Subscription.PLAN_ID.name(), subscription.getPlanId());
doc.put(FieldNames.Subscription.CUSTOMER_ID.name(), subscription.getCustomerId());
doc.put(FieldNames.Subscription.TRIAL_START.name(), subscription.getTrialStart());
doc.put(FieldNames.Subscription.TRIAL_END.name(), subscription.getTrialEnd());
doc.put(FieldNames.Subscription.SUBSCRIPTION_STATUS.name(), subscription.getSubscriptionStatus());
doc.put(FieldNames.Subscription.PAYMENT_STATUS.name(), subscription.getPaymentStatus());
doc.put(FieldNames.Subscription.SUBSCRIPTION_CREATED_AT.name(),
subscription.getSubscriptionCreatedAt());
doc.put(FieldNames.Subscription.SUBSCRIPTION_UPDATED_AT.name(),
subscription.getSubscriptionUpdatedAt());
doc.put(FieldNames.Subscription.LATEST_EVENT_TIME.name(), subscription.getLatestEventTime());
doc.put(FieldNames.Subscription.MANUAL_UPDATED_AT.name(), subscription.getManuallyUpdatedAt());
result.add(doc);
}
} else {
result = null;
}
@@ -210,12 +210,17 @@ public class UserProxy implements User {
}
@Override
public void setSubscription(Subscription subscription) {
public Subscription[] getSubscriptions() {
throw new UnsupportedOperationException();
}
@Override
public Subscription getSubscription() {
public void setSubscriptions(Subscription[] subscriptions) {
throw new UnsupportedOperationException();
}
@Override
public Subscription getSubscriptionByPlan(String planId) {
throw new UnsupportedOperationException();
}
}
@@ -33,7 +33,8 @@ Require-Bundle: javax.servlet;bundle-version="3.1.0",
com.sap.sse.shared.android,
com.sap.sse.replication.interfaces;visibility:=reexport,
com.sap.sse.security.persistence,
com.sap.sse.security.interface;visibility:=reexport
com.sap.sse.security.interface;visibility:=reexport,
org.apache.commons.lang;bundle-version="2.6.0"
Web-ContextPath: /security
Export-Package: com.sap.sse.security,
com.sap.sse.security.impl;x-friends:="com.sap.sailing.domain.test,com.sap.sailing.server.testsupport",
@@ -36,6 +36,7 @@ import javax.servlet.Filter;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
@@ -2532,11 +2533,18 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
throws UserManagementException {
User user = getUserByName(username);
if (user != null) {
Subscription currentSubscription = user.getSubscription();
if (shouldUpdateUserRolesForSubscription(currentSubscription, newSubscription)) {
updateUserRolesOnSubscriptionChange(user, currentSubscription, newSubscription);
String newSubscriptionPlanId = newSubscription.getPlanId();
if (StringUtils.isNotEmpty(newSubscriptionPlanId)) {
Subscription currentSubscription = user.getSubscriptionByPlan(newSubscriptionPlanId);
if (shouldUpdateUserRolesForSubscription(currentSubscription, newSubscription)) {
updateUserRolesOnSubscriptionChange(user, currentSubscription, newSubscription);
}
user.setSubscriptions(buildNewUserSubscriptions(user, newSubscription));
} else {
user.setSubscriptions(new Subscription[] { newSubscription });
}
user.setSubscription(newSubscription);
store.updateUser(user);
return null;
} else {
@@ -2544,6 +2552,33 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
}
}
private Subscription[] buildNewUserSubscriptions(User user, Subscription newSubscription) {
Subscription[] newUserSubscriptions = null;
Subscription[] subscriptions = user.getSubscriptions();
if (newSubscription != null) {
if (subscriptions == null || subscriptions.length == 0) {
newUserSubscriptions = new Subscription[] { newSubscription };
} else {
int i = 0;
for (Subscription subscription : subscriptions) {
if (subscription.getPlanId().equals(newSubscription.getPlanId())) {
subscriptions[i] = newSubscription;
newUserSubscriptions = subscriptions;
break;
}
i++;
}
if (i == subscriptions.length) {
newUserSubscriptions = Arrays.copyOf(subscriptions, subscriptions.length + 1);
newUserSubscriptions[newUserSubscriptions.length - 1] = newSubscription;
}
}
} else {
newUserSubscriptions = subscriptions;
}
return newUserSubscriptions;
}
private void updateUserRolesOnSubscriptionChange(User user, Subscription currentSubscription,
Subscription newSubscription) throws UserManagementException {
logger.log(Level.INFO, "Update user subscription roles for user " + user.getName());
@@ -2554,8 +2589,6 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
if (currentPlan != null) {
UUID[] roleDefinitionIds = currentPlan.getRoleDefinitionIds();
for (UUID roleDefId : roleDefinitionIds) {
// see also bug5300: this works, based on the AbstractRole.equals definition,
// as long as no qualified roles are assigned
store.removeRoleFromUser(user.getName(), new Role(getRoleDefinition(roleDefId), null, user));
}
}
@@ -2565,7 +2598,6 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
if (newPlan != null) {
UUID[] roleDefinitionIds = newPlan.getRoleDefinitionIds();
for (UUID roleDefId : roleDefinitionIds) {
// see also bug5300: we may want to support qualified roles, too, in the future
store.addRoleForUser(user.getName(), new Role(getRoleDefinition(roleDefId), null, user));
}
}
@@ -2584,7 +2616,7 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
result = true;
} else {
result = newSubscription != null && currentSubscription != null
&& newSubscription.isActiveSubscription() != currentSubscription.isActiveSubscription();
&& newSubscription.isActiveSubscription() != currentSubscription.isActiveSubscription();
}
return result;
}
@@ -2594,24 +2626,33 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
*/
private boolean isUserSubscriptionPlanChanged(Subscription currentSubscription, Subscription newSubscription) {
final boolean result;
if ((currentSubscription == null || currentSubscription.getPlanId() == null)
&& (newSubscription == null || newSubscription.getPlanId() == null)) {
if ((currentSubscription == null || currentSubscription.getSubscriptionId() == null)
&& (newSubscription == null || newSubscription.getSubscriptionId() == null)) {
result = false;
} else {
String currentPlan = null;
String newPlan = null;
String currentSubscriptionId = null;
String newSubscriptionId = null;
if (currentSubscription != null) {
currentPlan = currentSubscription.getPlanId();
currentSubscriptionId = currentSubscription.getSubscriptionId();
}
if (newSubscription != null) {
newPlan = newSubscription.getPlanId();
newSubscriptionId = newSubscription.getSubscriptionId();
}
if (currentPlan == null && newPlan == null) {
result = false;
} else if (currentPlan != null && newPlan != null) {
result = !currentPlan.equals(newPlan);
} else {
if ((currentSubscriptionId == null && newSubscriptionId != null)
|| (currentSubscriptionId != null && newSubscriptionId == null)) {
result = true;
} else {
if (currentPlan == null && newPlan == null) {
result = false;
} else if (currentPlan != null && newPlan != null) {
result = !currentPlan.equals(newPlan);
} else {
result = true;
}
}
}
return result;