Skip to content

Commit

Permalink
add auth/capture flow
Browse files Browse the repository at this point in the history
  • Loading branch information
hreinberger committed Jan 16, 2025
1 parent 3f7b05b commit db70094
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 9 deletions.
7 changes: 5 additions & 2 deletions src/app/components/form/address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,14 @@ export default async function Address() {
as="label"
size="2"
>
<Flex gap="2">
<Flex
gap="2"
direction="column"
>
<Switch
defaultChecked
radius="full"
/>{' '}
/>
Shipping address is the same as my billing address
</Flex>
</Text>
Expand Down
32 changes: 32 additions & 0 deletions src/app/components/form/methods/componentpaymentmethods.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Skeleton,
Code,
Box,
Switch,
} from '@radix-ui/themes';

import React, { Suspense, useEffect, useRef, useState } from 'react';
Expand Down Expand Up @@ -88,6 +89,37 @@ export default function ComponentPaymentMethods() {
<Code variant="ghost">2223 0000 1047 9399</Code>
</Callout.Text>
</Callout.Root>
<Separator
my="3"
size="4"
/>
<Flex>
<Text
as="label"
size="2"
>
<Flex
gap="2"
direction="column"
>
<Flex gap="2">
<Switch
radius="full"
name="captureMode"
value={'manual'}
/>
Authorize payment
</Flex>
<Text
size="1"
color="gray"
>
Authorized payments need to be captured
later
</Text>
</Flex>
</Text>
</Flex>
</Flex>
</Card>
</RadioGroup.Root>
Expand Down
25 changes: 23 additions & 2 deletions src/app/components/ui/paymentoverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,28 @@ import {
Dialog,
ScrollArea,
Text,
Heading,
} from '@radix-ui/themes';
import StateBadge from './orderstatebadge';

// Routing
import Link from 'next/link';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

// Mollie API
import { mollieGetPayment } from '@/app/lib/mollie';
import { mollieGetPayment, mollieCapturePayment } from '@/app/lib/mollie';
import { Payment } from '@mollie/api-client';

export default async function PaymentOverview({ id }: { id: string }) {
// get details for this payment
const payment: Payment = await mollieGetPayment(id);
// server function for capturing this payment if authorized
async function capturePayment() {
'use server';
await mollieCapturePayment(id);
revalidatePath('/payments/' + id);
redirect('/payments/' + id);
}
return (
<Flex
justify="center"
Expand Down Expand Up @@ -128,6 +137,18 @@ export default async function PaymentOverview({ id }: { id: string }) {
</Button>
</Link>
)}
{payment.status == 'authorized' && (
<form action={capturePayment}>
<Button
size="1"
color="green"
variant="soft"
type="submit"
>
Capture
</Button>
</form>
)}
</Flex>
</Card>
</Flex>
Expand Down
20 changes: 18 additions & 2 deletions src/app/lib/mollie.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
'use server';

import createMollieClient, {
CaptureMethod,
Locale,
Payment,
PaymentMethod,
SequenceType,
} from '@mollie/api-client';

const apiKey = process.env.MOLLIE_API_KEY;
const domain = process.env.DOMAIN || 'http://localhost:3000';
const webhookUrl = process.env.WEBHOOK_URL || 'http://not.provided';
Expand All @@ -28,6 +31,7 @@ export async function mollieCreatePayment({
country,
payment_method,
cardToken,
captureMode,
}: {
firstname: string;
lastname: string;
Expand All @@ -37,8 +41,9 @@ export async function mollieCreatePayment({
city: string;
zip_code: string;
country: string;
payment_method: string | undefined;
payment_method: PaymentMethod;
cardToken?: string;
captureMode?: CaptureMethod;
}) {
// we need to construct the billingAdress object first as long as this isn't fixed:
// https://github.com/mollie/mollie-api-node/issues/390#issuecomment-2467604847
Expand Down Expand Up @@ -107,9 +112,10 @@ export async function mollieCreatePayment({
redirectUrl: domain + '/success',
cancelUrl: domain,
webhookUrl: webhookUrl,
method: payment_method as undefined, // undefined for now
method: payment_method,
...{ billingAddress },
cardToken: cardToken,
captureMode: captureMode as CaptureMethod,
});
const redirectUrl = payment.getCheckoutUrl();
return redirectUrl;
Expand Down Expand Up @@ -142,3 +148,13 @@ export async function mollieGetMethods() {
});
return methods;
}

// Capture a payment in full

export async function mollieCapturePayment(id: string) {
console.log('Capturing payment with id: ' + id);
const capture = await mollieClient.paymentCaptures.create({
paymentId: id,
});
return capture;
}
7 changes: 5 additions & 2 deletions src/app/lib/server-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

// Lib
import { validateFormData, validateUrl } from '@/app/lib/validation';
import { mollieCreatePayment } from '@/app/lib/mollie';
import { mollieCreatePayment, mollieCapturePayment } from '@/app/lib/mollie';
import { PaymentMethod, CaptureMethod } from '@mollie/api-client';

// Next.js
import { redirect } from 'next/navigation';
import { revalidatePath } from 'next/cache';

export async function createPayment(formData: FormData) {
// This Server Action takes the form data, validates it and creates a payment
Expand All @@ -19,8 +21,9 @@ export async function createPayment(formData: FormData) {
city: string;
zip_code: string;
country: string;
payment_method: string | undefined;
payment_method: PaymentMethod;
cardToken?: string;
captureMode?: CaptureMethod;
} = await validateFormData(formData);

// Create a payment with the validated form data and retrieve the redirect URL
Expand Down
5 changes: 4 additions & 1 deletion src/app/lib/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import { z } from 'zod';

import { CaptureMethod, PaymentMethod } from '@mollie/api-client';

export async function validateFormData(formData: FormData) {
const form = Object.fromEntries(formData.entries());

Expand All @@ -26,8 +28,9 @@ export async function validateFormData(formData: FormData) {
.string()
.min(1, { message: 'Must be at least 1 character long.' }),
country: z.string().length(2),
payment_method: z.string(),
payment_method: z.nativeEnum(PaymentMethod),
cardToken: z.string().startsWith('tkn_').optional(),
captureMode: z.nativeEnum(CaptureMethod).optional(),
});

try {
Expand Down

0 comments on commit db70094

Please sign in to comment.