Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send ipp_channel metadata in payment intent from store management/POS use cases #14479

Merged
merged 7 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Hardware/Hardware.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
028C39E028255CFE0007BA25 /* Models+Copiable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028C39DF28255CFE0007BA25 /* Models+Copiable.generated.swift */; };
02B5147A28254ED300750B71 /* Codegen in Frameworks */ = {isa = PBXBuildFile; productRef = 02B5147928254ED300750B71 /* Codegen */; };
02FDAB102CEEF11F00B6C8AA /* PaymentChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FDAB0F2CEEF11D00B6C8AA /* PaymentChannel.swift */; };
030338102705F7D400764131 /* ReceiptTotalLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0303380F2705F7D400764131 /* ReceiptTotalLine.swift */; };
035DBA3929251ED6003E5125 /* CardReaderInputOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035DBA3829251ED6003E5125 /* CardReaderInputOptions.swift */; };
035DBA41292BBEB2003E5125 /* CardReaderDiscoveryMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035DBA40292BBEB2003E5125 /* CardReaderDiscoveryMethod.swift */; };
Expand Down Expand Up @@ -160,6 +161,7 @@
/* Begin PBXFileReference section */
02351FF56149ADCD11338B19 /* Pods-SampleReceiptPrinter.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleReceiptPrinter.release.xcconfig"; path = "Target Support Files/Pods-SampleReceiptPrinter/Pods-SampleReceiptPrinter.release.xcconfig"; sourceTree = "<group>"; };
028C39DF28255CFE0007BA25 /* Models+Copiable.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Models+Copiable.generated.swift"; sourceTree = "<group>"; };
02FDAB0F2CEEF11D00B6C8AA /* PaymentChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentChannel.swift; sourceTree = "<group>"; };
0303380F2705F7D400764131 /* ReceiptTotalLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptTotalLine.swift; sourceTree = "<group>"; };
035DBA3829251ED6003E5125 /* CardReaderInputOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderInputOptions.swift; sourceTree = "<group>"; };
035DBA40292BBEB2003E5125 /* CardReaderDiscoveryMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderDiscoveryMethod.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -469,6 +471,7 @@
D89B8F0B25DDC9D30001C726 /* ChargeStatus.swift */,
D88FDB2525DD21B000CB0DBD /* PaymentIntent.swift */,
E1E125AD26EB66B30068A9B0 /* FallibleCancelable.swift */,
02FDAB0F2CEEF11D00B6C8AA /* PaymentChannel.swift */,
D89B8F0125DDC7500001C726 /* PaymentIntentStatus.swift */,
D88FDB2325DD21B000CB0DBD /* PaymentIntentParameters.swift */,
03CF78D627DF9BE500523706 /* RefundParameters.swift */,
Expand Down Expand Up @@ -844,6 +847,7 @@
D845BDB8262D97B300A3E40F /* ReceiptDetails.swift in Sources */,
D88FDB2D25DD21B000CB0DBD /* PaymentIntentParameters.swift in Sources */,
028C39E028255CFE0007BA25 /* Models+Copiable.generated.swift in Sources */,
02FDAB102CEEF11F00B6C8AA /* PaymentChannel.swift in Sources */,
D89B8F0825DDC8F60001C726 /* Charge.swift in Sources */,
D845BDCC262D9B7700A3E40F /* CardBrand+Stripe.swift in Sources */,
D88FDB2E25DD21B000CB0DBD /* CardReaderEvent.swift in Sources */,
Expand Down
5 changes: 5 additions & 0 deletions Hardware/Hardware/CardReader/PaymentChannel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// The possible channels for a PaymentIntent initiated from the app.
public enum PaymentChannel {
case storeManagement
case pos
}
20 changes: 19 additions & 1 deletion Hardware/Hardware/CardReader/PaymentIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public extension PaymentIntent {
/// This key is also used by the plugin when it creates payment intents.
///
public static let paymentType = "payment_type"

/// The in-person payment channel, either `mobile_store_management` or `mobile_pos`.
/// This key is used for identifying the channel for in-person payments in analytics.
///
public static let channel = "ipp_channel"
}
}

Expand All @@ -106,7 +111,8 @@ public extension PaymentIntent {
siteURL: String? = nil,
orderID: Int64? = nil,
orderKey: String? = nil,
paymentType: PaymentTypes? = nil
paymentType: PaymentTypes? = nil,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit;

There's broken alignment for the rest of the parameters

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the indentation of these misaligned parameters in b601a7c

channel: PaymentChannel? = nil
) -> [String: String] {
var metadata = [String: String]()

Expand All @@ -119,6 +125,7 @@ public extension PaymentIntent {
}
metadata[PaymentIntent.MetadataKeys.orderKey] = orderKey
metadata[PaymentIntent.MetadataKeys.paymentType] = paymentType?.rawValue
metadata[PaymentIntent.MetadataKeys.channel] = channel?.metadataValue

return metadata
}
Expand All @@ -132,3 +139,14 @@ public extension PaymentIntent {
charges.first?.paymentMethod
}
}

