Files
purchases-ios/Sources/Networking/CustomerCenterConfigAPI.swift
Rosie Watson 8b229a2adc CC-582 | Allow for support ticket creation (#5779)
* feat: add contact support button and new sheet for ticket creation

* chore: use localisation

* feat: send support ticket details to backend

* feat: only show the support ticket creation button when config is set up

* feat: only show to correct customer type

* test: add base tests

* chore: remove unrequired var

* chore: use translations

* docs: update link to tuist

* refactor: move to cc api

* refactor: api calls

* test: update setup

* chore: better email validation

* chore: improved focus state

* chore: add dismiss

* chore: hide keyboard on dismiss

* chore: fix indentation

* feat: add charcter count

* chore: add rest of cc config

* test: clean up unit tests

* refactor: pull email validation out into struct

* chore: refine email checks

* refactor: pull out max characters into own var
2025-11-19 08:26:43 +00:00

154 lines
5.1 KiB
Swift

//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// CustomerCenterConfigAPI.swift
//
// Created by Cesar de la Vega on 31/5/24.
//
import Foundation
class CustomerCenterConfigAPI {
typealias CustomerCenterConfigResponseHandler = Backend.ResponseHandler<CustomerCenterConfigResponse>
typealias CreateTicketResponseHandler = (Result<CreateTicketResponse, BackendError>) -> Void
private let customerCenterConfigResponseCallbacksCache: CallbackCache<CustomerCenterConfigCallback>
private let backendConfig: BackendConfiguration
init(backendConfig: BackendConfiguration) {
self.backendConfig = backendConfig
self.customerCenterConfigResponseCallbacksCache = .init()
}
func getCustomerCenterConfig(appUserID: String,
isAppBackgrounded: Bool,
completion: @escaping CustomerCenterConfigResponseHandler) {
let config = NetworkOperation.UserSpecificConfiguration(httpClient: self.backendConfig.httpClient,
appUserID: appUserID)
let factory = GetCustomerCenterConfigOperation.createFactory(
configuration: config,
callbackCache: self.customerCenterConfigResponseCallbacksCache
)
let callback = CustomerCenterConfigCallback(cacheKey: factory.cacheKey, completion: completion)
let cacheStatus = self.customerCenterConfigResponseCallbacksCache.add(callback)
self.backendConfig.addCacheableOperation(
with: factory,
delay: .default(forBackgroundedApp: isAppBackgrounded),
cacheStatus: cacheStatus
)
}
func postCreateTicket(appUserID: String,
customerEmail: String,
ticketDescription: String,
completion: @escaping CreateTicketResponseHandler) {
let config = NetworkOperation.UserSpecificConfiguration(httpClient: self.backendConfig.httpClient,
appUserID: appUserID)
let operation = PostCreateTicketOperation(configuration: config,
customerEmail: customerEmail,
ticketDescription: ticketDescription,
responseHandler: completion)
self.backendConfig.operationQueue.addOperation(operation)
}
}
// MARK: - PostCreateTicketOperation
private class PostCreateTicketOperation: NetworkOperation {
private let configuration: UserSpecificConfiguration
private let customerEmail: String
private let ticketDescription: String
private let responseHandler: CreateTicketResponseHandler?
typealias CreateTicketResponseHandler = (Result<CreateTicketResponse, BackendError>) -> Void
init(configuration: UserSpecificConfiguration,
customerEmail: String,
ticketDescription: String,
responseHandler: CreateTicketResponseHandler?) {
self.customerEmail = customerEmail
self.ticketDescription = ticketDescription
self.configuration = configuration
self.responseHandler = responseHandler
super.init(configuration: configuration)
}
override func begin(completion: @escaping () -> Void) {
self.post(completion: completion)
}
private func post(completion: @escaping () -> Void) {
let appUserID = self.configuration.appUserID
guard appUserID.isNotEmpty else {
self.responseHandler?(.failure(.missingAppUserID()))
completion()
return
}
let body = Body(
appUserID: appUserID,
customerEmail: self.customerEmail,
issueDescription: self.ticketDescription
)
let request = HTTPRequest(method: .post(body), path: .postCreateTicket)
self.httpClient.perform(request) { (response: VerifiedHTTPResponse<CreateTicketResponse>.Result) in
defer {
completion()
}
self.responseHandler?(
response
.map { $0.body }
.mapError(BackendError.networkError)
)
}
}
}
// Restating inherited @unchecked Sendable from Foundation's Operation
extension PostCreateTicketOperation: @unchecked Sendable {}
private struct PostCreateTicketBody: HTTPRequestBody, Encodable {
let appUserID: String
let customerEmail: String
let issueDescription: String
enum CodingKeys: String, CodingKey {
case appUserID = "app_user_id"
case customerEmail = "customer_email"
case issueDescription = "issue_description"
}
}
private extension PostCreateTicketOperation {
typealias Body = PostCreateTicketBody
}
struct CreateTicketResponse: HTTPResponseBody, Decodable {
let sent: Bool
}