@@ -14,11 +14,12 @@ use axum::response::{IntoResponse, Response};
1414use cdk:: mint:: QuoteId ;
1515use cdk:: nuts:: nut21:: { Method , ProtectedEndpoint , RoutePath } ;
1616use cdk:: nuts:: {
17- BatchCheckMintQuoteRequest , BatchMintRequest , MeltQuoteBolt11Request , MeltQuoteBolt11Response ,
18- MeltQuoteBolt12Request , MeltQuoteCustomRequest , MintQuoteBolt11Request ,
19- MintQuoteBolt11Response , MintQuoteBolt12Request , MintQuoteBolt12Response ,
20- MintQuoteCustomRequest , MintRequest , MintResponse ,
17+ BatchCheckMintQuoteRequest , BatchMintRequest , MeltQuoteBolt11Request , MeltQuoteBolt12Request ,
18+ MeltQuoteCustomRequest , MintQuoteBolt11Request , MintQuoteBolt11Response ,
19+ MintQuoteBolt12Request , MintQuoteBolt12Response , MintQuoteCustomRequest , MintRequest ,
20+ MintResponse ,
2121} ;
22+ use cdk:: { MeltQuoteCreateResponse , MeltQuoteResponse } ;
2223use serde_json:: Value ;
2324use tracing:: instrument;
2425
6869 } )
6970 }
7071}
72+
73+ /// Serialize a `MeltQuoteResponse` into an HTTP JSON response that carries the
74+ /// per-variant payload shape (bolt11, bolt12, or custom) rather than the tag.
75+ fn melt_quote_response_to_json ( response : MeltQuoteResponse < QuoteId > ) -> Response {
76+ match response {
77+ MeltQuoteResponse :: Bolt11 ( r) => Json ( r) . into_response ( ) ,
78+ MeltQuoteResponse :: Bolt12 ( r) => Json ( r) . into_response ( ) ,
79+ MeltQuoteResponse :: Custom ( ( _, r) ) => Json ( r) . into_response ( ) ,
80+ }
81+ }
82+
83+ /// Serialize a `MeltQuoteCreateResponse` into an HTTP JSON response that
84+ /// carries the per-variant payload shape (bolt11, bolt12, or custom) rather
85+ /// than the tag.
86+ fn melt_quote_create_response_to_json ( response : MeltQuoteCreateResponse < QuoteId > ) -> Response {
87+ match response {
88+ MeltQuoteCreateResponse :: Bolt11 ( r) => Json ( r) . into_response ( ) ,
89+ MeltQuoteCreateResponse :: Bolt12 ( r) => Json ( r) . into_response ( ) ,
90+ MeltQuoteCreateResponse :: Custom ( ( _, r) ) => Json ( r) . into_response ( ) ,
91+ }
92+ }
93+
94+ async fn validate_melt_quote_method (
95+ state : & MintState ,
96+ method : & str ,
97+ quote_id : & QuoteId ,
98+ ) -> Result < ( ) , cdk:: Error > {
99+ let quote_method = state
100+ . mint
101+ . get_melt_quote_method ( quote_id)
102+ . await ?
103+ . to_string ( ) ;
104+
105+ if quote_method != method {
106+ return Err ( cdk:: Error :: InvalidPaymentMethod ) ;
107+ }
108+
109+ Ok ( ( ) )
110+ }
111+
71112/// Generic handler for custom payment method mint quotes
72113///
73114/// This handler works for ANY custom payment method (e.g., paypal, venmo, cashapp, bolt11, bolt12).
@@ -108,6 +149,9 @@ pub async fn post_mint_custom_quote(
108149 Ok ( Json ( response) . into_response ( ) )
109150 }
110151 "bolt12" => {
152+ if payload. get ( "pubkey" ) . is_none_or ( |v| v. is_null ( ) ) {
153+ return Err ( into_response ( cdk:: Error :: PubkeyRequired ) ) ;
154+ }
111155 let bolt12_request: MintQuoteBolt12Request =
112156 serde_json:: from_value ( payload) . map_err ( |e| {
113157 tracing:: error!( "Failed to parse bolt12 request: {}" , e) ;
@@ -132,7 +176,7 @@ pub async fn post_mint_custom_quote(
132176 } ) ?;
133177
134178 let quote_request = cdk:: mint:: MintQuoteRequest :: Custom {
135- method : cdk:: nuts:: PaymentMethod :: Custom ( method. clone ( ) ) ,
179+ method : cdk:: nuts:: PaymentMethod :: from ( method. clone ( ) ) ,
136180 request : custom_request,
137181 } ;
138182
@@ -251,10 +295,7 @@ pub async fn post_batch_check_mint_quote(
251295 let responses: Vec < cdk:: nuts:: MintQuoteCustomResponse < QuoteId > > = responses
252296 . into_iter ( )
253297 . map ( |r| match r {
254- cdk:: mint:: MintQuoteResponse :: Custom {
255- method : _,
256- response,
257- } => Ok ( response) ,
298+ cdk:: mint:: MintQuoteResponse :: Custom { response, .. } => Ok ( response) ,
258299 _ => Err ( cdk:: Error :: InvalidPaymentMethod ) ,
259300 } )
260301 . collect :: < Result < Vec < _ > , _ > > ( )
@@ -323,7 +364,7 @@ pub async fn post_melt_custom_quote(
323364 State ( state) : State < MintState > ,
324365 Path ( method) : Path < String > ,
325366 Json ( payload) : Json < Value > ,
326- ) -> Result < Json < MeltQuoteBolt11Response < QuoteId > > , Response > {
367+ ) -> Result < Response , Response > {
327368 state
328369 . mint
329370 . verify_auth (
@@ -375,7 +416,7 @@ pub async fn post_melt_custom_quote(
375416 }
376417 } ;
377418
378- Ok ( Json ( response) )
419+ Ok ( melt_quote_create_response_to_json ( response) )
379420}
380421
381422/// Get custom payment method melt quote status
@@ -384,7 +425,7 @@ pub async fn get_check_melt_custom_quote(
384425 auth : AuthHeader ,
385426 State ( state) : State < MintState > ,
386427 Path ( ( method, quote_id) ) : Path < ( String , QuoteId ) > ,
387- ) -> Result < Json < MeltQuoteBolt11Response < QuoteId > > , Response > {
428+ ) -> Result < Response , Response > {
388429 state
389430 . mint
390431 . verify_auth (
@@ -394,49 +435,66 @@ pub async fn get_check_melt_custom_quote(
394435 . await
395436 . map_err ( into_response) ?;
396437
397- // Note: check_melt_quote returns the response directly
398- // The payment method validation is done when the quote was created
438+ validate_melt_quote_method ( & state, & method, & quote_id)
439+ . await
440+ . map_err ( into_response) ?;
441+
399442 let quote = state
400443 . mint
401444 . check_melt_quote ( & quote_id)
402445 . await
403446 . map_err ( into_response) ?;
404447
405- Ok ( Json ( quote) )
448+ Ok ( melt_quote_response_to_json ( quote) )
406449}
407450
408- /// Melt tokens with custom payment method
409- #[ instrument( skip_all, fields( method = ?method) ) ]
410- pub async fn post_melt_custom (
451+ async fn post_melt_custom_internal (
411452 auth : AuthHeader ,
412453 prefer : PreferHeader ,
413- State ( state) : State < MintState > ,
414- Path ( method) : Path < String > ,
415- Json ( payload) : Json < cdk:: nuts:: MeltRequest < QuoteId > > ,
416- ) -> Result < Json < MeltQuoteBolt11Response < QuoteId > > , Response > {
454+ state : MintState ,
455+ method : String ,
456+ payload : cdk:: nuts:: MeltRequest < QuoteId > ,
457+ ) -> Result < MeltQuoteResponse < QuoteId > , cdk :: Error > {
417458 state
418459 . mint
419460 . verify_auth (
420461 auth. into ( ) ,
421462 & ProtectedEndpoint :: new ( Method :: Post , RoutePath :: Melt ( method. clone ( ) ) ) ,
422463 )
423- . await
424- . map_err ( into_response) ?;
464+ . await ?;
465+
466+ validate_melt_quote_method ( & state, & method, payload. quote ( ) ) . await ?;
425467
426468 // Check for async preference in either the Prefer header or the request body
427469 let respond_async = prefer. respond_async || payload. is_prefer_async ( ) ;
428470
429- let pending = state. mint . melt ( & payload) . await . map_err ( into_response ) ?;
471+ let pending = state. mint . melt ( & payload) . await ?;
430472
431473 let res = if respond_async {
432474 // Asynchronous processing - return immediately after setup
433475 pending. into_pending_response ( )
434476 } else {
435477 // Synchronous processing - wait for completion
436- pending. await . map_err ( into_response ) ?
478+ pending. await ?
437479 } ;
438480
439- Ok ( Json ( res) )
481+ Ok ( res)
482+ }
483+
484+ /// Melt tokens with custom payment method
485+ #[ instrument( skip_all, fields( method = ?method) ) ]
486+ pub async fn post_melt_custom (
487+ auth : AuthHeader ,
488+ prefer : PreferHeader ,
489+ State ( state) : State < MintState > ,
490+ Path ( method) : Path < String > ,
491+ Json ( payload) : Json < cdk:: nuts:: MeltRequest < QuoteId > > ,
492+ ) -> Result < Response , Response > {
493+ let res = post_melt_custom_internal ( auth, prefer, state, method, payload)
494+ . await
495+ . map_err ( into_response) ?;
496+
497+ Ok ( melt_quote_response_to_json ( res) )
440498}
441499
442500// ============================================================================
@@ -484,7 +542,7 @@ pub async fn cache_post_melt_custom(
484542 state : State < MintState > ,
485543 method : Path < String > ,
486544 payload : Json < cdk:: nuts:: MeltRequest < QuoteId > > ,
487- ) -> Result < Json < MeltQuoteBolt11Response < QuoteId > > , Response > {
545+ ) -> Result < Response , Response > {
488546 use std:: ops:: Deref ;
489547
490548 let State ( mint_state) = state. clone ( ) ;
@@ -500,18 +558,25 @@ pub async fn cache_post_melt_custom(
500558
501559 if let Some ( cached_response) = mint_state
502560 . cache
503- . get :: < MeltQuoteBolt11Response < QuoteId > > ( & cache_key)
561+ . get :: < MeltQuoteResponse < QuoteId > > ( & cache_key)
504562 . await
505563 {
506- return Ok ( Json ( cached_response) ) ;
564+ return Ok ( melt_quote_response_to_json ( cached_response) ) ;
507565 }
508566
509- let result = post_melt_custom ( auth, prefer, state, method, payload) . await ?;
567+ let result = post_melt_custom_internal (
568+ auth,
569+ prefer,
570+ mint_state. clone ( ) ,
571+ method. clone ( ) ,
572+ json_extracted_payload. clone ( ) ,
573+ )
574+ . await
575+ . map_err ( into_response) ?;
510576
511- // Cache the response
512- mint_state. cache . set ( cache_key, result. deref ( ) ) . await ;
577+ mint_state. cache . set ( cache_key, & result) . await ;
513578
514- Ok ( result)
579+ Ok ( melt_quote_response_to_json ( result) )
515580}
516581
517582/// Cached version of post_batch_mint for NUT-19 caching support
0 commit comments