Skip to content

PipraPay

This guide details the backend configuration and storefront integration for the PipraPay provider.

Add the module to your medusa-config.ts file:

medusa-config.ts
modules: [
{
resolve: "@medusajs/medusa/payment",
options: {
providers: [
{
resolve: "@crza69/medusa-plugins/providers/piprapay",
id: "piprapay",
options: {
apiKey: process.env.PIPRAPAY_API_KEY,
baseUrl: process.env.PIPRAPAY_URL,
backendUrl: process.env.ADMIN_URL,
},
},
],
},
},
],

The following steps are based on the Next.js Starter Medusa.

You will be modifying the following files:

  • medusa-config.ts - src - app - [countrycode] - cart - complete - page.tsx - lib - constants.tsx - data.ts - modules - checkout - components - payment-button - index.tsx
  1. Add Payment Icon and Title

    Update src/lib/constants.tsx to include the PipraPay mapping in paymentInfoMap:

    src/lib/constants.tsx
    export const paymentInfoMap: Record<
    string,
    { title: string; icon: React.JSX.Element }
    > = {
    pp_stripe_stripe: {
    title: "Credit card",
    icon: <CreditCard />,
    },
    "pp_medusa-payments_default": {
    title: "Credit card",
    icon: <CreditCard />,
    },
    "pp_stripe-ideal_stripe": {
    title: "iDeal",
    icon: <Ideal />,
    },
    "pp_stripe-bancontact_stripe": {
    title: "Bancontact",
    icon: <Bancontact />,
    },
    pp_paypal_paypal: {
    title: "PayPal",
    icon: <PayPal />,
    },
    pp_system_default: {
    title: "Manual Payment",
    icon: <CreditCard />,
    },
    pp_piprapay_piprapay: {
    title: "PipraPay",
    icon: <CreditCard />,
    },
    };
  2. Create Payment Component

    Modify src/modules/checkout/components/payment-button/index.tsx to include the PipraPayPaymentButton and handle the selection logic.

    src/modules/checkout/components/payment-button/index.tsx
    const PaymentButton: React.FC<PaymentButtonProps> = ({
    cart,
    "data-testid": dataTestId,
    }) => {
    const notReady =
    !cart ||
    !cart.shipping_address ||
    !cart.billing_address ||
    !cart.email ||
    (cart.shipping_methods?.length ?? 0) < 1;
    const paymentSession = cart.payment_collection?.payment_sessions?.[0];
    switch (true) {
    case isStripeLike(paymentSession?.provider_id):
    return (
    <StripePaymentButton
    notReady={notReady}
    cart={cart}
    data-testid={dataTestId}
    />
    );
    case isManual(paymentSession?.provider_id):
    return (
    <ManualTestPaymentButton
    notReady={notReady}
    data-testid={dataTestId}
    />
    );
    case paymentSession?.provider_id === "pp_piprapay_piprapay":
    return (
    <PipraPayPaymentButton
    notReady={notReady}
    cart={cart}
    data-testid={dataTestId}
    />
    );
    default:
    return <Button disabled>Select a payment method</Button>;
    }
    };
    const PipraPayPaymentButton = ({
    cart,
    notReady,
    "data-testid": dataTestId,
    }: {
    cart: HttpTypes.StoreCart;
    notReady: boolean;
    "data-testid"?: string;
    }) => {
    const [submitting, setSubmitting] = useState(false);
    const [errorMessage, setErrorMessage] = useState<string | null>(null);
    const session = cart.payment_collection?.payment_sessions?.find(
    (s) => s.status === "pending"
    );
    const handlePayment = async () => {
    setSubmitting(true);
    try {
    const { redirect_url } = session?.data || {};
    if (redirect_url) {
    window.location.href = redirect_url as string;
    return;
    }
    } catch (err: any) {
    setErrorMessage(err.message);
    setSubmitting(false);
    }
    };
    return (
    <>
    <Button
    disabled={notReady}
    isLoading={submitting}
    onClick={handlePayment}
    size="large"
    data-testid={dataTestId}
    >
    Place order
    </Button>
    <ErrorMessage
    error={errorMessage}
    data-testid="piprapay-payment-error-message"
    />
    </>
    );
    };
  3. Initiate Payment Session

    Update src/lib/data.ts (or your relevant data fetching file) to include redirect_url and cancel_url when initiating the payment session.

    src/lib/data.ts
    export async function initiatePaymentSession(
    cart: HttpTypes.StoreCart,
    data: HttpTypes.StoreInitializePaymentSession
    ) {
    const headers = {
    ...(await getAuthHeaders()),
    };
    const baseUrl = getBaseURL();
    const countryCode = cart.region?.countries?.[0]?.iso_2?.toLowerCase();
    data.data = {
    ...data.data,
    email: cart.email,
    redirect_url: `${baseUrl}/${countryCode}/cart/complete`,
    cancel_url: `${baseUrl}/${countryCode}/checkout?step=payment`,
    };
    return sdk.store.payment
    .initiatePaymentSession(cart, data, {}, headers)
    .then(async (resp) => {
    const cartCacheTag = await getCacheTag("carts");
    revalidateTag(cartCacheTag);
    return resp;
    })
    .catch(medusaError);
    }
  4. Create Payment Completion Page

    Create or update src/app/[countrycode]/cart/complete/page.tsx to handle the payment completion redirected from PipraPay.

    src/app/[countrycode]/cart/complete/page.tsx
    "use client";
    import { placeOrder } from "@lib/data/cart";
    import { Heading, Text } from "@medusajs/ui";
    import { useEffect, useState } from "react";
    const CartCompletePage = () => {
    const [error, setError] = useState<string | null>(null);
    useEffect(() => {
    const complete = async () => {
    try {
    await placeOrder();
    } catch (err: any) {
    setError(err.message);
    }
    };
    complete();
    }, []);
    if (error) {
    return (
    <div className="flex flex-col items-center justify-center min-h-[50vh] gap-4">
    <Heading level="h1" className="text-2xl text-ui-fg-base">
    Payment Error
    </Heading>
    <Text className="text-ui-fg-subtle">{error}</Text>
    </div>
    );
    }
    return (
    <div className="flex flex-col items-center justify-center min-h-[50vh] gap-4">
    <Heading level="h1" className="text-2xl text-ui-fg-base animate-pulse">
    Processing Payment...
    </Heading>
    <Text className="text-ui-fg-subtle">
    Please wait while we confirm your order.
    </Text>
    </div>
    );
    };
    export default CartCompletePage;