Repository: woocommerce/woocommerce-ios
Pull request #15602: Shipping Labels: Add new view for requesting refund
Author: login: itsmeichigo
Labels: name: type: task
description: An internally driven task.
name: feature: shipping labels
description: Related to creating, ordering, or printing shipping labels.
Created: 2025-05-08T03:34:56Z | Merged: 2025-05-12T04:01:42Z

<!-- Remember about a good descriptive title. -->

Closes WOOMOB-447
<!-- Id number of the GitHub issue this PR addresses. -->
<!-- Part of: # -> use this if your PR is one of many related to one issue -->

## Description
<!-- Take the time to write a good summary. Why is it needed? What does it do? When fixing bugs try to avoid just writing “See original issue” – clarify what the problem was and how you’ve fixed it. -->

This PR adds a new UI for requesting refund for shipping labels and navigation to the new screen.

## Steps to reproduce
<!-- Step-by-step testing instructions. For new user flows, consider instead stating the goal of the workflow and see if your PR reviewer can accomplish the workflow without specific steps! -->

1. Log in to a test store with the Woo Shipping plugin set up.
2. Navigate to the Orders tab and select a paid order with physical products.
3. Create a shipping label for the order if there isn't one yet.
4. Open the created shipping label and select Request refund.
5. Confirm that a new screen is displayed with refund details. The screen should match the design.

## Testing information
<!-- This is your opportunity to break out individual scenarios that need testing (when necessary) and/or include a checklist for the reviewer to go through. Consider documenting the following from your own completed testing: devices used, alternate workflows, edge cases, affected areas, critical flows, areas not tested, and any remaining unknowns. Provide feedback on this new section of the PR template through Sept 30, 2024 to Apps Quality; additional context here: https://woomobilep2.wordpress.com/2024/05/06/woocommerce-mobile-quality-report-march-april/#comment-12036 -->

Tested the UI on Xcode preview for different screen sizes/text sizes/light and dark modes.
Tested the navigation on simulator iPhone 16 Pro iOS 18.2.

## Screenshots
<!-- Include before and after images or gifs when appropriate. -->

<img src="https://github.com/user-attachments/assets/361e6001-2c1d-48df-b3b3-93b95b7892bb" width=320 />


---
- [x] I have considered if this change warrants user-facing release notes and have added them to `RELEASE-NOTES.txt` if necessary.

## Reviewer (or Author, in the case of optional code reviews):

Please make sure these conditions are met before approving the PR, or request changes if the PR needs improvement:

- [ ] The PR is small and has a clear, single focus, or a valid explanation is provided in the description. If needed, please request to split it into smaller PRs.
- [ ] Ensure Adequate Unit Test Coverage: The changes are reasonably covered by unit tests or an explanation is provided in the PR description.
- [ ] Manual Testing: The author listed all the tests they ran, including smoke tests when needed (e.g., for refactorings). The reviewer confirmed that the PR works as expected on all devices (phone/tablet) and no regressions are added.



Diff:
diff --git a/Networking/Networking/Model/ShippingLabel/ShippingLabel.swift b/Networking/Networking/Model/ShippingLabel/ShippingLabel.swift
index 3a7d5b36156..e19554afdd5 100644
--- a/Networking/Networking/Model/ShippingLabel/ShippingLabel.swift
+++ b/Networking/Networking/Model/ShippingLabel/ShippingLabel.swift
@@ -178,3 +178,11 @@ enum ShippingLabelDecodingError: Error {
     case missingSiteID
     case missingOrderID
 }
