use std::{collections::HashMap, fmt};
use serde_json::{Map as JsonMap, Value as JsonValue};
use crate::{
validation::{parse_utc_timestamp, validate_uuid_v4},
AllowanceId, EventId, PaykitError, PaymentAmount, PaymentEndpointIdentifier,
PaymentEndpointPayload, PaymentReference, PrivateMessageKind, Result,
};
use super::{PaymentConversion, PaymentConversionQuote, PaymentDeadline};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct PaymentRequestId(String);
impl PaymentRequestId {
pub fn new(id: impl Into<String>) -> Result<Self> {
validate_uuid_v4(id.into(), "Payment Request ID").map(Self)
}
pub fn new_v4() -> Self {
Self(uuid::Uuid::new_v4().to_string())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for PaymentRequestId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl AsRef<str> for PaymentRequestId {
fn as_ref(&self) -> &str {
&self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RecurrenceUnit {
Minute,
Hour,
Day,
Week,
Month,
Year,
}
impl RecurrenceUnit {
pub(super) fn as_str(self) -> &'static str {
match self {
Self::Minute => "minute",
Self::Hour => "hour",
Self::Day => "day",
Self::Week => "week",
Self::Month => "month",
Self::Year => "year",
}
}
pub(super) fn parse(value: &str) -> Result<Self> {
match value {
"minute" => Ok(Self::Minute),
"hour" => Ok(Self::Hour),
"day" => Ok(Self::Day),
"week" => Ok(Self::Week),
"month" => Ok(Self::Month),
"year" => Ok(Self::Year),
_ => Err(PaykitError::Validation(
"unsupported Recurrence unit".into(),
)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Recurrence {
pub(super) every: u32,
pub(super) unit: RecurrenceUnit,
pub(super) starts_at: String,
pub(super) anchor: String,
pub(super) ends_at: Option<String>,
}
#[derive(Clone, PartialEq)]
pub struct PaymentRequestTerms {
pub(super) amount: PaymentAmount,
pub(super) payment_reference: PaymentReference,
pub(super) proposal_expires_at: Option<String>,
pub(super) recurrence: Option<Recurrence>,
pub(super) accepted_payment_endpoint_identifiers: Vec<PaymentEndpointIdentifier>,
pub(super) payment_endpoints:
Option<HashMap<PaymentEndpointIdentifier, PaymentEndpointPayload>>,
pub(super) conversion: Option<PaymentConversion>,
pub(super) payment_deadline: Option<PaymentDeadline>,
pub(super) required_app_id: Option<crate::PaykitAppId>,
pub(super) metadata: JsonMap<String, JsonValue>,
}
impl fmt::Debug for PaymentRequestTerms {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PaymentRequestTerms")
.field("amount", &"<redacted>")
.field("payment_reference", &"<redacted>")
.field("proposal_expires_at", &self.proposal_expires_at)
.field("recurrence", &self.recurrence)
.field("conversion", &self.conversion)
.field("payment_deadline", &self.payment_deadline)
.field(
"accepted_payment_endpoint_identifiers",
&self.accepted_payment_endpoint_identifiers,
)
.field("required_app_id", &self.required_app_id)
.field(
"payment_endpoints",
&self.payment_endpoints.as_ref().map(|_| "<redacted>"),
)
.field(
"metadata",
&format_args!("<redacted:{} fields>", self.metadata.len()),
)
.finish()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BillingPeriod {
pub(super) starts_at: String,
pub(super) ends_at: String,
}
#[derive(Clone, PartialEq)]
pub struct PaymentRequest {
pub(super) version: u8,
pub(super) kind: PrivateMessageKind,
pub(super) event_id: EventId,
pub(super) payment_request_id: PaymentRequestId,
pub(super) request: PaymentRequestTerms,
}
impl fmt::Debug for PaymentRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PaymentRequest")
.field("version", &self.version)
.field("kind", &self.kind)
.field("event_id", &self.event_id)
.field("payment_request_id", &self.payment_request_id)
.field("request", &self.request)
.finish()
}
}
impl PaymentRequest {
pub(super) fn validate(&self) -> Result<()> {
crate::validation::validate_outgoing_version_kind(
self.version,
self.kind,
PrivateMessageKind::PaymentRequest,
"Payment Request",
)?;
self.request.validate()
}
pub fn new(
event_id: EventId,
payment_request_id: PaymentRequestId,
request: PaymentRequestTerms,
) -> Self {
Self {
version: 1,
kind: PrivateMessageKind::PaymentRequest,
event_id,
payment_request_id,
request,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PaymentRequestAcceptance {
pub(super) version: u8,
pub(super) kind: PrivateMessageKind,
pub(super) event_id: EventId,
pub(super) payment_request_id: PaymentRequestId,
}
impl PaymentRequestAcceptance {
pub fn new(event_id: EventId, payment_request_id: PaymentRequestId) -> Self {
Self {
version: 1,
kind: PrivateMessageKind::PaymentRequestAcceptance,
event_id,
payment_request_id,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PaymentRequestRejection {
pub(super) version: u8,
pub(super) kind: PrivateMessageKind,
pub(super) event_id: EventId,
pub(super) payment_request_id: PaymentRequestId,
pub(super) reason: Option<String>,
}
impl PaymentRequestRejection {
pub fn new(
event_id: EventId,
payment_request_id: PaymentRequestId,
reason: Option<String>,
) -> Self {
Self {
version: 1,
kind: PrivateMessageKind::PaymentRequestRejection,
event_id,
payment_request_id,
reason,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PaymentRequestCancellation {
pub(super) version: u8,
pub(super) kind: PrivateMessageKind,
pub(super) event_id: EventId,
pub(super) payment_request_id: PaymentRequestId,
pub(super) reason: Option<String>,
}
impl PaymentRequestCancellation {
pub fn new(
event_id: EventId,
payment_request_id: PaymentRequestId,
reason: Option<String>,
) -> Self {
Self {
version: 1,
kind: PrivateMessageKind::PaymentRequestCancellation,
event_id,
payment_request_id,
reason,
}
}
}
#[derive(Clone, PartialEq)]
pub struct PaymentProof {
pub(super) version: u8,
pub(super) kind: PrivateMessageKind,
pub(super) event_id: EventId,
pub(super) payment_request_id: PaymentRequestId,
pub(super) payment_reference: PaymentReference,
pub(super) billing_period: Option<BillingPeriod>,
pub(super) payment_app_id: crate::PaykitAppId,
pub(super) payment_endpoint_identifier: PaymentEndpointIdentifier,
pub(super) allowance_id: Option<AllowanceId>,
pub(super) conversion_quote_id: Option<EventId>,
pub(super) proof: JsonMap<String, JsonValue>,
}
impl fmt::Debug for PaymentProof {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PaymentProof")
.field("version", &self.version)
.field("kind", &self.kind)
.field("event_id", &self.event_id)
.field("payment_request_id", &self.payment_request_id)
.field("payment_reference", &"<redacted>")
.field("billing_period", &self.billing_period)
.field("payment_app_id", &self.payment_app_id)
.field("allowance_id", &self.allowance_id)
.field(
"payment_endpoint_identifier",
&self.payment_endpoint_identifier,
)
.field(
"proof",
&format_args!("<redacted:{} fields>", self.proof.len()),
)
.finish()
}
}
impl PaymentProof {
pub fn new(
event_id: EventId,
payment_request_id: PaymentRequestId,
payment_reference: PaymentReference,
billing_period: Option<BillingPeriod>,
payment_app_id: crate::PaykitAppId,
payment_endpoint_identifier: PaymentEndpointIdentifier,
proof: JsonMap<String, JsonValue>,
) -> Self {
Self {
version: 1,
kind: PrivateMessageKind::PaymentProof,
event_id,
payment_request_id,
payment_reference,
billing_period,
payment_app_id,
payment_endpoint_identifier,
allowance_id: None,
conversion_quote_id: None,
proof,
}
}
pub fn with_conversion_quote_id(mut self, quote_id: EventId) -> Self {
self.conversion_quote_id = Some(quote_id);
self
}
pub fn with_allowance_id(mut self, allowance_id: AllowanceId) -> Self {
self.allowance_id = Some(allowance_id);
self
}
pub fn allowance_id(&self) -> Option<&AllowanceId> {
self.allowance_id.as_ref()
}
pub(super) fn validate(&self) -> Result<()> {
crate::validation::validate_outgoing_version_kind(
self.version,
self.kind,
PrivateMessageKind::PaymentProof,
"Payment Proof",
)?;
if let Some(period) = &self.billing_period {
period.validate()?;
}
validate_method_specific_proof(&self.proof)
}
pub fn validate_for_request(&self, request: &PaymentRequest) -> Result<()> {
self.validate()?;
request.validate()?;
if self.payment_request_id != request.payment_request_id {
return Err(PaykitError::Validation(
"Payment Proof payment_request_id must match Payment Request".into(),
));
}
if self.payment_reference != request.request.payment_reference {
return Err(PaykitError::Validation(
"Payment Proof payment_reference must match Payment Request".into(),
));
}
if !request
.request
.accepted_payment_endpoint_identifiers
.contains(&self.payment_endpoint_identifier)
|| request
.request
.payment_endpoints
.as_ref()
.is_some_and(|endpoints| !endpoints.contains_key(&self.payment_endpoint_identifier))
{
return Err(PaykitError::Validation(
"Payment Proof payment_endpoint_identifier is not accepted by Payment Request"
.into(),
));
}
if request
.request
.required_app_id
.as_ref()
.is_some_and(|app_id| app_id != &self.payment_app_id)
{
return Err(PaykitError::Validation(
"Payment Proof payment_app_id does not match the Payment Request".into(),
));
}
self.validate_conversion(&request.request)?;
match (&request.request.recurrence, &self.billing_period) {
(None, Some(_)) => Err(PaykitError::Validation(
"Payment Proof billing_period must be null for one-time Payment Requests".into(),
)),
(Some(_), None) => Err(PaykitError::Validation(
"Payment Proof billing_period is required for recurring Payment Requests".into(),
)),
(_, Some(period)) => period.validate(),
(None, None) => Ok(()),
}
}
}
const ERC20_PAYMENT_PROOF_TYPE: &str = "erc20-transfer-eip712";
const UINT256_MAX_DECIMAL: &str =
"115792089237316195423570985008687907853269984665640564039457584007913129639935";
fn validate_method_specific_proof(proof: &JsonMap<String, JsonValue>) -> Result<()> {
if proof.get("type").and_then(JsonValue::as_str) != Some(ERC20_PAYMENT_PROOF_TYPE) {
return Ok(());
}
let index = proof
.get("receipt_log_index")
.and_then(JsonValue::as_str)
.ok_or_else(|| {
PaykitError::Validation(
"ERC-20 Payment Proof receipt_log_index must be a decimal string".into(),
)
})?;
let canonical = index == "0"
|| (!index.is_empty()
&& !index.starts_with('0')
&& index.bytes().all(|byte| byte.is_ascii_digit()));
if !canonical
|| index.len() > UINT256_MAX_DECIMAL.len()
|| (index.len() == UINT256_MAX_DECIMAL.len() && index > UINT256_MAX_DECIMAL)
{
return Err(PaykitError::Validation(
"ERC-20 Payment Proof receipt_log_index must be a canonical uint256 decimal string"
.into(),
));
}
Ok(())
}
#[derive(Clone, PartialEq)]
pub enum PaymentRequestEvent {
Request(PaymentRequest),
Acceptance(PaymentRequestAcceptance),
Rejection(PaymentRequestRejection),
Cancellation(PaymentRequestCancellation),
ConversionQuote(PaymentConversionQuote),
Proof(PaymentProof),
}
impl fmt::Debug for PaymentRequestEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PaymentRequestEvent")
.field("kind", &self.kind())
.field("event_id", &self.event_id())
.field("payment_request_id", &self.payment_request_id())
.finish()
}
}
impl PaymentRequestEvent {
pub fn kind(&self) -> PrivateMessageKind {
match self {
Self::Request(event) => event.kind,
Self::Acceptance(event) => event.kind,
Self::Rejection(event) => event.kind,
Self::Cancellation(event) => event.kind,
Self::Proof(event) => event.kind,
Self::ConversionQuote(event) => event.kind(),
}
}
pub fn event_id(&self) -> &EventId {
match self {
Self::Request(event) => &event.event_id,
Self::Acceptance(event) => &event.event_id,
Self::Rejection(event) => &event.event_id,
Self::Cancellation(event) => &event.event_id,
Self::Proof(event) => &event.event_id,
Self::ConversionQuote(event) => event.event_id(),
}
}
pub fn payment_request_id(&self) -> &PaymentRequestId {
match self {
Self::Request(event) => &event.payment_request_id,
Self::Acceptance(event) => &event.payment_request_id,
Self::Rejection(event) => &event.payment_request_id,
Self::Cancellation(event) => &event.payment_request_id,
Self::Proof(event) => &event.payment_request_id,
Self::ConversionQuote(event) => event.payment_request_id(),
}
}
}
#[derive(Clone, PartialEq)]
pub struct PaymentRequestEventMessage {
pub kind: PrivateMessageKind,
pub app_id: Option<crate::PaykitAppId>,
pub event_id: Option<EventId>,
pub payment_request_id: Option<PaymentRequestId>,
pub raw_json: String,
pub event: std::result::Result<PaymentRequestEvent, String>,
}
impl fmt::Debug for PaymentRequestEventMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let parsed_kind = self.event.as_ref().ok().map(PaymentRequestEvent::kind);
f.debug_struct("PaymentRequestEventMessage")
.field("kind", &self.kind)
.field("app_id", &self.app_id)
.field("event_id", &self.event_id)
.field("payment_request_id", &self.payment_request_id)
.field(
"raw_json",
&format_args!("<redacted:{} bytes>", self.raw_json.len()),
)
.field("parsed_kind", &parsed_kind)
.field("validation_error", &self.validation_error())
.finish()
}
}
impl PaymentRequestEventMessage {
pub fn kind(&self) -> PrivateMessageKind {
self.kind
}
pub fn app_id(&self) -> Option<&crate::PaykitAppId> {
self.app_id.as_ref()
}
pub fn is_valid(&self) -> bool {
self.event.is_ok()
}
pub fn parsed_event(&self) -> Option<&PaymentRequestEvent> {
self.event.as_ref().ok()
}
pub fn validation_error(&self) -> Option<&str> {
self.event.as_ref().err().map(String::as_str)
}
pub fn event_id(&self) -> Option<&EventId> {
self.event_id.as_ref()
}
pub fn payment_request_id(&self) -> Option<&PaymentRequestId> {
self.payment_request_id.as_ref()
}
}
impl Recurrence {
pub(super) fn validate(&self) -> Result<()> {
if self.every == 0 {
return Err(PaykitError::Validation(
"Recurrence every must be a positive integer".into(),
));
}
let starts_at = parse_utc_timestamp(&self.starts_at, "Recurrence starts_at")?;
parse_utc_timestamp(&self.anchor, "Recurrence anchor")?;
if let Some(ends_at) = &self.ends_at {
let ends_at = parse_utc_timestamp(ends_at, "Recurrence ends_at")?;
if ends_at <= starts_at {
return Err(PaykitError::Validation(
"Recurrence ends_at must be after starts_at".into(),
));
}
}
Ok(())
}
}
impl BillingPeriod {
pub(crate) fn validate_with_label(&self, label: &str) -> Result<()> {
let starts_at = parse_utc_timestamp(&self.starts_at, &format!("{label} starts_at"))?;
let ends_at = parse_utc_timestamp(&self.ends_at, &format!("{label} ends_at"))?;
if ends_at <= starts_at {
return Err(PaykitError::Validation(format!(
"{label} ends_at must be after starts_at"
)));
}
Ok(())
}
pub(super) fn validate(&self) -> Result<()> {
self.validate_with_label("Billing Period")
}
}
impl PaymentRequestTerms {
pub(crate) fn validate(&self) -> Result<()> {
self.amount.validate_with_label("Payment Request amount")?;
if let Some(proposal_expires_at) = &self.proposal_expires_at {
parse_utc_timestamp(proposal_expires_at, "Payment Request proposal_expires_at")?;
}
if let Some(recurrence) = &self.recurrence {
recurrence.validate()?;
}
self.validate_conversion()?;
if self.accepted_payment_endpoint_identifiers.is_empty() {
return Err(PaykitError::Validation(
"accepted_payment_endpoint_identifiers must not be empty".into(),
));
}
self.validate_payment_endpoints()?;
Ok(())
}
fn validate_payment_endpoints(&self) -> Result<()> {
let Some(endpoints) = &self.payment_endpoints else {
return Ok(());
};
if endpoints.is_empty() {
return Err(PaykitError::Validation(
"payment_endpoints must not be empty".into(),
));
}
if self.required_app_id.is_none() {
return Err(PaykitError::Validation(
"payment_endpoints requires required_app_id".into(),
));
}
for (identifier, payload) in endpoints {
if !self
.accepted_payment_endpoint_identifiers
.contains(identifier)
{
return Err(PaykitError::Validation(
"payment_endpoints keys must be accepted Payment Endpoint Identifiers".into(),
));
}
if payload.as_str().is_empty() {
return Err(PaykitError::Validation(
"payment_endpoints payloads must not be empty".into(),
));
}
}
Ok(())
}
}
impl Recurrence {
pub fn every(&self) -> u32 {
self.every
}
pub fn unit(&self) -> RecurrenceUnit {
self.unit
}
pub fn starts_at(&self) -> &str {
&self.starts_at
}
pub fn anchor(&self) -> &str {
&self.anchor
}
pub fn ends_at(&self) -> &Option<String> {
&self.ends_at
}
}
impl PaymentRequestTerms {
pub fn amount(&self) -> &PaymentAmount {
&self.amount
}
pub fn payment_reference(&self) -> &PaymentReference {
&self.payment_reference
}
pub fn proposal_expires_at(&self) -> &Option<String> {
&self.proposal_expires_at
}
pub fn recurrence(&self) -> &Option<Recurrence> {
&self.recurrence
}
pub fn accepted_payment_endpoint_identifiers(&self) -> &[PaymentEndpointIdentifier] {
&self.accepted_payment_endpoint_identifiers
}
pub fn payment_endpoints(
&self,
) -> Option<&HashMap<PaymentEndpointIdentifier, PaymentEndpointPayload>> {
self.payment_endpoints.as_ref()
}
pub fn required_app_id(&self) -> Option<&crate::PaykitAppId> {
self.required_app_id.as_ref()
}
pub fn conversion(&self) -> Option<&PaymentConversion> {
self.conversion.as_ref()
}
pub fn payment_deadline(&self) -> Option<&PaymentDeadline> {
self.payment_deadline.as_ref()
}
pub fn metadata(&self) -> &JsonMap<String, JsonValue> {
&self.metadata
}
}
impl BillingPeriod {
pub fn starts_at(&self) -> &str {
&self.starts_at
}
pub fn ends_at(&self) -> &str {
&self.ends_at
}
}
impl PaymentRequest {
pub fn version(&self) -> u8 {
self.version
}
pub fn kind(&self) -> PrivateMessageKind {
self.kind
}
pub fn event_id(&self) -> &EventId {
&self.event_id
}
pub fn payment_request_id(&self) -> &PaymentRequestId {
&self.payment_request_id
}
pub fn request(&self) -> &PaymentRequestTerms {
&self.request
}
}
impl PaymentRequestAcceptance {
pub fn version(&self) -> u8 {
self.version
}
pub fn kind(&self) -> PrivateMessageKind {
self.kind
}
pub fn event_id(&self) -> &EventId {
&self.event_id
}
pub fn payment_request_id(&self) -> &PaymentRequestId {
&self.payment_request_id
}
}
impl PaymentRequestRejection {
pub fn version(&self) -> u8 {
self.version
}
pub fn kind(&self) -> PrivateMessageKind {
self.kind
}
pub fn event_id(&self) -> &EventId {
&self.event_id
}
pub fn payment_request_id(&self) -> &PaymentRequestId {
&self.payment_request_id
}
pub fn reason(&self) -> &Option<String> {
&self.reason
}
}
impl PaymentRequestCancellation {
pub fn version(&self) -> u8 {
self.version
}
pub fn kind(&self) -> PrivateMessageKind {
self.kind
}
pub fn event_id(&self) -> &EventId {
&self.event_id
}
pub fn payment_request_id(&self) -> &PaymentRequestId {
&self.payment_request_id
}
pub fn reason(&self) -> &Option<String> {
&self.reason
}
}
impl PaymentProof {
pub fn version(&self) -> u8 {
self.version
}
pub fn kind(&self) -> PrivateMessageKind {
self.kind
}
pub fn event_id(&self) -> &EventId {
&self.event_id
}
pub fn payment_request_id(&self) -> &PaymentRequestId {
&self.payment_request_id
}
pub fn payment_reference(&self) -> &PaymentReference {
&self.payment_reference
}
pub fn billing_period(&self) -> &Option<BillingPeriod> {
&self.billing_period
}
pub fn payment_app_id(&self) -> &crate::PaykitAppId {
&self.payment_app_id
}
pub fn payment_endpoint_identifier(&self) -> &PaymentEndpointIdentifier {
&self.payment_endpoint_identifier
}
pub fn conversion_quote_id(&self) -> Option<&EventId> {
self.conversion_quote_id.as_ref()
}
pub fn proof(&self) -> &JsonMap<String, JsonValue> {
&self.proof
}
}
#[derive(Clone, Debug)]
pub struct RecurrenceConfig {
pub every: u32,
pub unit: RecurrenceUnit,
pub starts_at: String,
pub anchor: String,
pub ends_at: Option<String>,
}
impl TryFrom<RecurrenceConfig> for Recurrence {
type Error = PaykitError;
fn try_from(value: RecurrenceConfig) -> Result<Self> {
let recurrence = Self {
every: value.every,
unit: value.unit,
starts_at: value.starts_at,
anchor: value.anchor,
ends_at: value.ends_at,
};
recurrence.validate()?;
Ok(recurrence)
}
}
impl BillingPeriod {
pub fn new(starts_at: impl Into<String>, ends_at: impl Into<String>) -> Result<Self> {
Self::new_with_label(starts_at.into(), ends_at.into(), "Billing Period")
}
pub(crate) fn new_with_label(starts_at: String, ends_at: String, label: &str) -> Result<Self> {
let period = Self { starts_at, ends_at };
period.validate_with_label(label)?;
Ok(period)
}
}
#[derive(Clone)]
pub struct PaymentRequestTermsBuilder(PaymentRequestTerms);
impl PaymentRequestTerms {
pub fn builder(
amount: PaymentAmount,
payment_reference: PaymentReference,
accepted_payment_endpoint_identifiers: Vec<PaymentEndpointIdentifier>,
) -> PaymentRequestTermsBuilder {
PaymentRequestTermsBuilder(Self {
amount,
payment_reference,
accepted_payment_endpoint_identifiers,
payment_endpoints: None,
required_app_id: None,
proposal_expires_at: None,
recurrence: None,
conversion: None,
payment_deadline: None,
metadata: JsonMap::new(),
})
}
}
impl PaymentRequestTermsBuilder {
pub fn payment_endpoints(
mut self,
value: Option<HashMap<PaymentEndpointIdentifier, PaymentEndpointPayload>>,
) -> Self {
self.0.payment_endpoints = value;
self
}
pub fn required_app_id(mut self, value: Option<crate::PaykitAppId>) -> Self {
self.0.required_app_id = value;
self
}
pub fn proposal_expires_at(mut self, value: Option<String>) -> Self {
self.0.proposal_expires_at = value;
self
}
pub fn recurrence(mut self, value: Option<Recurrence>) -> Self {
self.0.recurrence = value;
self
}
pub fn conversion(mut self, value: Option<PaymentConversion>) -> Self {
self.0.conversion = value;
self
}
pub fn payment_deadline(mut self, value: Option<PaymentDeadline>) -> Self {
self.0.payment_deadline = value;
self
}
pub fn metadata(mut self, value: JsonMap<String, JsonValue>) -> Self {
self.0.metadata = value;
self
}
pub fn build(self) -> Result<PaymentRequestTerms> {
self.0.validate()?;
Ok(self.0)
}
}
#[cfg(test)]
mod tests;