canonicalize static method

String canonicalize({
  1. required String appKey,
  2. required String userToken,
  3. required int timestamp,
  4. Map<String, dynamic>? raw,
  5. bool? isPaying,
  6. String? plan,
  7. num? mrr,
  8. String? currency,
})

Builds the newline-delimited canonical string (cpt-user-attrs-v1) without a trailing newline.

Specification:

cpt-user-attrs-v1
<appKey>
<userToken>
<isPaying: true|false|unset>
<plan: value|null|unset>
<mrr: canonicalNumber|null|unset>
<currency: valueAsSent|unset>
<timestamp: epochSeconds>

Implementation

static String canonicalize({
  required String appKey,
  required String userToken,
  required int timestamp,
  Map<String, dynamic>? raw,
  bool? isPaying,
  String? plan,
  num? mrr,
  String? currency,
}) {
  final effectiveRaw = <String, dynamic>{};
  if (raw != null) {
    effectiveRaw.addAll(raw);
  } else {
    if (isPaying != null) effectiveRaw['isPaying'] = isPaying;
    if (plan != null) effectiveRaw['plan'] = plan;
    if (mrr != null) effectiveRaw['mrr'] = mrr;
    if (currency != null) effectiveRaw['currency'] = currency;
  }

  return [
    version,
    appKey,
    userToken,
    _canonicalField(
      effectiveRaw,
      'isPaying',
      (v) => (v == true || v == 'true') ? 'true' : 'false',
    ),
    _canonicalField(effectiveRaw, 'plan', (v) => v.toString()),
    _canonicalField(effectiveRaw, 'mrr', (v) {
      if (v is num) return canonicalNumber(v);
      final parsed = num.tryParse(v.toString());
      if (parsed != null) return canonicalNumber(parsed);
      throw ArgumentError.value(v, 'mrr', 'mrr must be a number');
    }),
    _canonicalField(effectiveRaw, 'currency', (v) => v.toString()),
    timestamp.toString(),
  ].join('\n');
}