+
+public extension ShippingLabel {
+    /// Following logic in the plugin
+    /// https://github.com/woocommerce/woocommerce-shipping/blob/0f67a1eb349cbe90ce471d88c7b31bd4950d6744/client/utils/label/refund.ts#L5
+    var refundDuration: Int {
+        carrierID == "dhlexpress" ? 31 : 14
+    }
+}
diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/Refund/WooShippingRefundView.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/Refund/WooShippingRefundView.swift
new file mode 100644
index 00000000000..219aa70af7d
--- /dev/null
+++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/Refund/WooShippingRefundView.swift	
@@ -0,0 +1,109 @@
+import SwiftUI
+
+/// View for requesting refund for a shipping label.
+///
+struct WooShippingRefundView: View {
+    @Environment(\.dismiss) var dismiss
+
+    let viewModel: WooShippingRefundViewModel
+
+    var body: some View {
+        ScrollableVStack(alignment: .leading,
+                         padding: Layout.contentPadding,
+                         spacing: Layout.contentSpacing) {
+            HStack {
+                Button(Localization.cancelButton) {
+                    dismiss()
+                }
+                Spacer()
+            }
+
+            Text(Localization.title)
+                .font(.title2)
+                .bold()
+                .padding(.vertical, Layout.titleExtraPadding)
+
+            Text(String.localizedStringWithFormat(Localization.description, viewModel.refundDuration))
+
+            Text(Localization.purchaseDate).bold() +
+            Text(" ") +
+            Text(viewModel.formattedPurchaseDate)
+
+            Text(Localization.refundAmount).bold() +
+            Text(" ") +
+            Text(viewModel.formattedRefundAmount)
+
+            Text(Localization.note)
+                .italic()
+            Spacer()
+        }
+        .font(.subheadline)
+        .multilineTextAlignment(.leading)
+        .safeAreaInset(edge: .bottom) {
+            VStack {
+                Button(String.localizedStringWithFormat(Localization.submitButton, "-" + viewModel.formattedRefundAmount)) {
+                    viewModel.submitRefundRequest()
+                }
+                .buttonStyle(PrimaryLoadingButtonStyle(isLoading: false))
+                .padding()
+            }
+            .background(Color(.systemBackground))
+        }
+    }
+}
+
+private extension WooShippingRefundView {
+    enum Layout {
+        static let contentSpacing = CGFloat(8)
+        static let titleExtraPadding = CGFloat(16)
+        static let contentPadding = CGFloat(16)
+    }
+
+    enum Localization {
+        static let title = NSLocalizedString(
+            "wooShippingRefundView.title",
+            value: "Request a shipping label refund",
+            comment: "title on the Request shipping label refund view"
+        )
+        static let description = NSLocalizedString(
+            "wooShippingRefundView.description",
+            value: "Request a refund for your unused shipping label. " +
+            "The refund process for the shipping label will begin immediately and " +
+            "is typically completed within %1$d business days.",
+            comment: "Description on the Request shipping label refund view. " +
+            "The placeholder is the number of day to process the refund."
+        )
+        static let purchaseDate = NSLocalizedString(
+            "wooShippingRefundView.purchaseDate",
+            value: "Purchase date:",
+            comment: "Purchase date label on the Request shipping label refund view."
+        )
+        static let refundAmount = NSLocalizedString(
+            "wooShippingRefundView.refundAmount",
+            value: "Amount eligible for refund:",
+            comment: "Refund amount label on the Request shipping label refund view."
+        )
+        static let note = NSLocalizedString(
+            "wooShippingRefundView.note",
+            value: "Please note that this refund request applies only to the unused " +
+            "shipping label and will not affect the order itself.",
+            comment: "Note on the Request shipping label refund view"
+        )
+        static let cancelButton = NSLocalizedString(
+            "wooShippingRefundView.cancelButton",
+            value: "Cancel",
+            comment: "Button to dismiss the Request shipping label refund view"
+        )
+        static let submitButton = NSLocalizedString(
+            "wooShippingRefundView.submitButton",
+            value: "Refund Label (%1$@)",
+            comment: "Button to submit refund request the Request shipping label refund view"
+        )
+    }
+}
+
+#Preview {
+    WooShippingRefundView(viewModel: .init(refundableAmount: 11.33,
+                                           refundDuration: 14,
+                                           purchaseDate: Date()))
+}
diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/Refund/WooShippingRefundViewModel.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/Refund/WooShippingRefundViewModel.swift
new file mode 100644
index 00000000000..757795270a6
--- /dev/null
+++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/Refund/WooShippingRefundViewModel.swift	
@@ -0,0 +1,31 @@
+import Foundation
+import Yosemite
+import WooFoundation
+
+/// View model for `WooShippingRefundView`
+///
+final class WooShippingRefundViewModel {
+    private let stores: StoresManager
+
+    let formattedPurchaseDate: String
+    let formattedRefundAmount: String
+    let refundDuration: Int
+
+    init(refundableAmount: Double,
+         refundDuration: Int,
+         purchaseDate: Date,
+         stores: StoresManager = ServiceLocator.stores,
+         currencySettings: CurrencySettings = ServiceLocator.currencySettings) {
+        self.stores = stores
+        self.refundDuration = refundDuration
+
+        let currencyFormatter = CurrencyFormatter(currencySettings: currencySettings)
+        formattedRefundAmount = currencyFormatter.formatAmount(refundableAmount.description) ?? refundableAmount.description
+
+        formattedPurchaseDate = purchaseDate.formatted(date: .abbreviated, time: .omitted)
+    }
+
+    func submitRefundRequest() {
+        // TODO: integrate refund API
+    }
+}
diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/ShipmentDetails/WooShippingShipmentDetailsView.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/ShipmentDetails/WooShippingShipmentDetailsView.swift
index c497ea0f332..fe9bc618f2e 100644
--- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/ShipmentDetails/WooShippingShipmentDetailsView.swift	
+++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/ShipmentDetails/WooShippingShipmentDetailsView.swift	
@@ -2,6 +2,7 @@ import SwiftUI
 
 struct WooShippingShipmentDetailsView: View {
     @ObservedObject private var viewModel: WooShippingShipmentDetailsViewModel
+    @State private var showingRefundRequest = false
 
     init(viewModel: WooShippingShipmentDetailsViewModel) {
         self.viewModel = viewModel
@@ -10,7 +11,9 @@ struct WooShippingShipmentDetailsView: View {
     var body: some View {
         VStack(spacing: Layout.verticalSpacing) {
             if viewModel.canViewLabel, let postPurchase = viewModel.postPurchase {
-                WooShippingPostPurchaseView(viewModel: postPurchase)
+                WooShippingPostPurchaseView(viewModel: postPurchase, onRefundRequest: {
+                    showingRefundRequest = true
+                })
             }
 
             WooShippingItems(itemsCountLabel: viewModel.itemsCountLabel,
@@ -37,6 +40,11 @@ struct WooShippingShipmentDetailsView: View {
                 WooShippingPackageAndRatePlaceholder(onSelectPackage: viewModel.selectPackage)
             }
         }
+        .sheet(isPresented: $showingRefundRequest) {
+            if let refundViewModel = viewModel.refundViewModel {
+                WooShippingRefundView(viewModel: refundViewModel)
+            }
+        }
     }
 }
 
diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/ShipmentDetails/WooShippingShipmentDetailsViewModel.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/ShipmentDetails/WooShippingShipmentDetailsViewModel.swift
index ca0cee7a25c..9fd52f43fe5 100644
--- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/ShipmentDetails/WooShippingShipmentDetailsViewModel.swift	
+++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/ShipmentDetails/WooShippingShipmentDetailsViewModel.swift	
@@ -129,6 +129,15 @@ final class WooShippingShipmentDetailsViewModel: ObservableObject {
                                     customsForm: customsForm)
     }
 
+    var refundViewModel: WooShippingRefundViewModel? {
+        guard let shippingLabel else {
+            return nil
+        }
+        return WooShippingRefundViewModel(refundableAmount: shippingLabel.refundableAmount,
+                                          refundDuration: shippingLabel.refundDuration,
+                                          purchaseDate: shippingLabel.dateCreated)
+    }
+
     private var debounceDuration: Double
 
     init(order: Order,
diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Post-Purchase/WooShippingPostPurchaseView.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Post-Purchase/WooShippingPostPurchaseView.swift
index e7d63b1975a..acee1087f0c 100644
--- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Post-Purchase/WooShippingPostPurchaseView.swift	
+++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Post-Purchase/WooShippingPostPurchaseView.swift	
@@ -3,6 +3,8 @@ import SwiftUI
 struct WooShippingPostPurchaseView: View {
     @ObservedObject private(set) var viewModel: WooShippingPostPurchaseViewModel
 
+    let onRefundRequest: () -> Void
+
     @State private var isPrintingLabel = false
     @State private var showingLabelPrintingError = false
 
@@ -105,7 +107,7 @@ struct WooShippingPostPurchaseView: View {
                         }
                     }
                     Button {
-                        // TODO: Request label refund
+                        onRefundRequest()
                     } label: {
                         Text(Localization.requestRefund)
                     }
@@ -263,7 +265,8 @@ private extension WooShippingPostPurchaseView {
                                                                             labelSizes: [.label, .legal, .a4],
                                                                             trackingURL: URL(string: "https://woocommerce.com"),
                                                                             pickupURL: WooShippingCarrier.usps.pickupURL,
-                                                                            commercialInvoiceURL: URL(string: "https://example.com")))
+                                                                            commercialInvoiceURL: URL(string: "https://example.com")),
+                                onRefundRequest: {})
         .padding()
 }
 
