@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; }
/* (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); } }