1use std::sync::Arc;
2
3use bsv::auth::utils::create_nonce;
4use bsv::primitives::public_key::PublicKey;
5use bsv::primitives::utils::from_hex;
6use bsv::remittance::types::PeerMessage;
7use bsv::script::templates::{P2PKH, ScriptTemplateLock};
8use bsv::wallet::interfaces::{
9 CreateActionArgs, CreateActionOptions, CreateActionOutput, GetPublicKeyArgs,
10 InternalizeActionArgs, InternalizeOutput, Payment, SignActionArgs, WalletInterface,
11};
12use bsv::wallet::types::{BooleanDefaultTrue, Counterparty, CounterpartyType, Protocol};
13
14use crate::client::MessageBoxClient;
15use crate::error::MessageBoxError;
16use crate::types::{IncomingPayment, PaymentCustomInstructions, PaymentToken};
17
18impl<W: WalletInterface + Clone + 'static + Send + Sync> MessageBoxClient<W> {
19 pub async fn create_payment_token(
29 &self,
30 recipient: &str,
31 amount: u64,
32 ) -> Result<PaymentToken, MessageBoxError> {
33 let prefix = create_nonce(self.wallet())
35 .await
36 .map_err(|e| MessageBoxError::Auth(format!("create_nonce prefix: {e}")))?;
37 let suffix = create_nonce(self.wallet())
38 .await
39 .map_err(|e| MessageBoxError::Auth(format!("create_nonce suffix: {e}")))?;
40
41 let pk_result = self
43 .wallet()
44 .get_public_key(
45 GetPublicKeyArgs {
46 identity_key: false,
47 protocol_id: Some(Protocol {
48 security_level: 2,
49 protocol: "3241645161d8".to_string(),
50 }),
51 key_id: Some(format!("{prefix} {suffix}")),
52 counterparty: Some(Counterparty {
53 counterparty_type: CounterpartyType::Other,
54 public_key: Some(
55 PublicKey::from_string(recipient)
56 .map_err(|e| MessageBoxError::Wallet(e.to_string()))?,
57 ),
58 }),
59 privileged: false,
60 privileged_reason: None,
61 for_self: None,
62 seek_permission: None,
63 },
64 self.originator(),
65 )
66 .await
67 .map_err(|e| MessageBoxError::Wallet(e.to_string()))?;
68
69 let hash_vec = pk_result.public_key.to_hash();
71 let mut hash = [0u8; 20];
72 hash.copy_from_slice(&hash_vec);
73 let lock_script = P2PKH::from_public_key_hash(hash)
74 .lock()
75 .map_err(|e| MessageBoxError::Wallet(format!("P2PKH lock error: {e}")))?;
76 let locking_script_bytes = from_hex(&lock_script.to_hex())
78 .map_err(|e| MessageBoxError::Wallet(format!("hex decode locking script: {e}")))?;
79
80 let custom_instructions = PaymentCustomInstructions {
82 derivation_prefix: prefix.clone(),
83 derivation_suffix: suffix.clone(),
84 payee: Some(recipient.to_string()),
85 };
86
87 let create_result = self
90 .wallet()
91 .create_action(
92 CreateActionArgs {
93 description: "PeerPay payment".to_string(),
94 input_beef: None,
95 inputs: vec![],
96 outputs: vec![CreateActionOutput {
97 locking_script: Some(locking_script_bytes),
98 satoshis: amount,
99 output_description: "Payment for PeerPay transaction".to_string(),
100 basket: None,
101 custom_instructions: Some(
102 serde_json::to_string(&custom_instructions)
103 .map_err(MessageBoxError::Json)?,
104 ),
105 tags: vec![],
106 }],
107 lock_time: None,
108 version: None,
109 labels: vec!["peerpay".to_string()],
110 options: Some(CreateActionOptions {
111 randomize_outputs: BooleanDefaultTrue(Some(false)),
112 sign_and_process: BooleanDefaultTrue(None),
113 accept_delayed_broadcast: BooleanDefaultTrue(None),
114 ..Default::default()
115 }),
116 reference: None,
117 },
118 self.originator(),
119 )
120 .await
121 .map_err(|e| MessageBoxError::Wallet(e.to_string()))?;
122
123 let tx = if let Some(tx_bytes) = create_result.tx {
126 tx_bytes
127 } else if let Some(signable) = create_result.signable_transaction {
128 let sign_result = self
129 .wallet()
130 .sign_action(
131 SignActionArgs {
132 reference: signable.reference,
133 spends: std::collections::HashMap::new(),
134 options: None,
135 },
136 self.originator(),
137 )
138 .await
139 .map_err(|e| MessageBoxError::Wallet(e.to_string()))?;
140 sign_result
141 .tx
142 .ok_or_else(|| MessageBoxError::Wallet("sign_action returned no tx".to_string()))?
143 } else {
144 return Err(MessageBoxError::Wallet(
145 "create_action returned neither tx nor signable_transaction".to_string(),
146 ));
147 };
148
149 Ok(PaymentToken {
152 custom_instructions,
153 transaction: tx,
154 amount,
155 output_index: None,
156 })
157 }
158
159 pub async fn send_payment(
163 &self,
164 recipient: &str,
165 amount: u64,
166 ) -> Result<String, MessageBoxError> {
167 let token = self.create_payment_token(recipient, amount).await?;
168 let token_json = serde_json::to_string(&token)?;
169 self.send_message(recipient, "payment_inbox", &token_json, false, false, None, None).await
170 }
171
172 pub async fn send_live_payment(
182 &self,
183 recipient: &str,
184 amount: u64,
185 ) -> Result<String, MessageBoxError> {
186 let token = self.create_payment_token(recipient, amount).await?;
187 let token_json = serde_json::to_string(&token)?;
188 let delivery = self.send_live_message(recipient, "payment_inbox", &token_json, false, false, None, None).await?;
189 Ok(delivery.message_id().to_string())
190 }
191
192 pub async fn listen_for_live_payments(
199 &self,
200 on_payment: Arc<dyn Fn(IncomingPayment) + Send + Sync>,
201 ) -> Result<(), MessageBoxError> {
202 let wrapper: Arc<dyn Fn(PeerMessage) + Send + Sync> = Arc::new(move |msg: PeerMessage| {
203 if let Ok(token) = serde_json::from_str::<PaymentToken>(&msg.body) {
204 let incoming = IncomingPayment {
205 token,
206 sender: msg.sender,
207 message_id: msg.message_id,
208 };
209 on_payment(incoming);
210 }
211 });
213
214 self.listen_for_live_messages("payment_inbox", wrapper, None).await
215 }
216
217 pub async fn accept_payment(&self, payment: &IncomingPayment) -> Result<(), MessageBoxError> {
223 use base64::{engine::general_purpose::STANDARD, Engine};
224
225 let sender_pk = PublicKey::from_string(&payment.sender)
226 .map_err(|e| MessageBoxError::Wallet(format!("invalid sender key: {e}")))?;
227
228 let prefix_bytes = STANDARD
229 .decode(&payment.token.custom_instructions.derivation_prefix)
230 .map_err(|e| MessageBoxError::Wallet(format!("base64 decode prefix: {e}")))?;
231 let suffix_bytes = STANDARD
232 .decode(&payment.token.custom_instructions.derivation_suffix)
233 .map_err(|e| MessageBoxError::Wallet(format!("base64 decode suffix: {e}")))?;
234
235 self.wallet()
236 .internalize_action(
237 InternalizeActionArgs {
238 tx: payment.token.transaction.clone(),
239 description: "PeerPay Payment".to_string(),
240 labels: vec!["peerpay".to_string()],
241 seek_permission: BooleanDefaultTrue(Some(true)),
242 outputs: vec![InternalizeOutput::WalletPayment {
243 output_index: payment.token.output_index.unwrap_or(0),
244 payment: Payment {
245 derivation_prefix: prefix_bytes,
246 derivation_suffix: suffix_bytes,
247 sender_identity_key: sender_pk,
248 },
249 }],
250 },
251 self.originator(),
252 )
253 .await
254 .map_err(|e| MessageBoxError::Wallet(e.to_string()))?;
255
256 self.acknowledge_message(vec![payment.message_id.clone()], None).await?;
257 Ok(())
258 }
259
260 pub async fn reject_payment(&self, payment: &IncomingPayment) -> Result<(), MessageBoxError> {
269 if payment.token.amount < 2000 {
270 return self.acknowledge_message(vec![payment.message_id.clone()], None).await;
271 }
272
273 self.accept_payment(payment).await?;
274
275 if let Err(e) = self.send_payment(&payment.sender, payment.token.amount - 1000).await {
276 if Self::is_401_error(&e) { return Ok(()); }
277 return Err(e);
278 }
279
280 if let Err(e) = self.acknowledge_message(vec![payment.message_id.clone()], None).await {
281 if Self::is_401_error(&e) { return Ok(()); }
282 return Err(e);
283 }
284
285 Ok(())
286 }
287
288 fn is_401_error(e: &MessageBoxError) -> bool {
290 matches!(e, MessageBoxError::Http(401, _))
291 || matches!(e, MessageBoxError::Auth(msg) if msg.contains("401"))
292 }
293
294 pub async fn list_incoming_payments(
301 &self,
302 ) -> Result<Vec<IncomingPayment>, MessageBoxError> {
303 let messages = self.list_messages("payment_inbox", false, None).await?;
304
305 let payments = messages
306 .into_iter()
307 .filter_map(|msg| {
308 serde_json::from_str::<PaymentToken>(&msg.body)
309 .ok()
310 .map(|token| IncomingPayment {
311 token,
312 sender: msg.sender,
313 message_id: msg.message_id,
314 })
315 })
316 .collect();
317
318 Ok(payments)
319 }
320
321 pub async fn acknowledge_notification(
329 &self,
330 message: &PeerMessage,
331 ) -> Result<bool, MessageBoxError> {
332 self.acknowledge_message(vec![message.message_id.clone()], None).await?;
334
335 let parsed = serde_json::from_str::<crate::http_ops::WrappedMessageBody>(&message.body);
337 let payment_data = parsed.ok().and_then(|w| w.payment);
338
339 if let Some(payment) = payment_data {
341 if let (Some(tx_bytes), Some(outputs)) = (&payment.tx, &payment.outputs) {
342 let description = payment
343 .description
344 .clone()
345 .unwrap_or_else(|| "MessageBox recipient payment".to_string());
346
347 let internalize_outputs: Vec<InternalizeOutput> = outputs
349 .iter()
350 .filter_map(|o| {
351 let sender_pk = o.sender_identity_key
352 .as_deref()
353 .and_then(|k| bsv::primitives::public_key::PublicKey::from_string(k).ok())?;
354 Some(InternalizeOutput::WalletPayment {
355 output_index: o.output_index.unwrap_or(0),
356 payment: Payment {
357 derivation_prefix: o.derivation_prefix.clone().unwrap_or_default(),
358 derivation_suffix: o.derivation_suffix.clone().unwrap_or_default(),
359 sender_identity_key: sender_pk,
360 },
361 })
362 })
363 .collect();
364
365 if internalize_outputs.is_empty() {
366 return Ok(false);
367 }
368
369 let args = InternalizeActionArgs {
370 tx: tx_bytes.clone(),
371 description,
372 labels: vec!["notification-payment".to_string()],
373 seek_permission: bsv::wallet::types::BooleanDefaultTrue(Some(false)),
374 outputs: internalize_outputs,
375 };
376
377 match self.wallet().internalize_action(args, self.originator()).await {
378 Ok(_) => return Ok(true),
379 Err(_) => return Ok(false),
380 }
381 }
382 }
383
384 Ok(false)
385 }
386}
387
388#[cfg(test)]
393mod tests {
394 use super::*;
395 use crate::types::{IncomingPayment, PaymentCustomInstructions, PaymentToken, ServerPeerMessage};
396 use bsv::primitives::private_key::PrivateKey;
397 use bsv::remittance::types::PeerMessage;
398 use bsv::wallet::error::WalletError;
399 use bsv::wallet::interfaces::*;
400 use bsv::wallet::proto_wallet::ProtoWallet;
401 use std::sync::Arc;
402
403 #[derive(Clone)]
405 struct ArcWallet(Arc<ProtoWallet>);
406
407 impl ArcWallet {
408 fn new() -> Self {
409 let key = PrivateKey::from_random().expect("random key");
410 ArcWallet(Arc::new(ProtoWallet::new(key)))
411 }
412
413 async fn identity_hex(&self) -> String {
414 self.get_public_key(
415 GetPublicKeyArgs {
416 identity_key: true,
417 protocol_id: None,
418 key_id: None,
419 counterparty: None,
420 privileged: false,
421 privileged_reason: None,
422 for_self: None,
423 seek_permission: None,
424 },
425 None,
426 )
427 .await
428 .expect("get_public_key")
429 .public_key
430 .to_der_hex()
431 }
432 }
433
434 #[async_trait::async_trait]
435 impl WalletInterface for ArcWallet {
436 async fn create_action(&self, args: CreateActionArgs, orig: Option<&str>) -> Result<CreateActionResult, WalletError> { self.0.create_action(args, orig).await }
437 async fn sign_action(&self, args: SignActionArgs, orig: Option<&str>) -> Result<SignActionResult, WalletError> { self.0.sign_action(args, orig).await }
438 async fn abort_action(&self, args: AbortActionArgs, orig: Option<&str>) -> Result<AbortActionResult, WalletError> { self.0.abort_action(args, orig).await }
439 async fn list_actions(&self, args: ListActionsArgs, orig: Option<&str>) -> Result<ListActionsResult, WalletError> { self.0.list_actions(args, orig).await }
440 async fn internalize_action(&self, args: InternalizeActionArgs, orig: Option<&str>) -> Result<InternalizeActionResult, WalletError> { self.0.internalize_action(args, orig).await }
441 async fn list_outputs(&self, args: ListOutputsArgs, orig: Option<&str>) -> Result<ListOutputsResult, WalletError> { self.0.list_outputs(args, orig).await }
442 async fn relinquish_output(&self, args: RelinquishOutputArgs, orig: Option<&str>) -> Result<RelinquishOutputResult, WalletError> { self.0.relinquish_output(args, orig).await }
443 async fn get_public_key(&self, args: GetPublicKeyArgs, orig: Option<&str>) -> Result<GetPublicKeyResult, WalletError> { self.0.get_public_key(args, orig).await }
444 async fn reveal_counterparty_key_linkage(&self, args: RevealCounterpartyKeyLinkageArgs, orig: Option<&str>) -> Result<RevealCounterpartyKeyLinkageResult, WalletError> { self.0.reveal_counterparty_key_linkage(args, orig).await }
445 async fn reveal_specific_key_linkage(&self, args: RevealSpecificKeyLinkageArgs, orig: Option<&str>) -> Result<RevealSpecificKeyLinkageResult, WalletError> { self.0.reveal_specific_key_linkage(args, orig).await }
446 async fn encrypt(&self, args: EncryptArgs, orig: Option<&str>) -> Result<EncryptResult, WalletError> { self.0.encrypt(args, orig).await }
447 async fn decrypt(&self, args: DecryptArgs, orig: Option<&str>) -> Result<DecryptResult, WalletError> { self.0.decrypt(args, orig).await }
448 async fn create_hmac(&self, args: CreateHmacArgs, orig: Option<&str>) -> Result<CreateHmacResult, WalletError> { self.0.create_hmac(args, orig).await }
449 async fn verify_hmac(&self, args: VerifyHmacArgs, orig: Option<&str>) -> Result<VerifyHmacResult, WalletError> { self.0.verify_hmac(args, orig).await }
450 async fn create_signature(&self, args: CreateSignatureArgs, orig: Option<&str>) -> Result<CreateSignatureResult, WalletError> { self.0.create_signature(args, orig).await }
451 async fn verify_signature(&self, args: VerifySignatureArgs, orig: Option<&str>) -> Result<VerifySignatureResult, WalletError> { self.0.verify_signature(args, orig).await }
452 async fn acquire_certificate(&self, args: AcquireCertificateArgs, orig: Option<&str>) -> Result<Certificate, WalletError> { self.0.acquire_certificate(args, orig).await }
453 async fn list_certificates(&self, args: ListCertificatesArgs, orig: Option<&str>) -> Result<ListCertificatesResult, WalletError> { self.0.list_certificates(args, orig).await }
454 async fn prove_certificate(&self, args: ProveCertificateArgs, orig: Option<&str>) -> Result<ProveCertificateResult, WalletError> { self.0.prove_certificate(args, orig).await }
455 async fn relinquish_certificate(&self, args: RelinquishCertificateArgs, orig: Option<&str>) -> Result<RelinquishCertificateResult, WalletError> { self.0.relinquish_certificate(args, orig).await }
456 async fn discover_by_identity_key(&self, args: DiscoverByIdentityKeyArgs, orig: Option<&str>) -> Result<DiscoverCertificatesResult, WalletError> { self.0.discover_by_identity_key(args, orig).await }
457 async fn discover_by_attributes(&self, args: DiscoverByAttributesArgs, orig: Option<&str>) -> Result<DiscoverCertificatesResult, WalletError> { self.0.discover_by_attributes(args, orig).await }
458 async fn is_authenticated(&self, orig: Option<&str>) -> Result<AuthenticatedResult, WalletError> { self.0.is_authenticated(orig).await }
459 async fn wait_for_authentication(&self, orig: Option<&str>) -> Result<AuthenticatedResult, WalletError> { self.0.wait_for_authentication(orig).await }
460 async fn get_height(&self, orig: Option<&str>) -> Result<GetHeightResult, WalletError> { self.0.get_height(orig).await }
461 async fn get_header_for_height(&self, args: GetHeaderArgs, orig: Option<&str>) -> Result<GetHeaderResult, WalletError> { self.0.get_header_for_height(args, orig).await }
462 async fn get_network(&self, orig: Option<&str>) -> Result<GetNetworkResult, WalletError> { self.0.get_network(orig).await }
463 async fn get_version(&self, orig: Option<&str>) -> Result<GetVersionResult, WalletError> { self.0.get_version(orig).await }
464 }
465
466 #[tokio::test]
477 async fn create_payment_token_uses_create_nonce() {
478 let sender = ArcWallet::new();
479 let recipient = ArcWallet::new();
480 let recipient_pk = recipient.identity_hex().await;
481
482 let client = crate::client::MessageBoxClient::new(
483 "https://example.com".to_string(),
484 sender,
485 None,
486 bsv::services::overlay_tools::Network::Mainnet,
487 );
488
489 let result = client.create_payment_token(&recipient_pk, 1000).await;
493 match &result {
497 Err(e) => {
498 let msg = e.to_string();
499 assert!(!msg.contains("create_nonce prefix:"), "should not fail at nonce step");
501 println!("create_payment_token expected error: {msg}");
503 }
504 Ok(_) => {
505 println!("create_payment_token succeeded unexpectedly — wallet may have funds");
507 }
508 }
509 }
510
511 #[allow(dead_code)]
514 fn send_payment_compile_check(client: &crate::client::MessageBoxClient<ArcWallet>) {
515 drop(client.send_payment("03abc", 1000));
517 }
518
519 #[test]
528 fn reject_payment_threshold_below_2000() {
529 const THRESHOLD: u64 = 2000;
533 assert_eq!(THRESHOLD, 2000, "threshold must be 2000 sats");
534
535 let amount: u64 = 3000;
537 let refund = amount - 1000;
538 assert_eq!(refund, 2000, "refund is amount minus 1000 sat fee");
539 }
540
541 #[test]
545 fn list_incoming_payments_skips_unparseable() {
546 let messages = vec![
549 ServerPeerMessage {
550 message_id: "msg1".to_string(),
551 body: "not valid json".to_string(),
552 sender: "03sender1".to_string(),
553 created_at: "2024-01-01T00:00:00Z".to_string(),
554 updated_at: "2024-01-01T00:00:00Z".to_string(),
555 acknowledged: None,
556 },
557 ServerPeerMessage {
558 message_id: "msg2".to_string(),
559 body: r#"{"customInstructions":{"derivationPrefix":"p","derivationSuffix":"s"},"transaction":[1,2,3],"amount":1000}"#.to_string(),
560 sender: "03sender2".to_string(),
561 created_at: "2024-01-01T00:00:00Z".to_string(),
562 updated_at: "2024-01-01T00:00:00Z".to_string(),
563 acknowledged: None,
564 },
565 ServerPeerMessage {
566 message_id: "msg3".to_string(),
567 body: r#"{"foo":"bar"}"#.to_string(),
568 sender: "03sender3".to_string(),
569 created_at: "2024-01-01T00:00:00Z".to_string(),
570 updated_at: "2024-01-01T00:00:00Z".to_string(),
571 acknowledged: None,
572 },
573 ];
574
575 let payments: Vec<IncomingPayment> = messages
577 .into_iter()
578 .filter_map(|msg| {
579 serde_json::from_str::<PaymentToken>(&msg.body)
580 .ok()
581 .map(|token| IncomingPayment {
582 token,
583 sender: msg.sender,
584 message_id: msg.message_id,
585 })
586 })
587 .collect();
588
589 assert_eq!(payments.len(), 1, "only valid payment token should be included");
591 assert_eq!(payments[0].message_id, "msg2");
592 assert_eq!(payments[0].sender, "03sender2");
593 assert_eq!(payments[0].token.amount, 1000);
594 }
595
596 #[test]
600 fn accept_payment_base64_round_trip() {
601 use base64::{engine::general_purpose::STANDARD, Engine};
602
603 let prefix = "dGVzdC1wcmVmaXg="; let suffix = "dGVzdC1zdWZmaXg="; let prefix_bytes = STANDARD.decode(prefix).unwrap();
609 let suffix_bytes = STANDARD.decode(suffix).unwrap();
610 assert_eq!(prefix_bytes, b"test-prefix");
611 assert_eq!(suffix_bytes, b"test-suffix");
612
613 let re_encoded = STANDARD.encode(&prefix_bytes);
615 assert_eq!(re_encoded, prefix, "round-trip must produce original base64");
616 }
617
618 #[test]
620 fn incoming_payment_round_trip() {
621 let token = PaymentToken {
622 custom_instructions: PaymentCustomInstructions {
623 derivation_prefix: "pfx".to_string(),
624 derivation_suffix: "sfx".to_string(),
625 payee: Some("03recipient".to_string()),
626 },
627 transaction: vec![0xde, 0xad, 0xbe, 0xef],
628 amount: 5000,
629 output_index: None,
630 };
631
632 let incoming = IncomingPayment {
633 token: token.clone(),
634 sender: "03sender_key".to_string(),
635 message_id: "abc123".to_string(),
636 };
637
638 assert_eq!(incoming.sender, "03sender_key");
639 assert_eq!(incoming.message_id, "abc123");
640 assert_eq!(incoming.token.amount, 5000);
641 assert_eq!(incoming.token.transaction, vec![0xde, 0xad, 0xbe, 0xef]);
642 assert_eq!(incoming.token.custom_instructions.derivation_prefix, "pfx");
643 assert_eq!(incoming.token.custom_instructions.derivation_suffix, "sfx");
644 assert_eq!(incoming.token.output_index, None);
645 }
646
647 #[allow(dead_code)]
653 fn send_live_payment_compile_check(client: &crate::client::MessageBoxClient<ArcWallet>) {
654 let _fut = client.send_live_payment("03abc", 1000);
655 }
656
657 #[test]
661 fn listen_for_live_payments_callback_parses_token() {
662 use std::sync::Mutex as StdMutex;
663
664 let received = Arc::new(StdMutex::new(Vec::<IncomingPayment>::new()));
665 let received_clone = received.clone();
666
667 let msg = PeerMessage {
669 message_id: "msg-live-1".to_string(),
670 sender: "03sender".to_string(),
671 recipient: "03recipient".to_string(),
672 message_box: "payment_inbox".to_string(),
673 body: r#"{"customInstructions":{"derivationPrefix":"p","derivationSuffix":"s"},"transaction":[1,2],"amount":500}"#.to_string(),
674 };
675
676 if let Ok(token) = serde_json::from_str::<PaymentToken>(&msg.body) {
678 let incoming = IncomingPayment {
679 token,
680 sender: msg.sender.clone(),
681 message_id: msg.message_id.clone(),
682 };
683 received_clone.lock().unwrap().push(incoming);
684 }
685
686 let payments = received.lock().unwrap();
687 assert_eq!(payments.len(), 1, "one valid payment should be parsed");
688 assert_eq!(payments[0].token.amount, 500);
689 assert_eq!(payments[0].sender, "03sender");
690 assert_eq!(payments[0].message_id, "msg-live-1");
691 }
692
693 #[test]
697 fn listen_for_live_payments_callback_skips_non_payment() {
698 use std::sync::Mutex as StdMutex;
699
700 let received = Arc::new(StdMutex::new(Vec::<IncomingPayment>::new()));
701 let received_clone = received.clone();
702
703 let msg = PeerMessage {
705 message_id: "msg-bad-1".to_string(),
706 sender: "03sender".to_string(),
707 recipient: "03recipient".to_string(),
708 message_box: "payment_inbox".to_string(),
709 body: r#"{"not":"a payment token"}"#.to_string(),
710 };
711
712 if let Ok(token) = serde_json::from_str::<PaymentToken>(&msg.body) {
714 let incoming = IncomingPayment {
715 token,
716 sender: msg.sender.clone(),
717 message_id: msg.message_id.clone(),
718 };
719 received_clone.lock().unwrap().push(incoming);
720 }
721
722 let payments = received.lock().unwrap();
723 assert_eq!(payments.len(), 0, "non-payment message must be silently skipped");
724 }
725}