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

feat(EMI-2112): failed payment order history #10839

Merged
merged 7 commits into from
Sep 26, 2024

Conversation

erikdstock
Copy link
Contributor

@erikdstock erikdstock commented Sep 24, 2024

This PR resolves EMI-2112

Description

This PR adds a payment failed state to the order history along with a CTA to fix the failed payment. The work to expose that new payment failed state is still in progress, so there are no user-facing changes yet.

The getOrderStatus() method now returns a string | null rather than the string 'unknown'. Unhandled (null) values are not rendered in the app - this should never happen.

PR Checklist

  • I have tested my changes on iOS and Android.
  • I hid my changes behind a feature flag, or they don't need one.
  • I have included screenshots or videos, or I have not changed the UI.
  • I have added tests, or my changes don't require any.
  • I added an app state migration, or my changes do not require one.
  • I have documented any follow-up work that this PR will require, or it does not require any.
  • I have added a changelog entry below, or my changes do not require one.
📱Order history (failed payment state simulated)

Simulator Screenshot - iPhone 15 Pro - 2024-09-24 at 13 48 20

image

📱After clicking CTA

Simulator Screenshot - iPhone 15 Pro - 2024-09-24 at 13 48 16

image

To the reviewers 👀

  • I would like at least one of the reviewers to run this PR on the simulator or device.
Changelog updates

Changelog updates

Cross-platform user-facing changes

  • Enhancements to the order history screen when an order has a failed payment

iOS user-facing changes

Android user-facing changes

Dev changes

Need help with something? Have a look at our docs, or get in touch with us.

@erikdstock erikdstock self-assigned this Sep 24, 2024
src/app/utils/getOrderStatus.ts Outdated Show resolved Hide resolved
Comment on lines 21 to 24
case "PAYMENT_FAILED":
return "payment failed"
default:
return "unknown"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

1: Need to check where else this function is used to make sure this text won't render in an unexpected place.
2: The 'unknown' text feels not ideal. I considered suppressing it on the OrderHistoryRow and just rendering no status in those cases.

Copy link
Member

Choose a reason for hiding this comment

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

+1 to the second part. I do think that not displaying anything if state is not known is better than displaying 'unknown' which just look like a bug to me :)

@erikdstock erikdstock marked this pull request as ready for review September 24, 2024 19:59
@ArtsyOpenSource
Copy link
Contributor

ArtsyOpenSource commented Sep 24, 2024

This PR contains the following changes:

  • Cross-platform user-facing changes (Enhancements to the order history screen when an order has a failed payment)

Generated by 🚫 dangerJS against 2076a7b

@erikdstock erikdstock force-pushed the erik.emi2112-failed-payment-order-history branch from 6c34604 to cd17866 Compare September 24, 2024 20:58
oxaudo
oxaudo previously approved these changes Sep 24, 2024
Copy link
Member

@oxaudo oxaudo left a comment

Choose a reason for hiding this comment

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

This looks good to me. Only had couple of neat picky comments here. Though def defer to our app specialists to weight in on best practices.

const hasFailedPayment = orderStatus === "payment failed"
const isActive =
!hasFailedPayment &&
orderStatus &&
Copy link
Member

Choose a reason for hiding this comment

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

Interesting as I believe looking at getOrderStatus below that orderStatus will always be present as there is a default value. So checking it seems redundant?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, at first I was returning null as the default instead of unknown. Then decided that was out of scope since we are handling all of the known cases, so best to leave it as I found it.

: "black60"

const hasFailedPayment = orderStatus === "payment failed"
const isActive =
Copy link
Member

Choose a reason for hiding this comment

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

#minor - think this isActive is a bit misleading now as it controls if we are showing the link. And we are showing the button in more cases now just a different button. Maybe something more descriptive as showDetailsButton and showPaymentUpdateButton ?

const isActive =
!hasFailedPayment &&
orderStatus &&
!(["canceled", "refunded"] as string[]).includes(orderStatus)
Copy link
Member

Choose a reason for hiding this comment

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

if we are doing it array style here maybe we can just include payment failed here?

