code: package io.paradoxical.cassieq.model;
  
  import com.fasterxml.jackson.core.JsonGenerator;
  import com.fasterxml.jackson.core.JsonParser;
  import com.fasterxml.jackson.databind.DeserializationContext;
  import com.fasterxml.jackson.databind.JsonDeserializer;
  import com.fasterxml.jackson.databind.JsonSerializer;
  import com.fasterxml.jackson.databind.SerializerProvider;
  import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
  import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  import lombok.Data;
  
  import java.io.IOException;
  import java.util.Base64;
  
  @Data
  @JsonSerialize(using = PopReceipt.JsonSerializeAdapter.class)
  @JsonDeserialize(using = PopReceipt.JsonDeserializeAdapater.class)
  public final class PopReceipt {
      private final MonotonicIndex messageIndex;
  
      private final int messageVersion;
  
      private final MessageTag messageTag;
  
      public static PopReceipt valueOf(String string) {
          return parsePopReceipt(string);
      }
  
      public static PopReceipt from(Message message) {
          return new PopReceipt(message.getIndex(), message.getVersion(), message.getTag());
      }
  
      @Override
      public String toString() {
          return getPopReceipt();
      }
  
      private String getPopReceipt() {
          final String receiptString = String.format("%s:%s:%s", getMessageIndex(), getMessageVersion(), getMessageTag());
  
          return Base64.getEncoder().withoutPadding().encodeToString(receiptString.getBytes());
      }
  
      private static PopReceipt parsePopReceipt(String popReceipt) {
          final byte[] rawReceipt = Base64.getDecoder().decode(popReceipt);
  
          final String receipt = new String(rawReceipt);
  
          final String[] components = receipt.split(":");
  
          final MonotonicIndex monotonicIndex = MonotonicIndex.valueOf(Long.parseLong(components[0]));
          final Integer messageVersion = Integer.parseInt(components[1]);
          final MessageTag messageTag = MessageTag.valueOf(components[2]);
  
          return new PopReceipt(monotonicIndex, messageVersion, messageTag);
      }
  
      public static class JsonDeserializeAdapater extends JsonDeserializer<PopReceipt> {
  
          @Override
          public PopReceipt deserialize(final JsonParser jp, final DeserializationContext ctxt)
                  throws IOException {
              return PopReceipt.valueOf(jp.getValueAsString());
          }
      }
  
      public static class JsonSerializeAdapter extends JsonSerializer<PopReceipt> {
          @SuppressWarnings("ConstantConditions")
          @Override
          public void serialize(final PopReceipt value, final JsonGenerator jgen, final SerializerProvider provider)
                  throws IOException {
              jgen.writeString(value.getPopReceipt());
          }
      }
  }
  