Accepting Payments

Overview

Fenerum integrates with leading payment providers to help you collect and manage customer payment methods. Once a payment card is registered in Fenerum, you can automatically charge it for invoices and subscriptions.

Supported Providers:

Modern payment infrastructure

Nordic payment gateway

Prerequisites

Before accepting payments, configure your payment provider in Fenerum:

  1. Navigate to SettingsIntegrations in Fenerum
  2. Add your payment provider's API credentials
  3. Enable the payment gateway for your organization

Stripe Integration

How It Works

Integrating Stripe with Fenerum involves two steps:

  1. Frontend: Collect card details using Stripe Elements (PCI-compliant, card data goes directly to Stripe)
  2. Backend: Register the Stripe payment method ID with Fenerum (your server, with secure token handling)

Security Note: Never expose your Fenerum API token in frontend code. Always call Fenerum's API from your backend where the token is safely stored in environment variables.

Frontend: Collect Payment Method

Use Stripe Elements to securely collect card information. The card data goes directly to Stripe—your server never sees it.

<!DOCTYPE html>
<html>
<head>
  <title>Add Payment Method</title>
  <script src="https://js.stripe.com/v3/"></script>
  <style>
    #payment-form { max-width: 500px; margin: 50px auto; padding: 20px; }
    #card-element { border: 1px solid #ccc; padding: 10px; border-radius: 4px; }
    button { margin-top: 20px; padding: 10px 20px; background: #5469d4; color: white; border: none; border-radius: 4px; cursor: pointer; }
    button:hover { background: #4158c4; }
    .success { color: green; margin-top: 20px; }
    .error { color: red; margin-top: 20px; }
  </style>
</head>
<body>
  <form id="payment-form">
    <h2>Add Payment Method</h2>
    <div id="card-element"></div>
    <button type="submit">Save Card</button>
    <div id="message"></div>
  </form>

  <script>
    // Initialize Stripe with your publishable key (safe for frontend)
    const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY')
    const elements = stripe.elements()
    const cardElement = elements.create('card')
    cardElement.mount('#card-element')

    const form = document.getElementById('payment-form')
    const messageDiv = document.getElementById('message')

    form.addEventListener('submit', async (event) => {
      event.preventDefault()
      messageDiv.textContent = ''

      // Step 1: Create payment method with Stripe (card data stays secure)
      const {error, paymentMethod} = await stripe.createPaymentMethod({
        type: 'card',
        card: cardElement,
      })

      if (error) {
        messageDiv.textContent = error.message
        messageDiv.className = 'error'
        return
      }

      // Step 2: Send ONLY the payment method ID to YOUR backend
      <!-- focus-start -->
      try {
        const response = await fetch('/api/register-payment-card', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            paymentMethodId: paymentMethod.id,
            accountId: 'ACCOUNT_UUID_HERE'
          })
        })

        const data = await response.json()
      <!-- focus-end -->

        if (response.ok) {
          messageDiv.textContent = `Card saved: ${data.brand} ending in ${data.card_number.slice(-4)}`
          messageDiv.className = 'success'
          form.reset()
        } else {
          messageDiv.textContent = data.error || 'Failed to save card'
          messageDiv.className = 'error'
        }
      } catch (err) {
        messageDiv.textContent = 'Network error: ' + err.message
        messageDiv.className = 'error'
      }
    })
  </script>
</body>
</html>

Key Points:

  • Stripe publishable key (pk_test_...) is safe to use in frontend code
  • Card data is sent directly to Stripe, never touching your server
  • Only the payment method ID is sent to your backend
  • No Fenerum API token in the frontend!

Backend: Register with Fenerum

Your backend receives the Stripe payment method ID and registers it with Fenerum using your API token stored in environment variables.

// Example backend endpoint (works with Express, Fastify, Next.js, etc.)
// Endpoint: POST /api/register-payment-card
// Body: { paymentMethodId: string, accountId: string }

async function registerPaymentCard(req, res) {
  const { paymentMethodId, accountId } = req.body

  // Validate inputs
  if (!paymentMethodId || !accountId) {
    return res.status(400).json({ error: 'Missing required fields' })
  }

  <!-- focus-start -->
  try {
    // Call Fenerum API with token from environment variables
    const response = await fetch('https://app.fenerum.com/api/v1/paymentcards/', {
      method: 'POST',
      headers: {
        'Authorization': `Token ${process.env.FENERUM_API_TOKEN}`,
        'X-Client-System': 'YourApp',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        account: accountId,
        token: paymentMethodId,
        gateway: 'stripe_new'
      })
    })

    const data = await response.json()
  <!-- focus-end -->

    if (!response.ok) {
      console.error('Fenerum API error:', data)
      return res.status(response.status).json({
        error: 'Failed to register card with Fenerum'
      })
    }

    // Return success with card details
    return res.status(200).json({
      uuid: data.uuid,
      brand: data.brand,
      card_number: data.card_number,
      month: data.month,
      year: data.year
    })
  } catch (error) {
    console.error('Error registering payment card:', error)
    return res.status(500).json({ error: 'Internal server error' })
  }
}

