1use std::{collections::HashMap, fmt};
2
3use serde_json::{Map as JsonMap, Value as JsonValue};
4
5use crate::{
6 validation::{parse_utc_timestamp, validate_uuid_v4},
7 AllowanceId, EventId, PaykitError, PaymentAmount, PaymentEndpointIdentifier,
8 PaymentEndpointPayload, PaymentReference, PrivateMessageKind, Result,
9};
10
11use super::{PaymentConversion, PaymentConversionQuote, PaymentDeadline};
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash)]
15pub struct PaymentRequestId(String);
16
17impl PaymentRequestId {
18 pub fn new(id: impl Into<String>) -> Result<Self> {
20 validate_uuid_v4(id.into(), "Payment Request ID").map(Self)
21 }
22
23 pub fn new_v4() -> Self {
25 Self(uuid::Uuid::new_v4().to_string())
26 }
27
28 pub fn as_str(&self) -> &str {
30 &self.0
31 }
32}
33
34impl std::fmt::Display for PaymentRequestId {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 f.write_str(&self.0)
37 }
38}
39
40impl AsRef<str> for PaymentRequestId {
41 fn as_ref(&self) -> &str {
42 &self.0
43 }
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub enum RecurrenceUnit {
54 Minute,
56 Hour,
58 Day,
60 Week,
62 Month,
64 Year,
66}
67
68impl RecurrenceUnit {
69 pub(super) fn as_str(self) -> &'static str {
70 match self {
71 Self::Minute => "minute",
72 Self::Hour => "hour",
73 Self::Day => "day",
74 Self::Week => "week",
75 Self::Month => "month",
76 Self::Year => "year",
77 }
78 }
79
80 pub(super) fn parse(value: &str) -> Result<Self> {
81 match value {
82 "minute" => Ok(Self::Minute),
83 "hour" => Ok(Self::Hour),
84 "day" => Ok(Self::Day),
85 "week" => Ok(Self::Week),
86 "month" => Ok(Self::Month),
87 "year" => Ok(Self::Year),
88 _ => Err(PaykitError::Validation(
92 "unsupported Recurrence unit".into(),
93 )),
94 }
95 }
96}
97
98#[derive(Clone, Debug, PartialEq, Eq)]
108pub struct Recurrence {
109 pub(super) every: u32,
111 pub(super) unit: RecurrenceUnit,
113 pub(super) starts_at: String,
115 pub(super) anchor: String,
117 pub(super) ends_at: Option<String>,
120}
121
122#[derive(Clone, PartialEq)]
132pub struct PaymentRequestTerms {
133 pub(super) amount: PaymentAmount,
135 pub(super) payment_reference: PaymentReference,
137 pub(super) proposal_expires_at: Option<String>,
140 pub(super) recurrence: Option<Recurrence>,
142 pub(super) accepted_payment_endpoint_identifiers: Vec<PaymentEndpointIdentifier>,
144 pub(super) payment_endpoints:
146 Option<HashMap<PaymentEndpointIdentifier, PaymentEndpointPayload>>,
147 pub(super) conversion: Option<PaymentConversion>,
149 pub(super) payment_deadline: Option<PaymentDeadline>,
151 pub(super) required_app_id: Option<crate::PaykitAppId>,
153 pub(super) metadata: JsonMap<String, JsonValue>,
155}
156
157impl fmt::Debug for PaymentRequestTerms {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 f.debug_struct("PaymentRequestTerms")
160 .field("amount", &"<redacted>")
161 .field("payment_reference", &"<redacted>")
162 .field("proposal_expires_at", &self.proposal_expires_at)
163 .field("recurrence", &self.recurrence)
164 .field("conversion", &self.conversion)
165 .field("payment_deadline", &self.payment_deadline)
166 .field(
167 "accepted_payment_endpoint_identifiers",
168 &self.accepted_payment_endpoint_identifiers,
169 )
170 .field("required_app_id", &self.required_app_id)
171 .field(
172 "payment_endpoints",
173 &self.payment_endpoints.as_ref().map(|_| "<redacted>"),
174 )
175 .field(
176 "metadata",
177 &format_args!("<redacted:{} fields>", self.metadata.len()),
178 )
179 .finish()
180 }
181}
182
183#[derive(Clone, Debug, PartialEq, Eq)]
204pub struct BillingPeriod {
205 pub(super) starts_at: String,
207 pub(super) ends_at: String,
209}
210
211#[derive(Clone, PartialEq)]
221pub struct PaymentRequest {
222 pub(super) version: u8,
224 pub(super) kind: PrivateMessageKind,
226 pub(super) event_id: EventId,
228 pub(super) payment_request_id: PaymentRequestId,
230 pub(super) request: PaymentRequestTerms,
232}
233
234impl fmt::Debug for PaymentRequest {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 f.debug_struct("PaymentRequest")
237 .field("version", &self.version)
238 .field("kind", &self.kind)
239 .field("event_id", &self.event_id)
240 .field("payment_request_id", &self.payment_request_id)
241 .field("request", &self.request)
242 .finish()
243 }
244}
245
246impl PaymentRequest {
247 pub(super) fn validate(&self) -> Result<()> {
248 crate::validation::validate_outgoing_version_kind(
249 self.version,
250 self.kind,
251 PrivateMessageKind::PaymentRequest,
252 "Payment Request",
253 )?;
254 self.request.validate()
255 }
256
257 pub fn new(
259 event_id: EventId,
260 payment_request_id: PaymentRequestId,
261 request: PaymentRequestTerms,
262 ) -> Self {
263 Self {
264 version: 1,
265 kind: PrivateMessageKind::PaymentRequest,
266 event_id,
267 payment_request_id,
268 request,
269 }
270 }
271}
272
273#[derive(Clone, Debug, PartialEq, Eq)]
283pub struct PaymentRequestAcceptance {
284 pub(super) version: u8,
286 pub(super) kind: PrivateMessageKind,
288 pub(super) event_id: EventId,
290 pub(super) payment_request_id: PaymentRequestId,
292}
293
294impl PaymentRequestAcceptance {
295 pub fn new(event_id: EventId, payment_request_id: PaymentRequestId) -> Self {
297 Self {
298 version: 1,
299 kind: PrivateMessageKind::PaymentRequestAcceptance,
300 event_id,
301 payment_request_id,
302 }
303 }
304}
305
306#[derive(Clone, Debug, PartialEq, Eq)]
316pub struct PaymentRequestRejection {
317 pub(super) version: u8,
319 pub(super) kind: PrivateMessageKind,
321 pub(super) event_id: EventId,
323 pub(super) payment_request_id: PaymentRequestId,
325 pub(super) reason: Option<String>,
327}
328
329impl PaymentRequestRejection {
330 pub fn new(
332 event_id: EventId,
333 payment_request_id: PaymentRequestId,
334 reason: Option<String>,
335 ) -> Self {
336 Self {
337 version: 1,
338 kind: PrivateMessageKind::PaymentRequestRejection,
339 event_id,
340 payment_request_id,
341 reason,
342 }
343 }
344}
345
346#[derive(Clone, Debug, PartialEq, Eq)]
356pub struct PaymentRequestCancellation {
357 pub(super) version: u8,
359 pub(super) kind: PrivateMessageKind,
361 pub(super) event_id: EventId,
363 pub(super) payment_request_id: PaymentRequestId,
365 pub(super) reason: Option<String>,
367}
368
369impl PaymentRequestCancellation {
370 pub fn new(
372 event_id: EventId,
373 payment_request_id: PaymentRequestId,
374 reason: Option<String>,
375 ) -> Self {
376 Self {
377 version: 1,
378 kind: PrivateMessageKind::PaymentRequestCancellation,
379 event_id,
380 payment_request_id,
381 reason,
382 }
383 }
384}
385
386#[derive(Clone, PartialEq)]
396pub struct PaymentProof {
397 pub(super) version: u8,
399 pub(super) kind: PrivateMessageKind,
401 pub(super) event_id: EventId,
403 pub(super) payment_request_id: PaymentRequestId,
405 pub(super) payment_reference: PaymentReference,
407 pub(super) billing_period: Option<BillingPeriod>,
409 pub(super) payment_app_id: crate::PaykitAppId,
411 pub(super) payment_endpoint_identifier: PaymentEndpointIdentifier,
413 pub(super) allowance_id: Option<AllowanceId>,
415 pub(super) conversion_quote_id: Option<EventId>,
417 pub(super) proof: JsonMap<String, JsonValue>,
419}
420
421impl fmt::Debug for PaymentProof {
422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423 f.debug_struct("PaymentProof")
424 .field("version", &self.version)
425 .field("kind", &self.kind)
426 .field("event_id", &self.event_id)
427 .field("payment_request_id", &self.payment_request_id)
428 .field("payment_reference", &"<redacted>")
429 .field("billing_period", &self.billing_period)
430 .field("payment_app_id", &self.payment_app_id)
431 .field("allowance_id", &self.allowance_id)
432 .field(
433 "payment_endpoint_identifier",
434 &self.payment_endpoint_identifier,
435 )
436 .field(
437 "proof",
438 &format_args!("<redacted:{} fields>", self.proof.len()),
439 )
440 .finish()
441 }
442}
443
444impl PaymentProof {
445 pub fn new(
447 event_id: EventId,
448 payment_request_id: PaymentRequestId,
449 payment_reference: PaymentReference,
450 billing_period: Option<BillingPeriod>,
451 payment_app_id: crate::PaykitAppId,
452 payment_endpoint_identifier: PaymentEndpointIdentifier,
453 proof: JsonMap<String, JsonValue>,
454 ) -> Self {
455 Self {
456 version: 1,
457 kind: PrivateMessageKind::PaymentProof,
458 event_id,
459 payment_request_id,
460 payment_reference,
461 billing_period,
462 payment_app_id,
463 payment_endpoint_identifier,
464 allowance_id: None,
465 conversion_quote_id: None,
466 proof,
467 }
468 }
469
470 pub fn with_conversion_quote_id(mut self, quote_id: EventId) -> Self {
472 self.conversion_quote_id = Some(quote_id);
473 self
474 }
475
476 pub fn with_allowance_id(mut self, allowance_id: AllowanceId) -> Self {
481 self.allowance_id = Some(allowance_id);
482 self
483 }
484
485 pub fn allowance_id(&self) -> Option<&AllowanceId> {
487 self.allowance_id.as_ref()
488 }
489
490 pub(super) fn validate(&self) -> Result<()> {
491 crate::validation::validate_outgoing_version_kind(
492 self.version,
493 self.kind,
494 PrivateMessageKind::PaymentProof,
495 "Payment Proof",
496 )?;
497 if let Some(period) = &self.billing_period {
498 period.validate()?;
499 }
500 validate_method_specific_proof(&self.proof)
501 }
502
503 pub fn validate_for_request(&self, request: &PaymentRequest) -> Result<()> {
510 self.validate()?;
511 request.validate()?;
512 if self.payment_request_id != request.payment_request_id {
513 return Err(PaykitError::Validation(
514 "Payment Proof payment_request_id must match Payment Request".into(),
515 ));
516 }
517 if self.payment_reference != request.request.payment_reference {
518 return Err(PaykitError::Validation(
519 "Payment Proof payment_reference must match Payment Request".into(),
520 ));
521 }
522 if !request
523 .request
524 .accepted_payment_endpoint_identifiers
525 .contains(&self.payment_endpoint_identifier)
526 || request
527 .request
528 .payment_endpoints
529 .as_ref()
530 .is_some_and(|endpoints| !endpoints.contains_key(&self.payment_endpoint_identifier))
531 {
532 return Err(PaykitError::Validation(
533 "Payment Proof payment_endpoint_identifier is not accepted by Payment Request"
534 .into(),
535 ));
536 }
537 if request
538 .request
539 .required_app_id
540 .as_ref()
541 .is_some_and(|app_id| app_id != &self.payment_app_id)
542 {
543 return Err(PaykitError::Validation(
544 "Payment Proof payment_app_id does not match the Payment Request".into(),
545 ));
546 }
547 self.validate_conversion(&request.request)?;
548 match (&request.request.recurrence, &self.billing_period) {
549 (None, Some(_)) => Err(PaykitError::Validation(
550 "Payment Proof billing_period must be null for one-time Payment Requests".into(),
551 )),
552 (Some(_), None) => Err(PaykitError::Validation(
553 "Payment Proof billing_period is required for recurring Payment Requests".into(),
554 )),
555 (_, Some(period)) => period.validate(),
556 (None, None) => Ok(()),
557 }
558 }
559}
560
561const ERC20_PAYMENT_PROOF_TYPE: &str = "erc20-transfer-eip712";
562const UINT256_MAX_DECIMAL: &str =
563 "115792089237316195423570985008687907853269984665640564039457584007913129639935";
564
565fn validate_method_specific_proof(proof: &JsonMap<String, JsonValue>) -> Result<()> {
566 if proof.get("type").and_then(JsonValue::as_str) != Some(ERC20_PAYMENT_PROOF_TYPE) {
567 return Ok(());
568 }
569 let index = proof
570 .get("receipt_log_index")
571 .and_then(JsonValue::as_str)
572 .ok_or_else(|| {
573 PaykitError::Validation(
574 "ERC-20 Payment Proof receipt_log_index must be a decimal string".into(),
575 )
576 })?;
577 let canonical = index == "0"
578 || (!index.is_empty()
579 && !index.starts_with('0')
580 && index.bytes().all(|byte| byte.is_ascii_digit()));
581 if !canonical
582 || index.len() > UINT256_MAX_DECIMAL.len()
583 || (index.len() == UINT256_MAX_DECIMAL.len() && index > UINT256_MAX_DECIMAL)
584 {
585 return Err(PaykitError::Validation(
586 "ERC-20 Payment Proof receipt_log_index must be a canonical uint256 decimal string"
587 .into(),
588 ));
589 }
590 Ok(())
591}
592
593#[derive(Clone, PartialEq)]
600pub enum PaymentRequestEvent {
601 Request(PaymentRequest),
603 Acceptance(PaymentRequestAcceptance),
605 Rejection(PaymentRequestRejection),
607 Cancellation(PaymentRequestCancellation),
609 ConversionQuote(PaymentConversionQuote),
611 Proof(PaymentProof),
613}
614
615impl fmt::Debug for PaymentRequestEvent {
616 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
617 f.debug_struct("PaymentRequestEvent")
618 .field("kind", &self.kind())
619 .field("event_id", &self.event_id())
620 .field("payment_request_id", &self.payment_request_id())
621 .finish()
622 }
623}
624
625impl PaymentRequestEvent {
626 pub fn kind(&self) -> PrivateMessageKind {
628 match self {
629 Self::Request(event) => event.kind,
630 Self::Acceptance(event) => event.kind,
631 Self::Rejection(event) => event.kind,
632 Self::Cancellation(event) => event.kind,
633 Self::Proof(event) => event.kind,
634 Self::ConversionQuote(event) => event.kind(),
635 }
636 }
637
638 pub fn event_id(&self) -> &EventId {
640 match self {
641 Self::Request(event) => &event.event_id,
642 Self::Acceptance(event) => &event.event_id,
643 Self::Rejection(event) => &event.event_id,
644 Self::Cancellation(event) => &event.event_id,
645 Self::Proof(event) => &event.event_id,
646 Self::ConversionQuote(event) => event.event_id(),
647 }
648 }
649
650 pub fn payment_request_id(&self) -> &PaymentRequestId {
652 match self {
653 Self::Request(event) => &event.payment_request_id,
654 Self::Acceptance(event) => &event.payment_request_id,
655 Self::Rejection(event) => &event.payment_request_id,
656 Self::Cancellation(event) => &event.payment_request_id,
657 Self::Proof(event) => &event.payment_request_id,
658 Self::ConversionQuote(event) => event.payment_request_id(),
659 }
660 }
661}
662
663#[derive(Clone, PartialEq)]
666pub struct PaymentRequestEventMessage {
667 pub kind: PrivateMessageKind,
669 pub app_id: Option<crate::PaykitAppId>,
671 pub event_id: Option<EventId>,
673 pub payment_request_id: Option<PaymentRequestId>,
675 pub raw_json: String,
677 pub event: std::result::Result<PaymentRequestEvent, String>,
680}
681
682impl fmt::Debug for PaymentRequestEventMessage {
683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684 let parsed_kind = self.event.as_ref().ok().map(PaymentRequestEvent::kind);
685 f.debug_struct("PaymentRequestEventMessage")
686 .field("kind", &self.kind)
687 .field("app_id", &self.app_id)
688 .field("event_id", &self.event_id)
689 .field("payment_request_id", &self.payment_request_id)
690 .field(
691 "raw_json",
692 &format_args!("<redacted:{} bytes>", self.raw_json.len()),
693 )
694 .field("parsed_kind", &parsed_kind)
695 .field("validation_error", &self.validation_error())
696 .finish()
697 }
698}
699
700impl PaymentRequestEventMessage {
701 pub fn kind(&self) -> PrivateMessageKind {
703 self.kind
704 }
705
706 pub fn app_id(&self) -> Option<&crate::PaykitAppId> {
708 self.app_id.as_ref()
709 }
710
711 pub fn is_valid(&self) -> bool {
713 self.event.is_ok()
714 }
715
716 pub fn parsed_event(&self) -> Option<&PaymentRequestEvent> {
718 self.event.as_ref().ok()
719 }
720
721 pub fn validation_error(&self) -> Option<&str> {
723 self.event.as_ref().err().map(String::as_str)
724 }
725
726 pub fn event_id(&self) -> Option<&EventId> {
731 self.event_id.as_ref()
732 }
733
734 pub fn payment_request_id(&self) -> Option<&PaymentRequestId> {
739 self.payment_request_id.as_ref()
740 }
741}
742
743impl Recurrence {
744 pub(super) fn validate(&self) -> Result<()> {
747 if self.every == 0 {
748 return Err(PaykitError::Validation(
749 "Recurrence every must be a positive integer".into(),
750 ));
751 }
752 let starts_at = parse_utc_timestamp(&self.starts_at, "Recurrence starts_at")?;
753 parse_utc_timestamp(&self.anchor, "Recurrence anchor")?;
754 if let Some(ends_at) = &self.ends_at {
755 let ends_at = parse_utc_timestamp(ends_at, "Recurrence ends_at")?;
756 if ends_at <= starts_at {
757 return Err(PaykitError::Validation(
758 "Recurrence ends_at must be after starts_at".into(),
759 ));
760 }
761 }
762 Ok(())
763 }
764}
765
766impl BillingPeriod {
767 pub(crate) fn validate_with_label(&self, label: &str) -> Result<()> {
768 let starts_at = parse_utc_timestamp(&self.starts_at, &format!("{label} starts_at"))?;
769 let ends_at = parse_utc_timestamp(&self.ends_at, &format!("{label} ends_at"))?;
770 if ends_at <= starts_at {
771 return Err(PaykitError::Validation(format!(
772 "{label} ends_at must be after starts_at"
773 )));
774 }
775 Ok(())
776 }
777
778 pub(super) fn validate(&self) -> Result<()> {
779 self.validate_with_label("Billing Period")
780 }
781}
782
783impl PaymentRequestTerms {
784 pub(crate) fn validate(&self) -> Result<()> {
785 self.amount.validate_with_label("Payment Request amount")?;
786 if let Some(proposal_expires_at) = &self.proposal_expires_at {
787 parse_utc_timestamp(proposal_expires_at, "Payment Request proposal_expires_at")?;
788 }
789 if let Some(recurrence) = &self.recurrence {
790 recurrence.validate()?;
791 }
792 self.validate_conversion()?;
793 if self.accepted_payment_endpoint_identifiers.is_empty() {
794 return Err(PaykitError::Validation(
795 "accepted_payment_endpoint_identifiers must not be empty".into(),
796 ));
797 }
798 self.validate_payment_endpoints()?;
799 Ok(())
800 }
801
802 fn validate_payment_endpoints(&self) -> Result<()> {
803 let Some(endpoints) = &self.payment_endpoints else {
804 return Ok(());
805 };
806 if endpoints.is_empty() {
807 return Err(PaykitError::Validation(
808 "payment_endpoints must not be empty".into(),
809 ));
810 }
811 if self.required_app_id.is_none() {
812 return Err(PaykitError::Validation(
813 "payment_endpoints requires required_app_id".into(),
814 ));
815 }
816 for (identifier, payload) in endpoints {
817 if !self
818 .accepted_payment_endpoint_identifiers
819 .contains(identifier)
820 {
821 return Err(PaykitError::Validation(
822 "payment_endpoints keys must be accepted Payment Endpoint Identifiers".into(),
823 ));
824 }
825 if payload.as_str().is_empty() {
826 return Err(PaykitError::Validation(
827 "payment_endpoints payloads must not be empty".into(),
828 ));
829 }
830 }
831 Ok(())
832 }
833}
834
835impl Recurrence {
836 pub fn every(&self) -> u32 {
838 self.every
839 }
840 pub fn unit(&self) -> RecurrenceUnit {
842 self.unit
843 }
844 pub fn starts_at(&self) -> &str {
846 &self.starts_at
847 }
848 pub fn anchor(&self) -> &str {
850 &self.anchor
851 }
852 pub fn ends_at(&self) -> &Option<String> {
854 &self.ends_at
855 }
856}
857
858impl PaymentRequestTerms {
859 pub fn amount(&self) -> &PaymentAmount {
861 &self.amount
862 }
863 pub fn payment_reference(&self) -> &PaymentReference {
865 &self.payment_reference
866 }
867 pub fn proposal_expires_at(&self) -> &Option<String> {
869 &self.proposal_expires_at
870 }
871 pub fn recurrence(&self) -> &Option<Recurrence> {
873 &self.recurrence
874 }
875 pub fn accepted_payment_endpoint_identifiers(&self) -> &[PaymentEndpointIdentifier] {
877 &self.accepted_payment_endpoint_identifiers
878 }
879 pub fn payment_endpoints(
890 &self,
891 ) -> Option<&HashMap<PaymentEndpointIdentifier, PaymentEndpointPayload>> {
892 self.payment_endpoints.as_ref()
893 }
894 pub fn required_app_id(&self) -> Option<&crate::PaykitAppId> {
896 self.required_app_id.as_ref()
897 }
898 pub fn conversion(&self) -> Option<&PaymentConversion> {
900 self.conversion.as_ref()
901 }
902 pub fn payment_deadline(&self) -> Option<&PaymentDeadline> {
904 self.payment_deadline.as_ref()
905 }
906 pub fn metadata(&self) -> &JsonMap<String, JsonValue> {
908 &self.metadata
909 }
910}
911
912impl BillingPeriod {
913 pub fn starts_at(&self) -> &str {
915 &self.starts_at
916 }
917 pub fn ends_at(&self) -> &str {
919 &self.ends_at
920 }
921}
922
923impl PaymentRequest {
924 pub fn version(&self) -> u8 {
926 self.version
927 }
928 pub fn kind(&self) -> PrivateMessageKind {
930 self.kind
931 }
932 pub fn event_id(&self) -> &EventId {
934 &self.event_id
935 }
936 pub fn payment_request_id(&self) -> &PaymentRequestId {
938 &self.payment_request_id
939 }
940 pub fn request(&self) -> &PaymentRequestTerms {
942 &self.request
943 }
944}
945
946impl PaymentRequestAcceptance {
947 pub fn version(&self) -> u8 {
949 self.version
950 }
951 pub fn kind(&self) -> PrivateMessageKind {
953 self.kind
954 }
955 pub fn event_id(&self) -> &EventId {
957 &self.event_id
958 }
959 pub fn payment_request_id(&self) -> &PaymentRequestId {
961 &self.payment_request_id
962 }
963}
964
965impl PaymentRequestRejection {
966 pub fn version(&self) -> u8 {
968 self.version
969 }
970 pub fn kind(&self) -> PrivateMessageKind {
972 self.kind
973 }
974 pub fn event_id(&self) -> &EventId {
976 &self.event_id
977 }
978 pub fn payment_request_id(&self) -> &PaymentRequestId {
980 &self.payment_request_id
981 }
982 pub fn reason(&self) -> &Option<String> {
984 &self.reason
985 }
986}
987
988impl PaymentRequestCancellation {
989 pub fn version(&self) -> u8 {
991 self.version
992 }
993 pub fn kind(&self) -> PrivateMessageKind {
995 self.kind
996 }
997 pub fn event_id(&self) -> &EventId {
999 &self.event_id
1000 }
1001 pub fn payment_request_id(&self) -> &PaymentRequestId {
1003 &self.payment_request_id
1004 }
1005 pub fn reason(&self) -> &Option<String> {
1007 &self.reason
1008 }
1009}
1010
1011impl PaymentProof {
1012 pub fn version(&self) -> u8 {
1014 self.version
1015 }
1016 pub fn kind(&self) -> PrivateMessageKind {
1018 self.kind
1019 }
1020 pub fn event_id(&self) -> &EventId {
1022 &self.event_id
1023 }
1024 pub fn payment_request_id(&self) -> &PaymentRequestId {
1026 &self.payment_request_id
1027 }
1028 pub fn payment_reference(&self) -> &PaymentReference {
1030 &self.payment_reference
1031 }
1032 pub fn billing_period(&self) -> &Option<BillingPeriod> {
1034 &self.billing_period
1035 }
1036 pub fn payment_app_id(&self) -> &crate::PaykitAppId {
1038 &self.payment_app_id
1039 }
1040 pub fn payment_endpoint_identifier(&self) -> &PaymentEndpointIdentifier {
1042 &self.payment_endpoint_identifier
1043 }
1044 pub fn conversion_quote_id(&self) -> Option<&EventId> {
1046 self.conversion_quote_id.as_ref()
1047 }
1048 pub fn proof(&self) -> &JsonMap<String, JsonValue> {
1050 &self.proof
1051 }
1052}
1053
1054#[derive(Clone, Debug)]
1058pub struct RecurrenceConfig {
1059 pub every: u32,
1061 pub unit: RecurrenceUnit,
1063 pub starts_at: String,
1065 pub anchor: String,
1067 pub ends_at: Option<String>,
1069}
1070
1071impl TryFrom<RecurrenceConfig> for Recurrence {
1072 type Error = PaykitError;
1073 fn try_from(value: RecurrenceConfig) -> Result<Self> {
1074 let recurrence = Self {
1075 every: value.every,
1076 unit: value.unit,
1077 starts_at: value.starts_at,
1078 anchor: value.anchor,
1079 ends_at: value.ends_at,
1080 };
1081 recurrence.validate()?;
1082 Ok(recurrence)
1083 }
1084}
1085
1086impl BillingPeriod {
1087 pub fn new(starts_at: impl Into<String>, ends_at: impl Into<String>) -> Result<Self> {
1091 Self::new_with_label(starts_at.into(), ends_at.into(), "Billing Period")
1092 }
1093
1094 pub(crate) fn new_with_label(starts_at: String, ends_at: String, label: &str) -> Result<Self> {
1095 let period = Self { starts_at, ends_at };
1096 period.validate_with_label(label)?;
1097 Ok(period)
1098 }
1099}
1100
1101#[derive(Clone)]
1103pub struct PaymentRequestTermsBuilder(PaymentRequestTerms);
1104
1105impl PaymentRequestTerms {
1106 pub fn builder(
1110 amount: PaymentAmount,
1111 payment_reference: PaymentReference,
1112 accepted_payment_endpoint_identifiers: Vec<PaymentEndpointIdentifier>,
1113 ) -> PaymentRequestTermsBuilder {
1114 PaymentRequestTermsBuilder(Self {
1115 amount,
1116 payment_reference,
1117 accepted_payment_endpoint_identifiers,
1118 payment_endpoints: None,
1119 required_app_id: None,
1120 proposal_expires_at: None,
1121 recurrence: None,
1122 conversion: None,
1123 payment_deadline: None,
1124 metadata: JsonMap::new(),
1125 })
1126 }
1127}
1128
1129impl PaymentRequestTermsBuilder {
1130 pub fn payment_endpoints(
1136 mut self,
1137 value: Option<HashMap<PaymentEndpointIdentifier, PaymentEndpointPayload>>,
1138 ) -> Self {
1139 self.0.payment_endpoints = value;
1140 self
1141 }
1142 pub fn required_app_id(mut self, value: Option<crate::PaykitAppId>) -> Self {
1144 self.0.required_app_id = value;
1145 self
1146 }
1147 pub fn proposal_expires_at(mut self, value: Option<String>) -> Self {
1149 self.0.proposal_expires_at = value;
1150 self
1151 }
1152 pub fn recurrence(mut self, value: Option<Recurrence>) -> Self {
1154 self.0.recurrence = value;
1155 self
1156 }
1157 pub fn conversion(mut self, value: Option<PaymentConversion>) -> Self {
1159 self.0.conversion = value;
1160 self
1161 }
1162 pub fn payment_deadline(mut self, value: Option<PaymentDeadline>) -> Self {
1164 self.0.payment_deadline = value;
1165 self
1166 }
1167 pub fn metadata(mut self, value: JsonMap<String, JsonValue>) -> Self {
1169 self.0.metadata = value;
1170 self
1171 }
1172 pub fn build(self) -> Result<PaymentRequestTerms> {
1174 self.0.validate()?;
1175 Ok(self.0)
1176 }
1177}
1178
1179#[cfg(test)]
1180mod tests;