Class MerchantVerificationService
In Model B, vouchers can ONLY be redeemed at the issuing merchant, not at the mint. This service provides both offline and online verification capabilities.
Verification Modes
- Offline: Signature + expiry validation only (no network required)
- Online: Offline checks + ledger status query (prevents double-spend)
Typical Flow
- Customer presents voucher token to merchant
- Merchant calls verifyOnline() to check validity and prevent double-spend
- If valid, merchant accepts payment and marks voucher as REDEEMED
- Voucher cannot be reused (terminal state)
Usage Example
// The issuer registry is required: with an empty one no issuer key is trusted, so every
// voucher verifies as untrusted. That is the honest answer, not a bug — configure it.
Map<String, String> keys = Map.of("corner-cafe", issuerPubkeyHex);
IssuerKeyRegistry issuers = issuerId -> Optional.ofNullable(keys.get(issuerId));
MerchantVerificationService service = new MerchantVerificationService(ledgerPort, issuers);
// Parse token to get signed voucher (implementation specific)
SignedVoucher voucher = parseToken(token);
// Verify online (recommended for production)
VerificationResult result = service.verifyOnline(voucher, "merchant123");
if (result.isValid()) {
// Accept payment
// Mark as redeemed
service.markRedeemed(voucher.getSecret().getVoucherId());
} else {
// Reject - show errors
System.err.println("Invalid voucher: " + result.getErrorMessage());
}
- See Also:
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic classResult of voucher verification. -
Constructor Summary
ConstructorsConstructorDescriptionMerchantVerificationService(@NonNull VoucherLedgerPort ledgerPort, @NonNull IssuerKeyRegistry issuerKeyRegistry) Constructs a MerchantVerificationService. -
Method Summary
Modifier and TypeMethodDescriptionvoidmarkRedeemed(@NonNull String voucherId) Marks a voucher as redeemed in the ledger.processPaymentPayload(@NonNull xyz.tcheeric.cashu.common.nut18.VoucherPaymentPayload payload, @NonNull xyz.tcheeric.cashu.common.nut18.VoucherPaymentRequest originalRequest) Validates a NUT-18V payment payload structure against the original request.redeem(@NonNull RedeemVoucherRequest request, @NonNull SignedVoucher voucher) Redeems a voucher (verify + mark as redeemed).validatePaymentPayload(@NonNull xyz.tcheeric.cashu.common.nut18.VoucherPaymentPayload payload, @NonNull xyz.tcheeric.cashu.common.nut18.VoucherPaymentRequest originalRequest) Validates a NUT-18V payment payload against the original payment request.verifyOffline(@NonNull SignedVoucher voucher, @NonNull String expectedIssuerId) Verifies a voucher offline (signature + expiry only).verifyOnline(@NonNull SignedVoucher voucher, @NonNull String expectedIssuerId) Verifies a voucher online (offline checks + ledger status).
-
Constructor Details
-
MerchantVerificationService
public MerchantVerificationService(@NonNull @NonNull VoucherLedgerPort ledgerPort, @NonNull @NonNull IssuerKeyRegistry issuerKeyRegistry) Constructs a MerchantVerificationService.- Parameters:
ledgerPort- the port for ledger operations (must not be null)issuerKeyRegistry- resolves the key an issuer is known to sign with (must not be null)
-
-
Method Details
-
verifyOffline
public MerchantVerificationService.VerificationResult verifyOffline(@NonNull @NonNull SignedVoucher voucher, @NonNull @NonNull String expectedIssuerId) Verifies a voucher offline (signature + expiry only).This method performs cryptographic signature verification and expiry checks without requiring network access. Useful for:
- Offline merchants (temporary network outage)
- Quick preliminary checks
- Testing environments
Warning: Offline verification cannot detect double-spending. Use online verification for production.
- Parameters:
voucher- the signed voucher to verify (must not be null)expectedIssuerId- the merchant's issuer ID (must not be null or blank)- Returns:
- the verification result
-
verifyOnline
public MerchantVerificationService.VerificationResult verifyOnline(@NonNull @NonNull SignedVoucher voucher, @NonNull @NonNull String expectedIssuerId) Verifies a voucher online (offline checks + ledger status).This method performs all offline checks plus queries the public ledger to check for:
- Voucher existence in ledger
- Current status (ISSUED vs REDEEMED/REVOKED/EXPIRED)
- Double-spend detection
This is the recommended verification mode for production.
- Parameters:
voucher- the signed voucher to verify (must not be null)expectedIssuerId- the merchant's issuer ID (must not be null or blank)- Returns:
- the verification result
-
markRedeemed
Marks a voucher as redeemed in the ledger.Call this method after successfully accepting a voucher payment. This records the redemption in the public ledger to prevent double-spending.
- Parameters:
voucherId- the voucher ID to mark as redeemed (must not be null or blank)- Throws:
IllegalArgumentException- if voucherId is invalidRuntimeException- if ledger update fails
-
redeem
public RedeemVoucherResponse redeem(@NonNull @NonNull RedeemVoucherRequest request, @NonNull @NonNull SignedVoucher voucher) Redeems a voucher (verify + mark as redeemed).This is a convenience method that combines verification and redemption in a single operation. It:
- Verifies the voucher online
- If valid, marks it as REDEEMED
- Returns the redemption response
- Parameters:
request- the redemption request (must not be null)voucher- the parsed voucher from the token (must not be null)- Returns:
- the redemption response
-
validatePaymentPayload
public MerchantVerificationService.VerificationResult validatePaymentPayload(@NonNull @NonNull xyz.tcheeric.cashu.common.nut18.VoucherPaymentPayload payload, @NonNull @NonNull xyz.tcheeric.cashu.common.nut18.VoucherPaymentRequest originalRequest) Validates a NUT-18V payment payload against the original payment request.This method checks that the payment payload matches the original request:
- Payment ID matches (if present in request)
- Issuer ID matches the merchant
- Amount meets or exceeds the requested amount
- Mint URL is permitted (if mints are restricted)
- Proofs have DLEQ if offline verification was required
- Parameters:
payload- the payment payload received from the customeroriginalRequest- the original payment request- Returns:
- the verification result
-
processPaymentPayload
public MerchantVerificationService.VerificationResult processPaymentPayload(@NonNull @NonNull xyz.tcheeric.cashu.common.nut18.VoucherPaymentPayload payload, @NonNull @NonNull xyz.tcheeric.cashu.common.nut18.VoucherPaymentRequest originalRequest) Validates a NUT-18V payment payload structure against the original request.This method is the main entry point for handling payment payloads received via the transport methods specified in the payment request. It validates the payload structure matches the original request.
Note: This method validates the payload structure but does NOT verify individual voucher proofs or mark vouchers as redeemed. The caller should extract vouchers from the proofs and verify them separately using
verifyOnline(xyz.tcheeric.cashu.voucher.domain.SignedVoucher, java.lang.String)orverifyOffline(xyz.tcheeric.cashu.voucher.domain.SignedVoucher, java.lang.String), then callmarkRedeemed(java.lang.String)after successful verification.- Parameters:
payload- the payment payload from the customeroriginalRequest- the original payment request- Returns:
- the verification result
-