WIP on agreement success path
This commit is contained in:
parent
b3ff744f4d
commit
20eee29adc
|
|
@ -1,16 +1,13 @@
|
|||
import { useEffect, useState } from "preact/hooks";
|
||||
import { Input } from "../components/Input.tsx";
|
||||
import { validateEmail } from "../lib/utils.ts";
|
||||
|
||||
export const DonationSection = function () {
|
||||
const [emailInput, setEmailInput] = useState<string>("");
|
||||
const [emailValid, setEmailValid] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const valid = emailInput.match(
|
||||
// https://emailregex.com/
|
||||
// deno-lint-ignore no-control-regex
|
||||
/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/,
|
||||
);
|
||||
const valid = validateEmail(emailInput);
|
||||
setEmailValid(!!valid);
|
||||
}, [emailInput]);
|
||||
|
||||
|
|
@ -45,7 +42,7 @@ export const DonationSection = function () {
|
|||
</Input>
|
||||
<a
|
||||
// don't navigate if email is not valid
|
||||
href={emailInput ? "/api/trigger-donation/vipps" : undefined}
|
||||
href={emailInput ? `/api/trigger-donation/vipps?email=${emailInput}` : undefined}
|
||||
onClick={(e) => {
|
||||
if (!emailValid) {
|
||||
e.preventDefault();
|
||||
|
|
|
|||
10
lib/utils.ts
10
lib/utils.ts
|
|
@ -36,3 +36,13 @@ function response(body: string, status: number, type: "json" | "xml") {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function validateEmail(input: string) {
|
||||
input.match(
|
||||
// https://emailregex.com/
|
||||
// deno-lint-ignore no-control-regex
|
||||
/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/,
|
||||
);
|
||||
|
||||
return !!input;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,41 +41,13 @@ const getAccessToken = async function () {
|
|||
return data.access_token as string;
|
||||
};
|
||||
|
||||
/**
|
||||
curl -X POST https://apitest.vipps.no/recurring/v3/agreements/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR-ACCESS-TOKEN" \
|
||||
-H "Ocp-Apim-Subscription-Key: YOUR-SUBSCRIPTION-KEY" \
|
||||
-H "Merchant-Serial-Number: YOUR-MSN" \
|
||||
-H 'Idempotency-Key: YOUR-IDEMPOTENCY-KEY' \
|
||||
-H "Vipps-System-Name: acme" \
|
||||
-H "Vipps-System-Version: 3.1.2" \
|
||||
-H "Vipps-System-Plugin-Name: acme-webshop" \
|
||||
-H "Vipps-System-Plugin-Version: 4.5.6" \
|
||||
-d '{
|
||||
"interval": {
|
||||
"unit" : "WEEK",
|
||||
"count": 2
|
||||
},
|
||||
"pricing": {
|
||||
"amount": 1000,
|
||||
"currency": "NOK"
|
||||
},
|
||||
"merchantRedirectUrl": "https://example.com/redirect-url",
|
||||
"merchantAgreementUrl": "https://example.com/agreement-url",
|
||||
"phoneNumber": "12345678",
|
||||
"productName": "Test product"
|
||||
}'
|
||||
*/
|
||||
|
||||
export const createAgreement = async function () {
|
||||
export const createAgreement = async function (email: string) {
|
||||
const token = await getAccessToken();
|
||||
const idempotencyKey = crypto.randomUUID();
|
||||
const response = await fetch(`${config.baseUrl}/recurring/v3/agreements/`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
"Idempotency-Key": idempotencyKey,
|
||||
"Idempotency-Key": email,
|
||||
...standardVippsHeaders,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
|
|
@ -105,3 +77,21 @@ export const createAgreement = async function () {
|
|||
return new Error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
export const getAgreement = async function (agreementId: string) {
|
||||
const token = await getAccessToken();
|
||||
const response = await fetch(`${config.baseUrl}/recurring/v3/agreements/${agreementId}`, {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,25 @@
|
|||
import { getHostName } from "../../../lib/utils.ts";
|
||||
import { getHostName, validateEmail } from "../../../lib/utils.ts";
|
||||
import { createAgreement } from "../../../lib/vipps/vipps.ts";
|
||||
|
||||
export const handler = async function (_req: Request): Promise<Response> {
|
||||
const agreement = await createAgreement();
|
||||
export const handler = async function (req: Request): Promise<Response> {
|
||||
const email = new URLSearchParams(req.url.split("?")[1] || "").get("email");
|
||||
const errorPage = `${getHostName()}/donations-error`;
|
||||
|
||||
if (!email) {
|
||||
console.error("Missing email in query params", req.url);
|
||||
return Response.redirect(errorPage);
|
||||
}
|
||||
|
||||
const valid = validateEmail(email);
|
||||
if (!valid) {
|
||||
console.error("Invalid email server side", email);
|
||||
return Response.redirect(errorPage);
|
||||
}
|
||||
|
||||
const agreement = await createAgreement(email);
|
||||
if (agreement instanceof Error) {
|
||||
return Response.redirect(`${getHostName()}/donations-error`);
|
||||
console.error("Failed to create Vipps agreement", agreement);
|
||||
return Response.redirect(errorPage);
|
||||
}
|
||||
|
||||
return Response.redirect(agreement.vippsConfirmationUrl);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,31 @@
|
|||
export default function () {
|
||||
import { PageProps } from "$fresh/server.ts";
|
||||
import { getAgreement } from "../lib/vipps/vipps.ts";
|
||||
|
||||
export const handler: Handlers<Props> = {
|
||||
async GET(request, ctx) {
|
||||
// TODO: somehow get the agreement ID
|
||||
// TODO: implement cancel page
|
||||
|
||||
const agreementId = "123";
|
||||
const agreement = await getAgreement(agreementId);
|
||||
if (agreement instanceof Error) {
|
||||
console.error("Failed to get Vipps agreement", agreement);
|
||||
return ctx.redirect("/donations-error");
|
||||
}
|
||||
|
||||
return ctx.render({ agreement });
|
||||
},
|
||||
};
|
||||
|
||||
export default function ({ data }: PageProps<{
|
||||
// TODO: confirm interface when you know it
|
||||
agreement: {
|
||||
status: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
status: string;
|
||||
}>) {
|
||||
return (
|
||||
<div className="my-16 text-center flex flex-col items-center min-h-screen ">
|
||||
<h1 className="text-6xl mb-8">
|
||||
|
|
|
|||
Loading…
Reference in New Issue