All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yahoo.vespa.hosted.controller.api.integration.billing.MockBillingController Maven / Gradle / Ivy

There is a newer version: 8.253.3
Show newest version
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.api.integration.billing;

import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.hosted.controller.tenant.TaxId;

import java.math.BigDecimal;
import java.time.Clock;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * @author olaa
 */
public class MockBillingController implements BillingController {

    private final Clock clock;
    private final BillingDatabaseClient dbClient;

    PlanId defaultPlan = PlanId.from("trial");
    List tenants = new ArrayList<>();
    Map plans = new HashMap<>();
    Map activeInstruments = new HashMap<>();
    Map> committedBills = new HashMap<>();
    public Map uncommittedBills = new HashMap<>();
    Map> unusedLineItems = new HashMap<>();
    Map collectionMethod = new HashMap<>();

    public MockBillingController(Clock clock, BillingDatabaseClient dbClient) {
        this.clock = clock;
        this.dbClient = dbClient;
    }

    @Override
    public PlanId getPlan(TenantName tenant) {
        return plans.getOrDefault(tenant, PlanId.from("trial"));
    }

    @Override
    public List tenantsWithPlan(List tenants, PlanId planId) {
        return tenants.stream()
                .filter(t -> plans.getOrDefault(t, PlanId.from("trial")).equals(planId))
                .toList();
    }

    @Override
    public String getPlanDisplayName(PlanId planId) {
        return "Plan with id: " + planId.value();
    }

    @Override
    public Quota getQuota(TenantName tenant) {
        return Quota.unlimited().withMaxClusterSize(5);
    }

    @Override
    public PlanResult setPlan(TenantName tenant, PlanId planId, boolean hasDeployments, boolean isAccountant) {
        plans.put(tenant, planId);
        return PlanResult.success();
    }

    @Override
    public Bill.Id createBillForPeriod(TenantName tenant, ZonedDateTime startTime, ZonedDateTime endTime, String agent) {
        var billId = Bill.Id.of("id-123");
        committedBills.computeIfAbsent(tenant, l -> new ArrayList<>())
                .add(new Bill(
                        billId,
                        tenant,
                        StatusHistory.open(clock),
                        List.of(),
                        startTime,
                        endTime
                ));
        return billId;
    }

    @Override
    public Bill.Id createBillForPeriod(TenantName tenant, LocalDate startDate, LocalDate endDate, String agent) {
        return createBillForPeriod(tenant, startDate.atStartOfDay(ZoneOffset.UTC), endDate.plusDays(1).atStartOfDay(ZoneOffset.UTC), agent);
    }

    @Override
    public Bill createUncommittedBill(TenantName tenant, LocalDate until) {
        return uncommittedBills.getOrDefault(tenant, emptyBill());
    }

    @Override
    public Map createUncommittedBills(LocalDate until) {
        return uncommittedBills;
    }

    @Override
    public List getUnusedLineItems(TenantName tenant) {
        return unusedLineItems.getOrDefault(tenant, List.of());
    }

    @Override
    public Optional getDefaultInstrument(TenantName tenant) {
        return Optional.ofNullable(activeInstruments.get(tenant));
    }

    @Override
    public String createClientToken(String tenant, String userId) {
        return "some-token";
    }

    @Override
    public boolean deleteInstrument(TenantName tenant, String userId, String instrumentId) {
        activeInstruments.remove(tenant);
        return true;
    }

    @Override
    public void updateBillStatus(Bill.Id billId, String agent, BillStatus status) {
        var now = clock.instant().atZone(ZoneOffset.UTC);
        committedBills.values().stream()
                .flatMap(List::stream)
                .filter(bill -> billId.equals(bill.id()))
                .forEach(bill -> bill.statusHistory().history.put(now, status));
    }

    @Override
    public void addLineItem(TenantName tenant, String description, BigDecimal amount, Optional billId, String agent) {
        if (billId.isPresent()) {
            throw new UnsupportedOperationException();
        } else {
            unusedLineItems.computeIfAbsent(tenant, l -> new ArrayList<>())
                    .add(new Bill.LineItem(
                            "line-item-id",
                            description,
                            amount,
                            "paid",
                            agent,
                            ZonedDateTime.now()));
        }
    }

