-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmpp.rs
More file actions
132 lines (120 loc) · 4.84 KB
/
mpp.rs
File metadata and controls
132 lines (120 loc) · 4.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// SPDX-License-Identifier: Apache-2.0
//! Merchant payment processor (MPP) role: verifies a closed checkout +
//! payment chain and emits a [`ap2_types::v0_2::PaymentReceipt`].
use std::sync::Arc;
use ap2_crypto::JsonWebKey;
use ap2_mandate::{
bind_receipt_reference, build_payment_receipt, CheckoutMandateChain, MandateClient,
PaymentMandateChain, PaymentReceiptOutcome,
};
use ap2_types::v0_2::{PaymentId, PaymentReceipt};
use crate::core::{
canonical_token_reference, root_token_reference, ActorError, ActorResult, Clock,
SettlementRequest, SystemClock, CHECKOUT_AUD, CHECKOUT_NONCE, CLOCK_SKEW_SECONDS, PAYMENT_AUD,
PAYMENT_NONCE,
};
/// Operations a merchant payment processor must implement.
pub trait MerchantPaymentProcessor: Send + Sync {
/// Verifies both checkout and payment chains end-to-end and produces
/// a [`PaymentReceipt`].
///
/// # Errors
///
/// Returns [`ActorError::Violations`] if either chain breaches a
/// constraint, [`ActorError::Mandate`] on SD-JWT verification
/// failure, or [`ActorError::InvalidInput`] for missing fields.
fn verify_and_settle(&self, request: &SettlementRequest) -> ActorResult<PaymentReceipt>;
}
/// In-memory reference implementation of [`MerchantPaymentProcessor`].
#[derive(Clone)]
pub struct DefaultMerchantPaymentProcessor {
user_public_key: JsonWebKey,
clock: Arc<dyn Clock>,
issuer: String,
}
impl DefaultMerchantPaymentProcessor {
/// Builds an MPP with explicit user key, clock, and issuer string.
#[must_use]
pub fn new(user_public_key: JsonWebKey, clock: Arc<dyn Clock>, issuer: String) -> Self {
Self {
user_public_key,
clock,
issuer,
}
}
/// Convenience constructor: uses the system wall clock and the
/// `"mpp.example"` issuer label.
#[must_use]
pub fn with_system_clock(user_public_key: JsonWebKey) -> Self {
Self::new(
user_public_key,
Arc::new(SystemClock),
"mpp.example".to_owned(),
)
}
}
impl MerchantPaymentProcessor for DefaultMerchantPaymentProcessor {
fn verify_and_settle(&self, request: &SettlementRequest) -> ActorResult<PaymentReceipt> {
if request.checkout_mandate_sd_jwt.is_empty() {
return Err(ActorError::InvalidInput(
"checkout_mandate_sd_jwt is required".to_owned(),
));
}
if request.payment_mandate_sd_jwt.is_empty() {
return Err(ActorError::InvalidInput(
"payment_mandate_sd_jwt is required".to_owned(),
));
}
let provider = |_token: &ap2_sd_jwt::ParsedToken| Ok(self.user_public_key.clone());
let checkout_payloads = MandateClient::verify_chain(
&request.checkout_mandate_sd_jwt,
&provider,
Some(CHECKOUT_AUD),
Some(CHECKOUT_NONCE),
CLOCK_SKEW_SECONDS,
Some(self.clock.now_unix()),
)?;
let checkout_chain = CheckoutMandateChain::parse(&checkout_payloads)
.map_err(|error| ActorError::InvalidInput(error.to_string()))?;
let expected_checkout_hash = canonical_token_reference(&checkout_chain.closed.checkout_jwt);
let checkout_violations = checkout_chain.verify(
Some(&expected_checkout_hash),
Some(&checkout_chain.closed.checkout_jwt),
);
if !checkout_violations.is_empty() {
return Err(ActorError::Violations(checkout_violations));
}
let payment_payloads = MandateClient::verify_chain(
&request.payment_mandate_sd_jwt,
&provider,
Some(PAYMENT_AUD),
Some(PAYMENT_NONCE),
CLOCK_SKEW_SECONDS,
Some(self.clock.now_unix()),
)?;
let payment_chain = PaymentMandateChain::parse(&payment_payloads)
.map_err(|error| ActorError::InvalidInput(error.to_string()))?;
let open_checkout_hash = root_token_reference(&request.checkout_mandate_sd_jwt);
let payment_violations = payment_chain.verify(
Some(&checkout_chain.closed.checkout_hash),
Some(&open_checkout_hash),
None,
);
if !payment_violations.is_empty() {
return Err(ActorError::Violations(payment_violations));
}
let leaf = MandateClient::closed_mandate_jwt(&request.payment_mandate_sd_jwt);
let reference = bind_receipt_reference(leaf);
let payment_id = PaymentId(format!("payment-{}", payment_chain.closed.transaction_id.0));
Ok(build_payment_receipt(
self.issuer.clone(),
self.clock.now_unix(),
reference,
payment_id.clone(),
PaymentReceiptOutcome::Success {
psp_confirmation_id: payment_id.0.clone(),
network_confirmation_id: payment_id.0,
},
))
}
}