nrss/lib/vipps/vipps.ts

146 lines
4.2 KiB
TypeScript

// 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";
}> {
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);
}
};