- Rename app: Waseel (name, slug, scheme waseel://, com.waseel.app ids, splash) - Payments: replace Stripe with Areeba hosted checkout (create/verify API routes, lib/areeba.ts, WebBrowser-based payment flow) - Maps: migrate address autocomplete to Places API (New), drop legacy library - Web support: map stub for web (native maps are iOS/Android only) - Auth: keep email + Google OAuth; fix OAuth redirect for Expo Go - Add scripts/seed-db.mjs (schema + Lebanese driver seed) - Pin Expo SDK 51 compatible package versions
116 lines
3.6 KiB
TypeScript
116 lines
3.6 KiB
TypeScript
// Server-side helpers for the Areeba payment gateway (MPGS-style API).
|
|
//
|
|
// Areeba issues merchant credentials after onboarding:
|
|
// AREEBA_API_BASE_URL e.g. https://<your-gateway-host>.areeba.com
|
|
// AREEBA_MERCHANT_ID your merchant id
|
|
// AREEBA_API_PASSWORD API password for the merchant
|
|
// AREEBA_API_VERSION gateway REST API version (default: 100)
|
|
//
|
|
// NOTE: verify the exact field names against the integration docs Areeba
|
|
// sends you — the gateway is Mastercard Payment Gateway (MPGS) based, and
|
|
// the payloads below follow that convention.
|
|
|
|
export const areebaConfig = () => {
|
|
const baseUrl = process.env.AREEBA_API_BASE_URL?.replace(/\/$/, "");
|
|
const merchantId = process.env.AREEBA_MERCHANT_ID;
|
|
const apiPassword = process.env.AREEBA_API_PASSWORD;
|
|
const apiVersion = process.env.AREEBA_API_VERSION || "100";
|
|
|
|
if (!baseUrl || !merchantId || !apiPassword) {
|
|
throw new Error(
|
|
"Missing Areeba configuration. Set AREEBA_API_BASE_URL, AREEBA_MERCHANT_ID and AREEBA_API_PASSWORD in .env",
|
|
);
|
|
}
|
|
|
|
return { baseUrl, merchantId, apiPassword, apiVersion };
|
|
};
|
|
|
|
const authHeader = (merchantId: string, apiPassword: string) =>
|
|
`Basic ${Buffer.from(`merchant.${merchantId}:${apiPassword}`).toString("base64")}`;
|
|
|
|
// Creates a checkout session and returns the hosted payment page URL.
|
|
export const createCheckoutSession = async ({
|
|
orderId,
|
|
amount,
|
|
currency,
|
|
description,
|
|
returnUrl,
|
|
}: {
|
|
orderId: string;
|
|
amount: number; // major units, e.g. 25.5 USD
|
|
currency: string;
|
|
description: string;
|
|
returnUrl: string;
|
|
}) => {
|
|
const { baseUrl, merchantId, apiPassword, apiVersion } = areebaConfig();
|
|
|
|
const res = await fetch(
|
|
`${baseUrl}/api/rest/version/${apiVersion}/merchant/${merchantId}/session`,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: authHeader(merchantId, apiPassword),
|
|
},
|
|
body: JSON.stringify({
|
|
apiOperation: "INITIATE_CHECKOUT",
|
|
order: {
|
|
id: orderId,
|
|
amount: amount.toFixed(2),
|
|
currency,
|
|
description,
|
|
},
|
|
interaction: {
|
|
operation: "PURCHASE",
|
|
merchant: { name: "Waseel" },
|
|
returnUrl,
|
|
},
|
|
}),
|
|
},
|
|
);
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok || !data?.session?.id) {
|
|
console.log("[AREEBA_CREATE_SESSION]: ", data);
|
|
throw new Error(
|
|
data?.error?.explanation || "Failed to create Areeba checkout session",
|
|
);
|
|
}
|
|
|
|
return {
|
|
sessionId: data.session.id as string,
|
|
// Used to verify the redirect result (compare with resultIndicator).
|
|
successIndicator: data.successIndicator as string | undefined,
|
|
checkoutUrl: `${baseUrl}/checkout/pay/${data.session.id}?checkoutVersion=1.0.0`,
|
|
};
|
|
};
|
|
|
|
// Retrieves an order and reports whether it was paid.
|
|
export const retrieveOrder = async (orderId: string) => {
|
|
const { baseUrl, merchantId, apiPassword, apiVersion } = areebaConfig();
|
|
|
|
const res = await fetch(
|
|
`${baseUrl}/api/rest/version/${apiVersion}/merchant/${merchantId}/order/${orderId}`,
|
|
{ headers: { Authorization: authHeader(merchantId, apiPassword) } },
|
|
);
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
console.log("[AREEBA_RETRIEVE_ORDER]: ", data);
|
|
throw new Error(
|
|
data?.error?.explanation || "Failed to retrieve Areeba order",
|
|
);
|
|
}
|
|
|
|
return {
|
|
status: data.status as string | undefined,
|
|
result: data.result as string | undefined,
|
|
amount: data.amount as string | undefined,
|
|
currency: data.currency as string | undefined,
|
|
// PURCHASE auto-captures; CAPTURED means the money was taken.
|
|
paid: data.result === "SUCCESS" && data.status === "CAPTURED",
|
|
};
|
|
};
|