示例#1
0
  @Override
  public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {
    // Validation: check for nulls

    if (plan == null) {
      errors.add(
          new ValidationError(
              String.format("Invalid plan for recurring section"),
              catalog.getCatalogURI(),
              DefaultRecurring.class,
              ""));
    }

    if (phase == null) {
      errors.add(
          new ValidationError(
              String.format("Invalid phase for recurring section"),
              catalog.getCatalogURI(),
              DefaultPlan.class,
              plan.getName().toString()));
    }

    if (billingPeriod == null) {
      errors.add(
          new ValidationError(
              String.format(
                  "Recurring section of Phase %s of plan %s has a recurring price but no billing period",
                  phase.getPhaseType().toString(), plan.getName()),
              catalog.getCatalogURI(),
              DefaultPlanPhase.class,
              phase.getPhaseType().toString()));
    }

    // Validation: if there is a recurring price there must be a billing period
    if ((recurringPrice != null)
        && (billingPeriod == null || billingPeriod == BillingPeriod.NO_BILLING_PERIOD)) {
      errors.add(
          new ValidationError(
              String.format(
                  "Recurring section of Phase %s of plan %s has a recurring price but no billing period",
                  phase.getPhaseType().toString(), plan.getName()),
              catalog.getCatalogURI(),
              DefaultPlanPhase.class,
              phase.getPhaseType().toString()));
    }

    // Validation: if there is no recurring price there should be no billing period
    if ((recurringPrice == null) && billingPeriod != BillingPeriod.NO_BILLING_PERIOD) {
      errors.add(
          new ValidationError(
              String.format(
                  "Recurring section of Phase %s of plan %s has no recurring price but does have a billing period. The billing period should be set to '%s'",
                  phase.getPhaseType().toString(), plan.getName(), BillingPeriod.NO_BILLING_PERIOD),
              catalog.getCatalogURI(),
              DefaultPlanPhase.class,
              phase.getPhaseType().toString()));
    }
    return errors;
  }