private extension PaymentChannel {
var metadataValue: String {
switch self {
case .storeManagement:
return "mobile_store_management"
case .pos:
return "mobile_pos"
}
}
}
9 changes: 9 additions & 0 deletions Hardware/HardwareTests/PaymentIntentMetadataTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,13 @@ final class PaymentIntentMetadataTests: XCTestCase {
XCTAssertEqual(paymentType, "recurring")
XCTAssertEqual(metadata.count, 1)
}

func test_channel_parameter_sets_ipp_channel_metadata() throws {
// Given
let channelValues: [PaymentChannel] = [.storeManagement, .pos]
let metadataValues = channelValues.map { PaymentIntent.initMetadata(channel: $0)["ipp_channel"] }

// Then
XCTAssertEqual(metadataValues, ["mobile_store_management", "mobile_pos"])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import struct Yosemite.Order
import struct Yosemite.CardPresentPaymentsConfiguration
import protocol Yosemite.StoresManager
import enum Yosemite.CardPresentPaymentAction
import enum Yosemite.PaymentChannel

final class CardPresentPaymentCollectOrderPaymentUseCaseAdaptor {
private let currencyFormatter: CurrencyFormatter
Expand All @@ -29,7 +30,8 @@ final class CardPresentPaymentCollectOrderPaymentUseCaseAdaptor {
onboardingPresenter: CardPresentPaymentsOnboardingPresenting,
configuration: CardPresentPaymentsConfiguration,
alertsPresenter: CardPresentPaymentsAlertPresenterAdaptor,
paymentEventSubject: any Subject<CardPresentPaymentEvent, Never>) -> Task<CardPresentPaymentAdaptedCollectOrderPaymentResult, Error> {
paymentEventSubject: any Subject<CardPresentPaymentEvent, Never>,
channel: PaymentChannel) -> Task<CardPresentPaymentAdaptedCollectOrderPaymentResult, Error> {
return Task {
guard let formattedAmount = currencyFormatter.formatAmount(order.total, with: order.currency) else {
throw CardPresentPaymentServiceError.invalidAmount
Expand Down Expand Up @@ -58,6 +60,7 @@ final class CardPresentPaymentCollectOrderPaymentUseCaseAdaptor {

orderPaymentUseCase.collectPayment(
using: connectionMethod.discoveryMethod,
channel: channel,
onFailure: { error in
guard let continuation = nillableContinuation else { return }
nillableContinuation = nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import enum Yosemite.PaymentChannel
import struct Yosemite.Order
import Combine

Expand Down Expand Up @@ -31,11 +32,13 @@ protocol CardPresentPaymentFacade {
/// - Parameters:
/// - order: The order to collect payment for
/// - connectionMethod: Allows specifying Tap to Pay or bluetooth reader.
/// - channel: The channel where the payment is being collected.
/// - Returns: `CardPresentPaymentResult` for a success, or cancellation.
/// - Throws: `CardPresentPaymentError` for any failures.
/// - Output: publishes intermediate events on the `paymentEventPublisher` as required.
func collectPayment(for order: Order,
using connectionMethod: CardReaderConnectionMethod) async throws -> CardPresentPaymentResult
using connectionMethod: CardReaderConnectionMethod,
channel: PaymentChannel) async throws -> CardPresentPaymentResult

/// Cancels any in-progress payment.
func cancelPayment()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ final class CardPresentPaymentInvalidatablePaymentOrchestrator: PaymentCaptureOr
paymentGatewayAccount: PaymentGatewayAccount,
paymentMethodTypes: [String],
stripeSmallestCurrencyUnitMultiplier: Decimal,
channel: PaymentChannel,
onPreparingReader: @escaping () -> Void,
onWaitingForInput: @escaping (CardReaderInput) -> Void,
onProcessingMessage: @escaping () -> Void,
Expand All @@ -28,6 +29,7 @@ final class CardPresentPaymentInvalidatablePaymentOrchestrator: PaymentCaptureOr
paymentGatewayAccount: paymentGatewayAccount,
paymentMethodTypes: paymentMethodTypes,
stripeSmallestCurrencyUnitMultiplier: stripeSmallestCurrencyUnitMultiplier,
channel: channel,
onPreparingReader: onPreparingReader,
onWaitingForInput: onWaitingForInput,
onProcessingMessage: onProcessingMessage,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import Combine
import enum Yosemite.PaymentChannel
import struct Yosemite.Order

#if DEBUG
Expand All @@ -18,7 +19,9 @@ struct CardPresentPaymentPreviewService: CardPresentPaymentFacade {
// no-op
}

func collectPayment(for order: Yosemite.Order, using connectionMethod: CardReaderConnectionMethod) async throws -> CardPresentPaymentResult {
func collectPayment(for order: Yosemite.Order,
using connectionMethod: CardReaderConnectionMethod,
channel: PaymentChannel) async throws -> CardPresentPaymentResult {
.success(CardPresentPaymentTransaction(receiptURL: URL(string: "https://example.net/receipts/123")!))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import struct Yosemite.Order
import struct Yosemite.CardPresentPaymentsConfiguration
import struct Yosemite.CardReader
import enum Yosemite.CardPresentPaymentAction
import enum Yosemite.PaymentChannel
import protocol Yosemite.StoresManager

final class CardPresentPaymentService: CardPresentPaymentFacade {
Expand Down Expand Up @@ -124,7 +125,7 @@ final class CardPresentPaymentService: CardPresentPaymentFacade {
}

@MainActor
func collectPayment(for order: Order, using connectionMethod: CardReaderConnectionMethod) async throws -> CardPresentPaymentResult {
func collectPayment(for order: Order, using connectionMethod: CardReaderConnectionMethod, channel: PaymentChannel) async throws -> CardPresentPaymentResult {
paymentTask?.cancel()

// What happens if `start` gets called while there's a connection ongoing but not finished?
Expand All @@ -142,7 +143,8 @@ final class CardPresentPaymentService: CardPresentPaymentFacade {
onboardingPresenter: onboardingAdaptor,
configuration: cardPresentPaymentsConfiguration,
alertsPresenter: paymentAlertsPresenterAdaptor,
paymentEventSubject: paymentEventSubject)
paymentEventSubject: paymentEventSubject,
channel: channel)

self.paymentTask = paymentTask

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ extension PointOfSaleAggregateModel {

@MainActor
private func collectPayment(for order: Order) async throws {
_ = try await cardPresentPaymentService.collectPayment(for: order, using: .bluetooth)
_ = try await cardPresentPaymentService.collectPayment(for: order, using: .bluetooth, channel: .pos)
}

func cancelThenCollectPayment() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ protocol PaymentCaptureOrchestrating {
paymentGatewayAccount: PaymentGatewayAccount,
paymentMethodTypes: [String],
stripeSmallestCurrencyUnitMultiplier: Decimal,
channel: PaymentChannel,
onPreparingReader: @escaping () -> Void,
onWaitingForInput: @escaping (CardReaderInput) -> Void,
onProcessingMessage: @escaping () -> Void,
Expand Down Expand Up @@ -70,6 +71,7 @@ final class PaymentCaptureOrchestrator: PaymentCaptureOrchestrating {
paymentGatewayAccount: PaymentGatewayAccount,
paymentMethodTypes: [String],
stripeSmallestCurrencyUnitMultiplier: Decimal,
channel: PaymentChannel,
onPreparingReader: @escaping () -> Void,
onWaitingForInput: @escaping (CardReaderInput) -> Void,
onProcessingMessage: @escaping () -> Void,
Expand All @@ -88,7 +90,8 @@ final class PaymentCaptureOrchestrator: PaymentCaptureOrchestrating {
country: paymentGatewayAccount.country,
statementDescriptor: paymentGatewayAccount.statementDescriptor,
paymentMethodTypes: paymentMethodTypes,
stripeSmallestCurrencyUnitMultiplier: stripeSmallestCurrencyUnitMultiplier)
stripeSmallestCurrencyUnitMultiplier: stripeSmallestCurrencyUnitMultiplier,
channel: channel)

/// Briefly suppress pass (wallet) presentation so that the merchant doesn't attempt to pay for the buyer's order when the
/// reader begins to collect payment.
Expand Down Expand Up @@ -279,14 +282,16 @@ private extension PaymentCaptureOrchestrator {
country: String,
statementDescriptor: String?,
paymentMethodTypes: [String],
stripeSmallestCurrencyUnitMultiplier: Decimal) -> PaymentParameters {
stripeSmallestCurrencyUnitMultiplier: Decimal,
channel: PaymentChannel) -> PaymentParameters {
let metadata = PaymentIntent.initMetadata(
store: stores.sessionManager.defaultSite?.name,
customerName: buildCustomerNameFromBillingAddress(order.billingAddress),
customerEmail: order.billingAddress?.email,
siteURL: stores.sessionManager.defaultSite?.url,
orderID: order.orderID,
paymentType: PaymentIntent.PaymentTypes.single
paymentType: PaymentIntent.PaymentTypes.single,
channel: channel
)

return PaymentParameters(amount: orderTotal as Decimal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ final class OrderDetailsViewModel {
paymentLink: order.paymentURL,
total: order.total,
formattedTotal: formattedTotal,
flow: .orderPayment)
flow: .orderPayment,
channel: .storeManagement)
}

/// Helpers
Expand Down
Loading
Loading