Skip to content

Pricing Configuration

Pedro González Marcos edited this page Jan 12, 2024 · 1 revision

Pricing Configuration

The packages uses a YAML file to represent all the pricing configuration, which includes: plans specifications, features used, values of these features for each plan…

The file must be placed inside the resources folder of the project, and must have the structure of this example:

features:
  feature1:
    description: feature1 description
    expression: # SPEL expression
    serverExpression: # SPEL expression that will be evaluated on the server side
    type: NUMERIC # The value of this field can be NUMERIC, TEXT or CONDITION
    defaultValue: 2
  feature2:
    description: feature2 description
    expression: # SPEL expression
    type: CONDITION
    defaultValue: true
  feature3:
    description: feature3 description
    expression: "" # This feature will be evaluated as false by default
    type: TEXT
    defaultValue: LOW
  # ...
plans:
  BASIC:
    description: Basic plan
    price: 0.0
    currency: EUR
    features:
      feature1:
        value: null
      feature2:
        value: false
      feature3:
        value: null
  PRO:
    description: Pro plan
    price: 12.0
    currency: EUR
    features:
      feature1:
        value: 6
      feature2:
        value: null
      feature3:
        value: HIGH

Important notes to have in mind while configuring the YAML

  • The features section must contain all the features that are going to be used in the app. Each feature must have a description, a type and a defaultValue and a expression.

  • Supported values for valueType field are:

    • NUMERIC (handles Integer, Double, Long…),
    • TEXT (handles String)
    • CONDITION (handles Boolean).
  • defaultValue field depends on the value of valueType:

    • If the valueType is CONDITION, the defaultValue must be a Boolean.
    • If the valueType is NUMERIC, the defaultValue must be Integer, Double, Long…
    • If the valueType is TEXT, the defaultValue must be a String.
  • expression field must be a String with the form of a SPEL expression, that evaluates the state of the feature. It can access the data of the user context using the userContext variable, while the plan's is available through planContext. For example, considering a user context that contains the following information:

    {
      "username": "John",
      "feature1use": 2
    }

    If we want to check if the use of the feature exceeds its limit, the SPEL expression should be:

    # ...
    feature1:
      # ...
      expression: userContext['feature1use'] <= planContext['feature1']
      # ...

    It's also possible to define a server side evaluation that will be used to evaluate any feature using @PricingPlanAware annotation. This use can be interesting on NUMERIC features, let's see an example.

    If we have a button on the UI to add items to a list, it should be only available while the amount of products is under the feature limit, so when it is reached, the button disapears. The expression that models this behaviour will be the following:

    # ...
    feature1:
      # ...
      expression: userContext['feature1use'] < planContext['feature1']
      # ...

    However, on the server side, we should consider that the application has a valid state if the limit is not exceeded, which is evaluated with the following expression:

    # ...
    feature1:
      # ...
      expression: userContext['feature1use'] <= planContext['feature1']
      # ...

    To handle this type of situations, features configuration includes an optional serverExpression attribute that will be used to evaluate the feature on the server side (when using @PricingPlanAware annotation). If this attribute is not defined, the expression will be used instead on any evaluation context. The snippet below shows how to define the situation described above:

    # ...
    feature1:
      # ...
      expression: userContext['feature1use'] < planContext['feature1']
      serverExpression: userContext['feature1use'] <= planContext['feature1']
      # ...
  • Each feature inside a plan must have a name that match with one of the declared in the features section. Each of this features must only contains a valueType attribute of a type supported by the feature. The valueType attribute can also can be set to null if you want the library to consider the defaultValue as the value of the field.

    The library will automatically add the rest of the attributes when parsing YAML to PricingManager.

Java objects to manage pricing

The package provides a set of java objects that model the YAML configuration. These objects can be used to access information about the pricing all over the app.

PricingManager

This class is the main object of the package. It contains all the information about the pricing configuration and can be used to evaluate the context of an user and generate a JWT with the results.

public class PricingManager {
    public Map<String, Plan> plans;
    public Map<String, Feature> features;

    // Getters and setters...
}

The name of each plan and feature is used as a key to access the information of the object. For example, to access the price of the plan BASIC we can use:

pricingManager.getPlans().get("BASIC").getPrice();

Plan

This class models the information of a plan. It contains the name, description, price and currency of the plan, as well as a map of the features used by the plan.

public class Plan {
    public String description;
    public Double price;
    public String currency;
    public Map<String, Feature> features;

    // Getters and setters...

    // toString()
}

Feature

This class models the information of a feature.

public class Feature {
    public String description;
    public FeatureType type;
    public Object defaultValue;
    public Object value;
    public String expression;
}

The class also includes a method called prepareToPlanWriting(). It is used to prepare the object to be written inside a plan in the YAML file by removing the setting the value of all the attributes to null, except value.

Clone this wiki locally