    @Override
    public void deleteLineItem(String lineItemId) {
        unusedLineItems.values()
                .forEach(lineItems -> lineItems.
                        removeIf(lineItem -> lineItem.id().equals(lineItemId))
                );
    }

    @Override
    public boolean setActivePaymentInstrument(InstrumentOwner paymentInstrument) {
        var instrumentId = paymentInstrument.getPaymentInstrumentId();
        activeInstruments.put(paymentInstrument.getTenantName(), createInstrument(instrumentId));
        return true;
    }

    @Override
    public InstrumentList listInstruments(TenantName tenant, String userId) {
        return null;
    }

    @Override
    public List getBillsForTenant(TenantName tenant) {
        return committedBills.getOrDefault(tenant, List.of());
    }

    @Override
    public List getBills() {
        return committedBills.values().stream().flatMap(Collection::stream).toList();
    }

    @Override
    public Bill getBill(Bill.Id billId) {
        return committedBills.values().stream()
                .flatMap(Collection::stream)
                .filter(bill -> bill.id().equals(billId))
                .findFirst()
                .orElseThrow();
    }

    @Override
    public CollectionMethod getCollectionMethod(TenantName tenant) {
        return collectionMethod.getOrDefault(tenant, CollectionMethod.AUTO);
    }

    @Override
    public CollectionResult setCollectionMethod(TenantName tenant, CollectionMethod method) {
        collectionMethod.put(tenant, method);
        return CollectionResult.success();
    }

    @Override
    public boolean tenantsWithPlanUnderLimit(Plan plan, int limit) {
        if (limit < 0) return true;

        var count = Stream.concat(tenants.stream(), plans.keySet().stream())
                .distinct()
                .map(tenant -> plans.getOrDefault(tenant, defaultPlan))
                .filter(p -> p.equals(plan.id()))
                .count();

        return count < limit;
    }

    @Override
    public AcceptedCountries getAcceptedCountries() {
        return new AcceptedCountries(List.of(
                new AcceptedCountries.Country(
                        "NO", "Norway", true,
                        List.of(new AcceptedCountries.TaxType("no_vat", "Norwegian VAT number", "[0-9]{9}MVA", "123456789MVA"))),
                new AcceptedCountries.Country(
                        "CA", "Canada", true,
                        List.of(new AcceptedCountries.TaxType("ca_gst_hst", "Canadian GST/HST number", "([0-9]{9}) ?RT ?([0-9]{4})", "123456789RT0002"),
                                new AcceptedCountries.TaxType("ca_pst_bc", "Canadian PST number (British Columbia)", "PST-?([0-9]{4})-?([0-9]{4})", "PST-1234-5678")))
                ));
    }

    @Override
    public void validateTaxId(TaxId id) throws IllegalArgumentException {
        if (id.isEmpty() || id.isLegacy() || id.country().isEmpty()) return;
        if (!List.of("eu_vat", "no_vat").contains(id.type().value()))
            throw new IllegalArgumentException("Unknown tax id type '%s'".formatted(id.type().value()));
        if (!id.code().value().matches("\\w+"))
            throw new IllegalArgumentException("Invalid tax id code '%s'".formatted(id.code().value()));
    }


    public void setTenants(List tenants) {
        this.tenants = tenants;
    }

    private PaymentInstrument createInstrument(String id) {
        return new PaymentInstrument(id,
                "name",
                "displayText",
                "brand",
                "type",
                "endingWith",
                "expiryDate",
                "addressLine1",
                "addressLine2",
                "zip",
                "city",
                "state",
                "country");
    }

    public void addBill(TenantName tenantName, Bill bill, boolean committed) {
        if (committed)
            committedBills.computeIfAbsent(tenantName, i -> new ArrayList<>())
                    .add(bill);
        else
            uncommittedBills.put(tenantName, bill);
    }

    private Bill emptyBill() {
        var start = clock.instant().atZone(ZoneOffset.UTC);
        var end = clock.instant().atZone(ZoneOffset.UTC);
        return new Bill(Bill.Id.of("empty"), TenantName.defaultName(), StatusHistory.open(clock), List.of(), start, end);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy