Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#3 Add support for various decimal points for currencies #4

Merged
merged 5 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
],
"homepage": "https://github.com/pixelpillow/lunar-api-mollie-adapter",
"license": "MIT",
"version": "0.1.3",
"version": "0.1.4",
"authors": [
{
"name": "Thomas van der Westen",
Expand Down Expand Up @@ -84,4 +84,4 @@
},
"minimum-stability": "dev",
"prefer-stable": true
}
}
6 changes: 1 addition & 5 deletions src/Domain/Payments/Data/PaymentIntent.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,12 @@ public function getId(): mixed
return $this->intent->id;
}

// id: $molliePayment->id,
// status: $molliePayment->status,
// amount: MollieManager::normalizeAmountToInteger($molliePayment->amount->value),

/**
* Get amount.
*/
public function getAmount(): int
{
return MollieManager::normalizeAmountToInteger($this->intent->amount->value);
return MollieManager::normalizeAmountToInteger($this->intent->amount->value, $this->intent->amount->currency);
}

/**
Expand Down
24 changes: 20 additions & 4 deletions src/Managers/MollieManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use Lunar\Models\Cart;
use Lunar\Models\Currency;
use Lunar\Models\Transaction;
use Mollie\Api\Exceptions\ApiException;
use Mollie\Api\Resources\IssuerCollection;
Expand Down Expand Up @@ -180,9 +181,9 @@ public static function getCancelUrl(Cart $cart): string
*
* @param int $amount The amount in cents
*/
public static function normalizeAmountToString(int $amount): string
public static function normalizeAmountToString(int $amount, int $decimal_places = 2): string
{
return number_format($amount / 100, 2, '.', '');
return number_format($amount / 100, $decimal_places, '.', '');
}

/**
Expand All @@ -191,9 +192,24 @@ public static function normalizeAmountToString(int $amount): string
* eg. 1000 instead of 10.00
* This is the opposite of normalizeAmountToString
*/
public static function normalizeAmountToInteger(string $amount): int
public static function normalizeAmountToInteger(string $amount, string $currency): int
{
return (int) str_replace('.', '', $amount);
/** @var Currency */
$currency = Currency::where('code', $currency)->first();

if ($currency === null) {
throw new InvalidConfigurationException('Currency "'.$currency.'" not found');
}

$parts = explode('.', $amount);
$fraction = isset($parts[1]) ? $parts[1] : '';
$fraction_length = strlen($fraction);

if ($fraction_length < $currency->decimal_places) {
$fraction .= str_repeat('0', $currency->decimal_places - $fraction_length);
}

return $parts[0].$fraction;
}

/**
Expand Down
25 changes: 10 additions & 15 deletions src/MolliePaymentAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public function createIntent(Cart $cart, array $meta = [], ?int $amount = null):

try {
$amount = $amount ?? null;

$molliePayment = $this->mollie->createPayment(
cart: $cart->calculate(),
paymentMethod: $paymentMethodType,
Expand Down Expand Up @@ -176,31 +177,31 @@ public function handleWebhook(Request $request): JsonResponse
if ($payment->isPaid() && $transaction->status !== 'paid') {
App::make(AuthorizeMolliePayment::class)($order, $paymentIntent, $transaction);

$this->createChildTransaction($transaction, ['status' => 'paid']);
$this->updateTransactionStatus($transaction, 'paid');

return response()->json(['message' => 'success']);
}

if ($payment->isCanceled()) {
OrderPaymentCanceled::dispatch($order, $this, $paymentIntent);

$this->createChildTransaction($transaction, ['status' => 'canceled']);
$this->updateTransactionStatus($transaction, 'canceled');

return response()->json(['message' => 'canceled']);
}

if ($payment->isFailed()) {
OrderPaymentFailed::dispatch($order, $this, $paymentIntent);

$this->createChildTransaction($transaction, ['status' => 'failed']);
$this->updateTransactionStatus($transaction, 'failed');

return response()->json(['message' => 'failed']);
}

if ($payment->isExpired()) {
OrderPaymentFailed::dispatch($order, $this, $paymentIntent);

$this->createChildTransaction($transaction, ['status' => 'expired']);
$this->updateTransactionStatus($transaction, 'expired');

return response()->json(['message' => 'expired']);
}
Expand All @@ -209,19 +210,13 @@ public function handleWebhook(Request $request): JsonResponse
}

/**
* Create updated child transaction.
*
* @param array<string,mixed> $data
* Update the transaction status
*/
protected function createChildTransaction(Transaction $transaction, array $data): void
protected function updateTransactionStatus(Transaction $transaction, string $status): void
{
$data = array_merge(
Arr::except($transaction->getAttributes(), 'id'),
['parent_transaction_id' => $transaction->id],
$data,
);

$child = (new Transaction)->fill($data)->save();
$transaction->update([
'status' => $status,
]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/MolliePaymentType.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public function refund(Transaction $transaction, int $amount = 0, $notes = null)
'success' => $refund->status !== RefundStatus::STATUS_FAILED,
'type' => 'refund',
'driver' => 'mollie',
'amount' => $this->mollie->normalizeAmountToInteger($refund->amount->value),
'amount' => $this->mollie->normalizeAmountToInteger($refund->amount->value, $refund->amount->currency),
'reference' => $refund->id,
'status' => $refund->status,
'notes' => $notes,
Expand Down
132 changes: 131 additions & 1 deletion tests/Feature/CreatePaymentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\URL;
use Lunar\Facades\CartSession;
use Lunar\Models\Currency;
use Mollie\Api\MollieApiClient;
use Mollie\Api\Resources\Payment;
use Mollie\Api\Types\PaymentMethod;
use Mollie\Api\Types\PaymentStatus;
use Pixelpillow\LunarApiMollieAdapter\Managers\MollieManager;
use Pixelpillow\LunarApiMollieAdapter\Tests\TestCase;

uses(TestCase::class, RefreshDatabase::class);
Expand Down Expand Up @@ -178,7 +180,9 @@
'https://api.mollie.com/*' => Http::response(json_encode($mollieMockPayment)),
]);

