com.goodhuddle.huddle.service.impl.PaymentServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.goodhuddle.huddle.service.impl.PaymentServiceImpl.java

Source

/*
 * (C) Copyright ${year} Nuxeo SA (http://nuxeo.com/) and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-2.1.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 */

package com.goodhuddle.huddle.service.impl;

import com.goodhuddle.huddle.domain.*;
import com.goodhuddle.huddle.domain.Subscription;
import com.goodhuddle.huddle.repository.*;
import com.goodhuddle.huddle.service.HuddleService;
import com.goodhuddle.huddle.service.MemberService;
import com.goodhuddle.huddle.service.PaymentService;
import com.goodhuddle.huddle.service.exception.*;
import com.goodhuddle.huddle.service.request.payment.CreateSubscriptionRequest;
import com.goodhuddle.huddle.service.request.payment.ProcessPaymentRequest;
import com.goodhuddle.huddle.service.request.payment.SearchPaymentsRequest;
import com.goodhuddle.huddle.service.request.payment.SearchSubscriptionsRequest;
import com.goodhuddle.huddle.service.request.payment.settings.UpdatePaymentSettingsRequest;
import com.stripe.exception.*;
import com.stripe.model.*;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class PaymentServiceImpl implements PaymentService {

    private static final Logger log = LoggerFactory.getLogger(PaymentServiceImpl.class);

    public static final String META_HUDDLE = "gh-huddle";

    private final PaymentRepository paymentRepository;
    private final SubscriptionRepository subscriptionRepository;
    private final PaymentSettingsRepository paymentSettingsRepository;
    private final MemberService memberService;
    private final HuddleService huddleService;

    @Autowired
    public PaymentServiceImpl(PaymentRepository paymentRepository, SubscriptionRepository subscriptionRepository,
            PaymentSettingsRepository paymentSettingsRepository, MemberService memberService,
            HuddleService huddleService) {

        this.paymentRepository = paymentRepository;
        this.subscriptionRepository = subscriptionRepository;
        this.paymentSettingsRepository = paymentSettingsRepository;
        this.memberService = memberService;
        this.huddleService = huddleService;
    }

    @Override
    public PaymentSettings getPaymentSettings() {
        List<PaymentSettings> settings = paymentSettingsRepository.findByHuddle(huddleService.getHuddle());
        return settings.size() > 0 ? settings.get(0) : new PaymentSettings(huddleService.getHuddle());
    }

    @Override
    public String getHuddlePublishableKey() {
        PaymentSettings settings = getPaymentSettings();
        return settings.getPublishableKey();
    }

    @Override
    public void checkChangeSettingsAllowed() throws SubscriptionsExistException {
        if (subscriptionRepository.countByHuddleAndStatus(huddleService.getHuddle(),
                SubscriptionStatus.active) > 0) {
            throw new SubscriptionsExistException(
                    "You must cancel all active subscriptions before changing your payment settings");
        }
    }

    @Override
    @Transactional(readOnly = false)
    public PaymentSettings updatePaymentSettings(UpdatePaymentSettingsRequest request)
            throws SubscriptionsExistException {
        checkChangeSettingsAllowed();
        PaymentSettings settings = getPaymentSettings();
        settings.setSecretKey(request.getSecretKey());
        settings.setPublishableKey(request.getPublishableKey());
        paymentSettingsRepository.save(settings);
        return settings;
    }

    @Override
    public Payment getPayment(long id) {
        return paymentRepository.findByHuddleAndId(huddleService.getHuddle(), id);
    }

    @Override
    public Page<? extends Payment> searchPayments(SearchPaymentsRequest request) {
        return paymentRepository.findAll(PaymentSpecification.search(huddleService.getHuddle(), request),
                new PageRequest(request.getPage(), request.getSize(), Sort.Direction.DESC, "paidOn"));
    }

    @Override
    @Transactional(readOnly = false)
    public Payment processPayment(ProcessPaymentRequest request)
            throws PaymentFailedException, PaymentsNotSetupException {

        try {

            Member payee = memberService.getOrCreateMemberByEmail(request.getEmail());
            PaymentSettings settings = getPaymentSettings();

            log.info("Making one-off Stripe payment of ${} by ", request.getAmountInCents() / 100,
                    request.getEmail());
            final Map<String, Object> chargeParams = new HashMap<>();
            chargeParams.put("amount", request.getAmountInCents());
            chargeParams.put("currency", request.getCurrency());
            chargeParams.put("card", request.getToken());

            Map<String, String> metaData = new HashMap<>();
            metaData.put("huddle", huddleService.getHuddle().getSlug());
            chargeParams.put("metadata", metaData);

            final Charge charge = Charge.create(chargeParams, settings.getSecretKey());
            log.debug("Stripe payment completed with success=", charge.getPaid());

            if (charge.getPaid()) {

                DateTime paymentDate = new DateTime(charge.getCreated() * 1000);
                Payment payment = paymentRepository.save(new Payment(huddleService.getHuddle(), request.getType(),
                        charge.getId(), charge.getBalanceTransaction(), request.getAmountInCents(),
                        charge.getCurrency(), request.getDescription(), request.getEmail(), payee, paymentDate));

                payment = postProcessPayment(payment);

                log.info("New payment '{}' processed with ID '{}'", request.getDescription(), payment.getId());
                return payment;
            } else {
                log.warn("Payment failed: {} ({})", charge.getFailureMessage(), charge.getFailureCode());
                throw new PaymentFailedException("Payment failed: " + charge.getFailureMessage(), null);
            }

        } catch (AuthenticationException | APIConnectionException | CardException | InvalidRequestException
                | APIException e) {
            log.error("Payment failed", e);
            throw new PaymentFailedException("Payment failed: " + e, null);
        }
    }

    @Override
    public Subscription getSubscription(long id) {
        return subscriptionRepository.findByHuddleAndId(huddleService.getHuddle(), id);
    }

    @Override
    public Subscription getSubscriptionByStripeCustomerId(String stripeCustomerId) {
        return subscriptionRepository.findByStripeCustomerId(stripeCustomerId);
    }

    @Override
    public Page<? extends Subscription> searchSubscriptions(SearchSubscriptionsRequest request) {
        return subscriptionRepository.findAll(SubscriptionSpecification.search(huddleService.getHuddle(), request),
                new PageRequest(request.getPage(), request.getSize(), Sort.Direction.DESC, "createdOn"));
    }

    @Override
    @Transactional(readOnly = false)
    public Subscription createSubscription(CreateSubscriptionRequest request)
            throws PaymentFailedException, PaymentsNotSetupException {

        try {

            Member payee = memberService.getOrCreateMemberByEmail(request.getEmail());
            PaymentSettings settings = getPaymentSettings();

            log.info("Making recurring Stripe payment of ${} by {}", request.getAmountInCents() / 100,
                    request.getEmail());

            String planId = createPaymentPlanId(request);
            Plan plan;
            try {
                plan = Plan.retrieve(planId, settings.getSecretKey());
            } catch (InvalidRequestException e) {
                // plan does not exist, create plan
                Map<String, Object> planParams = new HashMap<>();
                planParams.put("id", planId);
                planParams.put("name", createPaymentPlanName(request));
                planParams.put("amount", request.getAmountInCents());
                planParams.put("currency", request.getCurrency());
                planParams.put("interval", request.getFrequency().name());

                Map<String, String> metaData = new HashMap<>();
                metaData.put(META_HUDDLE, huddleService.getHuddle().getSlug());
                planParams.put("metadata", metaData);

                plan = Plan.create(planParams, settings.getSecretKey());
                log.debug("New Stripe payment plan created with ID '{}'", plan.getId());
            }

            // create customer / subscription

            final Map<String, Object> customerParams = new HashMap<>();
            customerParams.put("email", request.getEmail());
            customerParams.put("plan", plan.getId());
            customerParams.put("card", request.getToken());

            Map<String, String> metaData = new HashMap<>();
            metaData.put(META_HUDDLE, huddleService.getHuddle().getSlug());
            customerParams.put("metadata", metaData);

            final Customer customer = Customer.create(customerParams, settings.getSecretKey());

            String stripeSubscriptionId = customer.getSubscriptions().getData().get(0).getId();

            Subscription subscription = subscriptionRepository.save(new Subscription(huddleService.getHuddle(),
                    request.getFrequency(), request.getType(), customer.getId(), stripeSubscriptionId,
                    request.getAmountInCents(), request.getCurrency(), request.getDescription(), payee));

            subscriptionRepository.save(subscription);
            log.debug("Stripe recurring payment setup with subscription ID {}", stripeSubscriptionId);

            return subscription;

        } catch (AuthenticationException | APIConnectionException | CardException | InvalidRequestException
                | APIException e) {
            log.error("Payment failed", e);
            throw new PaymentFailedException("Payment failed: " + e, null);
        }
    }

    @Override
    @Transactional(readOnly = false)
    public Subscription cancelSubscription(long id) throws SubscriptionErrorException {
        try {

            Subscription subscription = subscriptionRepository.findByHuddleAndId(huddleService.getHuddle(), id);
            PaymentSettings settings = getPaymentSettings();

            String activeStripeApiKey = settings.getSecretKey();
            Customer stripeCustomer = Customer.retrieve(subscription.getStripeCustomerId(), activeStripeApiKey);
            boolean cancelled = false;
            for (com.stripe.model.Subscription stripeSubscription : stripeCustomer.getSubscriptions().getData()) {
                if (stripeSubscription.getId().equals(subscription.getStripeSubscriptionId())) {
                    log.debug("Cancelling Stripe subscription with ID {}", stripeSubscription.getId());
                    stripeSubscription.cancel(null, activeStripeApiKey);
                    cancelled = true;
                    break;
                }
            }

            if (!cancelled) {
                throw new SubscriptionErrorException("Unable to cancel Stripe Subscription with ID "
                        + subscription.getStripeSubscriptionId() + " because it could not be found");
            }

            subscription.setCancelled(memberService.getLoggedInMember());
            subscription = subscriptionRepository.save(subscription);
            log.info("Subscription with ID {} (for {}) was cancelled", subscription.getId(),
                    subscription.getMember().getDisplayName());

            return subscription;

        } catch (AuthenticationException | InvalidRequestException | APIConnectionException | CardException
                | APIException e) {
            log.error("Payment failed", e);
            throw new SubscriptionErrorException("Payment failed: " + e, null);
        }
    }

    @Override
    @Transactional(readOnly = false)
    public void handleStripeEvent(Event event) throws WebHookException {
        switch (event.getType()) {

        case "charge.succeeded":
            Charge charge = (Charge) event.getData().getObject();
            processSubscriptionPayment(charge);
            break;

        default:
            log.debug("Ignoring unknown Stripe event: {}", event.getType());
        }
    }

    private void processSubscriptionPayment(Charge charge) throws WebHookException {
        Subscription subscription = getSubscriptionByStripeCustomerId(charge.getCustomer());
        if (subscription != null) {
            log.info("Processing new payment of {} for subscription with ID {}", charge.getAmount(),
                    subscription.getId());

            DateTime paymentDate = new DateTime(charge.getCreated() * 1000);
            Payment payment = paymentRepository
                    .save(new Payment(subscription, charge.getId(), charge.getBalanceTransaction(),
                            charge.getAmount(), charge.getCurrency(), subscription.getDescription(), paymentDate));

            try {
                postProcessPayment(payment);
            } catch (CardException | APIException | AuthenticationException | InvalidRequestException
                    | APIConnectionException e) {
                log.error("Error post processing Stripe payment", e);
                throw new WebHookException("Error post processing Stripe payment", e);
            }

        } else {
            log.debug("Ignoring payment as it is not linked to a GoodHuddle subscription: customerId = {}",
                    charge.getCustomer());
        }
    }

    private Payment postProcessPayment(Payment payment) throws CardException, APIException, AuthenticationException,
            InvalidRequestException, APIConnectionException {

        // get the fee information
        PaymentSettings settings = getPaymentSettings();
        BalanceTransaction transaction = BalanceTransaction.retrieve(payment.getStripeBalanceTransactionId(),
                settings.getSecretKey());
        payment.setFeesInCents(transaction.getFee());
        paymentRepository.save(payment);

        if (payment.getSubscription() != null) {
            Subscription subscription = payment.getSubscription();
            DateTime nextPaymentDue = new DateTime(payment.getPaidOn());
            switch (subscription.getFrequency()) {
            case week:
                nextPaymentDue = nextPaymentDue.plusWeeks(1);
                break;
            case month:
                nextPaymentDue = nextPaymentDue.plusMonths(1);
                break;
            case year:
                nextPaymentDue = nextPaymentDue.plusYears(1);
                break;
            default:
                throw new IllegalArgumentException("Unsupported frequency: " + subscription.getFrequency());
            }
            subscription.setNextPaymentDue(nextPaymentDue);
            subscriptionRepository.save(subscription);
        }

        return payment;
    }

    private String createPaymentPlanId(CreateSubscriptionRequest request) {
        return request.getType().name() + "_" + request.getFrequency().name() + "_"
                + (request.getAmountInCents() / 100) + request.getCurrency();
    }

    private String createPaymentPlanName(CreateSubscriptionRequest request) {
        return "Recurring " + request.getFrequency().name() + " " + request.getType().name() + " of "
                + (request.getAmountInCents() / 100) + request.getCurrency();
    }
}