示例#2
0
 /* (non-Javadoc)
  * @see org.killbill.billing.catalog.IPriceList#findPlan(org.killbill.billing.catalog.api.IProduct, org.killbill.billing.catalog.api.BillingPeriod)
  */
 @Override
 public Collection<Plan> findPlans(final Product product, final BillingPeriod period) {
   final List<Plan> result = new ArrayList<Plan>(plans.size());
   for (final Plan cur : getPlans()) {
     if (cur.getProduct().equals(product)
         && (cur.getRecurringBillingPeriod() != null
             && cur.getRecurringBillingPeriod().equals(period))) {
       result.add(cur);
     }
   }
   return result;
 }
  @Test(groups = "slow")
  public void testWithPriceOverride() throws Exception {

    // Create a per-tenant catalog with one plan
    final SimplePlanDescriptor desc1 =
        new DefaultSimplePlanDescriptor(
            "bar-monthly",
            "Bar",
            ProductCategory.BASE,
            account.getCurrency(),
            BigDecimal.TEN,
            BillingPeriod.MONTHLY,
            0,
            TimeUnit.UNLIMITED,
            ImmutableList.<String>of());
    catalogUserApi.addSimplePlan(desc1, init, testCallContext);
    StaticCatalog catalog = catalogUserApi.getCurrentCatalog("dummy", testCallContext);
    assertEquals(catalog.getCurrentPlans().size(), 1);

    final Plan plan = catalog.getCurrentPlans().iterator().next();
    final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("bar-monthly", null);

    final List<PlanPhasePriceOverride> overrides = new ArrayList<PlanPhasePriceOverride>();
    overrides.add(
        new DefaultPlanPhasePriceOverride(
            plan.getFinalPhase().getName(), account.getCurrency(), null, BigDecimal.ONE));
    final Entitlement baseEntitlement = createEntitlement(spec, overrides, true);

    List<Invoice> invoices =
        invoiceUserApi.getInvoicesByAccount(account.getId(), false, testCallContext);
    assertEquals(invoices.size(), 1);
    assertEquals(invoices.get(0).getChargedAmount().compareTo(BigDecimal.ONE), 0);

    busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
    clock.addMonths(1);
    assertListenerStatus();

    invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, testCallContext);
    assertEquals(invoices.size(), 2);
    assertEquals(invoices.get(1).getChargedAmount().compareTo(BigDecimal.ONE), 0);

    // Change plan to original (non overridden plan)
    busHandler.pushExpectedEvents(
        NextEvent.CHANGE, NextEvent.INVOICE, NextEvent.INVOICE_PAYMENT, NextEvent.PAYMENT);
    baseEntitlement.changePlan(spec, null, ImmutableList.<PluginProperty>of(), testCallContext);
    assertListenerStatus();

    invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), false, testCallContext);
    assertEquals(invoices.size(), 3);
    assertEquals(
        invoices.get(2).getChargedAmount().compareTo(new BigDecimal("9.00")),
        0); // 10 (recurring) - 1 (repair)
  }
  private void checkForTaxCodesOnProducts(
      final Invoice invoice,
      final Collection<PluginProperty> properties,
      final TenantContext context) {
    final Map<String, String> planToProductCache = new HashMap<String, String>();
    final Map<String, String> productToTaxCodeCache = new HashMap<String, String>();

    for (final InvoiceItem invoiceItem : invoice.getInvoiceItems()) {
      final String planName = invoiceItem.getPlanName();
      if (planName == null) {
        continue;
      }

      if (planToProductCache.get(planName) == null) {
        try {
          final StaticCatalog catalog =
              killbillAPI.getCatalogUserApi().getCurrentCatalog(null, context);
          final Plan plan = catalog.findCurrentPlan(planName);
          planToProductCache.put(planName, plan.getProduct().getName());
        } catch (final CatalogApiException e) {
          continue;
        }
      }
      final String productName = planToProductCache.get(planName);
      if (productName == null) {
        continue;
      }

      if (productToTaxCodeCache.get(productName) == null) {
        try {
          final String taxCode = dao.getTaxCode(productName, context.getTenantId());
          productToTaxCodeCache.put(productName, taxCode);
        } catch (final SQLException e) {
          continue;
        }
      }

      final String taxCode = productToTaxCodeCache.get(productName);
      if (taxCode != null) {
        addTaxCodeToInvoiceItem(
            invoiceItem.getId(), productToTaxCodeCache.get(productName), properties);
      }
    }
  }
  @Test(groups = "fast")
  public void testCalculateBCDForAOWithBPCancelledBundleAligned() throws Exception {
    final DateTimeZone accountTimeZone = DateTimeZone.UTC;
    final DateTime bpStartDateUTC = new DateTime(2012, 7, 16, 21, 0, 0, DateTimeZone.UTC);
    final int expectedBCDUTC = 16;

    // Create a Bundle associated with a subscription
    final SubscriptionBaseBundle bundle = Mockito.mock(SubscriptionBaseBundle.class);
    final SubscriptionBase subscription = Mockito.mock(SubscriptionBase.class);
    Mockito.when(subscription.getStartDate()).thenReturn(bpStartDateUTC);

    // subscription.getCurrentPlan() will return null as expected (cancelled BP)
    Mockito.when(
            subscriptionInternalApi.getBaseSubscription(
                Mockito.<UUID>any(), Mockito.<InternalTenantContext>any()))
        .thenReturn(subscription);

    // Create a the base plan associated with that subscription
    final Plan plan = Mockito.mock(Plan.class);
    Mockito.when(plan.dateOfFirstRecurringNonZeroCharge(bpStartDateUTC, null))
        .thenReturn(bpStartDateUTC);
    final Catalog catalog = Mockito.mock(Catalog.class);
    Mockito.when(
            catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any()))
        .thenReturn(plan);
    Mockito.when(subscription.getLastActivePlan()).thenReturn(plan);

    final Account account = Mockito.mock(Account.class);
    Mockito.when(account.getTimeZone()).thenReturn(accountTimeZone);
    final Integer billCycleDayLocal =
        billCycleDayCalculator.calculateBcdForAlignment(
            BillingAlignment.BUNDLE,
            bundle,
            subscription,
            account,
            catalog,
            null,
            internalCallContext);

    Assert.assertEquals(billCycleDayLocal, (Integer) expectedBCDUTC);
  }
  private void verifyBCDCalculation(
      final DateTimeZone accountTimeZone, final DateTime startDateUTC, final int bcdLocal)
      throws AccountApiException, CatalogApiException {
    final BillCycleDayCalculator billCycleDayCalculator =
        new BillCycleDayCalculator(
            Mockito.mock(CatalogService.class), Mockito.mock(SubscriptionBaseInternalApi.class));

    final SubscriptionBase subscription = Mockito.mock(SubscriptionBase.class);
    Mockito.when(subscription.getStartDate()).thenReturn(startDateUTC);

    final Plan plan = Mockito.mock(Plan.class);
    Mockito.when(plan.dateOfFirstRecurringNonZeroCharge(startDateUTC, null))
        .thenReturn(startDateUTC);

    final Account account = Mockito.mock(Account.class);
    Mockito.when(account.getTimeZone()).thenReturn(accountTimeZone);

    final Integer bcd =
        billCycleDayCalculator.calculateBcdFromSubscription(
            subscription, plan, account, Mockito.mock(Catalog.class), internalCallContext);
    Assert.assertEquals(bcd, (Integer) bcdLocal);
  }
  @Override
  public SubscriptionBase createSubscription(
      final UUID bundleId,
      final PlanPhaseSpecifier spec,
      final DateTime requestedDateWithMs,
      final InternalCallContext context)
      throws SubscriptionBaseApiException {
    try {
      final String realPriceList =
          (spec.getPriceListName() == null)
              ? PriceListSet.DEFAULT_PRICELIST_NAME
              : spec.getPriceListName();
      final DateTime now = clock.getUTCNow();
      final DateTime requestedDate =
          (requestedDateWithMs != null) ? DefaultClock.truncateMs(requestedDateWithMs) : now;
      if (requestedDate.isAfter(now)) {
        throw new SubscriptionBaseApiException(
            ErrorCode.SUB_INVALID_REQUESTED_DATE, now.toString(), requestedDate.toString());
      }
      final DateTime effectiveDate = requestedDate;

      final Catalog catalog = catalogService.getFullCatalog();
      final Plan plan =
          catalog.findPlan(
              spec.getProductName(), spec.getBillingPeriod(), realPriceList, requestedDate);

      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(), realPriceList));
      }

      final SubscriptionBaseBundle bundle = dao.getSubscriptionBundleFromId(bundleId, context);
      if (bundle == null) {
        throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BUNDLE, bundleId);
      }

      DateTime bundleStartDate = null;
      final DefaultSubscriptionBase baseSubscription =
          (DefaultSubscriptionBase) dao.getBaseSubscription(bundleId, context);
      switch (plan.getProduct().getCategory()) {
        case BASE:
          if (baseSubscription != null) {
            if (baseSubscription.getState() == EntitlementState.ACTIVE) {
              throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundleId);
            }
          }
          bundleStartDate = requestedDate;
          break;
        case ADD_ON:
          if (baseSubscription == null) {
            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_NO_BP, bundleId);
          }
          if (effectiveDate.isBefore(baseSubscription.getStartDate())) {
            throw new SubscriptionBaseApiException(
                ErrorCode.SUB_INVALID_REQUESTED_DATE,
                effectiveDate.toString(),
                baseSubscription.getStartDate().toString());
          }
          addonUtils.checkAddonCreationRights(baseSubscription, plan);
          bundleStartDate = baseSubscription.getStartDate();
          break;
        case STANDALONE:
          if (baseSubscription != null) {
            throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_BP_EXISTS, bundleId);
          }
          // Not really but we don't care, there is no alignment for STANDALONE subscriptions
          bundleStartDate = requestedDate;
          break;
        default:
          throw new SubscriptionBaseError(
              String.format(
                  "Can't create subscription of type %s",
                  plan.getProduct().getCategory().toString()));
      }

      final UUID tenantId =
          nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
      return apiService.createPlan(
          new SubscriptionBuilder()
              .setId(UUID.randomUUID())
              .setBundleId(bundleId)
              .setCategory(plan.getProduct().getCategory())
              .setBundleStartDate(bundleStartDate)
              .setAlignStartDate(effectiveDate),
          plan,
          spec.getPhaseType(),
          realPriceList,
          requestedDate,
          effectiveDate,
          now,
          context.toCallContext(tenantId));
    } catch (CatalogApiException e) {
      throw new SubscriptionBaseApiException(e);
    }
  }