medusa-payment-pesapal
Version:
Pesapal payment provider plugin for Medusa v2 - Accept payments via Pesapal gateway with support for M-Pesa, Airtel Money, and other East African payment methods
280 lines (279 loc) • 11.9 kB
JavaScript
import { jsx, jsxs } from "react/jsx-runtime";
import { defineWidgetConfig } from "@medusajs/admin-sdk";
import { Container, Text, Heading, Switch, Label, Select, Input, Button, toast } from "@medusajs/ui";
import { useState, useEffect } from "react";
const PesapalConfigurationWidget = () => {
const [config2, setConfig] = useState({
consumer_key: "",
consumer_secret: "",
environment: "sandbox",
currency: "KES",
merchant_name: "",
ipn_url: "",
enabled: false
});
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
useEffect(() => {
const loadConfiguration = async () => {
setLoading(true);
try {
const response = await fetch("/admin/custom/pesapal/config", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
if (response.ok) {
const data = await response.json();
setConfig(data.config || config2);
}
} catch (error) {
console.error("Failed to load Pesapal configuration:", error);
toast.error("Failed to load configuration");
} finally {
setLoading(false);
}
};
loadConfiguration();
}, []);
const handleSave = async () => {
setSaving(true);
try {
const response = await fetch("/admin/custom/pesapal/config", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ config: config2 })
});
if (response.ok) {
toast.success("Pesapal configuration saved successfully");
} else {
const error = await response.json();
toast.error(error.message || "Failed to save configuration");
}
} catch (error) {
console.error("Failed to save Pesapal configuration:", error);
toast.error("Failed to save configuration");
} finally {
setSaving(false);
}
};
const handleTestConnection = async () => {
setLoading(true);
try {
const response = await fetch("/admin/custom/pesapal/test", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ config: config2 })
});
const result = await response.json();
if (response.ok && result.success) {
toast.success("Connection test successful!");
} else {
toast.error(result.message || "Connection test failed");
}
} catch (error) {
console.error("Failed to test Pesapal connection:", error);
toast.error("Failed to test connection");
} finally {
setLoading(false);
}
};
if (loading && !config2.consumer_key) {
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-8", children: /* @__PURE__ */ jsx(Text, { children: "Loading Pesapal configuration..." }) }) });
}
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-6", children: [
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Pesapal Configuration" }),
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Configure your Pesapal payment gateway settings" })
] }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
/* @__PURE__ */ jsx(
Switch,
{
checked: config2.enabled,
onCheckedChange: (checked) => setConfig((prev) => ({ ...prev, enabled: checked }))
}
),
/* @__PURE__ */ jsx(Label, { children: "Enable Pesapal" })
] })
] }),
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6", children: [
/* @__PURE__ */ jsxs("div", { className: "md:col-span-2", children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "environment", children: "Environment" }),
/* @__PURE__ */ jsxs(
Select,
{
value: config2.environment,
onValueChange: (value) => setConfig((prev) => ({ ...prev, environment: value })),
children: [
/* @__PURE__ */ jsx(Select.Trigger, { className: "w-full", children: /* @__PURE__ */ jsx(Select.Value, {}) }),
/* @__PURE__ */ jsxs(Select.Content, { children: [
/* @__PURE__ */ jsx(Select.Item, { value: "sandbox", children: "Sandbox (Testing)" }),
/* @__PURE__ */ jsx(Select.Item, { value: "live", children: "Live (Production)" })
] })
]
}
),
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle mt-1", children: "Use sandbox for testing and live for production" })
] }),
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "consumer_key", children: "Consumer Key" }),
/* @__PURE__ */ jsx(
Input,
{
id: "consumer_key",
type: "text",
placeholder: "Enter your Pesapal consumer key",
value: config2.consumer_key,
onChange: (e) => setConfig((prev) => ({ ...prev, consumer_key: e.target.value }))
}
),
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle mt-1", children: "Your Pesapal consumer key from the merchant dashboard" })
] }),
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "consumer_secret", children: "Consumer Secret" }),
/* @__PURE__ */ jsx(
Input,
{
id: "consumer_secret",
type: "password",
placeholder: "Enter your Pesapal consumer secret",
value: config2.consumer_secret,
onChange: (e) => setConfig((prev) => ({ ...prev, consumer_secret: e.target.value }))
}
),
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle mt-1", children: "Your Pesapal consumer secret (keep this secure)" })
] }),
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "currency", children: "Default Currency" }),
/* @__PURE__ */ jsxs(
Select,
{
value: config2.currency,
onValueChange: (value) => setConfig((prev) => ({ ...prev, currency: value })),
children: [
/* @__PURE__ */ jsx(Select.Trigger, { className: "w-full", children: /* @__PURE__ */ jsx(Select.Value, {}) }),
/* @__PURE__ */ jsxs(Select.Content, { children: [
/* @__PURE__ */ jsx(Select.Item, { value: "KES", children: "KES - Kenyan Shilling" }),
/* @__PURE__ */ jsx(Select.Item, { value: "USD", children: "USD - US Dollar" }),
/* @__PURE__ */ jsx(Select.Item, { value: "EUR", children: "EUR - Euro" }),
/* @__PURE__ */ jsx(Select.Item, { value: "GBP", children: "GBP - British Pound" }),
/* @__PURE__ */ jsx(Select.Item, { value: "UGX", children: "UGX - Ugandan Shilling" }),
/* @__PURE__ */ jsx(Select.Item, { value: "TZS", children: "TZS - Tanzanian Shilling" })
] })
]
}
)
] }),
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "merchant_name", children: "Merchant Name" }),
/* @__PURE__ */ jsx(
Input,
{
id: "merchant_name",
type: "text",
placeholder: "Your business name",
value: config2.merchant_name,
onChange: (e) => setConfig((prev) => ({ ...prev, merchant_name: e.target.value }))
}
),
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle mt-1", children: "Optional: Your business name for display purposes" })
] }),
/* @__PURE__ */ jsxs("div", { className: "md:col-span-2", children: [
/* @__PURE__ */ jsx(Label, { htmlFor: "ipn_url", children: "IPN Callback URL" }),
/* @__PURE__ */ jsx(
Input,
{
id: "ipn_url",
type: "url",
placeholder: "https://yourdomain.com/pesapal/webhook",
value: config2.ipn_url,
onChange: (e) => setConfig((prev) => ({ ...prev, ipn_url: e.target.value }))
}
),
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle mt-1", children: "URL where Pesapal will send payment notifications. Leave empty to use default." })
] })
] }),
config2.environment === "sandbox" && /* @__PURE__ */ jsxs("div", { className: "mt-6 p-4 bg-ui-bg-subtle rounded-lg border", children: [
/* @__PURE__ */ jsx(Heading, { level: "h3", className: "mb-3", children: "Sandbox Test Credentials" }),
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm", children: [
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(Label, { children: "Consumer Key:" }),
/* @__PURE__ */ jsx(Text, { className: "font-mono", children: "qkio1BGGYAXTu2JOfm7XSXNruoZsrqEW" })
] }),
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(Label, { children: "Consumer Secret:" }),
/* @__PURE__ */ jsx(Text, { className: "font-mono", children: "osGQ364R49cXKeOYSpaOnT++rHs=" })
] })
] }),
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle mt-2", children: "You can use these test credentials for Kenyan merchant sandbox testing" })
] }),
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mt-8", children: [
/* @__PURE__ */ jsx(
Button,
{
variant: "secondary",
onClick: handleTestConnection,
disabled: !config2.consumer_key || !config2.consumer_secret || loading,
children: loading ? "Testing..." : "Test Connection"
}
),
/* @__PURE__ */ jsx(
Button,
{
onClick: handleSave,
disabled: !config2.consumer_key || !config2.consumer_secret || saving,
children: saving ? "Saving..." : "Save Configuration"
}
)
] }),
/* @__PURE__ */ jsxs("div", { className: "mt-6 p-4 bg-ui-bg-base border rounded-lg", children: [
/* @__PURE__ */ jsx(Heading, { level: "h3", className: "mb-2", children: "Status Information" }),
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4 text-sm", children: [
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(Label, { children: "Provider Status:" }),
/* @__PURE__ */ jsx(Text, { className: config2.enabled ? "text-green-600" : "text-red-600", children: config2.enabled ? "Enabled" : "Disabled" })
] }),
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(Label, { children: "Environment:" }),
/* @__PURE__ */ jsx(Text, { className: config2.environment === "live" ? "text-orange-600" : "text-blue-600", children: config2.environment === "live" ? "Production" : "Sandbox" })
] })
] })
] })
] }) });
};
defineWidgetConfig({
zone: "order.details.after"
});
const widgetModule = { widgets: [
{
Component: PesapalConfigurationWidget,
zone: ["order.details.after"]
}
] };
const routeModule = {
routes: []
};
const menuItemModule = {
menuItems: []
};
const formModule = { customFields: {} };
const displayModule = {
displays: {}
};
const plugin = {
widgetModule,
routeModule,
menuItemModule,
formModule,
displayModule
};
export {
plugin as default
};