>
{orderStatus}
</Text>
{!!orderStatus && (
Copy link
Member

Choose a reason for hiding this comment

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

This would make sense if orderStatus could possibly be nil, but don't think is a valid pass now. Though I also think that not displaying anything for status is better than displaying the word 'unknown'

variant="fillDark"
onPress={() =>
navigate(`/orders/${order.internalID}/payment/new`, {
modal: true,
Copy link
Member

Choose a reason for hiding this comment

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

Interesting that there are modals in the app. Did not even know those exist.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a style of system-level navigation where instead of adding a view to a stack you are popping up a single web view that acts independently.

Comment on lines 21 to 24
case "PAYMENT_FAILED":
return "payment failed"
default:
return "unknown"
Copy link
Member

Choose a reason for hiding this comment

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

+1 to the second part. I do think that not displaying anything if state is not known is better than displaying 'unknown' which just look like a bug to me :)

Copy link
Member

@MrSltun MrSltun left a comment

Choose a reason for hiding this comment

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

Looks good! 🌟 I just added a few comments

Comment on lines 55 to 81
renderWithRelay({ CommerceOrder: () => mockOrder })

expect(tree.queryByTestId("artist-names")?.children[0]).toBe("Torbjørn Rødland")
expect(screen.getByTestId("artist-names")).toHaveTextContent("Torbjørn Rødland")
})

it("displays the partner name", () => {
const tree = renderWithRelay({ CommerceOrder: () => mockOrder })
renderWithRelay({ CommerceOrder: () => mockOrder })

expect(tree.queryByTestId("partner-name")?.children[0]).toBe("Andrea Festa Fine Art")
expect(screen.getByTestId("partner-name")).toHaveTextContent("Andrea Festa Fine Art")
})

it("displays the order creation date", () => {
const tree = renderWithRelay({ CommerceOrder: () => mockOrder })
renderWithRelay({ CommerceOrder: () => mockOrder })

expect(tree.queryByTestId("date")?.children[0]).toBe("5/18/2021")
expect(screen.queryByTestId("date")).toHaveTextContent("5/18/2021")
})

it("displays the price", () => {
const tree = renderWithRelay({ CommerceOrder: () => mockOrder })
renderWithRelay({ CommerceOrder: () => mockOrder })

expect(tree.queryByTestId("price")?.children[0]).toBe("11,200")
expect(screen.getByTestId("price")).toHaveTextContent("11,200")
})

it("displays the display state", () => {
const tree = renderWithRelay({ CommerceOrder: () => mockOrder })
renderWithRelay({ CommerceOrder: () => mockOrder })

expect(tree.queryByTestId("order-status")?.children[0]).toBe("pending")
expect(screen.getByTestId("order-status")).toHaveTextContent("pending")
Copy link
Member

Choose a reason for hiding this comment

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

Thank you for refactoring this! 🙏

src/app/Scenes/OrderHistory/OrderHistoryRow.tests.tsx Outdated Show resolved Hide resolved
const isActive =
!hasFailedPayment &&
orderStatus &&
!(["canceled", "refunded"] as string[]).includes(orderStatus)
Copy link
Member

Choose a reason for hiding this comment

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

Question: do we need the type casting as string[] here?

>
{orderStatus}
</Text>
{!!orderStatus && (
Copy link
Member

Choose a reason for hiding this comment

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

This will always return true, since getOrderStatus always returns a string that is not empty, maybe we could change the getOrderStatus return to give back null or an empty string?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the checks like this are because I was returning null at first, then decided not to mess with it. I'll take a closer look and see if that looks safe in the other places we use it.

@@ -1,6 +1,6 @@
import { CommerceOrderDisplayStateEnum } from "__generated__/OrderDetailsHeader_info.graphql"

export function getOrderStatus(displayState: CommerceOrderDisplayStateEnum) {
export function getOrderStatus(displayState: CommerceOrderDisplayStateEnum): string {
Copy link
Member

Choose a reason for hiding this comment

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

Question: Do we need the type for the return type to be string? I believe Typescript infers a type based on the returning value of the function on its own

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another relic of when I added types because it was string | null - when I changed it I also changed the types.

@MrSltun
Copy link
Member

MrSltun commented Sep 25, 2024

@erikdstock can we also test these changes on Android and add screenshots?

MrSltun
MrSltun previously approved these changes Sep 25, 2024
@erikdstock
Copy link
Contributor Author

@erikdstock can we also test these changes on Android and add screenshots?

I did test them on android - will see if i can get some screenshots together after responding to feedback

@erikdstock erikdstock dismissed stale reviews from MrSltun and oxaudo via 5183487 September 25, 2024 19:57
@erikdstock erikdstock force-pushed the erik.emi2112-failed-payment-order-history branch from a63ceef to 019047b Compare September 25, 2024 22:14
@erikdstock erikdstock merged commit 19ce985 into main Sep 26, 2024
7 checks passed
@erikdstock erikdstock deleted the erik.emi2112-failed-payment-order-history branch September 26, 2024 19:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants