Skip to content

Commit

Permalink
Merge pull request #8 from olssonm/dev/payouts
Browse files Browse the repository at this point in the history
Add payouts
  • Loading branch information
olssonm authored Aug 12, 2024
2 parents c0f6e67 + f8e4ab3 commit efa179c
Show file tree
Hide file tree
Showing 32 changed files with 1,395 additions and 493 deletions.
12 changes: 8 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest,ubuntu-20.04]
php: [8.1,8.2]
laravel: [^9.0,^10.0]
php: [8.1,8.2,8.3]
laravel: [11.*, 10.*]
dependency-version: [prefer-stable]
exclude:
- laravel: 11.*
php: 8.1

name: ${{ matrix.os }}, PHP ${{ matrix.php }} / Laravel ${{ matrix.laravel }}

steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -27,7 +31,7 @@ jobs:
run: composer self-update --2

- name: Install dependencies
run: composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-progress --no-suggest
run: composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-progress

- name: Execute tests
run: composer test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
/.phpunit.result.cache
.DS_Store
/composer.lock
/.phpunit.cache/test-results
.phpunit.cache
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# The MIT License (MIT)

Copyright (c) 2022-2023 Marcus Olsson <[email protected]>
Copyright (c) 2022-2024 Marcus Olsson <[email protected]>

> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
Expand Down
92 changes: 73 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A simple and easy to use wrapper for the Swish-API in PHP. Also includes provide

## Prerequisites

This package supports PHP ^8.1, as well as Laravel 7 and up to the latest version. PHP needs to be compiled with the cURL and SSL-extensions (in an abosolute majority of cases they should be available per default.)
This package supports PHP ^8.1, as well as Laravel 7 and up to the latest version. PHP needs to be compiled with the cURL and SSL extensions (in an absolute majority of cases, they should be available by default).

*Using PHP 7.4 or 8.0? v1.0 has support for these.*

Expand All @@ -25,7 +25,7 @@ You will need to have access to your Swish-certificates to use this package in p