@@ -273,6 +276,7 @@ private extension WooShippingPostPurchaseView {
                                                                             labelSizes: [.label, .legal, .a4],
                                                                             trackingURL: nil,
                                                                             pickupURL: nil,
-                                                                            commercialInvoiceURL: nil))
+                                                                            commercialInvoiceURL: nil),
+                                onRefundRequest: {})
         .padding()
 }
diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj
index ea1675db08c..8e31adec572 100644
--- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj
+++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj
@@ -2737,6 +2737,8 @@
 		DE653EAF2A72577500937C78 /* WooAnalyticsEvent+TestOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE653EAE2A72577500937C78 /* WooAnalyticsEvent+TestOrder.swift */; };
 		DE65C1F72C48E7DC003EF8D1 /* SupportButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE65C1F62C48E7DC003EF8D1 /* SupportButton.swift */; };
 		DE65C1F92C48E89C003EF8D1 /* WebCheckoutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE65C1F82C48E89C003EF8D1 /* WebCheckoutViewModel.swift */; };
+		DE6627E42DCC503E0068E12E /* WooShippingRefundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6627E32DCC503E0068E12E /* WooShippingRefundView.swift */; };
+		DE6627E62DCC52230068E12E /* WooShippingRefundViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6627E52DCC52180068E12E /* WooShippingRefundViewModel.swift */; };
 		DE66C56F2978F24200DAA978 /* ApplicationPasswordDisabledViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE66C56E2978F24200DAA978 /* ApplicationPasswordDisabledViewModelTests.swift */; };
 		DE67D46726B98FD000EFE8DB /* Publisher+WithLatestFrom.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE67D46626B98FD000EFE8DB /* Publisher+WithLatestFrom.swift */; };
 		DE67D46926BAA82600EFE8DB /* Publisher+WithLatestFromTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE67D46826BAA82600EFE8DB /* Publisher+WithLatestFromTests.swift */; };
@@ -5997,6 +5999,8 @@
 		DE653EAE2A72577500937C78 /* WooAnalyticsEvent+TestOrder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WooAnalyticsEvent+TestOrder.swift"; sourceTree = "<group>"; };
 		DE65C1F62C48E7DC003EF8D1 /* SupportButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportButton.swift; sourceTree = "<group>"; };
 		DE65C1F82C48E89C003EF8D1 /* WebCheckoutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebCheckoutViewModel.swift; sourceTree = "<group>"; };
+		DE6627E32DCC503E0068E12E /* WooShippingRefundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooShippingRefundView.swift; sourceTree = "<group>"; };
+		DE6627E52DCC52180068E12E /* WooShippingRefundViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooShippingRefundViewModel.swift; sourceTree = "<group>"; };
 		DE66C56E2978F24200DAA978 /* ApplicationPasswordDisabledViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationPasswordDisabledViewModelTests.swift; sourceTree = "<group>"; };
 		DE67D46626B98FD000EFE8DB /* Publisher+WithLatestFrom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+WithLatestFrom.swift"; sourceTree = "<group>"; };
 		DE67D46826BAA82600EFE8DB /* Publisher+WithLatestFromTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+WithLatestFromTests.swift"; sourceTree = "<group>"; };
@@ -12630,6 +12634,7 @@
 		CEAB739A2C81E3A000A7EB39 /* WooShipping Create Shipping Labels */ = {
 			isa = PBXGroup;
 			children = (
+				DE6627E22DCC50000068E12E /* Refund */,
 				DEDC5F5C2D9E7CB8005E38BD /* ShipmentDetails */,
 				EE7E75A62D83EAD200E6FF5B /* WooShipping Split Shipments */,
 				B90DD08C2D12FA6600EFC06A /* WooShipping Customs */,
@@ -13472,6 +13477,15 @@
 			path = WPComLogin;
 			sourceTree = "<group>";
 		};
+		DE6627E22DCC50000068E12E /* Refund */ = {
+			isa = PBXGroup;
+			children = (
+				DE6627E32DCC503E0068E12E /* WooShippingRefundView.swift */,
+				DE6627E52DCC52180068E12E /* WooShippingRefundViewModel.swift */,
+			);
+			path = Refund;
+			sourceTree = "<group>";
+		};
 		DE68979D2A8F7C8C00154588 /* AccountSettings */ = {
 			isa = PBXGroup;
 			children = (
@@ -16081,6 +16095,7 @@
 				CC254F3426C4113B005F3C82 /* ShippingLabelPackageSelection.swift in Sources */,
 				AECD57D226DFDF7500A3B580 /* EditOrderAddressForm.swift in Sources */,
 				26C6E8EC26E8FF4800C7BB0F /* LazyNavigationLink.swift in Sources */,
+				DE6627E62DCC52230068E12E /* WooShippingRefundViewModel.swift in Sources */,
 				45E9A6E724DAE23300A600E8 /* ProductReviewsViewModel.swift in Sources */,
 				DEC51A9D274F8528009F3DF4 /* JCPJetpackInstallStepsViewModel.swift in Sources */,
 				455DC3A327393C7E00D4644C /* OrderDatesFilterViewController.swift in Sources */,
@@ -16230,6 +16245,7 @@
 				456BEFB626D912EC002AC16C /* AuthenticatedWebView.swift in Sources */,
 				310D1B482734919E001D55B4 /* InPersonPaymentsLiveSiteInTestModeView.swift in Sources */,
 				AE1CC33829129A010021C8EF /* LinkBehavior.swift in Sources */,
+				DE6627E42DCC503E0068E12E /* WooShippingRefundView.swift in Sources */,
 				209CA0EE2B50070D0073D1AC /* WooTabContainerController.swift in Sources */,
 				26F65C9825DEDAF0008FAE29 /* GenerateVariationUseCase.swift in Sources */,
 				747AA08B2107CF8D0047A89B /* TracksProvider.swift in Sources */,


Reviews:
user: login: itsmeichigo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T03:58:08Z
user: login: RafaelKayumov
body: Works as described. Left a couple of non-critical comments.

I also noticed that there is no "handle" on the top of the refund sheet:

![Снимок экрана 2025-05-09 в 13 25 21](https://github.com/user-attachments/assets/7f184d78-46bc-4017-8607-7691392e7730)

![Снимок экрана 2025-05-09 в 13 25 26](https://github.com/user-attachments/assets/93214a60-f3ce-4ea9-81b4-ec28b6089f76)

It also feels like the "Cancel" button should be a bit larger.

![Снимок экрана 2025-05-09 в 13 27 40](https://github.com/user-attachments/assets/4ae1327c-c231-4fbe-961d-a27c03a465a4)

![Снимок экрана 2025-05-09 в 13 27 24](https://github.com/user-attachments/assets/7f769561-961f-415f-9500-b0347fbff150)


state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T10:29:54Z
user: login: itsmeichigo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T03:29:07Z
user: login: itsmeichigo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T03:30:10Z

Review comments:
diff_hunk: @@ -178,3 +178,11 @@ enum ShippingLabelDecodingError: Error {
     case missingSiteID
     case missingOrderID
 }
+
+public extension ShippingLabel {
+    /// Following logic in the plugin
+    /// https://github.com/woocommerce/woocommerce-shipping/blob/0f67a1eb349cbe90ce471d88c7b31bd4950d6744/client/utils/label/refund.ts#L5
+    var refundDuration: Int {
+        carrierID == "dhlexpress" ? 31 : 14
path: Networking/Networking/Model/ShippingLabel/ShippingLabel.swift
user: login: itsmeichigo
body: This hardcoded value will be replaced in #15603. Unit tests will be added there as well.
created_at: 2025-05-09T03:58:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,109 @@
+import SwiftUI
+
+/// View for requesting refund for a shipping label.
+///
+struct WooShippingRefundView: View {
+    @Environment(\.dismiss) var dismiss
+
+    let viewModel: WooShippingRefundViewModel
+
+    var body: some View {
+        ScrollableVStack(alignment: .leading,
+                         padding: Layout.contentPadding,
+                         spacing: Layout.contentSpacing) {
+            HStack {
+                Button(Localization.cancelButton) {
+                    dismiss()
+                }
+                Spacer()
+            }
+
+            Text(Localization.title)
+                .font(.title2)
+                .bold()
+                .padding(.vertical, Layout.titleExtraPadding)
+
+            Text(String.localizedStringWithFormat(Localization.description, viewModel.refundDuration))
+
+            Text(Localization.purchaseDate).bold() +
+            Text(" ") +
+            Text(viewModel.formattedPurchaseDate)
+
+            Text(Localization.refundAmount).bold() +
+            Text(" ") +
+            Text(viewModel.formattedRefundAmount)
+
+            Text(Localization.note)
+                .italic()
+            Spacer()
+        }
+        .font(.subheadline)
+        .multilineTextAlignment(.leading)
+        .safeAreaInset(edge: .bottom) {
+            VStack {
path: WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/Refund/WooShippingRefundView.swift
user: login: RafaelKayumov
body: Is `VStack` and background necessary here?
created_at: 2025-05-09T10:22:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,109 @@
+import SwiftUI
+
+/// View for requesting refund for a shipping label.
+///
+struct WooShippingRefundView: View {
+    @Environment(\.dismiss) var dismiss
+
+    let viewModel: WooShippingRefundViewModel
+
+    var body: some View {
+        ScrollableVStack(alignment: .leading,
+                         padding: Layout.contentPadding,
+                         spacing: Layout.contentSpacing) {
+            HStack {
+                Button(Localization.cancelButton) {
+                    dismiss()
+                }
+                Spacer()
+            }
+
+            Text(Localization.title)
+                .font(.title2)
+                .bold()
+                .padding(.vertical, Layout.titleExtraPadding)
+
+            Text(String.localizedStringWithFormat(Localization.description, viewModel.refundDuration))
+
+            Text(Localization.purchaseDate).bold() +
+            Text(" ") +
+            Text(viewModel.formattedPurchaseDate)
+
+            Text(Localization.refundAmount).bold() +
+            Text(" ") +
+            Text(viewModel.formattedRefundAmount)
+
+            Text(Localization.note)
+                .italic()
+            Spacer()
+        }
+        .font(.subheadline)
+        .multilineTextAlignment(.leading)
+        .safeAreaInset(edge: .bottom) {
+            VStack {
+                Button(String.localizedStringWithFormat(Localization.submitButton, "-" + viewModel.formattedRefundAmount)) {
path: WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/Refund/WooShippingRefundView.swift
user: login: RafaelKayumov
body: The button's rendered height is ~44, but the designed height is 48.

![Снимок экрана 2025-05-09 в 13 12 24](https://github.com/user-attachments/assets/236fadc6-f52e-4520-823a-771e49a30a0e)

created_at: 2025-05-09T10:23:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,109 @@
+import SwiftUI
+
+/// View for requesting refund for a shipping label.
+///
+struct WooShippingRefundView: View {
+    @Environment(\.dismiss) var dismiss
+
+    let viewModel: WooShippingRefundViewModel
+
+    var body: some View {
+        ScrollableVStack(alignment: .leading,
+                         padding: Layout.contentPadding,
+                         spacing: Layout.contentSpacing) {
+            HStack {
+                Button(Localization.cancelButton) {
+                    dismiss()
+                }
+                Spacer()
+            }
+
+            Text(Localization.title)
+                .font(.title2)
+                .bold()
+                .padding(.vertical, Layout.titleExtraPadding)
+
+            Text(String.localizedStringWithFormat(Localization.description, viewModel.refundDuration))
+
+            Text(Localization.purchaseDate).bold() +
+            Text(" ") +
+            Text(viewModel.formattedPurchaseDate)
+
+            Text(Localization.refundAmount).bold() +
+            Text(" ") +
+            Text(viewModel.formattedRefundAmount)
+
+            Text(Localization.note)
+                .italic()
+            Spacer()
+        }
+        .font(.subheadline)
+        .multilineTextAlignment(.leading)
+        .safeAreaInset(edge: .bottom) {
+            VStack {
path: WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/Refund/WooShippingRefundView.swift
user: login: itsmeichigo
body: The background is necessary to embed the button in a footer view that hides the text content when it's scrolled below the button. Since there's not much text here, the case happens only when a large font size is selected.
created_at: 2025-05-12T03:29:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,109 @@
+import SwiftUI
+
+/// View for requesting refund for a shipping label.
+///
+struct WooShippingRefundView: View {
+    @Environment(\.dismiss) var dismiss
+
+    let viewModel: WooShippingRefundViewModel
+
+    var body: some View {
+        ScrollableVStack(alignment: .leading,
+                         padding: Layout.contentPadding,
+                         spacing: Layout.contentSpacing) {
+            HStack {
+                Button(Localization.cancelButton) {
+                    dismiss()
+                }
+                Spacer()
+            }
+
+            Text(Localization.title)
+                .font(.title2)
+                .bold()
+                .padding(.vertical, Layout.titleExtraPadding)
+
+            Text(String.localizedStringWithFormat(Localization.description, viewModel.refundDuration))
+
+            Text(Localization.purchaseDate).bold() +
+            Text(" ") +
+            Text(viewModel.formattedPurchaseDate)
+
+            Text(Localization.refundAmount).bold() +
+            Text(" ") +
+            Text(viewModel.formattedRefundAmount)
+
+            Text(Localization.note)
+                .italic()
+            Spacer()
+        }
+        .font(.subheadline)
+        .multilineTextAlignment(.leading)
+        .safeAreaInset(edge: .bottom) {
+            VStack {
+                Button(String.localizedStringWithFormat(Localization.submitButton, "-" + viewModel.formattedRefundAmount)) {
path: WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/Refund/WooShippingRefundView.swift
user: login: itsmeichigo
body: I'd prefer keeping the button as-is to make it consistent with other primary buttons in the app.
created_at: 2025-05-12T03:30:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: wpmobilebot
created_at: 2025-05-08T03:44:17Z
body: <!-- REUSE_ID: prototype-build-link -->

<p><img align='top' src='https://raw.githubusercontent.com/buildkite/emojis/main/img-buildkite-64/firebase.png' width='20px' alt='App Icon' />📲 You can test the changes from this Pull Request in <b>WooCommerce iOS Prototype</b> by scanning the QR code below to install the corresponding build.</p>
<table>
<tr>
  <td rowspan='6' width='260px'><img src='https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Fappdistribution.firebase.google.com%2Ftesterapps%2F1%3A124902176124%3Aios%3A02259de1e7c42b291620f9%2Freleases%2F2h92hqo2icuco%3Futm_source%3Dfastlane' width='250' height='250' /></td>
  <td><b>App Name</b></td><td>WooCommerce iOS Prototype</td>
</tr>
<tr><td><b>Build Number</b></td><td><code>29716</code></td></tr>
<tr><td><b>Version</b></td><td><code>PR #15602</code></td></tr>
<tr><td><b>Bundle ID</b></td><td><code>com.automattic.alpha.woocommerce</code></td></tr>
<tr><td><b>Commit</b></td><td>2fcb342706ad74d1471f97818739e1da12922cae</td></tr>
<tr><td><b>Installation URL</b></td><td><a href='https://appdistribution.firebase.google.com/testerapps/1:124902176124:ios:02259de1e7c42b291620f9/releases/2h92hqo2icuco?utm_source=fastlane'>2h92hqo2icuco</a></td></tr>
</table>
<em>Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.</em>

author_association: COLLABORATOR
user: login: itsmeichigo
created_at: 2025-05-12T04:01:30Z
body: Thanks Rafael for the reviews!

> there is no "handle" on the top of the refund sheet:

I believe that the handle should only be used for bottom sheets, and it indicates that a sheet can be dragged up/down to enlarge or collapse. In this case, we're using a full-screen modal, so it makes more sense not to add the handle. 

> It also feels like the "Cancel" button should be a bit larger.

I'm using the same font as in the design, and the only difference is the 2pt padding. I'll keep the button as-is for now.
author_association: CONTRIBUTOR