Environment Variable Setup:

# .env file (NEVER commit this to version control!)
FENERUM_API_TOKEN=your_actual_token_here

Example Response:

{
  "uuid": "09655a26-12e0-4a82-8524-7851998886fc",
  "brand": "Visa",
  "card_number": "XXXXXXXXXXXX4242",
  "month": 12,
  "year": 2025
}

Customer Self-Service

Recommended: Use Fenerum Self-Service

The best way to let customers manage their payment methods is through Fenerum Self-Service—a fully hosted portal where customers can add payment cards, manage subscriptions, and view invoices without any coding required.

Key benefits:

  • Customers can add and manage payment cards themselves via Stripe or Quickpay
  • Automatic integration with your branding
  • No code required—just enable it in your organization settings
  • Secure authentication via email link

API Integration:

Generate a direct access link to Self-Service for your customers:

# Generate Self-Service access link
curl -X POST https://app.fenerum.com/api/self-service/initiate-organization/ \
  -H "Authorization: Token YOUR_FENERUM_TOKEN" \
  -H "X-Client-System: YourApp" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "customer@example.com"
  }'

Response:

{
  "url": "https://your-company.hostedsignup.com/access/abc123..."
}

Send this URL to your customer, and they can immediately access their account portal to add payment cards and manage their subscriptions.

When customers add a payment card through Self-Service, Fenerum automatically:

  • Saves the card on their account
  • Sets "Payment card" as the default collection method
  • Updates all active subscriptions to use the new payment method

Learn more: Self-Service Documentation

Quickpay Integration

How It Works

Fenerum uses QuickPay subscriptions to store payment cards. Here's the integration flow:

  1. Backend: Create a QuickPay payment link using QuickPay's API
  2. Frontend: Display the QuickPay payment form to the customer (card data goes directly to QuickPay)
  3. Callback: QuickPay sends a callback with the subscription ID when complete
  4. Backend: Register the subscription ID with Fenerum

Security Note: All API calls (both QuickPay and Fenerum) must happen on your backend where tokens are securely stored.

Your backend creates a QuickPay payment link using QuickPay's payment link API.

// Backend endpoint to create QuickPay payment link
// Endpoint: POST /api/create-quickpay-link
// Body: { accountId: string, customerEmail: string }

async function createQuickpayLink(req, res) {
  const { accountId, customerEmail } = req.body

  <!-- focus-start -->
  try {
    // Create QuickPay payment (subscription)
    const paymentResponse = await fetch('https://api.quickpay.net/payments', {
      method: 'POST',
      headers: {
        'Authorization': `Basic ${Buffer.from(':' + process.env.QUICKPAY_API_KEY).toString('base64')}`,
        'Accept-Version': 'v10',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        currency: 'DKK',
        order_id: `sub_${accountId}_${Date.now()}`,
        variables: {
          fenerum_account_id: accountId,
          customer_email: customerEmail
        }
      })
    })

    const payment = await paymentResponse.json()

    // Create payment link for the subscription
    const linkResponse = await fetch(`https://api.quickpay.net/payments/${payment.id}/link`, {
      method: 'PUT',
      headers: {
        'Authorization': `Basic ${Buffer.from(':' + process.env.QUICKPAY_API_KEY).toString('base64')}`,
        'Accept-Version': 'v10',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        amount: 0, // 0 for card authorization only
        auto_capture: false,
        callback_url: `${process.env.YOUR_DOMAIN}/api/quickpay-callback`
      })
    })

    const link = await linkResponse.json()
  <!-- focus-end -->

    return res.json({ url: link.url })
  } catch (error) {
    console.error('Error creating QuickPay link:', error)
    return res.status(500).json({ error: 'Internal server error' })
  }
}

Environment Setup:

QUICKPAY_API_KEY=your_quickpay_api_key_here
YOUR_DOMAIN=https://yourdomain.com
FENERUM_API_TOKEN=your_fenerum_token_here

Step 2: Redirect Route

Create a backend route that generates the QuickPay link and redirects the customer directly to QuickPay's hosted page.

// Backend redirect route
// When customer visits: GET /add-payment-card
// They are redirected to QuickPay's hosted payment page

async function redirectToQuickpay(req, res) {
  // Get logged-in user's information from session/auth
  const accountId = req.user.fenerumAccountId
  const email = req.user.email

  <!-- focus-start -->
  try {
    // Create QuickPay payment (subscription)
    const paymentResponse = await fetch('https://api.quickpay.net/payments', {
      method: 'POST',
      headers: {
        'Authorization': `Basic ${Buffer.from(':' + process.env.QUICKPAY_API_KEY).toString('base64')}`,
        'Accept-Version': 'v10',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        currency: 'DKK',
        order_id: `sub_${accountId}_${Date.now()}`,
        variables: {
          fenerum_account_id: accountId,
          customer_email: email
        }
      })
    })

    const payment = await paymentResponse.json()

    // Create payment link
    const linkResponse = await fetch(`https://api.quickpay.net/payments/${payment.id}/link`, {
      method: 'PUT',
      headers: {
        'Authorization': `Basic ${Buffer.from(':' + process.env.QUICKPAY_API_KEY).toString('base64')}`,
        'Accept-Version': 'v10',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        amount: 0,
        auto_capture: false,
        callback_url: `${process.env.YOUR_DOMAIN}/api/quickpay-callback`
      })
    })

    const link = await linkResponse.json()

    // Redirect customer to QuickPay
    return res.redirect(link.url)
  <!-- focus-end -->
  } catch (error) {
    console.error('Error creating QuickPay link:', error)
    return res.status(500).send('Failed to create payment link')
  }
}

Usage:

Simply provide customers with a link to your backend route:

<a href="/add-payment-card">Add Payment Method</a>

The route uses the logged-in user's session to get their account ID and email—no sensitive data in the URL.

Step 3: Handle QuickPay Callback

When the customer completes the payment form, QuickPay sends a callback to your server with the subscription ID.

// QuickPay callback handler
// Endpoint: POST /api/quickpay-callback
// Receives QuickPay callback when card is added

async function handleQuickpayCallback(req, res) {
  const { id: subscriptionId, variables } = req.body
  const accountId = variables?.fenerum_account_id

  if (!subscriptionId || !accountId) {
    return res.status(400).json({ error: 'Missing required data' })
  }

  <!-- focus-start -->
  try {
    // Register the QuickPay subscription with Fenerum
    const response = await fetch('https://app.fenerum.com/api/v1/paymentcards/', {
      method: 'POST',
      headers: {
        'Authorization': `Token ${process.env.FENERUM_API_TOKEN}`,
        'X-Client-System': 'YourApp',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        account: accountId,
        token: subscriptionId.toString(), // QuickPay subscription ID
        gateway: 'quickpay'
      })
    })

    const data = await response.json()
  <!-- focus-end -->

    if (response.ok) {
      console.log('Payment card registered with Fenerum:', data)
      return res.status(200).json({ success: true })
    } else {
      console.error('Failed to register card with Fenerum:', data)
      return res.status(500).json({ error: 'Failed to register card' })
    }
  } catch (error) {
    console.error('Error in QuickPay callback:', error)
    return res.status(500).json({ error: 'Internal server error' })
  }
}

Response from Fenerum:

{
  "uuid": "09655a26-12e0-4a82-8524-7851998886fc",
  "brand": "Visa",
  "card_number": "XXXXXXXXXXXX4242",
  "month": 12,
  "year": 2025,
  "payment_gateway": "quickpay",
  "account": "50e9aa0b-8d35-4da7-9a9b-8a35b11579b2"
}

Key Points:

  • QuickPay subscription ID must be stored as a string in Fenerum
  • The gateway value must be quickpay
  • Card data never touches your server—it goes directly to QuickPay
  • See the QuickPay integration guide for setup instructions

Customer Self-Service

Recommended: Use Fenerum Self-Service

The best approach is to use Fenerum Self-Service where customers can add QuickPay payment cards themselves through the hosted portal.

The Self-Service portal supports both Stripe and QuickPay payment methods, providing a unified experience for your customers. See the Customer Self-Service section under Stripe Integration above for API integration details.

Managing Payment Cards

List Cards for an Account

curl https://app.fenerum.com/api/v1/paymentcards/?account=CUST001 \
  -H "Authorization: Token YOUR_FENERUM_TOKEN" \
  -H "X-Client-System: YourApp"

Response:

{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "uuid": "09655a26-12e0-4a82-8524-7851998886fc",
      "active": true,
      "brand": "Visa",
      "card_number": "XXXXXXXXXXXX4242",
      "month": 12,
      "year": 2025,
      "payment_gateway": "stripe_new",
      "account": "50e9aa0b-8d35-4da7-9a9b-8a35b11579b2"
    }
  ]
}

Get Card Details

curl https://app.fenerum.com/api/v1/paymentcards/{card_uuid}/ \
  -H "Authorization: Token YOUR_FENERUM_TOKEN" \
  -H "X-Client-System: YourApp"

Disable a Card

curl -X POST https://app.fenerum.com/api/v1/paymentcards/{card_uuid}/disable/ \
  -H "Authorization: Token YOUR_FENERUM_TOKEN" \
  -H "X-Client-System: YourApp"

Note: Disabling a card in Fenerum doesn't delete it from the payment provider. It only prevents Fenerum from charging it.

Gateway Reference

When creating payment cards, use these gateway identifiers:

ProviderGateway ValueToken Type
Stripestripe_newPayment Method ID (pm_...)
QuickpayquickpaySubscription ID (integer)

Troubleshooting

Card registration fails:

  • Verify payment provider credentials in Fenerum settings
  • Check that the account UUID exists in Fenerum
  • Ensure the token format matches the gateway type (pm_... for Stripe, integer for Quickpay)
  • Confirm you're using the correct gateway value (stripe_new or quickpay)
Previous
Logging
background logo

We invoice 2 billion DKK annually for our customers. Let's put your invoicing on autopilot today!