TM-SGNL-iOS/SignalServiceKit/Subscriptions/Donations/DonationReceiptCredentialRequestError.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

130 lines
4.8 KiB
Swift

//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
/// Represents an error encountered while making a receipt credential request.
///
/// These are persisted in the ``SubscriptionReceiptCredentialRequestResultStore``
/// by the ``DonationReceiptCredentialRedemptionJobQueue``'s
/// ``SubscriptionReceiptCredentailRedemptionOperation``.
public struct DonationReceiptCredentialRequestError: Codable, Equatable {
public enum ErrorCode: Int {
case paymentStillProcessing = 204
case paymentFailed = 402
case localValidationFailed = 1
case serverValidationFailed = 400
case paymentNotFound = 404
case paymentIntentRedeemed = 409
}
public let errorCode: ErrorCode
/// If our error code is `.paymentFailed`, this holds the associated charge
/// failure.
///
/// We may have legacy or exceptional `.paymentFailed` errors persisted with
/// this `nil`.
public let chargeFailureCodeIfPaymentFailed: String?
/// We may have legacy errors persisted with this `nil`.
public let badge: ProfileBadge?
/// We may have legacy errors persisted with this `nil`.
public let amount: FiatMoney?
/// We may have legacy errors persisted with this `nil`.
public let paymentMethod: DonationPaymentMethod?
/// The epoch timestamp at which this error was created.
public let timestampMs: UInt64
public init(
errorCode: ErrorCode,
chargeFailureCodeIfPaymentFailed: String?,
badge: ProfileBadge,
amount: FiatMoney,
paymentMethod: DonationPaymentMethod
) {
owsPrecondition(
chargeFailureCodeIfPaymentFailed == nil || errorCode == .paymentFailed,
"Charge failure must only be populated if the error code is payment failed!"
)
self.errorCode = errorCode
self.chargeFailureCodeIfPaymentFailed = chargeFailureCodeIfPaymentFailed
self.badge = badge
self.amount = amount
self.paymentMethod = paymentMethod
self.timestampMs = Date().ows_millisecondsSince1970
}
/// When dealing with legacy persisted data, we may only have the raw error
/// code available. This should only be used if a full error cannot be
/// constructed!
public init(legacyErrorCode: ErrorCode) {
self.errorCode = legacyErrorCode
self.chargeFailureCodeIfPaymentFailed = nil
self.badge = nil
self.amount = nil
self.paymentMethod = nil
self.timestampMs = Date().ows_millisecondsSince1970
}
// MARK: - Codable
private enum CodingKeys: String, CodingKey {
case errorCode
case chargeFailureCodeIfPaymentFailed
case badge
case amount
case paymentMethod
case timestampMs
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
chargeFailureCodeIfPaymentFailed = try container.decodeIfPresent(String.self, forKey: .chargeFailureCodeIfPaymentFailed)
badge = try container.decodeIfPresent(ProfileBadge.self, forKey: .badge)
amount = try container.decodeIfPresent(FiatMoney.self, forKey: .amount)
timestampMs = try container.decode(UInt64.self, forKey: .timestampMs)
let errorCodeInt = try container.decode(Int.self, forKey: .errorCode)
let paymentMethodString = try container.decodeIfPresent(String.self, forKey: .paymentMethod)
guard let errorCode = ErrorCode(rawValue: errorCodeInt) else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: [CodingKeys.errorCode],
debugDescription: "Unexpected error code value: \(errorCodeInt)"
))
}
self.errorCode = errorCode
guard let paymentMethodString else {
paymentMethod = nil
return
}
guard let paymentMethod = DonationPaymentMethod(rawValue: paymentMethodString) else {
throw DecodingError.dataCorrupted(DecodingError.Context(
codingPath: [CodingKeys.paymentMethod],
debugDescription: "Unexpected payment method value: \(paymentMethodString)"
))
}
self.paymentMethod = paymentMethod
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(chargeFailureCodeIfPaymentFailed, forKey: .chargeFailureCodeIfPaymentFailed)
try container.encodeIfPresent(badge, forKey: .badge)
try container.encodeIfPresent(amount, forKey: .amount)
try container.encode(timestampMs, forKey: .timestampMs)
try container.encode(errorCode.rawValue, forKey: .errorCode)
try container.encodeIfPresent(paymentMethod?.rawValue, forKey: .paymentMethod)
}
}