Search in sources :

Example 36 with CallContext

use of org.killbill.billing.util.callcontext.CallContext in project killbill by killbill.

the class SubscriptionChecker method checkSubscriptionCreated.

public SubscriptionBase checkSubscriptionCreated(final UUID subscriptionId, final InternalCallContext context) throws SubscriptionBaseApiException {
    final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT, cacheControllerDispatcher.getCacheController(CacheType.OBJECT_ID));
    final CallContext callContext = context.toCallContext(tenantId);
    final SubscriptionBase subscription = subscriptionApi.getSubscriptionFromId(subscriptionId, context);
    Assert.assertNotNull(subscription);
    auditChecker.checkSubscriptionCreated(subscription.getBundleId(), subscriptionId, callContext);
    List<SubscriptionBaseTransition> subscriptionEvents = getSubscriptionEvents(subscription);
    Assert.assertTrue(subscriptionEvents.size() >= 1);
    auditChecker.checkSubscriptionEventCreated(subscription.getBundleId(), ((SubscriptionBaseTransitionData) subscriptionEvents.get(0)).getId(), callContext);
    auditChecker.checkBundleCreated(subscription.getBundleId(), callContext);
    return subscription;
}
Also used : SubscriptionBase(org.killbill.billing.subscription.api.SubscriptionBase) UUID(java.util.UUID) SubscriptionBaseTransition(org.killbill.billing.subscription.api.user.SubscriptionBaseTransition) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) CallContext(org.killbill.billing.util.callcontext.CallContext)

Example 37 with CallContext

use of org.killbill.billing.util.callcontext.CallContext in project killbill by killbill.

the class IncompletePaymentAttemptTask method doIteration.

@Override
public void doIteration(final PaymentAttemptModelDao attempt) {
    // We don't grab account lock here as the lock will be taken when calling the completeRun API.
    final InternalTenantContext tenantContext = internalCallContextFactory.createInternalTenantContext(attempt.getTenantRecordId(), attempt.getAccountRecordId());
    final CallContext callContext = createCallContext("AttemptCompletionJanitorTask", tenantContext);
    final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(attempt.getAccountId(), callContext);
    final List<PaymentTransactionModelDao> transactions = paymentDao.getPaymentTransactionsByExternalKey(attempt.getTransactionExternalKey(), tenantContext);
    final List<PaymentTransactionModelDao> filteredTransactions = ImmutableList.copyOf(Iterables.filter(transactions, new Predicate<PaymentTransactionModelDao>() {

        @Override
        public boolean apply(final PaymentTransactionModelDao input) {
            return input.getAttemptId().equals(attempt.getId());
        }
    }));
    // We only expect at most one transaction for a given attempt, but as a precaution we check for more; if this is the case we log a warn and continue processing the first one.
    if (filteredTransactions.size() > 1) {
        log.warn("Found {} transactions for paymentAttempt {}", filteredTransactions.size(), attempt.getId());
    }
    final PaymentTransactionModelDao transaction = filteredTransactions.isEmpty() ? null : filteredTransactions.get(0);
    if (transaction == null) {
        log.info("Moving attemptId='{}' to ABORTED", attempt.getId());
        paymentDao.updatePaymentAttempt(attempt.getId(), attempt.getTransactionId(), "ABORTED", internalCallContext);
        return;
    }
    // at which point the attempt can also be transition to a different state.
    if (transaction.getTransactionStatus() == TransactionStatus.UNKNOWN) {
        return;
    }
    try {
        log.info("Completing attemptId='{}'", attempt.getId());
        final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), tenantContext);
        // unclear
        final boolean isApiPayment = true;
        final PaymentStateControlContext paymentStateContext = new PaymentStateControlContext(attempt.toPaymentControlPluginNames(), isApiPayment, null, transaction.getPaymentId(), attempt.getPaymentExternalKey(), transaction.getId(), transaction.getTransactionExternalKey(), transaction.getTransactionType(), account, attempt.getPaymentMethodId(), transaction.getAmount(), transaction.getCurrency(), PluginPropertySerializer.deserialize(attempt.getPluginProperties()), internalCallContext, callContext);
        // Normally set by leavingState Callback
        paymentStateContext.setAttemptId(attempt.getId());
        // Normally set by raw state machine
        paymentStateContext.setPaymentTransactionModelDao(transaction);
        //
        // Will rerun the state machine with special callbacks to only make the executePluginOnSuccessCalls / executePluginOnFailureCalls calls
        // to the PaymentControlPluginApi plugin and transition the state.
        //
        pluginControlledPaymentAutomatonRunner.completeRun(paymentStateContext);
    } catch (final AccountApiException e) {
        log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
    } catch (final PluginPropertySerializerException e) {
        log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
    } catch (final PaymentApiException e) {
        log.warn("Error completing paymentAttemptId='{}'", attempt.getId(), e);
    }
}
Also used : Account(org.killbill.billing.account.api.Account) PaymentTransactionModelDao(org.killbill.billing.payment.dao.PaymentTransactionModelDao) PluginPropertySerializerException(org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException) InternalTenantContext(org.killbill.billing.callcontext.InternalTenantContext) AccountApiException(org.killbill.billing.account.api.AccountApiException) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) CallContext(org.killbill.billing.util.callcontext.CallContext) Predicate(com.google.common.base.Predicate) PaymentStateControlContext(org.killbill.billing.payment.core.sm.control.PaymentStateControlContext)

