Adding and filtering for payment metadata

Usage

As developers, we understand the necessity to associate external metadata to a certain payment. The Breez SDK allows you to easily do so with the set_payment_metadata method:

Rust
sdk.set_payment_metadata("target-payment-hash", r#"{"myCustomValue":true}"#).await?;
Swift
try sdk.setPaymentMetadata(hash: "target-payment-hash", metadata: #"{"myCustomValue":true}"#)    
Kotlin
try {
    sdk.setPaymentMetadata("target-payment-hash", """{"myCustomValue":true}""")
} catch (e: Exception) {
    // Handle error
}
React Native
await setPaymentMetadata('target-payment-hash', '{"myCustomValue":true}')
Dart
await BreezSDK().setPaymentMetadata(hash: "target-payment-hash", metadata: '{"myCustomValue":true}');
Python
sdk_services.set_payment_metadata("target-payment-hash", '{"myCustomValue":true}')    
Go
sdk.SetPaymentMetadata("target-payment-hash", `{"myCustomValue":true}`)
C#
sdk.SetPaymentMetadata("target-payment-hash", "{\"myCustomValue\":true}");

Once the metadata has been set, you can filter for the specified value using the list_payments method:

Rust
let metadata_filters = vec![
    MetadataFilter {
        json_path: "myCustomValue".to_string(),
        json_value: "true".to_string(),
    },
];

sdk.list_payments(ListPaymentsRequest {
    metadata_filters,
    ..Default::default(),
}).await?;
Swift
let metadataFilters = [
    MetadataFilter(
        jsonPath: "myCustomValue",
        jsonValue: "true"
    )
]

let payments = try? sdk.listPayments(
    req: ListPaymentsRequest(
        metadataFilters: metadataFilters
    )
)
Kotlin
val metadataFilters = listOf(MetadataFilter(
  jsonPath = "myCustomValue",
  jsonValue = "true"
))

try {
    sdk.listPayments(
        ListPaymentsRequest(
            metadataFilters = metadataFilters
        ))
} catch (e: Exception) {
    // handle error
}
React Native
const metadataFilters = [
  {
    jsonPath: 'myCustomValue',
    jsonValue: 'true'
  }
]

try {
  await listPayments({
    metadataFilters
  })
} catch (err) {
  // handle error
}
Dart
List<MetadataFilter> metadataFilters = [
  MetadataFilter(
    jsonPath: "myCustomValue",
    jsonValue: "true",
  ),
];

await BreezSDK().listPayments(
  req: ListPaymentsRequest(
    metadataFilters: metadataFilters
  ));
Python
metadata_filters = [
    breez_sdk.MetadataFilter("myCustomValue", "true")
]

try:
    sdk_services.list_payments(breez_sdk.ListPaymentsRequest(
        metadata_filters = metadata_filters
    ))
except Exception as error:
    # handle error
    raise
Go
metadataFilters := []breez_sdk.MetadataFilter{
    {JsonPath: "myCustomValue", JsonValue: "true"},
}

payments, err := sdk.ListPayments(breez_sdk.ListPaymentsRequest{
    MetadataFilters: &metadataFilters,
})

if err != nil {
    // handle error
}
C#
try
{
    var metadataFilters = new List<MetadataFilter>() {
      new MetadataFilter(
        jsonPath: "myCustomValue",
        jsonValue: "true"
      )
    };

    var payments = sdk.ListPayments(
        new ListPaymentsRequest(
            metadataFilters: metadataFilters
        )
    );
}
catch (Exception)
{
    // Handle error
}

Caveats

Searching for metadata is flexible, allowing you to search and compare all JSON-supported types (nested ones too, using JSONPath), but with a couple of caveats:

2.1 Filtering for Strings

Since the filter works as a one-to-one comparison to the JSON value, strings must be wrapped in double-quotes in order to be properly filtered:

Rust
// Note: The following are equivalent
let metadata_filters = vec![
    MetadataFilter {
        json_path: "customerName".to_string(),
        json_value: r#""Satoshi Nakamoto""#.to_string(),
    },
    MetadataFilter {
        json_path: "customerName".to_string(),
        json_value: serde_json::json!("Satoshi Nakamoto").to_string(),
    },
];
Swift
let metadataFilters = [
    MetadataFilter(
        jsonPath: "myCustomValue",
        jsonValue: #""true""#
    )
]
Kotlin
val metadataFilters = listOf(MetadataFilter(
  jsonPath = "customerName",
  jsonValue = "\"Satoshi Nakamoto\""
))
React Native
// Note: These are equivalent
const metadataFilters = [
  {
    jsonPath: 'customerName',
    jsonValue: 'Satoshi Nakamoto'
  },
  {
    jsonPath: 'customerName',
    jsonValue: JSON.stringify('Satoshi Nakamoto')
  }
]
Dart
metadataFilters = [
  MetadataFilter(
    jsonPath: "customerName",
    jsonValue: '"Satoshi Nakamoto"',
  ),
];
Python
metadata_filters = [
    breez_sdk.MetadataFilter("customerName", "\"Satoshi Nakamoto\""),
    breez_sdk.MetadataFilter("customerName", json.dumps("Satoshi Nakamoto")),
]
Go
metadataFilters := []breez_sdk.MetadataFilter{
    {JsonPath: "customerName", JsonValue: "\"Satoshi Nakamoto\""},
}

jsonValue, _ := json.Marshal("Satoshi Nakamoto")
metadataFilters = []breez_sdk.MetadataFilter{
    {
        JsonPath:  "customerName",
        JsonValue: string(jsonValue),
    },
}
C#
var metadataFilters = new List<MetadataFilter>() {
  new MetadataFilter(
    jsonPath: "customerName",
    jsonValue: "\"Satoshi Nakamoto\""
  ),
  new MetadataFilter(
    jsonPath: "customerName",
    jsonValue: JsonSerializer.Serialize("Satoshi Nakamoto")
  )
};

2.2 Filtering for Objects/Arrays

You can also compare complex objects against one another, but be careful of whitespaces! Since those are stripped during insertion, passing non-stripped filters will result in improper matching. For example, given the following metadata:

{
 "isNested": true,
 "parent": {  
  "nestedArray": [1, 2, 3]
 }
}

You would filter for payments matching the nested array as follows:

Rust
// This will *NOT* work
let metadata_filters = vec![
    MetadataFilter {
        json_path: "parent.nestedArray".to_string(),
        json_value: r#"[1, 2, 3]"#.to_string(),
    },
];

// Any of these will work
let metadata_filters = vec![
    MetadataFilter {
        json_path: "parent.nestedArray".to_string(),
        json_value: r#"[1,2,3]"#.to_string(),
    },
];

In Rust's case, this check can easily be overcome by using the serde_json crate (which you probably should be using anyway to serialize and insert the metadata):

let metadata_filters = vec![
    MetadataFilter {
        json_path: "parent.nestedArray".to_string(),
        json_value: serde_json::json!(&[1, 2, 3]).to_string(),
    },
];
Swift
// This will *NOT* work
var metadataFilters = [
    MetadataFilter(
        jsonPath: "myCustomValue",
        jsonValue: #"[1, 2, 3]"#
    )
]

// Any of these will work
metadataFilters = [
    MetadataFilter(
        jsonPath: "myCustomValue",
        jsonValue: #"[1,2,3]"#
    )
]
Kotlin
// This will *NOT* work
val _metadataFilters = listOf(MetadataFilter(
  jsonPath = "parent.nestedArray",
  jsonValue = "[1, 2, 3]"
))

// Any of these will work
val metadataFilters = listOf(MetadataFilter(
  jsonPath = "parent.nestedArray",
  jsonValue = "[1,2,3]"
))
React Native
// This will *NOT* work
const _metadataFilters = [
  {
    jsonPath: 'parent.nestedArray',
    jsonValue: '[1, 2, 3]'
  }
]

// Any of these will work
const metadataFilters = [
  {
    jsonPath: 'parent.nestedArray',
    jsonValue: '[1,2,3]'
  },
  {
    jsonPath: 'parent.nestedArray',
    jsonValue: JSON.stringify([1, 2, 3])
  }
]
Dart
// This will *NOT* work
metadataFilters = [
  MetadataFilter(
    jsonPath: "parent.nestedArray",
    jsonValue: "[1, 2, 3]",
  ),
];

// Any of these will work
metadataFilters = [
  MetadataFilter(
    jsonPath: "parent.nestedArray",
    jsonValue: "[1,2,3]",
  ),
];
Python
# This will *NOT* work
metadata_filters = [
    breez_sdk.MetadataFilter("parent.nestedArray", "[1, 2, 3]")
]

# Any of these will work
metadata_filters = [
    breez_sdk.MetadataFilter("parent.nestedArray", "[1,2,3]"),
    breez_sdk.MetadataFilter("parent.nestedArray", json.dumps([1,2,3], separators=(',', ':'))),
]
Go
// This will *NOT* work
metadataFilters := []breez_sdk.MetadataFilter{
    {JsonPath: "parent.nestedArray", JsonValue: "[1, 2, 3]"},
}

// Any of these will work
jsonValue, _ := json.Marshal([]int{1, 2, 3})

metadataFilters = []breez_sdk.MetadataFilter{
    {JsonPath: "parent.nestedArray", JsonValue: "[1,2,3]"},
    {JsonPath: "parent.nestedArray", JsonValue: string(jsonValue)},
}
C#
// This will *NOT* work
var _metadataFilters = new List<MetadataFilter>() {
  new MetadataFilter(
    jsonPath: "parent.nestedArray",
    jsonValue: "[1, 2, 3]"
  )
};

// Any of these will work
var metadataFilters = new List<MetadataFilter>() {
  new MetadataFilter(
    jsonPath: "parent.nestedArray",
    jsonValue: "[1,2,3]"
  ),
  new MetadataFilter(
    jsonPath: "parent.nestedArray",
    jsonValue: JsonSerializer.Serialize(new int[] {1, 2, 3})
  )
};

2.3 Same-key Insertion

In case the same key were to be specified twice during insertion, the last one occurring in the string is taken as valid by default. E.g.

{
 "completed": true,
 "completed": false
}

will insert the value as false.

2.4 Size Limits

Currently, the SDK limits metadata storage per payment to 1,000 UTF-8 encoded characters, and any insertion beyond that will fail.