Read more about testing in their MSS-environment in their [official documentation](https://developer.swish.nu/documentation/environments#:~:text=the%20certificate%20again.-,Merchant%20Swish%20Simulator,-The%20Swish%20server). A quick rundown on using/creating Swish-certificates [is published here](https://marcusolsson.me/artiklar/hur-man-skapar-certifikat-for-swish) (in Swedish).

When creating the client you will have to set which environment you are working with (otherwise it defaults to production-environment, `https://cpc.getswish.net/swish-cpcapi/api/v2`), you may use `Client::TEST_ENDPOINT` and `Client::PRODUCTION_ENDPOINT` for this:
When creating the client, you will have to set which environment you are working with (otherwise it defaults to the production environment, `https://cpc.getswish.net/swish-cpcapi/api/`), you may use `Client::TEST_ENDPOINT` and `Client::PRODUCTION_ENDPOINT` for this:

``` php
use Olssonm\Swish\Certificate;
Expand All @@ -34,7 +34,9 @@ use Olssonm\Swish\Client;
$certificate = new Certificate(
'/path/to/client.pem',
'client-passphrase',
'/path/to/root.pem' // Can also be omitted for "true" to verify peer
'/path/to/root.pem', // Can also be omitted for "true" to verify peer
'/path/to/signing.key', // Path to signing certificate, only used for payouts
'signing-passphrase' // Only used for payouts
);
$client = new Client($certificate, $endpoint = Client::TEST_ENDPOINT)
```
Expand All @@ -47,14 +49,16 @@ With the Laravel service provider and facades you can work with the package more
php artisan vendor:publish --provider="Olssonm\Swish\Providers\SwishServiceProvider"
```

In `/config/swish.php` you can then set your details accordingly:
In `/config/swish.php`, you can then set your details accordingly:

``` php
return [
'certificates' => [
'client' => env('SWISH_CLIENT_CERTIFICATE_PATH'),
'password' => env('SWISH_CLIENT_CERTIFICATE_PASSWORD'),
'root' => env('SWISH_ROOT_CERTIFICATE_PATH', true),
'signing' => env('SWISH_SIGNING_CERTIFICATE_PATH', null),
'signing_password' => env('SWISH_CLIENT_SIGNING_PASSWORD', null),
],
'endpoint' => \Olssonm\Swish\Client::PRODUCTION_ENDPOINT,
];
Expand Down Expand Up @@ -116,12 +120,55 @@ $response = Swish::create(new Payment([

### Payments and Refunds

> [!TIP]
> Read more about [payments](https://developer.swish.nu/api/payment-request/v2) and [refunds](https://developer.swish.nu/api/refunds/v2) in the official documentation
Always when using the client, use the Payment and Refund-classes <u>even if only the ID is needed for the action</u>, i.e:

``` php
$payment = $client->get(Payment(['id' => '5D59DA1B1632424E874DDB219AD54597']));
```

### Payouts

> [!TIP]
> Read more about [payouts](https://developer.swish.nu/api/payouts/v1) in the official documentation
Payouts need to be hashed using SHA512 and signed with a signing certificate before being sent to Swish – don't worry though, this package will handle most of this automatically. Just make sure that the path to your signing certificate is set:

``` php
$certificate = new Certificate(
'/path/to/client.pem',
'client-passphrase',
true,
'/path/to/signing.key',
'signing-passphrase'
);
$client = new Client($certificate);

$payout = $client->create(new Payout([]));
```

Additionally your certificate's (*note:* your signing certificate, not your client certificate) serial needs to be supplied. You can either use the `Certificate`-class to handle on the fly:

``` php
$certificate = new Certificate(/**/);
$payout = new Payout([
'signingCertificateSerialNumber' => $certificate->getSerial()
])
```

Or assign it yourself ([see gist for extracting serial](https://gist.github.com/olssonm/7da7d35dff2ec3ae74d0d0439003913c)):

``` php
$payout = new Payout([
'signingCertificateSerialNumber' => '4512B3EBDA6E3CE6BFB14ABA6274A02C'
])
```

> [!IMPORTANT]
> Note that Payouts uses `payoutInstructionUUID` instead of an `ID`, you should [set this yourself to keep track of it](#regarding-idsuuids). If it's missing, it will be set automatically upon creation.
### Regarding IDs/UUIDs

This package uses the v2 of the Swish API where a UUID is set by the merchant. This package handles all these aspects automatically as needed, you may however choose to manually set the ID/instructionUUID (either in Swish's own format, or a default v4-format):
Expand All @@ -136,18 +183,22 @@ $payment = new Payment([
]);
```

When generating the UUIDs on the fly, the package uses [Ramsey/Uuid](https://github.com/ramsey/uuid) to generate RFC4122 (v4) UUIDs. Swish accepts V1, 3, 4 and 5 UUIDs if you chose to set your own UUIDs.

If an invalid UUID is used, a `Olssonm\Swish\Exceptions\InvalidUuidException` will be thrown.

*Note 1:* Wheter you set a default UUID or one in the Swish-format – it will <u>always</u> be formatted for Swish automatically (dashes removed and in uppercase).
*Note 2:* This package uses [Ramsey/Uuid](https://github.com/ramsey/uuid) to generate RFC4122 (v4) UUIDs on the fly. Swish accepts V1, 3, 4 and 5 UUIDs if you chose to set your own UUIDs.
> [!NOTE]
> No matter if you set your own UUID och let the package handle the generation, the UUID will <u>always</u> be formatted for Swish automatically (dashes removed and in uppercase).
### Available methods

This package handles the most common Swish-related tasks; retrieve, make and cancel payments, as well as retrieve and make refunds. All of them are performed via `Olssonm\Swish\Client`;
This package handles the most common Swish-related tasks; retrieve, make and cancel payments. Retrieve and create payouts, aswell as refunds can be created and retrieved. All of them are performed via `Olssonm\Swish\Client`;

`$client->get(Payment $payment | Refund $refund);`
`$client->create(Payment $payment | Refund $refund);`
`$client->cancel(Payment $payment);`
``` php
$client->get(Payment $payment | Refund $refund | Payout $payout);
$client->create(Payment $payment | Refund $refund | Payout $payout);
$client->cancel(Payment $payment);
```

### Exception-handling

Expand All @@ -170,44 +221,47 @@ try {

For `4xx`-error a `\Olssonm\Swish\Exceptions\ClientException` will be thrown, and for `5xx`-errors `\Olssonm\Swish\Exceptions\ServerException`. Both of these implements Guzzles `BadResponseException` which makes the request- and response-objects available if needed.

## Callback
## Callbacks

Swish recommends to not use the `payments`-endpoint to get the status of a payment or refund (even if they themselves use it in some of their examples...), but instead use callbacks.

This package includes a simple helper to retrieve a `Payment` or `Refund` object from a callback that will contain all data from Swish:
This package includes a simple helper to retrieve a `Payment`, `Refund` or `Payout` object from a callback that will contain all data from Swish:

```php
use Olssonm\Swish\Callback;

$paymentOrRefund = Callback::parse($content = null);
$paymentOrRefund = Callback::parse();

// get_class($paymentOrRefund) = \Olssonm\Swish\Payment::class or \Olssonm\Swish\Refund::class
// get_class($paymentOrRefund) = \Olssonm\Swish\Payment::class, \Olssonm\Swish\Refund::class, \Olssonm\Swish\Payout::class
```

The helper automatically retrieve the current HTTP-request. You may however inject your own data if needed (or if you for example has a Laravel request-object ready):
The helper automatically retrieve the current HTTP-request (via `file_get_contents('php://input')`). You may however inject your own data if needed (or if you for example has a Laravel request-object ready):

```php
class SwishController
{
public function Callback(Request $request)
{
$data = Callback::parse($content = $request->getContent());
$data = Callback::parse($request->getContent());

if(get_class($data) == \Olssonm\Swish\Payment::class) {
// Handle payment callback
} else if(get_class($data) == \Olssonm\Swish\Refund::class) {
// Handle refund callback
} else if(get_class($data) == \Olssonm\Swish\Payout::class) {
// Handle payout callback
}
}
}
```

*Note: in a real world scenario you probably want to use separate callback-urls for your refunds and payments to prevent unnecessary parsing as the example above*
*In a real world scenario you probably want to use separate callback-urls for your refunds, payouts and payments to prevent unnecessary parsing as the example above.*

Please note that the callback from Swish is not encrypted or encoded in any way, instead you should make sure that the callback is coming from a [valid IP-range](https://developer.swish.nu/documentation/environments).
> [!CAUTION]
> Please note that the callback from Swish is not encrypted or encoded in any way , instead you should make sure that the callback is coming from a [valid IP-range](https://developer.swish.nu/documentation/environments).
## License

The MIT License (MIT). Please see the [LICENSE](LICENSE) for more information.

© 2022-2023 [Marcus Olsson](https://marcusolsson.me).
© 2022-2024 [Marcus Olsson](https://marcusolsson.me).
41 changes: 40 additions & 1 deletion UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,45 @@
# Upgrade guide

## v1.0 - v2.0
## v2.x - v3.0

### Overview

v3.0 introduces the ability to perform payouts via the Swish-API. To enable this, a signing certificate has to be used, and therefore the usage of the `Olssonm\Swish\Certificate`-class been somewhat altered.

Additionally, the `Olssonm\Swish\Util\Crypto` and `Olssonm\Swish\Util\Time` utilities has been added to enable the necessary signing of payloads for the payouts.

All other methods for creating and retrieving payments and refunds are intact.

### Changes

`Olssonm\Swish\Certificate` now accepts additional parameters – the path to your signing certificate and it's (optional) passphrase:

``` php
$certificate = new Certificate(
'/path/to/client.pem',
'client-passphrase',
'/path/to/root.pem',
'/path/to/signing.key', // <-- New
'signing-passphrase' // <-- New
);
```

Please note that they can be omitted if you are not planning to use the Payout-features.

If you're using Laravel and publish the config, you should add the `signing`-key to the swish.php-config:
``` php
return [
'certificates' => [
/* ... */
'signing' => env('SWISH_SIGNING_CERTIFICATE_PATH', null),
'signing_password' => env('SWISH_SIGNING_CERTIFICATE_PASSWORD', null),
],
'endpoint' => \Olssonm\Swish\Client::PRODUCTION_ENDPOINT,
]
```
## v1.x - v2.0
### Overview
v2.0 uses pem-certificates to simplify the handling for the authorization chain with Swish.
Expand Down
13 changes: 7 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
"require": {
"php": "^8.1",
"guzzlehttp/guzzle": "^6.3.1|^7.0",
"ramsey/uuid": "^4.2"
"ramsey/uuid": "^4.2",
"nesbot/carbon": "^2|^3"
},
"require-dev": {
"phpunit/phpunit": "^8.3 || ^9.3",
"orchestra/testbench": ">=4.0",
"phpunit/phpunit": "^8.3 || ^9.3 || ^10.0",
"orchestra/testbench": "^8.0 || ^9.0",
"squizlabs/php_codesniffer": "^3.5",
"pestphp/pest": "^1.0",
"pestphp/pest": "^1.0 || ^2.0",
"phpstan/phpstan": "^1.10"
},
"autoload": {
Expand All @@ -35,13 +36,13 @@
"scripts": {
"test": "./vendor/bin/pest",
"phpstan": "./vendor/bin/phpstan",
"coverage": "XDEBUG_MODE=coverage ./vendor/bin/pest --coverage",
"coverage": "XDEBUG_MODE=coverage vendor/bin/pest --coverage",
"phpsniff": "vendor/bin/phpcs --standard=\"PSR12\" ./src",
"phpfix": "vendor/bin/phpcbf --standard=\"PSR12\" ./src"
},
"extra": {
"branch-alias": {
"dev-main": "2.0.x-dev"
"dev-payouts": "3.0.x-dev"
},
"laravel": {
"providers": [
Expand Down
3 changes: 2 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ parameters:
level: 8
paths:
- src
checkGenericClassInNonGenericObjectType: false
ignoreErrors:
- identifier: missingType.generics
30 changes: 16 additions & 14 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="true">
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<php>
<ini name="xdebug.mode" value="coverage" />
</php>
<phpunit backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php"
colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true"
convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="true">
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<php>
<ini name="xdebug.mode" value="coverage" />
</php>
</phpunit>
16 changes: 16 additions & 0 deletions phpunit.xml.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="true">
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<php>
<ini name="xdebug.mode" value="coverage" />
</php>
</phpunit>
Loading

0 comments on commit efa179c

Please sign in to comment.