llp 3 місяців тому
батько
коміт
8326321db5

+ 8 - 0
packages/Longyi/Pay/Afterpay/src/Config/payment-methods.php

@@ -9,4 +9,12 @@ return [
         'active'      => true,
         'sort'        => 1,
     ],
+    'clearpay'  => [
+        'code'        => 'clearpay',
+        'title'       => 'Clearpay',
+        'description' => 'Clearpay',
+        'class'       => 'Longyi\Pay\Afterpay\Payment\Clearpay',
+        'active'      => true,
+        'sort'        => 1,
+    ],
 ];

+ 121 - 0
packages/Longyi/Pay/Afterpay/src/Config/system.php

@@ -61,6 +61,127 @@ return [
                 'channel_based' => true,
                 'locale_based'  => false,
                 'default'       => 5,
+            ], [
+                'name'          => 'min_order_total',
+                'title'         => 'Minimum Order Total',
+                'type'          => 'blade',
+                'path'          => 'Afterpay::admin.system.readonly-value',
+                'channel_based' => false,
+                'locale_based'  => false,
+            ], [
+                'name'          => 'max_order_total',
+                'title'         => 'Maximum Order Total',
+                'type'          => 'blade',
+                'path'          => 'Afterpay::admin.system.readonly-value',
+                'channel_based' => false,
+                'locale_based'  => false,
+            ], [
+                'name'          => 'update_limits',
+                'title'         => 'Update Limits',
+                'type'          => 'blade',
+                'path'          => 'Afterpay::admin.system.update-limits',
+                'channel_based' => false,
+                'locale_based'  => false,
+            ], [
+                'name'          => 'active',
+                'title'         => 'Status',
+                'type'          => 'boolean',
+                'validation'    => 'required',
+                'channel_based' => false,
+                'locale_based'  => true,
+            ]
+        ]
+    ],
+    [
+        'key'    => 'sales.payment_methods.clearpay',
+        'name'   => 'Clearpay',
+        'info'   => 'Information about Clearpay',
+        'sort'   => 5,
+        'fields' => [
+            [
+                'name'          => 'title',
+                'title'         => 'Title',
+                'type'          => 'text',
+                'validation'    => 'required',
+                'channel_based' => false,
+                'locale_based'  => true,
+            ], [
+                'name'          => 'description',
+                'title'         => 'Description',
+                'type'          => 'textarea',
+                'channel_based' => false,
+                'locale_based'  => true,
+            ], [
+                'name' => 'mode',
+                'title' => 'Mode',
+                'type' => 'select',
+                'options' => [
+                    [
+                        'title' => 'live',
+                        'value' => 'live',
+                    ], [
+                        'title' => 'test',
+                        'value' => 'test',
+                    ],
+                ],
+                'depend' => 'active:1',
+                'validation' => 'required_if:active,1',
+                'channel_based' => false,
+                'locale_based' => true,
+            ], [
+                'name' => 'client_id',
+                'title' => 'Merchant ID',
+                'type' => 'text',
+                'depend' => 'active:1',
+                'validation' => 'required_if:active,1',
+                'channel_based' => false,
+                'locale_based' => true,
+            ], [
+                'name' => 'secret_id',
+                'title' => 'Merchant Key',
+                'type' => 'password',
+                'depend' => 'active:1',
+                'validation' => 'required_if:active,1',
+                'channel_based' => false,
+                'locale_based' => true,
+            ], [
+                'name'          => 'sort',
+                'title'         => 'Sort Order',
+                'type'          => 'text',
+                'validation'    => 'numeric',
+                'channel_based' => true,
+                'locale_based'  => false,
+                'default'       => 5,
+            ],
+//            [
+//                'name'          => 'allowed_countries',
+//                'title'         => 'Allowed Countries',
+//                'type'          => 'multiselect',
+//                'options'       => 'Longyi\Pay\Afterpay\Payment\Clearpay@getCountryOptions',
+//                'channel_based' => false,
+//                'locale_based'  => false,
+//            ],
+            [
+                'name'          => 'min_order_total',
+                'title'         => 'Minimum Order Total',
+                'type'          => 'blade',
+                'path'          => 'Afterpay::admin.system.readonly-value',
+                'channel_based' => false,
+                'locale_based'  => false,
+            ], [
+                'name'          => 'max_order_total',
+                'title'         => 'Maximum Order Total',
+                'type'          => 'blade',
+                'path'          => 'Afterpay::admin.system.readonly-value',
+                'channel_based' => false,
+                'locale_based'  => false,
+            ], [
+                'name'          => 'update_limits',
+                'title'         => 'Update Limits',
+                'type'          => 'blade',
+                'path'          => 'Afterpay::admin.system.update-limits',
+                'channel_based' => false,
+                'locale_based'  => false,
             ], [
                 'name'          => 'active',
                 'title'         => 'Status',

+ 51 - 0
packages/Longyi/Pay/Afterpay/src/Http/Controllers/AfterpayController.php

@@ -3,7 +3,9 @@
 namespace Longyi\Pay\Afterpay\Http\Controllers;
 
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Artisan;
 use Longyi\Pay\Afterpay\Payment\Afterpay;
+use Longyi\Pay\Afterpay\Payment\Clearpay;
 use Webkul\Sales\Repositories\OrderRepository;
 
 class AfterpayController extends Controller
@@ -11,6 +13,7 @@ class AfterpayController extends Controller
     public function __construct(
         protected OrderRepository $orderRepository,
         protected Afterpay $afterpay,
+        protected Clearpay $clearpay,
     ) {}
     public function return(Request $request)
     {
@@ -35,4 +38,52 @@ class AfterpayController extends Controller
             \Log::error($e->getMessage() . ' orderId=' . $orderId);
         }
     }
+
+    public function updateLimits(Request $request)
+    {
+        $code = $request->input('code', 'afterpay');
+
+        try {
+            $result = $this->$code->fetchConfiguration($code);
+
+            if (! $result) {
+                return response()->json([
+                    'success' => false,
+                    'message' => 'Failed to fetch configuration from Afterpay',
+                ]);
+            }
+
+            // 写入数据库
+            $prefix = 'sales.payment_methods.' . $code . '.';
+            $fields = [
+                'min_order_total' => $result['min_order_total'] ?? '',
+                'max_order_total' => $result['max_order_total'] ?? '',
+            ];
+
+            foreach ($fields as $field => $value) {
+                \Webkul\Core\Models\CoreConfig::updateOrCreate(
+                    ['code' => $prefix . $field],
+                    ['value' => $value]
+                );
+            }
+
+            // 清除配置缓存
+            Artisan::call('config:clear');
+            Artisan::call('cache:clear');
+
+            return response()->json([
+                'success'         => true,
+                'message'         => 'Limits updated successfully',
+                'min_order_total' => $result['min_order_total'] ?? '',
+                'max_order_total' => $result['max_order_total'] ?? '',
+            ]);
+        } catch (\Exception $e) {
+            \Log::channel('payment')->error("{$code} updateLimits error: " . $e->getMessage());
+
+            return response()->json([
+                'success' => false,
+                'message' => $e->getMessage(),
+            ]);
+        }
+    }
 }

+ 1 - 0
packages/Longyi/Pay/Afterpay/src/Http/routes.php

@@ -4,3 +4,4 @@ use Illuminate\Support\Facades\Route;
 use Longyi\Pay\Afterpay\Http\Controllers\AfterpayController;
 
 Route::get('afterpay/return', [AfterpayController::class, 'return'])->name('afterpay.return');
+Route::post('admin/afterpay/update-limits', [AfterpayController::class, 'updateLimits'])->name('admin.afterpay.update_limits');

+ 60 - 3
packages/Longyi/Pay/Afterpay/src/Payment/Afterpay.php

@@ -20,12 +20,14 @@ class Afterpay extends Payment
     protected $currentInput;
     protected $createPaymentUlr = 'https://global-api-sandbox.afterpay.com/v2/checkouts';
     protected $capturePaymentUlr = 'https://global-api-sandbox.afterpay.com/v2/payments/capture';
+    protected $configurationUlr = 'https://global-api-sandbox.afterpay.com/v2/configuration';
 
     public function __construct()
     {
         if ($this->getConfigData('mode') == 'live') {
             $this->createPaymentUlr = 'https://global-api.afterpay.com/v2/checkouts';
             $this->capturePaymentUlr = 'https://global-api.afterpay.com/v2/payments/capture';
+            $this->configurationUlr = 'https://global-api.afterpay.com/v2/configuration';
         }
         $this->clientId = $this->getConfigData('client_id');
         $this->apikey = $this->getConfigData('secret_id');
@@ -217,16 +219,71 @@ class Afterpay extends Payment
         return $url ? Storage::url($url) : bagisto_asset('images/cash-on-delivery.png', 'shop');
     }
 
-    public function isAvailable()
+
+
+    public function fetchConfiguration(string $code = 'afterpay'): ?array
+    {
+        try {
+            $client = new Client();
+            $response = $client->request('GET', $this->configurationUlr, [
+                'headers' => [
+                    'Accept' => 'application/json',
+                    'Content-Type' => 'application/json',
+                    'Authorization' => 'Basic ' . base64_encode("{$this->clientId}:{$this->apikey}")
+                ],
+                'timeout' => 30,
+                'verify'  => false,
+            ]);
+
+            $responseBody = $response->getBody()->getContents();
+            Log::channel('payment')->info('Afterpay fetchConfiguration response', [
+                'code'   => $code,
+                'status' => $response->getStatusCode(),
+                'body'   => $responseBody,
+            ]);
+
+            $result = json_decode($responseBody, true);
+
+            return [
+                'min_order_total' => $result['minimumAmount']['amount'] ?? '',
+                'max_order_total' => $result['maximumAmount']['amount'] ?? '',
+            ];
+        } catch (\Exception $e) {
+            Log::channel('payment')->error('Afterpay fetchConfiguration error: ' . $e->getMessage());
+            throw $e;
+        }
+    }
+
+    public function isAvailable($isShow = false)
     {
         if (! parent::isAvailable()) {
             return false;
         }
+        if ($isShow) {
+            return true;
+        }
         $info = $this->getOrder() ?: Cart::getCart();
         if ($info && $billingCountry = $info->billing_address->country ?? false) {
-            $allowedCountries = ['AU', 'CA', 'NZ', 'GB', 'US'];
-            return in_array($billingCountry, $allowedCountries);
+            $allowedCountries = ['CA', 'US'];//加拿大,美国
+            if (! in_array($billingCountry, $allowedCountries)) {
+                return false;
+            }
         }
+
+        // 检查订单金额是否在限额范围内
+//        if ($info) {
+//            $grandTotal = $info->grand_total ?? $info->sub_total ?? 0;
+//            $minOrderTotal = $this->getConfigData('min_order_total');
+//            $maxOrderTotal = $this->getConfigData('max_order_total');
+//
+//            if (! empty($minOrderTotal) && $grandTotal < (float) $minOrderTotal) {
+//                return false;
+//            }
+//            if (! empty($maxOrderTotal) && $grandTotal > (float) $maxOrderTotal) {
+//                return false;
+//            }
+//        }
+
         return true;
     }
 

+ 148 - 0
packages/Longyi/Pay/Afterpay/src/Payment/Clearpay.php

@@ -0,0 +1,148 @@
+<?php
+
+namespace Longyi\Pay\Afterpay\Payment;
+
+use GuzzleHttp\Client;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Storage;
+use Webkul\Payment\Payment\Payment;
+use Webkul\Sales\Models\Order;
+use Webkul\Sales\Repositories\OrderRepository;
+use Webkul\Checkout\Facades\Cart;
+use function Symfony\Component\Translation\t;
+
+class Clearpay extends Afterpay
+{
+    public function __construct()
+    {
+        parent::__construct();
+        $this->clientId = $this->getConfigData('client_id');
+        $this->apikey = $this->getConfigData('secret_id');
+        $this->secret = $this->getConfigData('webhook_id');
+    }
+
+    protected $code  = 'clearpay';
+
+    public function createGatewayOrder()
+    {
+        try {
+            return $this->createPayment(
+                $this->getOrder(),
+                ['merchantOrderId' => $this->getOrder()->increment_id],
+                $this->getInput()
+            );
+        } catch (\Exception $e) {
+            throw new \Exception($e->getMessage());
+        }
+    }
+
+    public function createPayment($order, $override, $input = null)
+    {
+        $shop = $order->shipping_address;
+        $bill = $order->billing_address;
+        $telephone = $shop->phone ?: $bill->phone;
+        $grandTotal = round($order->grand_total, 2);
+        foreach ($order->items as $item) {
+            $products[] = [
+                'name' => (string)$item->name,
+                'quantity' => $item->qty_ordered,
+                'sku' => $item->sku,
+                'price' => [
+                    'amount' => $this->formatCurrencyValue((float) $item->price),
+                    'currency' => $order->order_currency_code
+                ],
+            ];
+        }
+        $data = [
+            'purchaseCountry' => $shop->country,
+            'amount' => [
+                'amount' => $grandTotal,
+                'currency' => $order->order_currency_code
+            ],
+            'consumer' => [
+                'email' => $order->customer_email,
+                'givenNames' => $order->customer_first_name,
+                'surname' => $order->customer_last_name,
+                'phoneNumber' => $telephone
+            ],
+            'merchantReference' => $override['merchantOrderId'],
+            'billing' => [
+                'name' => $bill->first_name . ' ' .$bill->last_name,
+                'line1' => $bill->address,
+                'area1' => $bill->state,
+                'area2' => $bill->city,
+                'region' => $bill->state,
+                'postcode' => $bill->postcode,
+                'phoneNumber' => $bill->phone,
+                'countryCode' => $bill->country,
+            ],
+            'shipping' => [
+                'name' => $shop->first_name . ' ' .$shop->last_name,
+                'line1' => $shop->address,
+                'area1' => $shop->state,
+                'area2' => $shop->city,
+                'region' => $shop->state,
+                'postcode' => $shop->postcode,
+                'phoneNumber' => $shop->phone,
+                'countryCode' => $shop->country,
+            ],
+            'merchant' => [
+                'redirectConfirmUrl' => $input->paymentSuccessUrl . '?orderId=' . $order->id,
+                'redirectCancelUrl' => $input->paymentCancelUrl. '?orderId=' . $order->id
+            ],
+            'items' => $products
+        ];
+        $client = new Client();
+        $response = $client->request('POST', $this->createPaymentUlr, [
+            'headers' => [
+                'Accept' => 'application/json',
+                'Content-Type' => 'application/json',
+                'Authorization' =>  'Basic '.base64_encode("{$this->clientId}:{$this->apikey}")
+            ],
+            'json' => $data,
+            'timeout' => 30,
+            'verify' => false
+        ]);
+        $responseBody = $response->getBody()->getContents();
+        Log::channel('payment')->info('Clearpay createPayment response', [
+            'order_id'  => $order->increment_id ?? null,
+            'status'    => $response->getStatusCode(),
+            'body'      => $responseBody,
+        ]);
+
+        $resultObject = json_decode($responseBody);
+        if (empty($resultObject->token)) {
+            throw new \Exception($resultObject->message);
+        } else {
+            return $resultObject->token;
+        }
+    }
+
+    public function getCountryOptions(): array
+    {
+        return core()->countries()->map(function ($country) {
+            return [
+                'title' => $country->name,
+                'value' => $country->code,
+            ];
+        })->toArray();
+    }
+    public function isAvailable($isShow = true)
+    {
+        if (! parent::isAvailable($isShow)) {
+            return false;
+        }
+        $info = $this->getOrder() ?: Cart::getCart();
+        if ($info && $billingCountry = $info->billing_address->country ?? false) {
+//            $allowedCountries = $this->getConfigData('allowed_countries');
+//            if (empty($allowedCountries)) {
+//                return true;
+//            }
+//            $allowedCountries = explode(',', $allowedCountries);
+//            return in_array($billingCountry, $allowedCountries);
+            $allowedCountries = ['GB'];//英国
+            return in_array($billingCountry, $allowedCountries);
+        }
+        return false;
+    }
+}

+ 9 - 0
packages/Longyi/Pay/Afterpay/src/Resources/views/admin/system/readonly-value.blade.php

@@ -0,0 +1,9 @@
+<div class="flex flex-col gap-1">
+    <span class="text-sm font-medium text-gray-600 dark:text-gray-300">
+        {{ trans($field->getTitle()) }}
+    </span>
+
+    <span class="afterpay-limit-value w-max rounded-md border border-gray-200 bg-gray-50 px-3 py-2 text-sm text-gray-600 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300" data-field="{{ $field->getName() }}">
+        {{ system_config()->getConfigData($field->getNameKey()) ?: '-' }}
+    </span>
+</div>

+ 76 - 0
packages/Longyi/Pay/Afterpay/src/Resources/views/admin/system/update-limits.blade.php

@@ -0,0 +1,76 @@
+@php
+    $uid = str_replace('.', '_', $field->getItemKey());
+    $fnName = 'updateLimits_' . $uid;
+@endphp
+
+<div class="flex items-center gap-2.5">
+    <button
+        type="button"
+        class="primary-button"
+        onclick="{{ $fnName }}()"
+    >
+        <span id="{{ $uid }}_btn_text">Update Limits</span>
+        <span id="{{ $uid }}_btn_loading" class="hidden items-center gap-1">
+            <svg class="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
+                <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
+                <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
+            </svg>
+            Updating...
+        </span>
+    </button>
+    <span
+        id="{{ $uid }}_msg"
+        class="text-sm hidden"
+    ></span>
+</div>
+
+<script>
+    (function () {
+        var uid = '{{ $uid }}';
+        var code = uid.split('_').pop();
+
+        window['{{ $fnName }}'] = function () {
+            var btnText = document.getElementById(uid + '_btn_text');
+            var btnLoading = document.getElementById(uid + '_btn_loading');
+            var msgEl = document.getElementById(uid + '_msg');
+            var section = btnText.closest('.box-shadow');
+
+            btnText.classList.add('hidden');
+            btnLoading.classList.remove('hidden');
+            msgEl.classList.add('hidden');
+
+            fetch('{{ route('admin.afterpay.update_limits') }}?code=' + code, {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                    'X-CSRF-TOKEN': '{{ csrf_token() }}',
+                    'Accept': 'application/json',
+                },
+            })
+            .then(function (res) { return res.json(); })
+            .then(function (data) {
+                if (data.success) {
+                    var minEl = section.querySelector('.afterpay-limit-value[data-field="min_order_total"]');
+                    var maxEl = section.querySelector('.afterpay-limit-value[data-field="max_order_total"]');
+                    if (minEl) minEl.textContent = data.min_order_total || '-';
+                    if (maxEl) maxEl.textContent = data.max_order_total || '-';
+                    msgEl.textContent = data.message;
+                    msgEl.className = 'text-sm text-green-600';
+                } else {
+                    msgEl.textContent = data.message || 'Update failed';
+                    msgEl.className = 'text-sm text-red-600';
+                }
+                msgEl.classList.remove('hidden');
+            })
+            .catch(function () {
+                msgEl.textContent = 'Network error, please try again';
+                msgEl.className = 'text-sm text-red-600';
+                msgEl.classList.remove('hidden');
+            })
+            .finally(function () {
+                btnText.classList.remove('hidden');
+                btnLoading.classList.add('hidden');
+            });
+        };
+    })();
+</script>

+ 3 - 1
packages/Longyi/Pay/AwxKlarna/src/Payment/AwxKlarna.php

@@ -334,6 +334,8 @@ class AwxKlarna extends Payment
     public function getDescription()
     {
         $info = $this->getOrder() ?: Cart::getCart();
-        return round($info->grand_total / 4, 2);
+        $amount = round($info->grand_total / 4, 2);
+        $symbol = core()->getCurrentCurrency()->symbol;
+        return "4 interest-free payments of {$symbol}{$amount}";
     }
 }