// // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import SignalServiceKit import SignalUI #if USE_DEBUG_UI class DebugUIPayments: DebugUIPage, Dependencies { let name = "Payments" func section(thread: TSThread?) -> OWSTableSection? { var sectionItems = [OWSTableItem]() if let contactThread = thread as? TSContactThread { sectionItems.append(OWSTableItem(title: "Create all possible payment models") { [weak self] in self?.insertAllPaymentModelVariations(contactThread: contactThread) }) sectionItems.append(OWSTableItem(title: "Send tiny payments: 10") { Self.sendTinyPayments(contactThread: contactThread, count: 10) }) // For testing defragmentation. sectionItems.append(OWSTableItem(title: "Send tiny payments: 17") { Self.sendTinyPayments(contactThread: contactThread, count: 17) }) // For testing defragmentation. sectionItems.append(OWSTableItem(title: "Send tiny payments: 40") { Self.sendTinyPayments(contactThread: contactThread, count: 40) }) // For testing defragmentation. sectionItems.append(OWSTableItem(title: "Send tiny payments: 100") { Self.sendTinyPayments(contactThread: contactThread, count: 100) }) sectionItems.append(OWSTableItem(title: "Send tiny payments: 1000") { Self.sendTinyPayments(contactThread: contactThread, count: 1000) }) } sectionItems.append(OWSTableItem(title: "Delete all payment models") { [weak self] in self?.deleteAllPaymentModels() }) sectionItems.append(OWSTableItem(title: "Reconcile now") { Self.databaseStorage.write { transaction in Self.payments.scheduleReconciliationNow(transaction: transaction) } }) return OWSTableSection(title: "Payments", items: sectionItems) } // MARK: - private func insertAllPaymentModelVariations(contactThread: TSContactThread) { let address = contactThread.contactAddress let aci = address.aci! databaseStorage.write { transaction in let paymentAmounts = [ TSPaymentAmount(currency: .mobileCoin, picoMob: 1), TSPaymentAmount(currency: .mobileCoin, picoMob: 1000), TSPaymentAmount(currency: .mobileCoin, picoMob: 1000 * 1000), TSPaymentAmount(currency: .mobileCoin, picoMob: 1000 * 1000 * 1000), TSPaymentAmount(currency: .mobileCoin, picoMob: 1000 * 1000 * 1000 * 1000), TSPaymentAmount(currency: .mobileCoin, picoMob: 1000 * 1000 * 1000 * 1000 * 1000) ] func insertPaymentModel(paymentType: TSPaymentType, paymentState: TSPaymentState) -> TSPaymentModel { let mcReceiptData = Randomness.generateRandomBytes(32) var mcTransactionData: Data? if paymentState.isIncoming { } else { mcTransactionData = Randomness.generateRandomBytes(32) } var memoMessage: String? if Bool.random() { memoMessage = "Pizza Party 🍕" } // TODO: requestUuidString // TODO: isUnread // TODO: mcRecipientPublicAddressData // TODO: mobileCoin // TODO: feeAmount let mobileCoin = MobileCoinPayment(recipientPublicAddressData: nil, transactionData: mcTransactionData, receiptData: mcReceiptData, incomingTransactionPublicKeys: nil, spentKeyImages: nil, outputPublicKeys: nil, ledgerBlockTimestamp: 0, ledgerBlockIndex: 0, feeAmount: nil) let paymentModel = TSPaymentModel(paymentType: paymentType, paymentState: paymentState, paymentAmount: paymentAmounts.randomElement()!, createdDate: Date(), senderOrRecipientAci: paymentType.isUnidentified ? nil : AciObjC(aci), memoMessage: memoMessage, isUnread: false, interactionUniqueId: nil, mobileCoin: mobileCoin) do { try Self.paymentsHelper.tryToInsertPaymentModel(paymentModel, transaction: transaction) } catch { owsFailDebug("Error: \(error)") } return paymentModel } var paymentModel: TSPaymentModel // MARK: - Incoming paymentModel = insertPaymentModel(paymentType: .incomingPayment, paymentState: .incomingUnverified) paymentModel = insertPaymentModel(paymentType: .incomingPayment, paymentState: .incomingVerified) paymentModel = insertPaymentModel(paymentType: .incomingPayment, paymentState: .incomingComplete) // MARK: - Outgoing paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingUnsubmitted) paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingUnverified) paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingVerified) paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingSending) paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingSent) paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingComplete) // MARK: - Failures // TODO: We probably don't want to create .none and .unknown // paymentModel = insertPaymentModel(paymentState: .outgoingFailed) // paymentModel.update(withPaymentFailure: .none, // paymentState: .outgoingFailed, // transaction: transaction) // // paymentModel = insertPaymentModel(paymentState: .incomingFailed) // paymentModel.update(withPaymentFailure: .none, // paymentState: .incomingFailed, // transaction: transaction) // // paymentModel = insertPaymentModel(paymentState: .outgoingFailed) // paymentModel.update(withPaymentFailure: .unknown, // paymentState: .outgoingFailed, // transaction: transaction) // // paymentModel = insertPaymentModel(paymentState: .incomingFailed) // paymentModel.update(withPaymentFailure: .unknown, // paymentState: .incomingFailed, // transaction: transaction) paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingFailed) paymentModel.update(withPaymentFailure: .insufficientFunds, paymentState: .outgoingFailed, transaction: transaction) paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingFailed) paymentModel.update(withPaymentFailure: .validationFailed, paymentState: .outgoingFailed, transaction: transaction) paymentModel = insertPaymentModel(paymentType: .incomingPayment, paymentState: .incomingFailed) paymentModel.update(withPaymentFailure: .validationFailed, paymentState: .incomingFailed, transaction: transaction) paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingFailed) paymentModel.update(withPaymentFailure: .notificationSendFailed, paymentState: .outgoingFailed, transaction: transaction) paymentModel = insertPaymentModel(paymentType: .incomingPayment, paymentState: .incomingFailed) paymentModel.update(withPaymentFailure: .invalid, paymentState: .incomingFailed, transaction: transaction) paymentModel = insertPaymentModel(paymentType: .outgoingPayment, paymentState: .outgoingFailed) paymentModel.update(withPaymentFailure: .invalid, paymentState: .outgoingFailed, transaction: transaction) // MARK: - Unidentified paymentModel = insertPaymentModel(paymentType: .incomingUnidentified, paymentState: .incomingComplete) paymentModel = insertPaymentModel(paymentType: .outgoingUnidentified, paymentState: .outgoingComplete) } } private static func sendTinyPayments(contactThread: TSContactThread, count: UInt) { let picoMob = PaymentsConstants.picoMobPerMob + UInt64.random(in: 0..<1000) let paymentAmount = TSPaymentAmount(currency: .mobileCoin, picoMob: picoMob) let recipient = SendPaymentRecipientImpl.address(address: contactThread .contactAddress) firstly(on: DispatchQueue.global()) { () -> Promise in Self.paymentsImpl.prepareOutgoingPayment( recipient: recipient, paymentAmount: paymentAmount, memoMessage: "Tiny: \(count)", isOutgoingTransfer: false, canDefragment: false ) }.then(on: DispatchQueue.global()) { (preparedPayment: PreparedPayment) in Self.paymentsImpl.initiateOutgoingPayment(preparedPayment: preparedPayment) }.then(on: DispatchQueue.global()) { (paymentModel: TSPaymentModel) in Self.paymentsImpl.blockOnOutgoingVerification(paymentModel: paymentModel) }.done(on: DispatchQueue.global()) { _ in if count > 1 { Self.sendTinyPayments(contactThread: contactThread, count: count - 1) } else { Logger.info("Complete.") } }.catch(on: DispatchQueue.global()) { error in owsFailDebug("Error: \(error)") } } private func deleteAllPaymentModels() { databaseStorage.write { transaction in TSPaymentModel.anyRemoveAllWithInstantiation(transaction: transaction) } } } #endif