Example 38 with CallContext

use of org.killbill.billing.util.callcontext.CallContext in project killbill by killbill.

the class IncompletePaymentTransactionTask method updatePaymentAndTransactionInternal.

private boolean updatePaymentAndTransactionInternal(final PaymentModelDao payment, @Nullable final Integer attemptNumber, @Nullable final UUID userToken, final PaymentTransactionModelDao paymentTransaction, final PaymentTransactionInfoPlugin paymentTransactionInfoPlugin, final InternalTenantContext internalTenantContext) {
    final CallContext callContext = createCallContext("IncompletePaymentTransactionTask", internalTenantContext);
    // First obtain the new transactionStatus,
    // Then compute the new paymentState; this one is mostly interesting in case of success (to compute the lastSuccessPaymentState below)
    final TransactionStatus transactionStatus = computeNewTransactionStatusFromPaymentTransactionInfoPlugin(paymentTransactionInfoPlugin, paymentTransaction.getTransactionStatus());
    final String newPaymentState;
    switch(transactionStatus) {
        case PENDING:
            newPaymentState = paymentStateMachineHelper.getPendingStateForTransaction(paymentTransaction.getTransactionType());
            break;
        case SUCCESS:
            newPaymentState = paymentStateMachineHelper.getSuccessfulStateForTransaction(paymentTransaction.getTransactionType());
            break;
        case PAYMENT_FAILURE:
            newPaymentState = paymentStateMachineHelper.getFailureStateForTransaction(paymentTransaction.getTransactionType());
            break;
        case PLUGIN_FAILURE:
            newPaymentState = paymentStateMachineHelper.getErroredStateForTransaction(paymentTransaction.getTransactionType());
            break;
        case UNKNOWN:
        default:
            if (transactionStatus != paymentTransaction.getTransactionStatus()) {
                log.info("Unable to repair paymentId='{}', paymentTransactionId='{}', currentTransactionStatus='{}', newTransactionStatus='{}'", payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus);
            }
            // We can't get anything interesting from the plugin...
            insertNewNotificationForUnresolvedTransactionIfNeeded(paymentTransaction.getId(), attemptNumber, userToken, internalTenantContext.getAccountRecordId(), internalTenantContext.getTenantRecordId());
            return false;
    }
    // Our status did not change, so we just insert a new notification (attemptNumber will be incremented)
    if (transactionStatus == paymentTransaction.getTransactionStatus()) {
        log.debug("Janitor IncompletePaymentTransactionTask repairing payment {}, transaction {}, transitioning transactionStatus from {} -> {}", payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus);
        insertNewNotificationForUnresolvedTransactionIfNeeded(paymentTransaction.getId(), attemptNumber, userToken, internalTenantContext.getAccountRecordId(), internalTenantContext.getTenantRecordId());
        return false;
    }
    // Recompute new lastSuccessPaymentState. This is important to be able to allow new operations on the state machine (for e.g an AUTH_SUCCESS would now allow a CAPTURE operation)
    final String lastSuccessPaymentState = paymentStateMachineHelper.isSuccessState(newPaymentState) ? newPaymentState : null;
    // Update processedAmount and processedCurrency
    final BigDecimal processedAmount;
    if (TransactionStatus.SUCCESS.equals(transactionStatus) || TransactionStatus.PENDING.equals(transactionStatus)) {
        if (paymentTransactionInfoPlugin == null || paymentTransactionInfoPlugin.getAmount() == null) {
            processedAmount = paymentTransaction.getProcessedAmount();
        } else {
            processedAmount = paymentTransactionInfoPlugin.getAmount();
        }
    } else {
        processedAmount = BigDecimal.ZERO;
    }
    final Currency processedCurrency;
    if (paymentTransactionInfoPlugin == null || paymentTransactionInfoPlugin.getCurrency() == null) {
        processedCurrency = paymentTransaction.getProcessedCurrency();
    } else {
        processedCurrency = paymentTransactionInfoPlugin.getCurrency();
    }
    // Update the gatewayErrorCode, gatewayError if we got a paymentTransactionInfoPlugin
    final String gatewayErrorCode = paymentTransactionInfoPlugin != null ? paymentTransactionInfoPlugin.getGatewayErrorCode() : paymentTransaction.getGatewayErrorCode();
    final String gatewayError = paymentTransactionInfoPlugin != null ? paymentTransactionInfoPlugin.getGatewayError() : paymentTransaction.getGatewayErrorMsg();
    log.info("Repairing paymentId='{}', paymentTransactionId='{}', currentTransactionStatus='{}', newTransactionStatus='{}'", payment.getId(), paymentTransaction.getId(), paymentTransaction.getTransactionStatus(), transactionStatus);
    final InternalCallContext internalCallContext = internalCallContextFactory.createInternalCallContext(payment.getAccountId(), callContext);
    paymentDao.updatePaymentAndTransactionOnCompletion(payment.getAccountId(), paymentTransaction.getAttemptId(), payment.getId(), paymentTransaction.getTransactionType(), newPaymentState, lastSuccessPaymentState, paymentTransaction.getId(), transactionStatus, processedAmount, processedCurrency, gatewayErrorCode, gatewayError, internalCallContext);
    return true;
}
Also used : Currency(org.killbill.billing.catalog.api.Currency) TransactionStatus(org.killbill.billing.payment.api.TransactionStatus) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) CallContext(org.killbill.billing.util.callcontext.CallContext) BigDecimal(java.math.BigDecimal)

