sofort/sofortlib-php
PHP client library for the SOFORT API: initiate SOFORT Überweisung payments, Paycode/Billcode, refunds, and iDEAL. Fetch transaction details, parse XML responses, and generate iDEAL forward URLs and checksums. Includes examples and PHPUnit tests.
Installation
composer require sofort/sofortlib-php:^3.3.2
Update the package to the latest version and run composer update.
First Use Case: Basic Payment Request with Project ID
use Sofort\Sofort;
$sofort = new Sofort([
'project_id' => env('SOFORT_PROJECT_ID'), // Now supports per-transaction override
'shop_id' => env('SOFORT_SHOP_ID'),
'shop_password' => env('SOFORT_SHOP_PASSWORD'),
'sandbox' => env('APP_ENV') === 'local',
]);
// Set project_id per transaction (new in 3.3.2)
$payment = $sofort->createPayment([
'amount' => 100.00,
'currency' => 'EUR',
'order_id' => 'ORDER-12345',
'customer_email' => 'customer@example.com',
'project_id' => 'CUSTOM_PROJECT_123', // Optional: Override global project_id
]);
header('Location: ' . $payment->getPaymentUrl());
Where to Look First
Sofort.php (updated constructor logic) and Payment.php (new project_id handling)..env (e.g., SOFORT_PROJECT_ID, SOFORT_SHOP_ID).Payment Creation with Dynamic Project ID
// Global project_id (default)
$sofort = new Sofort(['project_id' => 'DEFAULT_PROJECT']);
// Per-transaction override
$payment = $sofort->createPayment([
'amount' => 200.00,
'project_id' => 'SPECIAL_PROJECT_456', // Overrides global setting
]);
Handling Callback with Project ID Validation
$callback = $sofort->handleCallback($_POST);
if ($callback->isValid() && $callback->getProjectId() === 'EXPECTED_PROJECT') {
// Process only if project_id matches expected value
Order::where('id', $callback->getOrderId())->update(['status' => 'paid']);
}
Refund Processing with Project ID
$refund = $sofort->createRefund([
'transaction_id' => $transactionId,
'amount' => 50.00,
'project_id' => 'REFUND_PROJECT_789', // Optional override
]);
Laravel Service Provider (Updated) Bind the SOFORT client with support for dynamic project IDs:
$this->app->singleton(Sofort::class, function ($app) {
return new Sofort([
'project_id' => config('services.sofort.default_project_id'),
'shop_id' => config('services.sofort.shop_id'),
'shop_password' => config('services.sofort.shop_password'),
'sandbox' => config('services.sofort.sandbox'),
]);
});
Middleware for Project ID Validation Extend callback validation to include project ID checks:
public function handle($request, Closure $next) {
$callback = app(Sofort::class)->handleCallback($request->all());
if (!$callback->isValid() || !in_array($callback->getProjectId(), config('services.sofort.allowed_projects'))) {
abort(403, 'Invalid project ID or callback');
}
return $next($request);
}
Logging Project-Specific Transactions Log project IDs alongside payment responses:
$payment = $sofort->createPayment($data);
\Log::info('SOFORT Payment', [
'project_id' => $payment->getProjectId(),
'response' => $payment->getResponse(),
]);
Project ID Conflicts
project_id overrides the global setting. Ensure this is intentional.getProjectId() in callbacks to prevent unauthorized transactions.Sandbox vs. Live Mode (Reiterated)
Deprecated Methods (Still Applies)
Project ID Format
PROJ_123 or CUSTOM_456). Validate format early.Enable Debug Mode (Updated)
$sofort = new Sofort([...], true); // Enable debug logging
Logs now include project ID in responses for clarity.
Common Errors (Updated)
| Error | Cause | Solution |
|---|---|---|
Project ID not found |
Invalid project_id in request |
Verify project ID exists in SOFORT dashboard. |
Project ID mismatch |
Callback project_id differs from request |
Validate getProjectId() in middleware. |
Unauthorized project |
Project ID lacks permissions | Check SOFORT project settings. |
Dynamic Project ID Resolution
Override the getProjectId() method in a custom Sofort class:
class CustomSofort extends Sofort {
public function getProjectId(array $options = []): string {
return $options['project_id'] ?? parent::getProjectId();
}
}
Project-Based Routing
Route callbacks to different handlers based on project_id:
Route::post('/sofort/callback', function () {
$callback = app(Sofort::class)->handleCallback(request()->all());
$handler = "App\\Handlers\\Project{$callback->getProjectId()}Handler";
$handler::process($callback);
});
Testing Project ID Scenarios Mock project ID behavior in tests:
$mock = Mockery::mock(Sofort::class);
$mock->shouldReceive('createPayment')
->withArgs(function ($args) {
return $args['project_id'] === 'TEST_PROJECT';
})
->andReturn(new Payment(['success' => true]));
$this->app->instance(Sofort::class, $mock);
Multi-Tenant Support Use project IDs to isolate tenants:
$tenantProjectId = Tenant::find($request->tenant_id)->project_id;
$payment = $sofort->createPayment([
'project_id' => $tenantProjectId,
// ... other data
]);
How can I help you explore Laravel packages today?