Benefits of Sealed Classes:
- Exhaustive when expressions (compiler checks all cases)
- Type-safe error handling
- No need for catch-all branches
- IDE support for autocomplete
dataclassPaymentRequired(valpaymentRequest:PaymentRequest,overridevalmessage:String="Payment required to access this resource"):X402Error(message=message,code="PAYMENT_REQUIRED",details=mapOf("payment_request"topaymentRequest.toMap()))
Error Code:PAYMENT_REQUIRED
When Thrown:
- Server responds with HTTP 402
- Payment is required to access resource
try{valauth=client.createPayment(request)}catch(e:X402Error.InsufficientFunds){println("Insufficient funds!")println("Required: ${e.requiredAmount} USDC")println("Available: ${e.availableAmount} USDC")valshortfall=e.requiredAmount.toDouble()-e.availableAmount.toDouble()println("Need to add: $shortfall USDC")// Prompt user to add fundsalertUserToAddFunds(account.publicKey,shortfall)}
dataclassPaymentExpired(valpaymentRequest:PaymentRequest,overridevalmessage:String="Payment request has expired"):X402Error(message=message,code="PAYMENT_EXPIRED",details=mapOf("payment_request"topaymentRequest.toMap(),"expires_at"topaymentRequest.expiresAt.toString()))
Error Code:PAYMENT_EXPIRED
When Thrown:
- Payment request's expiresAt timestamp has passed
- Attempting to create payment for expired request
suspendfunhandleExpiredPayment(){try{valauth=client.createPayment(request)}catch(e:X402Error.PaymentExpired){println("Payment request expired at: ${e.paymentRequest.expiresAt}")println("Retrying to get fresh payment request...")// Make new request to get fresh payment detailstry{valresponse=client.get(url)}catch(e2:X402Error.PaymentRequired){// Handle new payment requestvalnewRequest=e2.paymentRequestvalauth=client.createPayment(newRequest)valretry=client.get(url,auth)}}}
fungetUserFriendlyErrorMessage(e:X402Error):String=when(e){isX402Error.InsufficientFunds->{valshortfall=e.requiredAmount.toDouble()-e.availableAmount.toDouble()"You don't have enough funds. Required: ${e.requiredAmount} USDC, "+"Available: ${e.availableAmount} USDC. "+"Please add $shortfall USDC to continue."}isX402Error.PaymentRequired->{"This resource requires a payment of ${e.paymentRequest.maxAmountRequired} USDC. "+"Would you like to proceed?"}isX402Error.PaymentExpired->{"The payment request expired. Please try again."}isX402Error.PaymentVerificationFailed->{"Your payment couldn't be verified. ${e.reason}. "+"Please contact support if this persists."}isX402Error.TransactionBroadcastFailed->{"There was a problem processing your payment. ${e.reason}. "+"Please check your connection and try again."}isX402Error.InvalidPaymentRequest->{"The server sent an invalid payment request. ${e.reason}. "+"Please contact the API provider."}isX402Error.Generic->{when(e.code){"PAYMENT_LIMIT_EXCEEDED"->"Payment amount exceeds your configured limit.""MAX_RETRIES_EXCEEDED"->"Maximum retry attempts exceeded. Please try again later."else->"An error occurred: ${e.message}"}}}// Usagetry{valresponse=client.get(url)}catch(e:X402Error){showUserMessage(getUserFriendlyErrorMessage(e))}
// Extension to check if error is retryablefunX402Error.isRetryable():Boolean=when(this){isX402Error.PaymentExpired->trueisX402Error.TransactionBroadcastFailed->trueisX402Error.PaymentVerificationFailed->falseisX402Error.InsufficientFunds->falseisX402Error.PaymentRequired->falseisX402Error.InvalidPaymentRequest->falseisX402Error.Generic->X402Error.isRetryable(this.code)}// Extension to get suggested wait time before retryfunX402Error.getRetryDelay():Long?=when(this){isX402Error.PaymentExpired->0L// Retry immediatelyisX402Error.TransactionBroadcastFailed->2000L// Wait 2 secondselse->null// Don't retry}// Extension to check if user action is neededfunX402Error.requiresUserAction():Boolean=when(this){isX402Error.InsufficientFunds->trueisX402Error.PaymentRequired->true// If not auto-handledelse->false}// Usagetry{valresponse=client.get(url)}catch(e:X402Error){if(e.requiresUserAction()){alertUser(getUserFriendlyErrorMessage(e))}elseif(e.isRetryable()){e.getRetryDelay()?.let{delay->delay(delay)// Retry}}}
suspendfunhandlePayment(url:String){try{valresponse=autoClient.get(url)}catch(e:X402Error.InsufficientFunds){// Alert user immediatelynotifyUser("Please add funds to your account")// Log for monitoringlogger.error{"Insufficient funds for user: $userId"}// Send alert to admin dashboardalertDashboard("User $userId has insufficient funds")}}
classCircuitBreaker{privatevarfailureCount=0privatevalfailureThreshold=5privatevalcooldownMs=60000LprivatevarlastFailureTime=0LsuspendfunshouldAttemptPayment():Boolean{if(failureCount>=failureThreshold){if(System.currentTimeMillis()-lastFailureTime<cooldownMs){returnfalse// Circuit open}else{reset()// Try again after cooldown}}returntrue}funrecordFailure(){failureCount++lastFailureTime=System.currentTimeMillis()}funrecordSuccess(){reset()}privatefunreset(){failureCount=0}}// UsagevalcircuitBreaker=CircuitBreaker()suspendfunmakePaymentWithCircuitBreaker(request:PaymentRequest){if(!circuitBreaker.shouldAttemptPayment()){throwX402Error.Generic("CIRCUIT_OPEN","Too many payment failures, circuit breaker is open")}try{valauth=client.createPayment(request)circuitBreaker.recordSuccess()}catch(e:X402Error){circuitBreaker.recordFailure()throwe}}
importkotlinx.datetime.Clockimportkotlinx.serialization.Serializable@SerializabledataclassPaymentAuditEntry(valtimestamp:String,valuserId:String,valpaymentId:String,valamount:String,valpaymentAddress:String,valsuccess:Boolean,valsignature:String?=null,valerrorCode:String?=null,valerrorMessage:String?=null)suspendfunlogPaymentActivity(userId:String,request:PaymentRequest,auth:PaymentAuthorization?,success:Boolean,error:X402Error?){valauditEntry=PaymentAuditEntry(timestamp=Clock.System.now().toString(),userId=userId,paymentId=request.paymentId,amount=request.maxAmountRequired,paymentAddress=request.paymentAddress,success=success,signature=auth?.signature,errorCode=error?.code,errorMessage=error?.message)// Save to audit logsaveToAuditLog(auditEntry)}
// Get error informationvalinfo=X402Error.getInfo("PAYMENT_REQUIRED")println("Message: ${info?.message}")println("Retryable: ${info?.retry}")println("User action: ${info?.userAction}")// Check if retryablevalisRetryable=X402Error.isRetryable("PAYMENT_EXPIRED")// Get all error codesvalallCodes=X402Error.getAllCodes()allCodes.forEach{(code,info)->println("$code: ${info.message}")}