Hi developers!
Welcome to the bunq PHP SDK integration tests. Currently we are not targeting the 100% test coverage, but rather want to be certain that the most common scenarios can run without any errors.
First create a certificate and key using this command. Change "Your App Id" to a unique id.
You can run the tests via command line:
or via PhpStorm, but first you must configure PhpStorm by doing the following:
Got to preferences --> Language and Frameworks --> Php --> Test Frameworks and addsdk_php/vendor/autoload.php as path to script with use composer autoloader checked.
Afterwards you can right click on the tests folders and should be able to run the tests cases form the IDE.
openssl req -x509 -newkey rsa:4096 -keyout tests/resource/key.pem -out tests/resource/certificate.cert -days 365 -nodes -subj '/CN=Your App Id/C=NL'vendor/phpunit/phpunit/phpunitBy integrating with bunq's Public API, you agree to comply with bunq’s Terms & Conditions and all applicable usage policies. Please review them carefully before getting started.
Welcome to the bunq PHP SDK documentation! 👨💻
Before you dive into this documentation, please consider:
Learning what objects you will work with by reading about our bunq API objects 🤓
Checking out 🙌
Grabbing your Production API key from or the bunq app 🗝
Generating a Sandbox API key using 🗝
Visiting where you can share your creations, questions and experience 🎤
Give us your feedback, create pull requests, build your very own bunq apps and most importantly: have fun! 💪
Here you can find our SDK code and start building:
Now let's dive into the SDK documentation.
In the next sections, you’ll find everything you need to get comfortable with the bunq SDK — how to use it, run tests, and deal with any exceptions that might come your way. Whether you're just starting out or deep into development, we've got your back!
$ composer require bunq/sdk_phpIn order to start making calls with the bunq API, you must first register your API key and device, and create a session. In the SDKs, we group these actions and call it "creating an API context". There are two ways to do it. One is through our interactive script, and the other is programmatically from your code.
src/Util/InstallationUtil.php
Creating an API context using bunq-install interactive script
After installing bunq SDK into your project, run the command below from your project root folder:
And then follow the steps the script offers.
Creating an API context programmatically
The context can be created by executing the following code snippet:
Load the API Context:
Please note: initializing your application is a heavy task and it is recommended to do it only once per device.
PSD2
It is possible to create an ApiContext as PSD2 Service Provider. Although this might seem a complex task, we wrote some helper implementations to get you started. You need to create a certificate and private key to get you started. Our sandbox environment currently accepts all certificates, if these criteria are met:
Up to 64 characters
PISP and/or AISP used in the end.
Make sure you have your unique eIDAS certificate number and certificates ready when you want to perform these tasks on our production environment.
Creating a PSD2 context is very easy:
This context can be saved the same way as a normal ApiContext. After creating this context, create an OAuth client to get your users to grant you access. For a more detailed example, check the repository.
Proxy
You can use a proxy with the bunq PHP SDK. This option must be a string. This proxy will be used for all requests done with the context for which it was provided. You will be prompted to provide a proxy URL when using the interactive installation script.
Safety considerations
The file storing the context details (i.e. bunq.conf) is a key to your account. Anyone having access to it is able to perform any Public API actions with your account. Therefore, we recommend choosing a truly safe place to store it.
If you rather save the context in a database, you can use the fromJson() and toJson() methods.
There is a class for each endpoint. Each class has functions for each supported action. These actions can be create, get, update, delete and listing.
Before you can start making calls, you must ensure that you have create an ApiContext and loaded in into BunqContext as shown in the examples above.
The SDK will take care of your user Id, as this id will never change per ApiContext. The SDK also uses your first active monetary account as primary monetary account. This is almost always the same as your billing account. This means that when you do not explicitly pass a Monetary Account ID, the SDK will use the Monetary Account ID of your billing account.
Create a monetary account:
Get account details:
Close a monetary account:
Make a payment:
Make a payment to another monetary account:
Create a payment request:
Accept a payment request:
Create a bunqme tab:
Create a public attachment:
Get attachment content:
Create an avatar:
Get allowed card names:
Order a debit card:
Create notification filters for a monetary account:
Create notification filters for a user:
Create push notification filters:
Create OAuth authorization URI:
Delete a session:
Reset a session:
Reading objects
To use the read method you must pass the identifier of the object to read (ID or UUID) except for the endpoints User, UserPerson, UserCompany and MonetaryAccount. The SDK will use the default IDs when none are passed. For all other endpoints you must pass the identifier.
This type of calls always returns a model.
Updating objects
Deleting objects
Listing objects
When you make a request via the SDK, there is a chance of request failing due to various reasons. When such a failure happens, an exception corresponding to the error occurred is thrown.
Possible Exceptions
BadRequestException If the request returns with status code 400
UnauthorizedException
401ForbiddenException If the request returns with status code 403
NotFoundException If the request returns with status code 404
MethodNotAllowedException If the request returns with status code 405
TooManyRequestsException If the request returns with status code 429
PleaseContactBunqException If the request returns with status code 500. If you get this exception, please contact us preferably via the support chat in the bunq app.
UnknownApiErrorException If none of the above mentioned exceptions are thrown, this exception will be thrown instead.
For more information regarding these errors, please take a look on the documentation page here:
Base exception
All the exceptions have the same base exception which looks like this:
The Exception class which is being extended has an getMessage() method which is final and therefore cannot be overridden.
This means that each exception will have a response code and an error message related to the specific error returned by API.
Exception handling
Since each API error has a distinct SDK exception type corresponding to it, you can catch the exact exceptions you expect 👏.
$ vendor/bin/bunq-installuse bunq\Context\ApiContext;
use bunq\Util\BunqEnumApiEnvironmentType;
use bunq\Util\InstallationUtil;
// Automatically install and save the API context
InstallationUtil::automaticInstall(
BunqEnumApiEnvironmentType::SANDBOX(), // Use PRODUCTION() for production
'/path/to/save/context.conf'
);use bunq\Context\BunqContext;
BunqContext::loadApiContext(
ApiContext::restore('/path/to/saved/context.conf')
);$apiContext = ApiContext::createForPsd2(
BunqEnumApiEnvironmentType::SANDBOX(), // Could be PRODUCTION as well.
SecurityUtil::getCertificateFromFile($pathToCertificate),
SecurityUtil::getPrivateKeyFromFile($pathToKey),
[
SecurityUtil::getCertificateFromFile($pathToCertificateInChain), // Could be one file containing chain, or multiple certificate files in array.
],
$description
)$proxyUrl = 'socks5://localhost:1080'; // The proxy for all requests, null to disable
$apiContext = ApiContext::create(
...
$proxyUrl
);use bunq\Model\Generated\Endpoint\MonetaryAccountBankApiObject;
$accountId = MonetaryAccountBankApiObject::create(
'EUR', // Currency
'My account description' // Description
)->getValue();$monetaryAccount = MonetaryAccountBankApiObject::get($accountId)->getValue();MonetaryAccountBankApiObject::update(
$accountId,
null, // New description (null = no change)
null, // Avatar (null = no change)
null, // Status (null = no change)
'CANCELLED', // Status
'REDEMPTION_VOLUNTARY', // Sub-status
'OTHER', // Reason
'Closing this test account' // Reason description
);use bunq\Model\Generated\Endpoint\PaymentApiObject;
use bunq\Model\Generated\Object\AmountObject;
use bunq\Model\Generated\Object\PointerObject;
// Create payment to another user
$payment = PaymentApiObject::create(
new AmountObject('0.01', 'EUR'), // Amount and currency
new PointerObject('EMAIL', '[email protected]'), // Recipient
'Payment description' // Description
);// Get the IBAN pointer of the recipient account
$recipientAccountAlias = $monetaryAccount->getAlias()[0]; // Get the first alias
$paymentId = PaymentApiObject::create(
new AmountObject('0.01', 'EUR'),
$recipientAccountAlias,
'Payment description'
);use bunq\Model\Generated\Endpoint\PaymentBatchApiObject;
// Create an array of payments
$payments = [];for ($i = 0; $i < 10; $i++) {
$payment = new PaymentApiObject(
new AmountObject('0.01', 'EUR'),
new PointerObject('EMAIL', '[email protected]'),
'Batch payment #' . $i
);
$payments[] = $payment;
}
// Create the batch payment
$batchId = PaymentBatchApiObject::create($payments)->getValue();use bunq\Model\Generated\Endpoint\RequestInquiryApiObject;
$requestId = RequestInquiryApiObject::create(
new AmountObject('0.01', 'EUR'),
new PointerObject('EMAIL', '[email protected]'),
'Request description',
false // Don't allow bunqme
)->getValue();use bunq\Model\Generated\Endpoint\RequestResponseApiObject;
// List responses to find the ID
$responses = RequestResponseApiObject::listing($monetaryAccountId)->getValue();
$requestResponseId = $responses[0]->getId();
// Accept the request
RequestResponseApiObject::update(
$requestResponseId,
$monetaryAccountId,
null,
'ACCEPTED' // Status
);use bunq\Model\Generated\Endpoint\BunqMeTabApiObject;
use bunq\Model\Generated\Endpoint\BunqMeTabEntryApiObject;
$tabId = BunqMeTabApiObject::create(
new BunqMeTabEntryApiObject(
new AmountObject('0.01', 'EUR'),
'Tab description'
)
)->getValue();
// Get tab details
$tab = BunqMeTabApiObject::get($tabId);use bunq\Http\ApiClient;
use bunq\Model\Generated\Endpoint\AttachmentPublicApiObject;
// Read the file content
$fileContents = file_get_contents('/path/to/file.png');
// Create the attachment
$attachmentUuid = AttachmentPublicApiObject::create(
$fileContents,
[
ApiClient::HEADER_CONTENT_TYPE => 'image/png',
ApiClient::HEADER_ATTACHMENT_DESCRIPTION => 'Attachment description'
]
)->getValue();use bunq\Model\Generated\Endpoint\AttachmentPublicContentApiObject;
$fileContents = AttachmentPublicContentApiObject::listing($attachmentUuid)->getValue();use bunq\Model\Generated\Endpoint\AvatarApiObject;
// First create a public attachment
$attachmentUuid = AttachmentPublicApiObject::create(
$fileContents,
[
ApiClient::HEADER_CONTENT_TYPE => 'image/png',
ApiClient::HEADER_ATTACHMENT_DESCRIPTION => 'Avatar image'
]
)->getValue();
// Create the avatar using the attachment
$avatarUuid = AvatarApiObject::create($attachmentUuid)->getValue();use bunq\Model\Generated\Endpoint\CardNameApiObject;
$cardNamesAllowed = CardNameApiObject::listing()->getValue();
$possibleNames = $cardNamesAllowed[0]->getPossibleCardNameArray();use bunq\Model\Generated\Endpoint\CardDebitApiObject;
use bunq\Model\Generated\Object\CardPinAssignmentObject;
$cardDebit = CardDebitApiObject::create(
'My Card', // Card description
$possibleNames[0], // Name on card
'MASTERCARD', // Card type
'MASTERCARD_DEBIT', // Product type
$userAlias->getName(), // Second line on card
$userAlias, // User alias
[ // PIN assignments
new CardPinAssignmentObject(
'PRIMARY', // PIN assignment type
'MANUAL', // Routing type
'1234', // PIN code
$primaryAccountId // Account ID
),
]
)->getValue();use bunq\Model\Generated\Endpoint\NotificationFilterUrlMonetaryAccountInternal;
use bunq\Model\Generated\Object\NotificationFilterUrlObject;
$filter = new NotificationFilterUrlObject(
'MUTATION', // Category
'https://example.com/callback' // Callback URL
);
$filters = NotificationFilterUrlMonetaryAccountInternal::createWithListResponse(
$monetaryAccountId,
[$filter]
)->getValue();use bunq\Model\Generated\Endpoint\NotificationFilterUrlUserInternal;
$filter = new NotificationFilterUrlObject(
'MUTATION',
'https://example.com/callback'
);
$filters = NotificationFilterUrlUserInternal::createWithListResponse(
[$filter]
)->getValue();use bunq\Model\Generated\Endpoint\NotificationFilterPushUserInternal;
use bunq\Model\Generated\Object\NotificationFilterPushObject;
$filter = new NotificationFilterPushObject('MUTATION');
$filters = NotificationFilterPushUserInternal::createWithListResponse(
[$filter]
)->getValue();use bunq\Model\Core\BunqEnumOauthResponseType;
use bunq\Model\Core\OauthAuthorizationUri;
use bunq\Model\Generated\Endpoint\OauthClientApiObject;
$authUri = OauthAuthorizationUri::create(
BunqEnumOauthResponseType::CODE(),
'https://redirect.example.com',
new OauthClientApiObject('client_status'),
'state_parameter'
)->getAuthorizationUriString();use bunq\Model\Generated\Endpoint\SessionApiObject;
SessionApiObject::delete(0); // 0 refers to the current sessionBunqContext::getApiContext()->resetSession();
BunqContext::getApiContext()->save('/path/to/context.conf');// Get the primary monetary account
$primaryAccount = BunqContext::getUserContext()->getPrimaryMonetaryAccount();
// Get the user alias
$userAlias = null;
if (BunqContext::getUserContext()->isOnlyUserPersonSet()) {
$userAlias = BunqContext::getUserContext()->getUserPerson()->getAlias()[0];
} elseif (BunqContext::getUserContext()->isOnlyUserCompanySet()) {
$userAlias = BunqContext::getUserContext()->getUserCompany()->getAlias()[0];
} elseif (BunqContext::getUserContext()->isOnlyUserApiKeySet()) {
$userAlias = BunqContext::getUserContext()
->getUserApiKey()
->getRequestedByUser()
->getReferencedObject()
->getAlias()[0];
}
// Refresh user context
BunqContext::getUserContext()->refreshUserContext();BunqContext::loadApiContext($apiContext); // if it has not been loaded yet.
$userCompany = UserCompany::get();
printf($userCompany->getPublicNickName());BunqContext::loadApiContext($apiContext); // if it has not been loaded yet.
MonetaryAccountBank::update(
$monetaryAccount->getId(),
$description
);BunqContext::loadApiContext($apiContext); // if it has not been loaded yet.
CustomerStatementExport::delete($customerStatementExportId);BunqContext::loadApiContext($apiContext); // if it has not been loaded yet.
$monetaryAccountList = MonetaryAccount::listing();
foreach ($monetaryAccountList as $monetaryAccount) {
printf($monetaryAccount->getMonetaryAccountBank->getDescription() . PHP_EOL);
}<?php
class ApiException extends Exception
{
/**
* @var int
*/
private $responseCode;
/**
* @param string $message
* @param int $responseCode
*/
public function __construct(string $message, int $responseCode)
{
// Some hidden code
}
/**
* @return int
*/
public function getResponseCode(): int
{
return $this->responseCode;
}
}<?php
use bunq\Context\ApiContext;
use bunq\Util\BunqEnumApiEnvironmentType;
use bunq\Exception\BadRequestException;
const API_KEY = 'Some invalid API key ';
const DEVICE_DESCRIPTION = 'This will cause BadRequestException to be thrown.';
try{
// Make a call that might fail
$apiContext = ApiContext::create(BunqEnumApiEnvironmentType::SANDBOX(), API_KEY, DEVICE_DESCRIPTION);
} catch (BadRequestException $error){
// Do something if exception is thrown.
echo $error->getResponseCode() . PHP_EOL;
echo $error->getMessage() . PHP_EOL;
}