fix(payment): allow Stripe after closing QRIS
This commit is contained in:
@@ -85,11 +85,14 @@ async function registerIndonesiaPaymentMocks(page: Page) {
|
||||
});
|
||||
await page.route("**/api/payment/create-order", async (route) => {
|
||||
createOrderCount += 1;
|
||||
const body = route.request().postDataJSON() as { planId: string };
|
||||
const body = route.request().postDataJSON() as {
|
||||
planId: string;
|
||||
payChannel: "ezpay" | "stripe";
|
||||
};
|
||||
const plan =
|
||||
idrPlans.plans.find((item) => item.planId === body.planId) ??
|
||||
idrGiftCatalog.plans.find((item) => item.planId === body.planId);
|
||||
const orderId = `order_qris_${body.planId}`;
|
||||
const orderId = `order_${body.payChannel}_${body.planId}`;
|
||||
orderStatuses.set(orderId, "pending");
|
||||
orderPlans.set(orderId, {
|
||||
planId: body.planId,
|
||||
@@ -98,7 +101,13 @@ async function registerIndonesiaPaymentMocks(page: Page) {
|
||||
await route.fulfill({
|
||||
json: apiEnvelope({
|
||||
orderId,
|
||||
payParams: {
|
||||
payParams:
|
||||
body.payChannel === "stripe"
|
||||
? {
|
||||
provider: "stripe",
|
||||
clientSecret: "pi_e2e_secret_mock",
|
||||
}
|
||||
: {
|
||||
provider: "ezpay",
|
||||
countryCode: "ID",
|
||||
channelCode: "ID_QRIS_DYNAMIC_QR",
|
||||
@@ -183,7 +192,7 @@ async function expectQrisOrder(
|
||||
await expect(
|
||||
dialog.getByRole("img", { name: "QRIS payment QR code" }),
|
||||
).toBeVisible();
|
||||
return `order_qris_${planId}`;
|
||||
return `order_ezpay_${planId}`;
|
||||
}
|
||||
|
||||
async function expectCheckoutButtonLayout(page: Page) {
|
||||
@@ -299,6 +308,47 @@ test("closing QRIS restores a resumable checkout without creating a duplicate or
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("closing QRIS allows switching to Stripe and creating a Stripe order", async ({
|
||||
page,
|
||||
}) => {
|
||||
const payment = await registerIndonesiaPaymentMocks(page);
|
||||
await prepareIndonesiaUser(page);
|
||||
await page.goto("/subscription?type=topup&character=elio");
|
||||
|
||||
await expectQrisOrder(
|
||||
page,
|
||||
/Pay and Top Up/i,
|
||||
"dol_1000",
|
||||
/Rp\s*88[.,]900/,
|
||||
);
|
||||
const dialog = page.getByRole("dialog", { name: "Scan to pay with QRIS" });
|
||||
await dialog.getByRole("button", { name: "Close" }).click();
|
||||
await expect(dialog).toBeHidden();
|
||||
|
||||
const stripeButton = page.getByRole("button", {
|
||||
name: "Stripe",
|
||||
exact: true,
|
||||
});
|
||||
await expect(stripeButton).toBeEnabled();
|
||||
await stripeButton.click();
|
||||
await expect(stripeButton).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(
|
||||
page.getByRole("button", { name: "Resume QRIS payment" }),
|
||||
).toHaveCount(0);
|
||||
|
||||
const stripeRequestPromise = page.waitForRequest(
|
||||
"**/api/payment/create-order",
|
||||
);
|
||||
await page.getByRole("button", { name: "Pay and Top Up" }).click();
|
||||
const stripeRequest = await stripeRequestPromise;
|
||||
expect(stripeRequest.postDataJSON()).toMatchObject({
|
||||
planId: "dol_1000",
|
||||
payChannel: "stripe",
|
||||
recipientCharacterId: "elio",
|
||||
});
|
||||
expect(payment.getCreateOrderCount()).toBe(2);
|
||||
});
|
||||
|
||||
test("payment issue submits Other to the feedback API without creating an order", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
@@ -286,7 +286,7 @@ export function SubscriptionScreen({
|
||||
<PaymentMethodSelector
|
||||
config={renderedPaymentMethodConfig}
|
||||
value={payment.payChannel}
|
||||
disabled={isPaymentBusy}
|
||||
disabled={payment.isCreatingOrder}
|
||||
caption={
|
||||
paymentMethodConfig.ezpayDisplayName === "QRIS"
|
||||
? "QRIS by default in Indonesia"
|
||||
|
||||
@@ -292,7 +292,7 @@ export function TipScreen({
|
||||
config={renderedPaymentMethodConfig}
|
||||
value={payment.payChannel}
|
||||
density="compact"
|
||||
disabled={isPaymentBusy}
|
||||
disabled={payment.isCreatingOrder}
|
||||
className={styles.paymentMethodSlot}
|
||||
analyticsKey="tip.payment_method"
|
||||
onChange={handlePaymentMethodChange}
|
||||
|
||||
@@ -301,6 +301,37 @@ describe("payment order flow", () => {
|
||||
actor.stop();
|
||||
});
|
||||
|
||||
it("can abandon a pending QRIS order and switch to Stripe", async () => {
|
||||
const createOrderSpy = vi.fn<CreateOrderSpy>();
|
||||
const actor = createActor(
|
||||
createTestPaymentMachine({ createOrderSpy, orderStatus: "pending" }),
|
||||
).start();
|
||||
await initialize(actor);
|
||||
actor.send({ type: "PaymentPayChannelChanged", payChannel: "ezpay" });
|
||||
actor.send({ type: "PaymentCreateOrderSubmitted" });
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("waitingForPayment"));
|
||||
|
||||
actor.send({ type: "PaymentPayChannelChanged", payChannel: "stripe" });
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("ready"));
|
||||
|
||||
expect(actor.getSnapshot().context).toMatchObject({
|
||||
payChannel: "stripe",
|
||||
currentOrderId: null,
|
||||
payParams: null,
|
||||
orderStatus: null,
|
||||
orderPollingStartedAt: null,
|
||||
});
|
||||
|
||||
actor.send({ type: "PaymentCreateOrderSubmitted" });
|
||||
await waitFor(actor, (snapshot) => snapshot.matches("waitingForPayment"));
|
||||
expect(createOrderSpy).toHaveBeenNthCalledWith(2, {
|
||||
planId: "vip_monthly",
|
||||
payChannel: "stripe",
|
||||
autoRenew: true,
|
||||
});
|
||||
actor.stop();
|
||||
});
|
||||
|
||||
it("moves to failed when polling reports failure", async () => {
|
||||
const actor = createActor(
|
||||
createTestPaymentMachine({ orderStatus: "failed" }),
|
||||
|
||||
@@ -265,6 +265,10 @@ const pollingOrderState = paymentMachineSetup.createStateConfig({
|
||||
},
|
||||
},
|
||||
on: {
|
||||
PaymentPayChannelChanged: {
|
||||
target: "ready",
|
||||
actions: "changePayChannel",
|
||||
},
|
||||
PaymentReset: {
|
||||
target: "ready",
|
||||
actions: "resetOrder",
|
||||
@@ -277,6 +281,10 @@ const waitingForPaymentState = paymentMachineSetup.createStateConfig({
|
||||
[POLL_DELAY_MS]: "pollingOrder",
|
||||
},
|
||||
on: {
|
||||
PaymentPayChannelChanged: {
|
||||
target: "ready",
|
||||
actions: "changePayChannel",
|
||||
},
|
||||
PaymentReset: {
|
||||
target: "ready",
|
||||
actions: "resetOrder",
|
||||
|
||||
Reference in New Issue
Block a user