Example 39 with CallContext

use of org.killbill.billing.util.callcontext.CallContext in project killbill by killbill.

the class PaymentBusEventHandler method processInvoiceEvent.

@AllowConcurrentEvents
@Subscribe
public void processInvoiceEvent(final InvoiceCreationInternalEvent event) {
    log.info("Received invoice creation notification for accountId='{}', invoiceId='{}'", event.getAccountId(), event.getInvoiceId());
    final Collection<PluginProperty> properties = new ArrayList<PluginProperty>();
    final PluginProperty propertyInvoiceId = new PluginProperty(InvoicePaymentControlPluginApi.PROP_IPCD_INVOICE_ID, event.getInvoiceId().toString(), false);
    properties.add(propertyInvoiceId);
    final InternalCallContext internalContext = internalCallContextFactory.createInternalCallContext(event.getSearchKey2(), event.getSearchKey1(), "PaymentRequestProcessor", CallOrigin.INTERNAL, UserType.SYSTEM, event.getUserToken());
    final CallContext callContext = internalCallContextFactory.createCallContext(internalContext);
    // We let the plugin compute how much should be paid
    final BigDecimal amountToBePaid = null;
    final List<String> paymentControlPluginNames = paymentConfig.getPaymentControlPluginNames(internalContext) != null ? new LinkedList<String>(paymentConfig.getPaymentControlPluginNames(internalContext)) : new LinkedList<String>();
    paymentControlPluginNames.add(InvoicePaymentControlPluginApi.PLUGIN_NAME);
    final String transactionType = TransactionType.PURCHASE.name();
    Account account = null;
    Payment payment = null;
    PaymentTransaction paymentTransaction = null;
    try {
        account = accountApi.getAccountById(event.getAccountId(), internalContext);
        logEnterAPICall(log, transactionType, account, account.getPaymentMethodId(), null, null, amountToBePaid, account.getCurrency(), null, null, null, paymentControlPluginNames);
        payment = pluginControlPaymentProcessor.createPurchase(false, account, account.getPaymentMethodId(), null, amountToBePaid, account.getCurrency(), null, null, properties, paymentControlPluginNames, callContext, internalContext);
        paymentTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);
    } catch (final AccountApiException e) {
        log.warn("Failed to process invoice payment", e);
    } catch (final PaymentApiException e) {
        // Log as warn unless nothing left to be paid
        if (e.getCode() != ErrorCode.PAYMENT_PLUGIN_API_ABORTED.getCode()) {
            log.warn("Failed to process invoice payment {}", e.toString());
        }
    } finally {
        logExitAPICall(log, transactionType, account, payment != null ? payment.getPaymentMethodId() : null, payment != null ? payment.getId() : null, paymentTransaction != null ? paymentTransaction.getId() : null, paymentTransaction != null ? paymentTransaction.getProcessedAmount() : null, paymentTransaction != null ? paymentTransaction.getProcessedCurrency() : null, payment != null ? payment.getExternalKey() : null, paymentTransaction != null ? paymentTransaction.getExternalKey() : null, paymentTransaction != null ? paymentTransaction.getTransactionStatus() : null, paymentControlPluginNames, null);
    }
}
Also used : Account(org.killbill.billing.account.api.Account) ArrayList(java.util.ArrayList) PaymentApiException(org.killbill.billing.payment.api.PaymentApiException) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) CallContext(org.killbill.billing.util.callcontext.CallContext) BigDecimal(java.math.BigDecimal) PaymentTransaction(org.killbill.billing.payment.api.PaymentTransaction) PluginProperty(org.killbill.billing.payment.api.PluginProperty) Payment(org.killbill.billing.payment.api.Payment) AccountApiException(org.killbill.billing.account.api.AccountApiException) AllowConcurrentEvents(com.google.common.eventbus.AllowConcurrentEvents) Subscribe(com.google.common.eventbus.Subscribe)

Example 40 with CallContext

use of org.killbill.billing.util.callcontext.CallContext in project killbill by killbill.

the class DefaultSubscriptionInternalApi method createSubscription.

@Override
public SubscriptionBase createSubscription(final UUID bundleId, final PlanPhaseSpecifier spec, final List<PlanPhasePriceOverride> overrides, final DateTime requestedDateWithMs, final boolean isMigrated, final InternalCallContext context) throws SubscriptionBaseApiException {
    try {
        final DateTime now = clock.getUTCNow();
        final DateTime effectiveDate = (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
        /*
            if (requestedDate.isAfter(now)) {
                throw new SubscriptionBaseApiException(ErrorCode.SUB_INVALID_REQUESTED_DATE, now.toString(), requestedDate.toString());
            }
            */
        final CallContext callContext = internalCallContextFactory.createCallContext(context);
        final Catalog catalog = catalogService.getFullCatalog(true, true, context);
        final PlanPhasePriceOverridesWithCallContext overridesWithContext = new DefaultPlanPhasePriceOverridesWithCallContext(overrides, callContext);
        final Plan plan = catalog.createOrFindPlan(spec, overridesWithContext, effectiveDate);
        final PlanPhase phase = plan.getAllPhases()[0];
        if (phase == null) {
            throw new SubscriptionBaseError(String.format("No initial PlanPhase for Product %s, term %s and set %s does not exist in the catalog", spec.getProductName(), spec.getBillingPeriod().toString(), plan.getPriceListName()));
        }
        final SubscriptionBaseBundle bundle = dao.getSubscriptionBundleFromId(bundleId, context);
        if (bundle == null) {
            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleId);
        }
        final DefaultSubscriptionBase baseSubscription = (DefaultSubscriptionBase) dao.getBaseSubscription(bundleId, context);
        // verify the number of subscriptions (of the same kind) allowed per bundle
        if (ProductCategory.ADD_ON.toString().equalsIgnoreCase(plan.getProduct().getCategory().toString())) {
            if (plan.getPlansAllowedInBundle() != -1 && plan.getPlansAllowedInBundle() > 0 && addonUtils.countExistingAddOnsWithSamePlanName(getSubscriptionsForBundle(bundleId, null, context), plan.getName()) >= plan.getPlansAllowedInBundle()) {
                // a new ADD_ON subscription of the same plan can't be added because it has reached its limit by bundle
                throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_AO_MAX_PLAN_ALLOWED_BY_BUNDLE, plan.getName());
            }
        }
        final DateTime bundleStartDate = getBundleStartDateWithSanity(bundleId, baseSubscription, plan, effectiveDate, context);
        return apiService.createPlan(new SubscriptionBuilder().setId(UUIDs.randomUUID()).setBundleId(bundleId).setBundleExternalKey(bundle.getExternalKey()).setCategory(plan.getProduct().getCategory()).setBundleStartDate(bundleStartDate).setAlignStartDate(effectiveDate).setMigrated(isMigrated), plan, spec.getPhaseType(), plan.getPriceListName(), effectiveDate, now, callContext);
    } catch (final CatalogApiException e) {
        throw new SubscriptionBaseApiException(e);
    }
}
Also used : SubscriptionBuilder(org.killbill.billing.subscription.api.user.SubscriptionBuilder) Plan(org.killbill.billing.catalog.api.Plan) PlanPhasePriceOverridesWithCallContext(org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext) CallContext(org.killbill.billing.util.callcontext.CallContext) InternalCallContext(org.killbill.billing.callcontext.InternalCallContext) DateTime(org.joda.time.DateTime) Catalog(org.killbill.billing.catalog.api.Catalog) PlanPhasePriceOverridesWithCallContext(org.killbill.billing.catalog.api.PlanPhasePriceOverridesWithCallContext) SubscriptionBaseError(org.killbill.billing.subscription.exceptions.SubscriptionBaseError) CatalogApiException(org.killbill.billing.catalog.api.CatalogApiException) PlanPhase(org.killbill.billing.catalog.api.PlanPhase) SubscriptionBaseBundle(org.killbill.billing.subscription.api.user.SubscriptionBaseBundle) DefaultSubscriptionBaseBundle(org.killbill.billing.subscription.api.user.DefaultSubscriptionBaseBundle) DefaultSubscriptionBase(org.killbill.billing.subscription.api.user.DefaultSubscriptionBase) SubscriptionBaseApiException(org.killbill.billing.subscription.api.user.SubscriptionBaseApiException) PlanPhasePriceOverride(org.killbill.billing.catalog.api.PlanPhasePriceOverride)

Aggregations

CallContext (org.killbill.billing.util.callcontext.CallContext)82 ApiOperation (io.swagger.annotations.ApiOperation)51 ApiResponses (io.swagger.annotations.ApiResponses)51 Produces (javax.ws.rs.Produces)48 TimedResource (org.killbill.commons.metrics.TimedResource)48 Consumes (javax.ws.rs.Consumes)47 Path (javax.ws.rs.Path)43 UUID (java.util.UUID)40 Account (org.killbill.billing.account.api.Account)38 POST (javax.ws.rs.POST)35 PluginProperty (org.killbill.billing.payment.api.PluginProperty)29 LocalDate (org.joda.time.LocalDate)16 Payment (org.killbill.billing.payment.api.Payment)15 InternalCallContext (org.killbill.billing.callcontext.InternalCallContext)14 DefaultCallContext (org.killbill.billing.callcontext.DefaultCallContext)11 Invoice (org.killbill.billing.invoice.api.Invoice)10 PaymentOptions (org.killbill.billing.payment.api.PaymentOptions)10 PUT (javax.ws.rs.PUT)9 Entitlement (org.killbill.billing.entitlement.api.Entitlement)9 DELETE (javax.ws.rs.DELETE)7