$amount = 2000;
$currency = Currency::getDefault();

$amount = MollieManager::normalizeAmountToInteger('20.00', $currency->code);

$response = $this
->jsonApi()
Expand Down Expand Up @@ -209,3 +213,129 @@
'amount' => $amount,
]);
});

test('a payment with a currency with 4 decimals can be created', function () {
/** @var TestCase $this */
$url = URL::signedRoute(
'v1.orders.createPaymentIntent',
[
'order' => $this->order->getRouteKey(),
],
);

Event::fake(OrderPaymentSuccessful::class);

$mollieMockPayment = new Payment(app(MollieApiClient::class));
$mollieMockPayment->id = uniqid('tr_');
$mollieMockPayment->status = PaymentStatus::STATUS_PAID;
$mollieMockPayment->method = PaymentMethod::IDEAL;
$mollieMockPayment->amount = [
'value' => '20.00',
'currency' => 'USD',
];

$mollieMockPayment->_links = [
'checkout' => [
'href' => 'https://www.mollie.com/checkout/test-mode?method=ideal&token=6.5gwscs',
],
];

Http::fake([
'https://api.mollie.com/*' => Http::response(json_encode($mollieMockPayment)),
]);

Currency::factory()->create([
'code' => 'USD',
'decimal_places' => 4,
]);

$amount = MollieManager::normalizeAmountToInteger('20.00', 'USD');

$response = $this
->jsonApi()
->expects('orders')
->withData([
'type' => 'orders',
'id' => (string) $this->order->getRouteKey(),
'attributes' => [
'payment_method' => 'mollie',
'amount' => $amount,
'meta' => [
'payment_method_type' => PaymentMethod::IDEAL,
'payment_method_issuer' => 'ideal_ABNANL2A',
],
],
])
->post($url);

$response->assertSuccessful();

// Expect a checkout url to be returned
expect($response->json('meta.payment_intent.meta.mollie_checkout_url'))->toBeString();

// Expect a transaction to be created
$this->assertDatabaseHas('lunar_transactions', [
'order_id' => $this->order->id,
'card_type' => PaymentMethod::IDEAL,
'amount' => $amount,
]);
});

test('can normalize amount to integer', function () {
$currency = Currency::getDefault();

$amount = MollieManager::normalizeAmountToInteger('20.00', $currency->code);
expect($amount)->toBeInt();
expect($amount)->toBe(2000);

$amount = MollieManager::normalizeAmountToInteger('20.96', $currency->code);
expect($amount)->toBeInt();
expect($amount)->toBe(2096);

$amount = MollieManager::normalizeAmountToInteger('20.60', $currency->code);
expect($amount)->toBeInt();
expect($amount)->toBe(2060);

$amount = MollieManager::normalizeAmountToInteger('20.6', $currency->code);
expect($amount)->toBeInt();
expect($amount)->toBe(2060);

$amount = MollieManager::normalizeAmountToInteger('20', $currency->code);
expect($amount)->toBeInt();
expect($amount)->toBe(2000);

$amount = MollieManager::normalizeAmountToInteger('20.00', $currency->code);
expect($amount)->toBeInt();
expect($amount)->toBe(2000);
});

test('can normalize amount to integer for a currency with 4 decimals', function () {
tdwesten marked this conversation as resolved.
Show resolved Hide resolved
$currency = Currency::factory()->create([
'code' => 'USD',
'decimal_places' => 4,
]);

$amount = MollieManager::normalizeAmountToInteger('20.00', $currency->code);
tdwesten marked this conversation as resolved.
Show resolved Hide resolved
expect($amount)->toBeInt();
expect($amount)->toBe(200000);

$amount = MollieManager::normalizeAmountToInteger('20.96', $currency->code);
expect($amount)->toBeInt();
expect($amount)->toBe(209600);

$amount = MollieManager::normalizeAmountToInteger('20.60', $currency->code);
expect($amount)->toBeInt();
expect($amount)->toBe(206000);

$amount = MollieManager::normalizeAmountToInteger('20', $currency->code);
expect($amount)->toBeInt();
expect($amount)->toBe(200000);

$amount = MollieManager::normalizeAmountToInteger('20.6', $currency->code);
expect($amount)->toBeInt();
expect($amount)->toBe(206000);

$amount = MollieManager::normalizeAmountToInteger('20.00', $currency->code);
expect($amount)->toBeInt();
expect($amount)->toBe(200000);
});
Loading