fix(payment): allow Stripe after closing QRIS
Docker Image / Build and Push Docker Image (push) Successful in 2m4s
Docker Image / Build and Push Docker Image (push) Successful in 2m4s
This commit is contained in:
@@ -85,11 +85,14 @@ async function registerIndonesiaPaymentMocks(page: Page) {
|
|||||||
});
|
});
|
||||||
await page.route("**/api/payment/create-order", async (route) => {
|
await page.route("**/api/payment/create-order", async (route) => {
|
||||||
createOrderCount += 1;
|
createOrderCount += 1;
|
||||||
const body = route.request().postDataJSON() as { planId: string };
|
const body = route.request().postDataJSON() as {
|
||||||
|
planId: string;
|
||||||
|
payChannel: "ezpay" | "stripe";
|
||||||
|
};
|
||||||
const plan =
|
const plan =
|
||||||
idrPlans.plans.find((item) => item.planId === body.planId) ??
|
idrPlans.plans.find((item) => item.planId === body.planId) ??
|
||||||
idrGiftCatalog.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");
|
orderStatuses.set(orderId, "pending");
|
||||||
orderPlans.set(orderId, {
|
orderPlans.set(orderId, {
|
||||||
planId: body.planId,
|
planId: body.planId,
|
||||||
@@ -98,7 +101,13 @@ async function registerIndonesiaPaymentMocks(page: Page) {
|
|||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
json: apiEnvelope({
|
json: apiEnvelope({
|
||||||
orderId,
|
orderId,
|
||||||
payParams: {
|
payParams:
|
||||||
|
body.payChannel === "stripe"
|
||||||
|
? {
|
||||||
|
provider: "stripe",
|
||||||
|
clientSecret: "pi_e2e_secret_mock",
|
||||||
|
}
|
||||||
|
: {
|
||||||
provider: "ezpay",
|
provider: "ezpay",
|
||||||
countryCode: "ID",
|
countryCode: "ID",
|
||||||
channelCode: "ID_QRIS_DYNAMIC_QR",
|
channelCode: "ID_QRIS_DYNAMIC_QR",
|
||||||
@@ -183,7 +192,7 @@ async function expectQrisOrder(
|
|||||||
await expect(
|
await expect(
|
||||||
dialog.getByRole("img", { name: "QRIS payment QR code" }),
|
dialog.getByRole("img", { name: "QRIS payment QR code" }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
return `order_qris_${planId}`;
|
return `order_ezpay_${planId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function expectCheckoutButtonLayout(page: Page) {
|
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 });
|
).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 ({
|
test("payment issue submits Other to the feedback API without creating an order", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ export function SubscriptionScreen({
|
|||||||
<PaymentMethodSelector
|
<PaymentMethodSelector
|
||||||
config={renderedPaymentMethodConfig}
|
config={renderedPaymentMethodConfig}
|
||||||
value={payment.payChannel}
|
value={payment.payChannel}
|
||||||
disabled={isPaymentBusy}
|
disabled={payment.isCreatingOrder}
|
||||||
caption={
|
caption={
|
||||||
paymentMethodConfig.ezpayDisplayName === "QRIS"
|
paymentMethodConfig.ezpayDisplayName === "QRIS"
|
||||||
? "QRIS by default in Indonesia"
|
? "QRIS by default in Indonesia"
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ export function TipScreen({
|
|||||||
config={renderedPaymentMethodConfig}
|
config={renderedPaymentMethodConfig}
|
||||||
value={payment.payChannel}
|
value={payment.payChannel}
|
||||||
density="compact"
|
density="compact"
|
||||||
disabled={isPaymentBusy}
|
disabled={payment.isCreatingOrder}
|
||||||
className={styles.paymentMethodSlot}
|
className={styles.paymentMethodSlot}
|
||||||
analyticsKey="tip.payment_method"
|
analyticsKey="tip.payment_method"
|
||||||
onChange={handlePaymentMethodChange}
|
onChange={handlePaymentMethodChange}
|
||||||
|
|||||||
@@ -301,6 +301,37 @@ describe("payment order flow", () => {
|
|||||||
actor.stop();
|
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 () => {
|
it("moves to failed when polling reports failure", async () => {
|
||||||
const actor = createActor(
|
const actor = createActor(
|
||||||
createTestPaymentMachine({ orderStatus: "failed" }),
|
createTestPaymentMachine({ orderStatus: "failed" }),
|
||||||
|
|||||||
@@ -265,6 +265,10 @@ const pollingOrderState = paymentMachineSetup.createStateConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
on: {
|
on: {
|
||||||
|
PaymentPayChannelChanged: {
|
||||||
|
target: "ready",
|
||||||
|
actions: "changePayChannel",
|
||||||
|
},
|
||||||
PaymentReset: {
|
PaymentReset: {
|
||||||
target: "ready",
|
target: "ready",
|
||||||
actions: "resetOrder",
|
actions: "resetOrder",
|
||||||
@@ -277,6 +281,10 @@ const waitingForPaymentState = paymentMachineSetup.createStateConfig({
|
|||||||
[POLL_DELAY_MS]: "pollingOrder",
|
[POLL_DELAY_MS]: "pollingOrder",
|
||||||
},
|
},
|
||||||
on: {
|
on: {
|
||||||
|
PaymentPayChannelChanged: {
|
||||||
|
target: "ready",
|
||||||
|
actions: "changePayChannel",
|
||||||
|
},
|
||||||
PaymentReset: {
|
PaymentReset: {
|
||||||
target: "ready",
|
target: "ready",
|
||||||
actions: "resetOrder",
|
actions: "resetOrder",
|
||||||
|
|||||||
Reference in New Issue
Block a user