More Tutorials

Payconiq by Bancontact integration with Chargebee.js

Payment Method Helper
Chargebee.js
Payments

This tutorial shows you how to integrate Payconiq by Bancontact payments using Chargebee.js. Payconiq by Bancontact is a European mobile payment solution that enables customers to pay by scanning QR codes with their smartphone app.

Supported regions

Payconiq by Bancontact is available in:

  • Belgium
  • Netherlands
  • Luxembourg

What you'll build

By the end of this tutorial, you'll have a working integration that:

  • Creates payment intents for Payconiq by Bancontact.
  • Handles Payconiq payment flow.
  • Creates subscriptions after successful payment authorization.

Prerequisites

Before you begin, ensure you have enabled Payconiq by Bancontact via Adyen in Chargebee Billing.

Set up Chargebee.js

Include the Chargebee.js script on your page and initialize a Chargebee instance before you start. For the script tag, the Chargebee.init options, and the tearDown() caveat, see Set up Chargebee.js.

Create a payment intent

Server-side implementation

Create a payment intent using the Create Payment Intent API:

curl https://{site-name}.chargebee.com/api/v2/payment_intents \
    -u {fullaccess_api_key}: \
    -d amount=500 \
    -d currency_code="EUR" \
    -d payment_method_type="payconiq_by_bancontact"

Client-side implementation

Call your server endpoint from the frontend:

function createPaymentIntent() {
	return fetch('/payment-intents', {
		method: 'POST',
		headers: {
			"Content-Type": "application/json"
		},
		body: JSON.stringify({
			amount: 500,
			currency_code: 'EUR',
			payment_method_type: 'payconiq_by_bancontact'
		})
	}).then(function(response) {
		return response.json();
	}).then(function(responseJson) {
		return responseJson.payment_intent;
	});
}

Implement the payment flow

Step 1: Load the payment method

Load the Payconiq by Bancontact integration using the load method:

cbInstance.load("payconiq_by_bancontact")

Step 2: Handle the payment

Use the handlePayment method to initiate the payment process.

When you call handlePayment(), it opens a modal popup overlay on your page that displays a QR code for the customer to scan with their Payconiq app. The modal stays open while the customer scans the QR code and completes the payment. It closes automatically when authorization completes.

cbInstance.load("payconiq_by_bancontact").then(() => {
	cbInstance.handlePayment("payconiq_by_bancontact", {
		paymentIntent: () => {
			return createPaymentIntent();
		},
		renderInfo: {
  		    heading: "Scan QR code",
            timerLabel: "This QR code is valid for {time}", // timer will be reflected in place of {time}
            timerDurationSeconds: 15 * 60, // 15 minutes
            buttonText: "Continue to Payconiq by Bancontact",
		}
	}).then(intent => {
		// Payment intent status is `in_progress` at this point - customer needs to scan QR code.
		// Poll for authorization before creating subscription.
		return waitForAuthorization(intent.id).then(authorizedIntent => {
			return createSubscription(authorizedIntent.id);
		});
	}).catch(err => {
		console.error('Payment failed:', err);
	});
});

function createSubscription(paymentIntentId) {
	return fetch('/subscriptions', {
		method: 'POST',
		headers: {
			"Content-Type": "application/json"
		},
		body: JSON.stringify({
			paymentIntentId: paymentIntentId,
			plan_id: 'pro_plan',
			plan_quantity: 1,
			billingAddress: {
				// Add billing address details
			},
			customer: {
				// Add customer details
			}
		})
	}).then(response => response.json());
}

Step 3: Monitor payment status

After calling handlePayment(), the payment intent status is in_progress while the customer scans the QR code. Poll the payment intent status until it becomes authorized or expires.

Use the Retrieve payment intent API to check the status:

async function waitForAuthorization(intentId, MAX_RETRIES = 450) { // ~15 min at 2s interval, matches timerDurationSeconds
  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
    const response = await fetch(`/payment-intents/${intentId}`, {
      method: "GET",
      headers: {
        "Content-Type": "application/json"
      }
    });
    
    const intent = await response.json();
    const status = intent?.payment_intent?.status;
    
    if (status === "authorized") {
      return intent.payment_intent;
    }
    if (status === "expired") {
      throw new Error(`Payment ${status}`);
    }
    
    // If not done, wait before the next attempt
    await new Promise(r => setTimeout(r, 2000));
  }
  throw new Error(`Authorization not received after ${MAX_RETRIES} retries`);
}

Payment status reference

For detailed information about payment intent statuses, see the Payment Intent API documentation.

StatusDescriptionAction
initedPayment intent createdNot applicable in this flow
in_progressCustomer is scanning QR codeContinue polling
authorizedPayment successfulCreate subscription
consumedPayment used for subscriptionDo not reuse
expiredPayment timed outShow error, allow retry

Create a subscription (server)

Pass the ID of the successfully authorized payment_intent to Chargebee’s create a subscription API.

curl  https://{site}.chargebee.com/api/v2/customers/__test__8asz8Ru9WhHOJO/subscription_for_items \
     -X POST \
     -u {site_api_key}: \
     -d payment_intent[id]="<Id of authorized payment_intent recieved in last step.>" \
     -d subscription_items[item_price_id][0]="basic-EUR" \
     -d subscription_items[billing_cycles][0]=2 \
     -d subscription_items[quantity][0]=1 \
     -d subscription_items[item_price_id][1]="day-pass-EUR" \
     -d subscription_items[unit_price][1]=100
Was this tutorial helpful ?
Need more help?

We're always happy to help you with any questions you might have! Click here to reach out to us.