// deno-lint-ignore-file ban-ts-comment import "jsr:@std/dotenv/load"; import { encodeHex } from "jsr:@std/encoding/hex"; import { getHostUrl } from "../utils.ts"; import { STATUS_CODE } from "$fresh/server.ts"; const config = { clientId: Deno.env.get("VIPPS_CLIENT_ID"), clientSecret: Deno.env.get("VIPPS_CLIENT_SECRET"), ocpApimSubscriptionKeyPrimary: Deno.env.get( "VIPPS_OCP_APIM_SUBSCRIPTION_KEY_PRIMARY", ), ocpApimSubscriptionKeySecondary: Deno.env.get( "VIPPS_OCM_APIM_SUBSCRIPTION_KEY_SECONDARY", ), msn: Deno.env.get("VIPPS_MSN"), baseUrl: Deno.env.get("VIPPS_API_BASE_URL"), }; // ensure all environment variables are set for (const [key, value] of Object.entries(config)) { if (!value) { throw new Error(`Missing environment variable: ${key}`); } } const standardVippsHeaders = { "Content-Type": "application/json", "Ocp-Apim-Subscription-Key": config.ocpApimSubscriptionKeyPrimary, "Merchant-Serial-Number": config.msn, "Vipps-System-Name": "Krets AS", } as const; const getAccessToken = async function () { console.log(`Fetching ${`${config.baseUrl}/accesstoken/get`}`); const response = await fetch(`${config.baseUrl}/accesstoken/get`, { method: "POST", // @ts-ignore headers: { client_id: config.clientId, client_secret: config.clientSecret, ...standardVippsHeaders, }, }); const data = await response.json(); return data.access_token as string; }; export const createAgreement = async function (email: string) { const token = await getAccessToken(); const amount = 5000; // 50 NOK const response = await fetch(`${config.baseUrl}/recurring/v3/agreements/`, { method: "POST", // @ts-ignore headers: { authorization: `Bearer ${token}`, // anonymized and trimmed to not be too long (then it fails) "Idempotency-Key": `${encodeHex(email).slice(0, 10)}-${Date.now()}`, ...standardVippsHeaders, }, body: JSON.stringify({ interval: { unit: "MONTH", count: 1, }, initialCharge: { amount: amount, description: "Initial charge", transactionType: "DIRECT_CAPTURE", }, pricing: { amount: amount, currency: "NOK", }, // email is used to identify the user in the success page merchantRedirectUrl: `${getHostUrl()}/donations-success?email=${email}`, merchantAgreementUrl: `${getHostUrl()}`, productName: "Månedlig støtte til NRSS", }), }); if (response.status === STATUS_CODE.Created) { const body = response.json(); return body as unknown as { vippsConfirmationUrl: string; agreementId: string; }; } else { const errorMessage = `Failed to create Vipps agreement ${response.status} ${await response.text()}`; console.error(errorMessage); return new Error(errorMessage); } }; export const getAgreement = async function (agreementId: string): Promise< | { status: "PENDING" | "ACTIVE" | "STOPPED" | "EXPIRED"; } | Error > { const token = await getAccessToken(); const response = await fetch( `${config.baseUrl}/recurring/v3/agreements/${agreementId}`, { // @ts-ignore headers: { authorization: `Bearer ${token}`, ...standardVippsHeaders, }, }, ); if (response.status === STATUS_CODE.OK) { return response.json(); } else { const errorMessage = `Failed to get Vipps agreement ${response.status} ${await response.text()}`; console.error(errorMessage); return new Error(errorMessage); } }; export const cancelAgreement = async function (agreementId: string) { const token = await getAccessToken(); const response = await fetch( `${config.baseUrl}/recurring/v3/agreements/${agreementId}`, { method: "PATCH", // @ts-ignore headers: { authorization: `Bearer ${token}`, "Idempotency-Key": `${agreementId}-${Date.now()}`, ...standardVippsHeaders, }, body: JSON.stringify({ status: "STOPPED", }), }, ); if (response.status === STATUS_CODE.NoContent) { return true; } else { const errorMessage = `Failed to cancel Vipps agreement ${response.status} ${await response.text()}`; console.error(errorMessage); return new Error(errorMessage); } };