Ver código fonte

支付方式

llp 1 semana atrás
pai
commit
85feadfecb
34 arquivos alterados com 2312 adições e 0 exclusões
  1. 4 0
      bootstrap/providers.php
  2. 4 0
      composer.json
  3. 12 0
      packages/Longyi/Pay/Airwallex/src/Config/payment-methods.php
  4. 82 0
      packages/Longyi/Pay/Airwallex/src/Config/system.php
  5. 57 0
      packages/Longyi/Pay/Airwallex/src/Http/Controllers/AirwallexController.php
  6. 12 0
      packages/Longyi/Pay/Airwallex/src/Http/Controllers/Controller.php
  7. 7 0
      packages/Longyi/Pay/Airwallex/src/Http/routes.php
  8. 230 0
      packages/Longyi/Pay/Airwallex/src/Payment/Airwallex.php
  9. 39 0
      packages/Longyi/Pay/Airwallex/src/Providers/AirwallexServiceProvider.php
  10. 12 0
      packages/Longyi/Pay/Applepay/src/Config/payment-methods.php
  11. 65 0
      packages/Longyi/Pay/Applepay/src/Config/system.php
  12. 130 0
      packages/Longyi/Pay/Applepay/src/Http/Controllers/ApplepayController.php
  13. 12 0
      packages/Longyi/Pay/Applepay/src/Http/Controllers/Controller.php
  14. 8 0
      packages/Longyi/Pay/Applepay/src/Http/routes.php
  15. 178 0
      packages/Longyi/Pay/Applepay/src/Payment/Applepay.php
  16. 41 0
      packages/Longyi/Pay/Applepay/src/Providers/ApplepayServiceProvider.php
  17. 12 0
      packages/Longyi/Pay/AwxAfterpay/src/Config/payment-methods.php
  18. 82 0
      packages/Longyi/Pay/AwxAfterpay/src/Config/system.php
  19. 131 0
      packages/Longyi/Pay/AwxAfterpay/src/Http/Controllers/AfterpayController.php
  20. 12 0
      packages/Longyi/Pay/AwxAfterpay/src/Http/Controllers/Controller.php
  21. 13 0
      packages/Longyi/Pay/AwxAfterpay/src/Http/routes.php
  22. 283 0
      packages/Longyi/Pay/AwxAfterpay/src/Payment/AwxAfterpay.php
  23. 41 0
      packages/Longyi/Pay/AwxAfterpay/src/Providers/AwxAfterpayServiceProvider.php
  24. 112 0
      packages/Longyi/Pay/AwxAfterpay/src/Resources/views/checkout/onepage/paypal-smart-button.blade.php
  25. 16 0
      packages/Longyi/Pay/AwxAfterpay/src/Resources/views/standard-redirect.blade.php
  26. 12 0
      packages/Longyi/Pay/AwxKlarna/src/Config/payment-methods.php
  27. 82 0
      packages/Longyi/Pay/AwxKlarna/src/Config/system.php
  28. 12 0
      packages/Longyi/Pay/AwxKlarna/src/Http/Controllers/Controller.php
  29. 130 0
      packages/Longyi/Pay/AwxKlarna/src/Http/Controllers/KlarnaController.php
  30. 13 0
      packages/Longyi/Pay/AwxKlarna/src/Http/routes.php
  31. 299 0
      packages/Longyi/Pay/AwxKlarna/src/Payment/AwxKlarna.php
  32. 41 0
      packages/Longyi/Pay/AwxKlarna/src/Providers/AwxKlarnaServiceProvider.php
  33. 112 0
      packages/Longyi/Pay/AwxKlarna/src/Resources/views/checkout/onepage/paypal-smart-button.blade.php
  34. 16 0
      packages/Longyi/Pay/AwxKlarna/src/Resources/views/standard-redirect.blade.php

+ 4 - 0
bootstrap/providers.php

@@ -15,6 +15,10 @@ return [
     Longyi\RewardPoints\Providers\RewardPointsServiceProvider::class,
     Longyi\Member\Providers\MemberServiceProvider::class,
     Longyi\Gift\Providers\GiftServiceProvider::class,
+    Longyi\Pay\AwxAfterpay\Providers\AwxAfterpayServiceProvider::class,
+    Longyi\Pay\AwxKlarna\Providers\AwxKlarnaServiceProvider::class,
+    Longyi\Pay\Applepay\Providers\ApplepayServiceProvider::class,
+    Longyi\Pay\Airwallex\Providers\AirwallexServiceProvider::class,
     Webkul\Attribute\Providers\AttributeServiceProvider::class,
     Webkul\BookingProduct\Providers\BookingProductServiceProvider::class,
     Webkul\CMS\Providers\CMSServiceProvider::class,

+ 4 - 0
composer.json

@@ -72,6 +72,10 @@
             "Longyi\\RewardPoints\\": "packages/Longyi/RewardPoints/src/",
             "Longyi\\Member\\": "packages/Longyi/Member/src/",
             "Longyi\\Gift\\": "packages/Longyi/Gift/src/",
+            "Longyi\\Pay\\Airwallex\\": "packages/Longyi/Pay/Airwallex/src/",
+            "Longyi\\Pay\\Applepay\\": "packages/Longyi/Pay/Applepay/src/",
+            "Longyi\\Pay\\AwxKlarna\\": "packages/Longyi/Pay/AwxKlarna/src/",
+            "Longyi\\Pay\\AwxAfterpay\\": "packages/Longyi/Pay/AwxAfterpay/src/",
             "Webkul\\Admin\\": "packages/Webkul/Admin/src",
             "Webkul\\Attribute\\": "packages/Webkul/Attribute/src",
             "Webkul\\BookingProduct\\": "packages/Webkul/BookingProduct/src",

+ 12 - 0
packages/Longyi/Pay/Airwallex/src/Config/payment-methods.php

@@ -0,0 +1,12 @@
+<?php
+
+return [
+    'airwallex'  => [
+        'code'        => 'airwallex',
+        'title'       => 'Airwallex',
+        'description' => 'Airwallex',
+        'class'       => 'Longyi\Pay\Airwallex\Payment\Airwallex',
+        'active'      => true,
+        'sort'        => 1,
+    ],
+];

+ 82 - 0
packages/Longyi/Pay/Airwallex/src/Config/system.php

@@ -0,0 +1,82 @@
+<?php
+
+return [
+    [
+        'key'    => 'sales.payment_methods.airwallex',
+        'name'   => 'Airwallex', // use translation
+        'info'   => 'Information about Airwallex', // use translation
+        'sort'   => 8,
+        'fields' => [
+            [
+                'name'          => 'title',
+                'title'         => 'Title', // use translation
+                'type'          => 'text',
+                'validation'    => 'required',
+                'channel_based' => false,
+                'locale_based'  => true,
+            ], [
+                'name'          => 'description',
+                'title'         => 'Description', // use translation
+                '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' => 'ClientId',
+                'type' => 'text',
+                'depend' => 'active:1',
+                'validation' => 'required_if:active,1',
+                'channel_based' => false,
+                'locale_based' => true,
+            ], [
+                'name' => 'secret_id',
+                'title' => 'Secret',
+                'type' => 'password',
+                'depend' => 'active:1',
+                'validation' => 'required_if:active,1',
+                'channel_based' => false,
+                'locale_based' => true,
+            ], [
+                'name' => 'webhook_id',
+                'title' => 'WebhookId',
+                'type' => 'text',
+                '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'          => 'active',
+                'title'         => 'Status', // use translation
+                'type'          => 'boolean',
+                'validation'    => 'required',
+                'channel_based' => false,
+                'locale_based'  => true,
+            ]
+        ]
+    ]
+];

+ 57 - 0
packages/Longyi/Pay/Airwallex/src/Http/Controllers/AirwallexController.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace Longyi\Pay\Airwallex\Http\Controllers;
+
+use Longyi\Pay\Airwallex\Payment\Airwallex;
+use Webkul\Checkout\Facades\Cart;
+use Webkul\Sales\Models\Order;
+use Webkul\Sales\Repositories\OrderRepository;
+use Webkul\Sales\Transformers\OrderResource;
+use Longyi\Pay\AwxKlarna\Payment\AwxKlarna;
+
+class AirwallexController extends Controller
+{
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct(
+        protected OrderRepository $orderRepository,
+        protected Airwallex $airwallex,
+    ) {}
+
+
+    public function hook()
+    {
+        $param = @file_get_contents('php://input');
+        $header = $this->getAllHeaders();
+        $data = [
+            'timestamp' => $header['x-timestamp'],
+            'signature' => $header['x-signature'],
+            'content' => $param
+        ];
+        $result = $this->airwallex->checkSignature($data, 'secret');
+        if (!$result) {
+            @header("HTTP/1.0 400 Bad Request");
+            exit;
+        }
+        $notifyData = json_decode($param, true);
+        $orderNo = $notifyData['data']['object']['merchant_order_id'] ?: 0;
+        if (empty($orderNo)) {
+            @header("HTTP/1.0 400 Bad Request");
+            exit;
+        }
+        $prefix = $this->airwallex->getPrefix();
+        if (strpos($orderNo, $prefix) === false) {
+            @header("HTTP/1.0 200 ok");
+            exit;
+        }
+        $type = $notifyData['name'];
+        $orderId = str_replace($prefix, '', $orderNo);
+        $transactionId = $notifyData['data']['object']['id'] ?: '';
+        $eventType = "payments_webhook_" . strtolower(str_replace(".", "_", $type));
+        $this->airwallex->$eventType($orderId, $transactionId);
+    }
+
+}

+ 12 - 0
packages/Longyi/Pay/Airwallex/src/Http/Controllers/Controller.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace Longyi\Pay\Airwallex\Http\Controllers;
+
+use Illuminate\Foundation\Bus\DispatchesJobs;
+use Illuminate\Foundation\Validation\ValidatesRequests;
+use Illuminate\Routing\Controller as BaseController;
+
+class Controller extends BaseController
+{
+    use DispatchesJobs, ValidatesRequests;
+}

+ 7 - 0
packages/Longyi/Pay/Airwallex/src/Http/routes.php

@@ -0,0 +1,7 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use Longyi\Pay\Airwallex\Http\Controllers\AirwallexController;
+
+// Webhook 路由(独立于 web 中间件,避免 CSRF 验证)
+Route::post('airwallex/webhook/hook', [AirwallexController::class, 'hook'])->name('airwallex.webhook.hook');

+ 230 - 0
packages/Longyi/Pay/Airwallex/src/Payment/Airwallex.php

@@ -0,0 +1,230 @@
+<?php
+
+namespace Longyi\Pay\Airwallex\Payment;
+
+use GuzzleHttp\Client;
+use Illuminate\Support\Facades\Storage;
+use Webkul\Checkout\Facades\Cart;
+use Webkul\Payment\Payment\Payment;
+use Webkul\Sales\Models\Order;
+use Webkul\Sales\Repositories\OrderRepository;
+
+class Airwallex extends Payment
+{
+    /**
+     * Payment method code
+     *
+     * @var string
+     */
+    protected $code  = 'airwallex';
+    protected $clientId;
+    protected $apikey;
+    protected $secret;
+    protected $prefix = 'AS';
+    protected $tokenCacheKey = 'airwallex_airwallex_token';
+    protected $tokenUrl = 'https://api-demo.airwallex.com/api/v1/authentication/login';
+    protected $createPaymentUlr = 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/create';
+    protected $confirmIntentsUlr = 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/{id}/confirm';
+    protected $captureIntentsUlr = 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/{id}/capture';
+    protected $paymentIntentsUlr = 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/';
+
+    public function __construct()
+    {
+        if ($this->getConfigData('mode') == 'live') {
+            $this->tokenUrl = 'https://api.airwallex.com/api/v1/authentication/login';
+            $this->createPaymentUlr = 'https://api.airwallex.com/api/v1/pa/payment_intents/create';
+            $this->confirmIntentsUlr = 'https://api.airwallex.com/api/v1/pa/payment_intents/{id}/confirm';
+            $this->captureIntentsUlr = 'https://api.airwallex.com/api/v1/pa/payment_intents/{id}/capture';
+            $this->paymentIntentsUlr = 'https://api.airwallex.com/api/v1/pa/payment_intents/';
+        }
+        $this->clientId = $this->getConfigData('client_id');
+        $this->apikey = $this->getConfigData('secret_id');
+        $this->secret = $this->getConfigData('webhook_id');
+    }
+
+
+    /**
+     * Get redirect url.
+     */
+    public function getRedirectUrl()
+    {
+    }
+
+    public function getPaymentData($cart)
+    {
+        return $this->createIntents($cart, ['merchantOrderId' => $cart->id]);
+    }
+
+    public function createIntents($cart, $override)
+    {
+        $shop = $cart['shipping_address'];
+        $products = [];
+        foreach ($cart->items as $item) {
+            $data = [
+                'name' => $item->name,
+                'desc' => $item->name,
+                'unit_price' => (float) $item->price,
+                'sku' => $item->sku,
+                'quantity' => $item->quantity
+            ];
+            array_push($products, $data);
+        }
+        $grandTotal = round($cart['grand_total'], 2);
+        $data = [
+            'request_id' => $this->createUuid(),
+            'amount' => $grandTotal,
+            'descriptor' => $this->prefix,
+            'merchant_order_id' => $this->prefix . $override['merchantOrderId'],
+            'currency' => $cart['order_currency_code'],
+            'customer' => [
+                'email' => $cart['customer_email'],
+                'first_name' => $cart['customer_first_name'],
+                'last_name' => $cart['customer_last_name']
+            ],
+            'order' => [
+                'products' => $products,
+                'shipping' => [
+                    'address' => [
+                        'city' => $shop['city'],
+                        'postcode' => $shop['postcode'],
+                        'state' => $shop['state'],
+                        'country_code'=> $shop['country'],
+                        'street' =>$shop['address'],
+                    ],
+                    'first_name' => $shop['first_name'],
+                    'last_name' => $shop['last_name'],
+                    'phone_number' => $shop['phone'],
+                    'email' => $shop['email']
+                ],
+            ],
+        ];
+        $client = new Client();
+        $response = $client->request('POST', $this->createPaymentUlr, [
+            'headers' => [
+                'Accept' => 'application/json',
+                'Content-Type' => 'application/json',
+                'Authorization' =>  'Bearer '.$this->getToken()
+            ],
+            'json' => $data,
+            'timeout' => 30,
+            'verify' => false
+        ]);
+        $resultObject = json_decode($response->getBody()->getContents());
+        return $resultObject;
+    }
+
+    public function getToken()
+    {
+        $token = cache()->get($this->tokenCacheKey);
+        if (!$token) {
+            $client = new Client();
+            $response = $client->request('POST', $this->tokenUrl, [
+                'headers' => [
+                    'Accept' => 'application/json',
+                    'Content-Type' => 'application/json',
+                    'x-client-id' => $this->clientId,
+                    'x-api-key' => $this->apikey
+                ],
+                'timeout' => 30,
+                'verify' => false
+            ]);
+            $result = json_decode($response->getBody()->getContents());
+            $token = $result->token;
+            cache()->put($this->tokenCacheKey, $token, 20);
+        }
+        return $token;
+
+    }
+
+    //成功修改状态
+    public function payments_webhook_payment_intent_succeeded($orderId, $transactionId)
+    {
+        $order = app(OrderRepository::class)->find($orderId);
+
+        if (!$order) {
+            return false;
+        }
+        if ($order->status != Order::STATUS_PENDING) {
+            return false;
+        }
+        $order->status = Order::STATUS_PROCESSING;
+        $order->save();
+        if ($order->payment) {
+            $order->payment->update([
+                'transaction_id' => $transactionId,
+            ]);
+        }
+    }
+    //授权成功修改状态
+    public function payments_webhook_payment_intent_requires_capture($orderId, $transactionId)
+    {
+        $order = app(OrderRepository::class)->find($orderId);
+
+        if (!$order) {
+            return false;
+        }
+        if ($order->status != Order::STATUS_PENDING) {
+            return false;
+        }
+        $order->status = Order::STATUS_PROCESSING;
+        $order->save();
+        if ($order->payment) {
+            $order->payment->update([
+                'transaction_id' => $transactionId,
+            ]);
+        }
+    }
+
+    /**
+     * 获取一个唯一的id
+     */
+    public function createUuid($prefix = "")
+    {
+        $chars = md5(uniqid(mt_rand(), true));
+        $uuid = substr($chars, 0, 8) . '-'
+            . substr($chars, 8, 4) . '-'
+            . substr($chars, 12, 4) . '-'
+            . substr($chars, 16, 4) . '-'
+            . substr($chars, 20, 12);
+        return $prefix . $uuid;
+    }
+
+    public function checkSignature($data, $exp)
+    {
+        if (empty($data)) {
+            return false;
+        }
+        if (hash_hmac('sha256', $data['timestamp'].$data['content'], $this->$exp) != $data['signature']) {
+            return false;
+        }
+        return true;
+    }
+
+    public function getPrefix()
+    {
+        return $this->prefix;
+    }
+
+    /**
+     * Get payment method image.
+     *
+     * @return array
+     */
+    public function getImage()
+    {
+        $url = $this->getConfigData('image');
+
+        return $url ? Storage::url($url) : bagisto_asset('images/cash-on-delivery.png', 'shop');
+    }
+
+    public function isAvailable()
+    {
+        if (! parent::isAvailable()) {
+            return false;
+        }
+        $cart = Cart::getCart();
+        $billingCountry = $cart->billing_address->country;
+        $allowedCountries = ['AU', 'CA', 'NZ', 'GB', 'US'];
+        return in_array($billingCountry, $allowedCountries);
+    }
+}

+ 39 - 0
packages/Longyi/Pay/Airwallex/src/Providers/AirwallexServiceProvider.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace Longyi\Pay\Airwallex\Providers;
+
+use Illuminate\Support\ServiceProvider;
+
+class AirwallexServiceProvider extends ServiceProvider
+{
+    /**
+     * Register services.
+     */
+    public function register(): void
+    {
+        $this->registerConfig();
+    }
+
+    /**
+     * Bootstrap services.
+     */
+    public function boot(): void
+    {
+    }
+
+    /**
+     * Register package config.
+     *
+     * @return void
+     */
+    protected function registerConfig()
+    {
+        $this->mergeConfigFrom(
+            dirname(__DIR__) . '/Config/payment-methods.php', 'payment_methods'
+        );
+
+        $this->mergeConfigFrom(
+            dirname(__DIR__) . '/Config/system.php', 'core'
+        );
+    }
+}

+ 12 - 0
packages/Longyi/Pay/Applepay/src/Config/payment-methods.php

@@ -0,0 +1,12 @@
+<?php
+
+return [
+    'applepay'  => [
+        'code'        => 'applepay',
+        'title'       => 'Applepay',
+        'description' => 'Applepay',
+        'class'       => 'Longyi\Pay\Applepay\Payment\Applepay',
+        'active'      => true,
+        'sort'        => 1,
+    ],
+];

+ 65 - 0
packages/Longyi/Pay/Applepay/src/Config/system.php

@@ -0,0 +1,65 @@
+<?php
+
+return [
+    [
+        'key'    => 'sales.payment_methods.applepay',
+        'name'   => 'Applepay', // use translation
+        'info'   => 'Information about Applepay', // use translation
+        'sort'   => 7,
+        'fields' => [
+            [
+                'name'          => 'title',
+                'title'         => 'Title', // use translation
+                'type'          => 'text',
+                'validation'    => 'required',
+                'channel_based' => false,
+                'locale_based'  => true,
+            ], [
+                'name'          => 'description',
+                'title'         => 'Description', // use translation
+                'type'          => 'textarea',
+                'channel_based' => false,
+                'locale_based'  => true,
+            ], [
+                'name' => 'client_id',
+                'title' => 'ClientId',
+                'type' => 'text',
+                'depend' => 'active:1',
+                'validation' => 'required_if:active,1',
+                'channel_based' => false,
+                'locale_based' => true,
+            ], [
+                'name' => 'secret_id',
+                'title' => 'Secret',
+                'type' => 'password',
+                'depend' => 'active:1',
+                'validation' => 'required_if:active,1',
+                'channel_based' => false,
+                'locale_based' => true,
+            ], [
+                'name' => 'webhook_id',
+                'title' => 'WebhookId',
+                'type' => 'text',
+                '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'          => 'active',
+                'title'         => 'Status', // use translation
+                'type'          => 'boolean',
+                'validation'    => 'required',
+                'channel_based' => false,
+                'locale_based'  => true,
+            ]
+        ]
+    ]
+];

+ 130 - 0
packages/Longyi/Pay/Applepay/src/Http/Controllers/ApplepayController.php

@@ -0,0 +1,130 @@
+<?php
+
+namespace Longyi\Pay\Applepay\Http\Controllers;
+
+use Webkul\Checkout\Facades\Cart;
+use Webkul\Sales\Models\Order;
+use Webkul\Sales\Repositories\OrderRepository;
+use Webkul\Sales\Transformers\OrderResource;
+use Longyi\Pay\Applepay\Payment\Applepay;
+
+class ApplepayController extends Controller
+{
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct(
+        protected OrderRepository $orderRepository,
+        protected Applepay $applepay,
+    ) {}
+
+    /**
+     * Redirects to the klarna.
+     *
+     * @return \Illuminate\View\View
+     */
+    public function placeOrder()
+    {
+        $cart = Cart::getCart();
+        $data = (new OrderResource($cart))->jsonSerialize();
+        $order = $this->orderRepository->create($data);
+        Cart::deActivateCart();
+        session()->flash('order_id', $order->id);
+        return view('AwxKlarna::standard-redirect', ['order' => $order]);
+    }
+
+    public function redirect()
+    {
+        $orderId = request()->input('orderId');
+        if (!$orderId) {
+            return response()->json([
+                'error' => 'Order ID is required'
+            ], 400);
+        }
+        $order = $this->orderRepository->find($orderId);
+        if (!$order) {
+            return response()->json([
+                'error' => 'Order not found'
+            ], 404);
+        }
+        if ($order->status != Order::STATUS_PENDING) {
+            return response()->json([
+                'error' => 'No payment required for the order'
+            ], 404);
+        }
+        $cartData = [
+            'shipping_address' => $order->shipping_address ? $order->shipping_address->toArray() : [],
+            'billing_address' => $order->billing_address ? $order->billing_address->toArray() : [],
+            'grand_total' => $order->grand_total,
+            'order_currency_code' => $order->order_currency_code,
+            'customer_email' => $order->customer_email,
+            'customer_first_name' => $order->customer_first_name,
+            'customer_last_name' => $order->customer_last_name
+        ];
+        try {
+            $checkoutUrl = $this->awxKlarna->createPayment($cartData, [
+                'merchantOrderId' => $order->id
+            ]);
+            if (!$checkoutUrl) {
+                throw new \Exception('Failed to get checkout URL from Airwallex');
+            }
+            return redirect()->away($checkoutUrl);
+        } catch (\Exception $e) {
+            return response()->json([
+                'error' => 'Payment initialization failed: ' . $e->getMessage()
+            ], 500);
+        }
+    }
+    /**
+     * Success payment.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function return()
+    {
+        $awxReturnResult = request()->input('awx_return_result');
+        $orderId = request()->input('orderId');
+        if ($awxReturnResult == 'success') {
+            session()->flash('order_id', $orderId);
+            return redirect()->route('shop.checkout.onepage.success');
+        } else {
+            session()->flash('error', 'afterpay cancel');
+            return redirect()->route('shop.checkout.cart.index');
+        }
+    }
+
+    public function hook()
+    {
+        $param = @file_get_contents('php://input');
+        $header = $this->getAllHeaders();
+        $data = [
+            'timestamp' => $header['x-timestamp'],
+            'signature' => $header['x-signature'],
+            'content' => $param
+        ];
+        $result = $this->awxKlarna->checkSignature($data, 'secret');
+        if (!$result) {
+            @header("HTTP/1.0 400 Bad Request");
+            exit;
+        }
+        $notifyData = json_decode($param, true);
+        $orderNo = $notifyData['data']['object']['merchant_order_id'] ?: 0;
+        if (empty($orderNo)) {
+            @header("HTTP/1.0 400 Bad Request");
+            exit;
+        }
+        $prefix = $this->awxKlarna->getPrefix();
+        if (strpos($orderNo, $prefix) === false) {
+            @header("HTTP/1.0 200 ok");
+            exit;
+        }
+        $type = $notifyData['name'];
+        $orderId = str_replace($prefix, '', $orderNo);
+        $transactionId = $notifyData['data']['object']['id'] ?: '';
+        $eventType = "payments_webhook_" . strtolower(str_replace(".", "_", $type));
+        $this->awxKlarna->$eventType($orderId, $transactionId);
+    }
+
+}

+ 12 - 0
packages/Longyi/Pay/Applepay/src/Http/Controllers/Controller.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace Longyi\Pay\Applepay\Http\Controllers;
+
+use Illuminate\Foundation\Bus\DispatchesJobs;
+use Illuminate\Foundation\Validation\ValidatesRequests;
+use Illuminate\Routing\Controller as BaseController;
+
+class Controller extends BaseController
+{
+    use DispatchesJobs, ValidatesRequests;
+}

+ 8 - 0
packages/Longyi/Pay/Applepay/src/Http/routes.php

@@ -0,0 +1,8 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use Longyi\Pay\Applepay\Http\Controllers\ApplepayController;
+
+
+// Webhook 路由(独立于 web 中间件,避免 CSRF 验证)
+Route::post('applepay/webhook/hook', [ApplepayController::class, 'hook'])->name('applepay.webhook.hook');

+ 178 - 0
packages/Longyi/Pay/Applepay/src/Payment/Applepay.php

@@ -0,0 +1,178 @@
+<?php
+
+namespace Longyi\Pay\Applepay\Payment;
+
+use GuzzleHttp\Client;
+use Webkul\Payment\Payment\Payment;
+
+class Applepay extends Payment
+{
+    /**
+     * Payment method code
+     *
+     * @var string
+     */
+    protected $code  = 'applepay';
+    protected $clientId;
+    protected $apikey;
+    protected $secret;
+    protected $prefix = 'QQS';
+    public $createOrderApi = 'https://api-m.sandbox.paypal.com/v2/checkout/orders';
+    public $captureOrderApi = 'https://api-m.sandbox.paypal.com/v2/checkout/orders/{id}/capture';
+    public $detailOrderApi = 'https://api-m.sandbox.paypal.com/v2/checkout/orders/{id}';
+    public $updateOrderApi = 'https://api-m.sandbox.paypal.com/v2/checkout/orders/{id}';
+    public $sigApi = 'https://api-m.sandbox.paypal.com/v1/notifications/verify-webhook-signature';
+    public $orderTrackApi = 'https://api-m.sandbox.paypal.com/v2/checkout/orders/{id}/track';
+    public $addTrackApi = 'https://api-m.sandbox.paypal.com/v1/shipping/trackers-batch';
+
+    public function __construct()
+    {
+        if ($this->getConfigData('mode') == 'live') {
+            $this->createOrderApi = 'https://api-m.paypal.com/v2/checkout/orders';
+            $this->captureOrderApi = 'https://api-m.paypal.com/v2/checkout/orders/{id}/capture';
+            $this->detailOrderApi = 'https://api-m.paypal.com/v2/checkout/orders/{id}';
+            $this->updateOrderApi = 'https://api-m.paypal.com/v2/checkout/orders/{id}';
+            $this->sigApi = 'https://api-m.paypal.com/v1/notifications/verify-webhook-signature';
+            $this->orderTrackApi = 'https://api-m.paypal.com/v2/checkout/orders/{id}/track';
+            $this->addTrackApi = 'https://api-m.paypal.com/v1/shipping/trackers-batch';
+        }
+        $this->clientId = $this->getConfigData('client_id');
+        $this->apikey = $this->getConfigData('secret_id');
+        $this->secret = $this->getConfigData('webhook_id');
+    }
+    /**
+     * Get redirect url.
+     */
+    public function getRedirectUrl()
+    {
+
+    }
+
+    public function createOrder($cart)
+    {
+        $billingAddressLines = $this->getAddressLines($cart->billing_address->address);
+        $data = [
+            'intent' => 'CAPTURE',
+            'purchase_units' => [
+                [
+                    'amount' => [
+                        'currency_code' => $cart->cart_currency_code,
+                        'value' => $this->formatCurrencyValue((float) $cart->sub_total + $cart->tax_total + ($cart->selected_shipping_rate ? $cart->selected_shipping_rate->price : 0) - $cart->discount_amount),
+                        'breakdown' => [
+                            'item_total' => [
+                                'currency_code' => $cart->cart_currency_code,
+                                'value'         => $this->formatCurrencyValue((float) $cart->sub_total),
+                            ],
+
+                            'shipping'   => [
+                                'currency_code' => $cart->cart_currency_code,
+                                'value'         => $this->formatCurrencyValue((float) ($cart->selected_shipping_rate ? $cart->selected_shipping_rate->price : 0)),
+                            ],
+
+                            'tax_total'  => [
+                                'currency_code' => $cart->cart_currency_code,
+                                'value'         => $this->formatCurrencyValue((float) $cart->tax_total),
+                            ],
+
+                            'discount'   => [
+                                'currency_code' => $cart->cart_currency_code,
+                                'value'         => $this->formatCurrencyValue((float) $cart->discount_amount),
+                            ],
+                        ],
+                    ],
+                    'items' => $this->getLineItems($cart),
+                    'shipping' => [
+                        'address' => [
+                            'address_line_1' => current($billingAddressLines),
+                            'address_line_2' => last($billingAddressLines),
+                            'admin_area_2'   => $cart->shipping_address->city,
+                            'admin_area_1'   => $cart->shipping_address->state,
+                            'postal_code'    => $cart->shipping_address->postcode,
+                            'country_code'   => $cart->shipping_address->country,
+                        ],
+                    ],
+                ]
+            ]
+        ];
+        $client = new Client();
+        $response = $client->request('POST', $this->createOrderApi, [
+            'headers' => [
+                'Accept' => 'application/json',
+                'Content-Type' => 'application/json',
+                'Authorization' =>  'Bearer '.base64_encode("{$this->apikey}:{$this->secret}")
+            ],
+            'json' => $data,
+            'timeout' => 30,
+            'verify' => false
+        ]);
+        $resultObject = json_decode($response->getBody()->getContents());
+        return $resultObject;
+    }
+    /**
+     * Return cart items.
+     *
+     * @param  string  $cart
+     * @return array
+     */
+    protected function getLineItems($cart)
+    {
+        $lineItems = [];
+
+        foreach ($cart->items as $item) {
+            $lineItems[] = [
+                'unit_amount' => [
+                    'currency_code' => $cart->cart_currency_code,
+                    'value'         => $this->formatCurrencyValue((float) $item->price),
+                ],
+                'quantity'    => $item->quantity,
+                'name'        => $item->name,
+                'sku'         => $item->sku,
+                'category'    => $item->getTypeInstance()->isStockable() ? 'PHYSICAL_GOODS' : 'DIGITAL_GOODS',
+            ];
+        }
+
+        return $lineItems;
+    }
+    protected function getAddressLines($address)
+    {
+        $address = explode(PHP_EOL, $address, 2);
+
+        $addressLines = [current($address)];
+
+        if (isset($address[1])) {
+            $addressLines[] = str_replace(["\r\n", "\r", "\n"], ' ', last($address));
+        } else {
+            $addressLines[] = '';
+        }
+
+        return $addressLines;
+    }
+
+    public function formatCurrencyValue($number): float
+    {
+        return round((float) $number, 2);
+    }
+    public function isAvailable()
+    {
+        if (! parent::isAvailable()) {
+            return false;
+        }
+        $userAgent = request()->header('User-Agent');
+        if (! $userAgent) {
+            return false;
+        }
+        $appleDevices = [
+            'iPhone',
+            'iPad',
+            'iPod',
+            'Macintosh',
+            'Mac OS X',
+        ];
+        foreach ($appleDevices as $device) {
+            if (strpos($userAgent, $device) !== false) {
+                return true;
+            }
+        }
+        return true;
+    }
+}

+ 41 - 0
packages/Longyi/Pay/Applepay/src/Providers/ApplepayServiceProvider.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace Longyi\Pay\Applepay\Providers;
+
+use Illuminate\Support\ServiceProvider;
+
+class ApplepayServiceProvider extends ServiceProvider
+{
+    /**
+     * Register services.
+     */
+    public function register(): void
+    {
+        $this->registerConfig();
+    }
+
+    /**
+     * Bootstrap services.
+     */
+    public function boot(): void
+    {
+        // 加载路由
+        $this->loadRoutesFrom(__DIR__ . '/../Http/routes.php');
+    }
+
+    /**
+     * Register package config.
+     *
+     * @return void
+     */
+    protected function registerConfig()
+    {
+        $this->mergeConfigFrom(
+            dirname(__DIR__) . '/Config/payment-methods.php', 'payment_methods'
+        );
+
+        $this->mergeConfigFrom(
+            dirname(__DIR__) . '/Config/system.php', 'core'
+        );
+    }
+}

+ 12 - 0
packages/Longyi/Pay/AwxAfterpay/src/Config/payment-methods.php

@@ -0,0 +1,12 @@
+<?php
+
+return [
+    'awxafterpay'  => [
+        'code'        => 'awxafterpay',
+        'title'       => 'AwxAfterpay',
+        'description' => 'AwxAfterpay',
+        'class'       => 'Longyi\Pay\AwxAfterpay\Payment\AwxAfterpay',
+        'active'      => true,
+        'sort'        => 1,
+    ],
+];

+ 82 - 0
packages/Longyi/Pay/AwxAfterpay/src/Config/system.php

@@ -0,0 +1,82 @@
+<?php
+
+return [
+    [
+        'key'    => 'sales.payment_methods.awxafterpay',
+        'name'   => 'AwxAfterpay', // use translation
+        'info'   => 'Information about AwxAfterpay', // use translation
+        'sort'   => 5,
+        'fields' => [
+            [
+                'name'          => 'title',
+                'title'         => 'Title', // use translation
+                'type'          => 'text',
+                'validation'    => 'required',
+                'channel_based' => false,
+                'locale_based'  => true,
+            ], [
+                'name'          => 'description',
+                'title'         => 'Description', // use translation
+                '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' => 'ClientId',
+                'type' => 'text',
+                'depend' => 'active:1',
+                'validation' => 'required_if:active,1',
+                'channel_based' => false,
+                'locale_based' => true,
+            ], [
+                'name' => 'secret_id',
+                'title' => 'Secret',
+                'type' => 'password',
+                'depend' => 'active:1',
+                'validation' => 'required_if:active,1',
+                'channel_based' => false,
+                'locale_based' => true,
+            ], [
+                'name' => 'webhook_id',
+                'title' => 'WebhookId',
+                'type' => 'text',
+                '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'          => 'active',
+                'title'         => 'Status', // use translation
+                'type'          => 'boolean',
+                'validation'    => 'required',
+                'channel_based' => false,
+                'locale_based'  => true,
+            ]
+        ]
+    ]
+];

+ 131 - 0
packages/Longyi/Pay/AwxAfterpay/src/Http/Controllers/AfterpayController.php

@@ -0,0 +1,131 @@
+<?php
+
+namespace Longyi\Pay\AwxAfterpay\Http\Controllers;
+
+use Longyi\Pay\AwxAfterpay\Payment\AwxAfterpay;
+use Webkul\Checkout\Facades\Cart;
+use Webkul\Sales\Models\Order;
+use Webkul\Sales\Repositories\OrderRepository;
+use Webkul\Sales\Transformers\OrderResource;
+use Longyi\Pay\AwxKlarna\Payment\AwxKlarna;
+
+class AfterpayController extends Controller
+{
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct(
+        protected OrderRepository $orderRepository,
+        protected AwxAfterpay $awxAfterpay,
+    ) {}
+
+    /**
+     * Redirects to the klarna.
+     *
+     * @return \Illuminate\View\View
+     */
+    public function placeOrder()
+    {
+        $cart = Cart::getCart();
+        $data = (new OrderResource($cart))->jsonSerialize();
+        $order = $this->orderRepository->create($data);
+        Cart::deActivateCart();
+        session()->flash('order_id', $order->id);
+        return view('AwxAfterpay::standard-redirect', ['order' => $order]);
+    }
+
+    public function redirect()
+    {
+        $orderId = request()->input('orderId');
+        if (!$orderId) {
+            return response()->json([
+                'error' => 'Order ID is required'
+            ], 400);
+        }
+        $order = $this->orderRepository->find($orderId);
+        if (!$order) {
+            return response()->json([
+                'error' => 'Order not found'
+            ], 404);
+        }
+        if ($order->status != Order::STATUS_PENDING) {
+            return response()->json([
+                'error' => 'No payment required for the order'
+            ], 404);
+        }
+        $cartData = [
+            'shipping_address' => $order->shipping_address ? $order->shipping_address->toArray() : [],
+            'billing_address' => $order->billing_address ? $order->billing_address->toArray() : [],
+            'grand_total' => $order->grand_total,
+            'order_currency_code' => $order->order_currency_code,
+            'customer_email' => $order->customer_email,
+            'customer_first_name' => $order->customer_first_name,
+            'customer_last_name' => $order->customer_last_name
+        ];
+        try {
+            $checkoutUrl = $this->awxAfterpay->createPayment($cartData, [
+                'merchantOrderId' => $order->id
+            ]);
+            if (!$checkoutUrl) {
+                throw new \Exception('Failed to get checkout URL from Airwallex');
+            }
+            return redirect()->away($checkoutUrl);
+        } catch (\Exception $e) {
+            return response()->json([
+                'error' => 'Payment initialization failed: ' . $e->getMessage()
+            ], 500);
+        }
+    }
+    /**
+     * Success payment.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function return()
+    {
+        $awxReturnResult = request()->input('awx_return_result');
+        $orderId = request()->input('orderId');
+        if ($awxReturnResult == 'success') {
+            session()->flash('order_id', $orderId);
+            return redirect()->route('shop.checkout.onepage.success');
+        } else {
+            session()->flash('error', 'klarna cancel');
+            return redirect()->route('shop.checkout.cart.index');
+        }
+    }
+
+    public function hook()
+    {
+        $param = @file_get_contents('php://input');
+        $header = $this->getAllHeaders();
+        $data = [
+            'timestamp' => $header['x-timestamp'],
+            'signature' => $header['x-signature'],
+            'content' => $param
+        ];
+        $result = $this->awxAfterpay->checkSignature($data, 'secret');
+        if (!$result) {
+            @header("HTTP/1.0 400 Bad Request");
+            exit;
+        }
+        $notifyData = json_decode($param, true);
+        $orderNo = $notifyData['data']['object']['merchant_order_id'] ?: 0;
+        if (empty($orderNo)) {
+            @header("HTTP/1.0 400 Bad Request");
+            exit;
+        }
+        $prefix = $this->awxAfterpay->getPrefix();
+        if (strpos($orderNo, $prefix) === false) {
+            @header("HTTP/1.0 200 ok");
+            exit;
+        }
+        $type = $notifyData['name'];
+        $orderId = str_replace($prefix, '', $orderNo);
+        $transactionId = $notifyData['data']['object']['id'] ?: '';
+        $eventType = "payments_webhook_" . strtolower(str_replace(".", "_", $type));
+        $this->awxAfterpay->$eventType($orderId, $transactionId);
+    }
+
+}

+ 12 - 0
packages/Longyi/Pay/AwxAfterpay/src/Http/Controllers/Controller.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace Longyi\Pay\AwxAfterpay\Http\Controllers;
+
+use Illuminate\Foundation\Bus\DispatchesJobs;
+use Illuminate\Foundation\Validation\ValidatesRequests;
+use Illuminate\Routing\Controller as BaseController;
+
+class Controller extends BaseController
+{
+    use DispatchesJobs, ValidatesRequests;
+}

+ 13 - 0
packages/Longyi/Pay/AwxAfterpay/src/Http/routes.php

@@ -0,0 +1,13 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use Longyi\Pay\AwxAfterpay\Http\Controllers\AfterpayController;
+
+Route::group(['middleware' => ['web']], function () {
+    Route::prefix('afterpay/standard')->group(function () {
+        Route::post('/redirect', [AfterpayController::class, 'redirect'])->name('afterpay.standard.redirect');
+        Route::get('/placeOrder', [AfterpayController::class, 'placeOrder'])->name('afterpay.standard.placeOrder');
+
+        Route::get('/return', [AfterpayController::class, 'return'])->name('afterpay.standard.return');
+    });
+});

+ 283 - 0
packages/Longyi/Pay/AwxAfterpay/src/Payment/AwxAfterpay.php

@@ -0,0 +1,283 @@
+<?php
+
+namespace Longyi\Pay\AwxAfterpay\Payment;
+
+use GuzzleHttp\Client;
+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;
+
+class AwxAfterpay extends Payment
+{
+    protected $clientId;
+    protected $apikey;
+    protected $secret;
+    protected $prefix = 'QQS';
+    protected $tokenCacheKey = 'airwallex_klarna_token';
+    protected $tokenUrl = 'https://api-demo.airwallex.com/api/v1/authentication/login';
+    protected $createPaymentUlr = 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/create';
+    protected $confirmIntentsUlr = 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/{id}/confirm';
+    protected $captureIntentsUlr = 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/{id}/capture';
+    protected $paymentIntentsUlr = 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/';
+
+    public function __construct()
+    {
+        if ($this->getConfigData('mode') == 'live') {
+            $this->tokenUrl = 'https://api.airwallex.com/api/v1/authentication/login';
+            $this->createPaymentUlr = 'https://api.airwallex.com/api/v1/pa/payment_intents/create';
+            $this->confirmIntentsUlr = 'https://api.airwallex.com/api/v1/pa/payment_intents/{id}/confirm';
+            $this->captureIntentsUlr = 'https://api.airwallex.com/api/v1/pa/payment_intents/{id}/capture';
+            $this->paymentIntentsUlr = 'https://api.airwallex.com/api/v1/pa/payment_intents/';
+        }
+        $this->clientId = $this->getConfigData('client_id');
+        $this->apikey = $this->getConfigData('secret_id');
+        $this->secret = $this->getConfigData('webhook_id');
+    }
+    /**
+     * Payment method code
+     *
+     * @var string
+     */
+    protected $code  = 'awxafterpay';
+
+    /**
+     * Get redirect url.
+     */
+    public function getRedirectUrl()
+    {
+        return route('afterpay.standard.placeOrder');
+    }
+
+    public function createPayment($cart, $override)
+    {
+        $shop = $cart['shipping_address'];
+        $bill = $cart['billing_address'];
+        $telephone = $shop['phone'] ?: $bill['phone'];
+        $grandTotal = round($cart['grand_total'], 2);
+        $products[] = [
+            'name' => 'total',
+            'unit_price' => $grandTotal,
+            'quantity' => 1
+        ];
+        $data = [
+            'request_id' => $this->createUuid(),
+            'amount' => $grandTotal,
+            'descriptor' => $this->prefix,
+            'return_url' => $this->getBaseUlr() . '/klarna/standard/return?orderId=' . $override['merchantOrderId'],
+            'merchant_order_id' => $this->prefix . $override['merchantOrderId'],
+            'currency' => $cart['order_currency_code'],
+            'customer' => [
+                'email' => $cart['customer_email'],
+                'first_name' => $cart['customer_first_name'],
+                'last_name' => $cart['customer_last_name'],
+                'phone_number' => $telephone
+            ],
+//            'metadata' => $cart['items'],
+            'order' => [
+                'products' => $products,
+                'shipping' => [
+                    'address' => [
+                        'city' => $shop['city'],
+                        'postcode' => $shop['postcode'],
+                        'state' => $shop['state'],
+                        'country_code' => $shop['country'],
+                        'street' => $shop['address'],
+                    ],
+                    'first_name' => $shop['first_name'],
+                    'last_name' => $shop['last_name'],
+                    'phone_number' => $shop['phone'],
+                    'email' => $shop['email']
+                ],
+            ]
+        ];
+        $client = new Client();
+        $response = $client->request('POST', $this->createPaymentUlr, [
+            'headers' => [
+                'Accept' => 'application/json',
+                'Content-Type' => 'application/json',
+                'Authorization' =>  'Bearer '.$this->getToken()
+            ],
+            'json' => $data,
+            'timeout' => 30,
+            'verify' => false
+        ]);
+        $resultObject = json_decode($response->getBody()->getContents());
+        if (empty($resultObject->id)) {
+            throw new \Exception($resultObject->details->original_response_message ?:$resultObject->message);
+        } else {
+            $id = $resultObject->id;
+            $confirmResultObject = $this->confirmRequest($id, $cart);
+            if (empty($confirmResultObject->next_action->url)) {
+                throw new \Exception($confirmResultObject->details->original_response_message ?:$confirmResultObject->message);
+            }
+            return $confirmResultObject->next_action->url;
+        }
+    }
+
+    public function confirmRequest($paymentIntentId, $cart)
+    {
+        $bill = $cart['billing_address'];
+        $data = [
+            'request_id' => $this->createUuid(),
+            'payment_method' => [
+                'type' => 'afterpay',
+                'afterpay' => [
+                    'shopper_email' => $bill['email'],
+                    'billing' => [
+                        'first_name' => $bill['first_name'],
+                        'last_name' => $bill['last_name'],
+                        'phone_number' => $bill['phone'],
+                        'address' => [
+                            'country_code' => $bill['country'],
+                            'city' => $bill['city'],
+                            'street' => $bill['address'],
+                            'postcode' => $bill['postcode'],
+                            'state' => $bill['state']
+                        ],
+                    ],
+                ]
+            ]
+        ];
+        $client = new Client();
+        $response = $client->request('POST',
+            str_replace('{id}', $paymentIntentId, $this->confirmIntentsUlr),
+            [
+                'headers' => [
+                    'Accept' => 'application/json',
+                    'Content-Type' => 'application/json',
+                    'Authorization' =>  'Bearer '.$this->getToken()
+                ],
+                'json' => $data,
+                'timeout' => 30,
+                'verify' => false
+            ]);
+        $result = json_decode($response->getBody()->getContents());
+        return $result;
+    }
+
+    public function getToken()
+    {
+        $token = cache()->get($this->tokenCacheKey);
+        if (!$token) {
+            $client = new Client();
+            $response = $client->request('POST', $this->tokenUrl, [
+                'headers' => [
+                    'Accept' => 'application/json',
+                    'Content-Type' => 'application/json',
+                    'x-client-id' => $this->clientId,
+                    'x-api-key' => $this->apikey
+                ],
+                'timeout' => 30,
+                'verify' => false
+            ]);
+            $result = json_decode($response->getBody()->getContents());
+            $token = $result->token;
+            cache()->put($this->tokenCacheKey, $token, 20);
+        }
+        return $token;
+
+    }
+
+    //成功修改状态
+    public function payments_webhook_payment_intent_succeeded($orderId, $transactionId)
+    {
+        $order = app(OrderRepository::class)->find($orderId);
+
+        if (!$order) {
+            return false;
+        }
+        if ($order->status != Order::STATUS_PENDING) {
+            return false;
+        }
+        $order->status = Order::STATUS_PROCESSING;
+        $order->save();
+        if ($order->payment) {
+            $order->payment->update([
+                'transaction_id' => $transactionId,
+            ]);
+        }
+    }
+    //授权成功修改状态
+    public function payments_webhook_payment_intent_requires_capture($orderId, $transactionId)
+    {
+        $order = app(OrderRepository::class)->find($orderId);
+
+        if (!$order) {
+            return false;
+        }
+        if ($order->status != Order::STATUS_PENDING) {
+            return false;
+        }
+        $order->status = Order::STATUS_PROCESSING;
+        $order->save();
+        if ($order->payment) {
+            $order->payment->update([
+                'transaction_id' => $transactionId,
+            ]);
+        }
+    }
+    /**
+     * 获取一个唯一的id
+     */
+    public function createUuid($prefix = "")
+    {
+        $chars = md5(uniqid(mt_rand(), true));
+        $uuid = substr($chars, 0, 8) . '-'
+            . substr($chars, 8, 4) . '-'
+            . substr($chars, 12, 4) . '-'
+            . substr($chars, 16, 4) . '-'
+            . substr($chars, 20, 12);
+        return $prefix . $uuid;
+    }
+
+    public function getBaseUlr()
+    {
+        return  config('app.url');
+    }
+
+    public function getAwxAfterpayUrl()
+    {
+        return route('afterpay.standard.redirect');
+    }
+
+    public function checkSignature($data, $exp)
+    {
+        if (empty($data)) {
+            return false;
+        }
+        if (hash_hmac('sha256', $data['timestamp'].$data['content'], $this->$exp) != $data['signature']) {
+            return false;
+        }
+        return true;
+    }
+
+    public function getPrefix()
+    {
+        return $this->prefix;
+    }
+
+    /**
+     * Get payment method image.
+     *
+     * @return array
+     */
+    public function getImage()
+    {
+        $url = $this->getConfigData('image');
+
+        return $url ? Storage::url($url) : bagisto_asset('images/cash-on-delivery.png', 'shop');
+    }
+
+    public function isAvailable()
+    {
+        if (! parent::isAvailable()) {
+            return false;
+        }
+        $cart = Cart::getCart();
+        $billingCountry = $cart->billing_address->country;
+        $allowedCountries = ['AU', 'CA', 'NZ', 'GB', 'US'];
+        return in_array($billingCountry, $allowedCountries);
+    }
+}

+ 41 - 0
packages/Longyi/Pay/AwxAfterpay/src/Providers/AwxAfterpayServiceProvider.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace Longyi\Pay\AwxAfterpay\Providers;
+
+use Illuminate\Support\ServiceProvider;
+
+class AwxAfterpayServiceProvider extends ServiceProvider
+{
+    /**
+     * Register services.
+     */
+    public function register(): void
+    {
+        $this->registerConfig();
+    }
+
+    /**
+     * Bootstrap services.
+     */
+    public function boot(): void
+    {
+        $this->loadRoutesFrom(__DIR__.'/../Http/routes.php');
+        $this->loadViewsFrom(__DIR__ . '/../Resources/views', 'AwxAfterpay');
+    }
+
+    /**
+     * Register package config.
+     *
+     * @return void
+     */
+    protected function registerConfig()
+    {
+        $this->mergeConfigFrom(
+            dirname(__DIR__) . '/Config/payment-methods.php', 'payment_methods'
+        );
+
+        $this->mergeConfigFrom(
+            dirname(__DIR__) . '/Config/system.php', 'core'
+        );
+    }
+}

+ 112 - 0
packages/Longyi/Pay/AwxAfterpay/src/Resources/views/checkout/onepage/paypal-smart-button.blade.php

@@ -0,0 +1,112 @@
+@if (
+    request()->routeIs('shop.checkout.onepage.index')
+    && (bool) core()->getConfigData('sales.payment_methods.paypal_smart_button.active')
+)
+    @php
+        $clientId = core()->getConfigData('sales.payment_methods.paypal_smart_button.client_id');
+
+        $acceptedCurrency = core()->getConfigData('sales.payment_methods.paypal_smart_button.accepted_currencies');
+
+        $currentCurrency = core()->getCurrentCurrencyCode();
+
+        $acceptedCurrenciesArray = array_map('trim', explode(',', $acceptedCurrency));
+
+        $currencyToUse = in_array($currentCurrency, $acceptedCurrenciesArray)
+            ? $currentCurrency
+            : $acceptedCurrenciesArray[0];
+    @endphp
+
+    @pushOnce('scripts')
+        <script
+            src="https://www.paypal.com/sdk/js?client-id={{ $clientId }}&currency={{ $currencyToUse }}"
+            data-partner-attribution-id="Bagisto_Cart"
+        >
+        </script>
+
+        <script
+            type="text/x-template"
+            id="v-paypal-smart-button-template"
+        >
+            <div class="w-full paypal-button-container"></div>
+        </script>
+
+        <script type="module">
+            app.component('v-paypal-smart-button', {
+                template: '#v-paypal-smart-button-template',
+
+                mounted() {
+                    this.register();
+                },
+
+                methods: {
+                    register() {
+                        if (typeof paypal == 'undefined') {
+                            this.$emitter.emit('add-flash', { type: 'error', message: '@lang('paypal::app.errors.invalid-configs')' });
+
+                            return;
+                        }
+
+                        paypal.Buttons(this.getOptions()).render('.paypal-button-container');
+                    },
+
+                    getOptions() {
+                        let options = {
+                            style: {
+                                layout: 'vertical',
+                                shape: 'rect',
+                            },
+
+                            authorizationFailed: false,
+
+                            enableStandardCardFields: false,
+
+                            alertBox: (message) => {
+                                this.$emitter.emit('add-flash', { type: 'error', message: message });
+                            },
+
+                            createOrder: (data, actions) => {
+                                return this.$axios.get("{{ route('paypal.smart-button.create-order') }}")
+                                    .then(response => response.data.result)
+                                    .then(order => order.id)
+                                    .catch(error => {
+                                        if (error.response.data.error === 'invalid_client') {
+                                            options.authorizationFailed = true;
+
+                                            options.alertBox('@lang('paypal::app.errors.invalid-configs')');
+                                        }
+
+                                        return error;
+                                    });
+                            },
+
+                            onApprove: (data, actions) => {
+                                this.$axios.post("{{ route('paypal.smart-button.capture-order') }}", {
+                                    _token: "{{ csrf_token() }}",
+                                    orderData: data
+                                })
+                                .then(response => {
+                                    if (response.data.success) {
+                                        if (response.data.redirect_url) {
+                                            window.location.href = response.data.redirect_url;
+                                        } else {
+                                            window.location.href = "{{ route('shop.checkout.onepage.success') }}";
+                                        }
+                                    }
+                                })
+                                .catch(error => window.location.href = "{{ route('shop.checkout.cart.index') }}");
+                            },
+
+                            onError: (error) => {
+                                if (! options.authorizationFailed) {
+                                    options.alertBox('@lang('paypal::app.errors.something-went-wrong')');
+                                }
+                            },
+                        };
+
+                        return options;
+                    },
+                },
+            });
+        </script>
+    @endPushOnce
+@endif

+ 16 - 0
packages/Longyi/Pay/AwxAfterpay/src/Resources/views/standard-redirect.blade.php

@@ -0,0 +1,16 @@
+<?php $awxAfterpayStandard = app('Longyi\Pay\AwxAfterpay\Payment\AwxAfterpay') ?>
+
+<body data-gr-c-s-loaded="true" cz-shortcut-listen="true">
+    You will be redirected to the afterpay website in a few seconds.
+
+
+    <form action="{{ $awxAfterpayStandard->getAwxAfterpayUrl() }}" id="afterpay_standard_checkout" method="POST">
+        <input value="Click here if you are not redirected within 10 seconds..." type="submit">
+        <input type="hidden" name="_token" value="{{ csrf_token() }}">
+        <input type="hidden" name="orderId" value="{{ $order->id }}">
+    </form>
+
+    <script type="text/javascript">
+        document.getElementById("afterpay_standard_checkout").submit();
+    </script>
+</body>

+ 12 - 0
packages/Longyi/Pay/AwxKlarna/src/Config/payment-methods.php

@@ -0,0 +1,12 @@
+<?php
+
+return [
+    'awxklarna'  => [
+        'code'        => 'awxklarna',
+        'title'       => 'AwxKlarna',
+        'description' => 'AwxKlarna',
+        'class'       => 'Longyi\Pay\AwxKlarna\Payment\AwxKlarna',
+        'active'      => true,
+        'sort'        => 1,
+    ],
+];

+ 82 - 0
packages/Longyi/Pay/AwxKlarna/src/Config/system.php

@@ -0,0 +1,82 @@
+<?php
+
+return [
+    [
+        'key' => 'sales.payment_methods.awxklarna',
+        'name' => 'AwxKlarna', // use translation
+        'info' => 'Information about AwxKlarna', // use translation
+        'sort' => 6,
+        'fields' => [
+            [
+                'name' => 'title',
+                'title' => 'Title', // use translation
+                'type' => 'text',
+                'validation' => 'required',
+                'channel_based' => false,
+                'locale_based' => true,
+            ], [
+                'name' => 'description',
+                'title' => 'Description', // use translation
+                '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' => 'ClientId',
+                'type' => 'text',
+                'depend' => 'active:1',
+                'validation' => 'required_if:active,1',
+                'channel_based' => false,
+                'locale_based' => true,
+            ], [
+                'name' => 'secret_id',
+                'title' => 'Secret',
+                'type' => 'password',
+                'depend' => 'active:1',
+                'validation' => 'required_if:active,1',
+                'channel_based' => false,
+                'locale_based' => true,
+            ], [
+                'name' => 'webhook_id',
+                'title' => 'WebhookId',
+                'type' => 'text',
+                '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'       => 6,
+            ], [
+                'name' => 'active',
+                'title' => 'Status', // use translation
+                'type' => 'boolean',
+                'validation' => 'required',
+                'channel_based' => false,
+                'locale_based' => true,
+            ]
+        ]
+    ]
+];

+ 12 - 0
packages/Longyi/Pay/AwxKlarna/src/Http/Controllers/Controller.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace Longyi\Pay\AwxKlarna\Http\Controllers;
+
+use Illuminate\Foundation\Bus\DispatchesJobs;
+use Illuminate\Foundation\Validation\ValidatesRequests;
+use Illuminate\Routing\Controller as BaseController;
+
+class Controller extends BaseController
+{
+    use DispatchesJobs, ValidatesRequests;
+}

+ 130 - 0
packages/Longyi/Pay/AwxKlarna/src/Http/Controllers/KlarnaController.php

@@ -0,0 +1,130 @@
+<?php
+
+namespace Longyi\Pay\AwxKlarna\Http\Controllers;
+
+use Webkul\Checkout\Facades\Cart;
+use Webkul\Sales\Models\Order;
+use Webkul\Sales\Repositories\OrderRepository;
+use Webkul\Sales\Transformers\OrderResource;
+use Longyi\Pay\AwxKlarna\Payment\AwxKlarna;
+
+class KlarnaController extends Controller
+{
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct(
+        protected OrderRepository $orderRepository,
+        protected AwxKlarna $awxKlarna,
+    ) {}
+
+    /**
+     * Redirects to the klarna.
+     *
+     * @return \Illuminate\View\View
+     */
+    public function placeOrder()
+    {
+        $cart = Cart::getCart();
+        $data = (new OrderResource($cart))->jsonSerialize();
+        $order = $this->orderRepository->create($data);
+        Cart::deActivateCart();
+        session()->flash('order_id', $order->id);
+        return view('AwxKlarna::standard-redirect', ['order' => $order]);
+    }
+
+    public function redirect()
+    {
+        $orderId = request()->input('orderId');
+        if (!$orderId) {
+            return response()->json([
+                'error' => 'Order ID is required'
+            ], 400);
+        }
+        $order = $this->orderRepository->find($orderId);
+        if (!$order) {
+            return response()->json([
+                'error' => 'Order not found'
+            ], 404);
+        }
+        if ($order->status != Order::STATUS_PENDING) {
+            return response()->json([
+                'error' => 'No payment required for the order'
+            ], 404);
+        }
+        $cartData = [
+            'shipping_address' => $order->shipping_address ? $order->shipping_address->toArray() : [],
+            'billing_address' => $order->billing_address ? $order->billing_address->toArray() : [],
+            'grand_total' => $order->grand_total,
+            'order_currency_code' => $order->order_currency_code,
+            'customer_email' => $order->customer_email,
+            'customer_first_name' => $order->customer_first_name,
+            'customer_last_name' => $order->customer_last_name
+        ];
+        try {
+            $checkoutUrl = $this->awxKlarna->createPayment($cartData, [
+                'merchantOrderId' => $order->id
+            ]);
+            if (!$checkoutUrl) {
+                throw new \Exception('Failed to get checkout URL from Airwallex');
+            }
+            return redirect()->away($checkoutUrl);
+        } catch (\Exception $e) {
+            return response()->json([
+                'error' => 'Payment initialization failed: ' . $e->getMessage()
+            ], 500);
+        }
+    }
+    /**
+     * Success payment.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function return()
+    {
+        $awxReturnResult = request()->input('awx_return_result');
+        $orderId = request()->input('orderId');
+        if ($awxReturnResult == 'success') {
+            session()->flash('order_id', $orderId);
+            return redirect()->route('shop.checkout.onepage.success');
+        } else {
+            session()->flash('error', 'afterpay cancel');
+            return redirect()->route('shop.checkout.cart.index');
+        }
+    }
+
+    public function hook()
+    {
+        $param = @file_get_contents('php://input');
+        $header = $this->getAllHeaders();
+        $data = [
+            'timestamp' => $header['x-timestamp'],
+            'signature' => $header['x-signature'],
+            'content' => $param
+        ];
+        $result = $this->awxKlarna->checkSignature($data, 'secret');
+        if (!$result) {
+            @header("HTTP/1.0 400 Bad Request");
+            exit;
+        }
+        $notifyData = json_decode($param, true);
+        $orderNo = $notifyData['data']['object']['merchant_order_id'] ?: 0;
+        if (empty($orderNo)) {
+            @header("HTTP/1.0 400 Bad Request");
+            exit;
+        }
+        $prefix = $this->awxKlarna->getPrefix();
+        if (strpos($orderNo, $prefix) === false) {
+            @header("HTTP/1.0 200 ok");
+            exit;
+        }
+        $type = $notifyData['name'];
+        $orderId = str_replace($prefix, '', $orderNo);
+        $transactionId = $notifyData['data']['object']['id'] ?: '';
+        $eventType = "payments_webhook_" . strtolower(str_replace(".", "_", $type));
+        $this->awxKlarna->$eventType($orderId, $transactionId);
+    }
+
+}

+ 13 - 0
packages/Longyi/Pay/AwxKlarna/src/Http/routes.php

@@ -0,0 +1,13 @@
+<?php
+
+use Illuminate\Support\Facades\Route;
+use Longyi\Pay\AwxKlarna\Http\Controllers\KlarnaController;
+
+Route::group(['middleware' => ['web']], function () {
+    Route::prefix('klarna/standard')->group(function () {
+        Route::post('/redirect', [KlarnaController::class, 'redirect'])->name('klarna.standard.redirect');
+        Route::get('/placeOrder', [KlarnaController::class, 'placeOrder'])->name('klarna.standard.placeOrder');
+
+        Route::get('/return', [KlarnaController::class, 'return'])->name('klarna.standard.return');
+    });
+});

+ 299 - 0
packages/Longyi/Pay/AwxKlarna/src/Payment/AwxKlarna.php

@@ -0,0 +1,299 @@
+<?php
+
+namespace Longyi\Pay\AwxKlarna\Payment;
+
+use Illuminate\Support\Facades\Storage;
+use Webkul\Checkout\Facades\Cart;
+use Webkul\Payment\Payment\Payment;
+use GuzzleHttp\Client;
+use Webkul\Sales\Models\Order;
+use Webkul\Sales\Repositories\OrderRepository;
+
+class AwxKlarna extends Payment
+{
+
+    protected $clientId;
+    protected $apikey;
+    protected $secret;
+    protected $prefix = 'QQS';
+    protected $tokenCacheKey = 'airwallex_klarna_token';
+    protected $tokenUrl = 'https://api-demo.airwallex.com/api/v1/authentication/login';
+    protected $createPaymentUlr = 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/create';
+    protected $confirmIntentsUlr = 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/{id}/confirm';
+    protected $captureIntentsUlr = 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/{id}/capture';
+    protected $paymentIntentsUlr = 'https://api-demo.airwallex.com/api/v1/pa/payment_intents/';
+
+    public function __construct()
+    {
+        if ($this->getConfigData('mode') == 'live') {
+            $this->tokenUrl = 'https://api.airwallex.com/api/v1/authentication/login';
+            $this->createPaymentUlr = 'https://api.airwallex.com/api/v1/pa/payment_intents/create';
+            $this->confirmIntentsUlr = 'https://api.airwallex.com/api/v1/pa/payment_intents/{id}/confirm';
+            $this->captureIntentsUlr = 'https://api.airwallex.com/api/v1/pa/payment_intents/{id}/capture';
+            $this->paymentIntentsUlr = 'https://api.airwallex.com/api/v1/pa/payment_intents/';
+        }
+        $this->clientId = $this->getConfigData('client_id');
+        $this->apikey = $this->getConfigData('secret_id');
+        $this->secret = $this->getConfigData('webhook_id');
+    }
+
+    /**
+     * Payment method code
+     *
+     * @var string
+     */
+    protected $code = 'awxklarna';
+
+    /**
+     * Get redirect url.
+     */
+    public function getRedirectUrl()
+    {
+        return route('klarna.standard.placeOrder');
+    }
+
+    public function createPayment($cart, $override)
+    {
+        $shop = $cart['shipping_address'];
+        $bill = $cart['billing_address'];
+        $telephone = $shop['phone'] ?: $bill['phone'];
+        $grandTotal = round($cart['grand_total'], 2);
+        $products[] = [
+            'name' => 'total',
+            'unit_price' => $grandTotal,
+            'quantity' => 1
+        ];
+        $data = [
+            'request_id' => $this->createUuid(),
+            'amount' => $grandTotal,
+            'descriptor' => $this->prefix,
+            'return_url' => $this->getBaseUlr() . '/klarna/standard/return?orderId=' . $override['merchantOrderId'],
+            'merchant_order_id' => $this->prefix . $override['merchantOrderId'],
+            'currency' => $cart['order_currency_code'],
+            'customer' => [
+                'email' => $cart['customer_email'],
+                'first_name' => $cart['customer_first_name'],
+                'last_name' => $cart['customer_last_name'],
+                'phone_number' => $telephone
+            ],
+//            'metadata' => $cart['items'],
+            'order' => [
+                'products' => $products,
+                'shipping' => [
+                    'address' => [
+                        'city' => $shop['city'],
+                        'postcode' => $shop['postcode'],
+                        'state' => $shop['state'],
+                        'country_code' => $shop['country'],
+                        'street' => $shop['address'],
+                    ],
+                    'first_name' => $shop['first_name'],
+                    'last_name' => $shop['last_name'],
+                    'phone_number' => $shop['phone'],
+                    'email' => $shop['email']
+                ],
+            ]
+        ];
+        $client = new Client();
+        $response = $client->request('POST', $this->createPaymentUlr, [
+            'headers' => [
+                'Accept' => 'application/json',
+                'Content-Type' => 'application/json',
+                'Authorization' =>  'Bearer '.$this->getToken()
+            ],
+            'json' => $data,
+            'timeout' => 30,
+            'verify' => false
+        ]);
+        $resultObject = json_decode($response->getBody()->getContents());
+        if (empty($resultObject->id)) {
+            throw new \Exception($resultObject->details->original_response_message ?:$resultObject->message);
+        } else {
+            $id = $resultObject->id;
+            $confirmResultObject = $this->confirmRequest($id, $cart);
+            if (empty($confirmResultObject->next_action->url)) {
+                throw new \Exception($confirmResultObject->details->original_response_message ?:$confirmResultObject->message);
+            }
+            return $confirmResultObject->next_action->url;
+        }
+    }
+
+    public function confirmRequest($paymentIntentId, $cart)
+    {
+        $bill = $cart['billing_address'];
+        $data = [
+            'request_id' => $this->createUuid(),
+            'payment_method' => [
+                'type' => 'klarna',
+                'klarna' => [
+                    'country_code' => $bill['country'],
+                    'billing' => [
+                        'email' => $bill['email'],
+                        'first_name' => $bill['first_name'],
+                        'last_name' => $bill['last_name'],
+                        'phone_number' => $bill['phone'],
+                        'address' => [
+                            'country_code' => $bill['country'],
+                            'city' => $bill['city'],
+                            'street' => $bill['address'],
+                            'postcode' => $bill['postcode'],
+                            'state' => $bill['state']
+                        ],
+                    ],
+                ],
+                'payment_method_options' => [
+                    'klarna' => [
+                        'auto_capture' => false
+                    ]
+                ]
+            ]
+        ];
+        $client = new Client();
+        $response = $client->request('POST',
+            str_replace('{id}', $paymentIntentId, $this->confirmIntentsUlr),
+            [
+                'headers' => [
+                    'Accept' => 'application/json',
+                    'Content-Type' => 'application/json',
+                    'Authorization' =>  'Bearer '.$this->getToken()
+                ],
+                'json' => $data,
+                'timeout' => 30,
+                'verify' => false
+            ]);
+        $result = json_decode($response->getBody()->getContents());
+        return $result;
+    }
+
+    public function getToken()
+    {
+        $token = cache()->get($this->tokenCacheKey);
+        if (!$token) {
+            $client = new Client();
+            $response = $client->request('POST', $this->tokenUrl, [
+                'headers' => [
+                    'Accept' => 'application/json',
+                    'Content-Type' => 'application/json',
+                    'x-client-id' => $this->clientId,
+                    'x-api-key' => $this->apikey
+                ],
+                'timeout' => 30,
+                'verify' => false
+            ]);
+            $result = json_decode($response->getBody()->getContents());
+            $token = $result->token;
+            cache()->put($this->tokenCacheKey, $token, 20);
+        }
+        return $token;
+
+    }
+
+    //成功修改状态
+    public function payments_webhook_payment_intent_succeeded($orderId, $transactionId)
+    {
+        $order = app(OrderRepository::class)->find($orderId);
+
+        if (!$order) {
+            return false;
+        }
+        if ($order->status != Order::STATUS_PENDING) {
+            return false;
+        }
+        $order->status = Order::STATUS_PROCESSING;
+        $order->save();
+        if ($order->payment) {
+            $order->payment->update([
+                'transaction_id' => $transactionId,
+            ]);
+        }
+    }
+    //授权成功修改状态
+    public function payments_webhook_payment_intent_requires_capture($orderId, $transactionId)
+    {
+        $order = app(OrderRepository::class)->find($orderId);
+
+        if (!$order) {
+            return false;
+        }
+        if ($order->status != Order::STATUS_PENDING) {
+            return false;
+        }
+        $order->status = Order::STATUS_PROCESSING;
+        $order->save();
+        if ($order->payment) {
+            $order->payment->update([
+                'transaction_id' => $transactionId,
+            ]);
+        }
+    }
+    /**
+     * 获取一个唯一的id
+     */
+    public function createUuid($prefix = "")
+    {
+        $chars = md5(uniqid(mt_rand(), true));
+        $uuid = substr($chars, 0, 8) . '-'
+            . substr($chars, 8, 4) . '-'
+            . substr($chars, 12, 4) . '-'
+            . substr($chars, 16, 4) . '-'
+            . substr($chars, 20, 12);
+        return $prefix . $uuid;
+    }
+
+    public function getBaseUlr()
+    {
+        return  config('app.url');
+    }
+
+    public function getAwxKlarnaUrl()
+    {
+        return route('klarna.standard.redirect');
+    }
+
+    public function checkSignature($data, $exp)
+    {
+        if (empty($data)) {
+            return false;
+        }
+        if (hash_hmac('sha256', $data['timestamp'].$data['content'], $this->$exp) != $data['signature']) {
+            return false;
+        }
+        return true;
+    }
+
+    public function getPrefix()
+    {
+        return $this->prefix;
+    }
+
+    /**
+     * Get payment method image.
+     *
+     * @return array
+     */
+    public function getImage()
+    {
+        $url = $this->getConfigData('image');
+
+        return $url ? Storage::url($url) : bagisto_asset('images/cash-on-delivery.png', 'shop');
+    }
+
+    public function isAvailable()
+    {
+        if (! parent::isAvailable()) {
+            return false;
+        }
+        $cart = Cart::getCart();
+        $billingCountry = $cart->billing_address->country;
+        $allowedCountries = ['AU', 'AT', 'BE', 'CA', 'CZ', 'DK', 'FI', 'FR', 'DE', 'GR', 'IE',
+            'IT', 'NL', 'NO', 'PL', 'PT', 'ES', 'SE', 'CH', 'GB', 'US'
+        ];
+        return in_array($billingCountry, $allowedCountries);
+    }
+
+    public function getDescription()
+    {
+        $cart = Cart::getCart();
+        return round($cart->grand_total / 4, 2);
+    }
+}

+ 41 - 0
packages/Longyi/Pay/AwxKlarna/src/Providers/AwxKlarnaServiceProvider.php

@@ -0,0 +1,41 @@
+<?php
+
+namespace Longyi\Pay\AwxKlarna\Providers;
+
+use Illuminate\Support\ServiceProvider;
+
+class AwxKlarnaServiceProvider extends ServiceProvider
+{
+    /**
+     * Register services.
+     */
+    public function register(): void
+    {
+        $this->registerConfig();
+    }
+
+    /**
+     * Bootstrap services.
+     */
+    public function boot(): void
+    {
+        $this->loadRoutesFrom(__DIR__.'/../Http/routes.php');
+        $this->loadViewsFrom(__DIR__ . '/../Resources/views', 'AwxKlarna');
+    }
+
+    /**
+     * Register package config.
+     *
+     * @return void
+     */
+    protected function registerConfig()
+    {
+        $this->mergeConfigFrom(
+            dirname(__DIR__) . '/Config/payment-methods.php', 'payment_methods'
+        );
+
+        $this->mergeConfigFrom(
+            dirname(__DIR__) . '/Config/system.php', 'core'
+        );
+    }
+}

+ 112 - 0
packages/Longyi/Pay/AwxKlarna/src/Resources/views/checkout/onepage/paypal-smart-button.blade.php

@@ -0,0 +1,112 @@
+@if (
+    request()->routeIs('shop.checkout.onepage.index')
+    && (bool) core()->getConfigData('sales.payment_methods.paypal_smart_button.active')
+)
+    @php
+        $clientId = core()->getConfigData('sales.payment_methods.paypal_smart_button.client_id');
+
+        $acceptedCurrency = core()->getConfigData('sales.payment_methods.paypal_smart_button.accepted_currencies');
+
+        $currentCurrency = core()->getCurrentCurrencyCode();
+
+        $acceptedCurrenciesArray = array_map('trim', explode(',', $acceptedCurrency));
+
+        $currencyToUse = in_array($currentCurrency, $acceptedCurrenciesArray)
+            ? $currentCurrency
+            : $acceptedCurrenciesArray[0];
+    @endphp
+
+    @pushOnce('scripts')
+        <script
+            src="https://www.paypal.com/sdk/js?client-id={{ $clientId }}&currency={{ $currencyToUse }}"
+            data-partner-attribution-id="Bagisto_Cart"
+        >
+        </script>
+
+        <script
+            type="text/x-template"
+            id="v-paypal-smart-button-template"
+        >
+            <div class="w-full paypal-button-container"></div>
+        </script>
+
+        <script type="module">
+            app.component('v-paypal-smart-button', {
+                template: '#v-paypal-smart-button-template',
+
+                mounted() {
+                    this.register();
+                },
+
+                methods: {
+                    register() {
+                        if (typeof paypal == 'undefined') {
+                            this.$emitter.emit('add-flash', { type: 'error', message: '@lang('paypal::app.errors.invalid-configs')' });
+
+                            return;
+                        }
+
+                        paypal.Buttons(this.getOptions()).render('.paypal-button-container');
+                    },
+
+                    getOptions() {
+                        let options = {
+                            style: {
+                                layout: 'vertical',
+                                shape: 'rect',
+                            },
+
+                            authorizationFailed: false,
+
+                            enableStandardCardFields: false,
+
+                            alertBox: (message) => {
+                                this.$emitter.emit('add-flash', { type: 'error', message: message });
+                            },
+
+                            createOrder: (data, actions) => {
+                                return this.$axios.get("{{ route('paypal.smart-button.create-order') }}")
+                                    .then(response => response.data.result)
+                                    .then(order => order.id)
+                                    .catch(error => {
+                                        if (error.response.data.error === 'invalid_client') {
+                                            options.authorizationFailed = true;
+
+                                            options.alertBox('@lang('paypal::app.errors.invalid-configs')');
+                                        }
+
+                                        return error;
+                                    });
+                            },
+
+                            onApprove: (data, actions) => {
+                                this.$axios.post("{{ route('paypal.smart-button.capture-order') }}", {
+                                    _token: "{{ csrf_token() }}",
+                                    orderData: data
+                                })
+                                .then(response => {
+                                    if (response.data.success) {
+                                        if (response.data.redirect_url) {
+                                            window.location.href = response.data.redirect_url;
+                                        } else {
+                                            window.location.href = "{{ route('shop.checkout.onepage.success') }}";
+                                        }
+                                    }
+                                })
+                                .catch(error => window.location.href = "{{ route('shop.checkout.cart.index') }}");
+                            },
+
+                            onError: (error) => {
+                                if (! options.authorizationFailed) {
+                                    options.alertBox('@lang('paypal::app.errors.something-went-wrong')');
+                                }
+                            },
+                        };
+
+                        return options;
+                    },
+                },
+            });
+        </script>
+    @endPushOnce
+@endif

+ 16 - 0
packages/Longyi/Pay/AwxKlarna/src/Resources/views/standard-redirect.blade.php

@@ -0,0 +1,16 @@
+<?php $awxKlarnaStandard = app('Longyi\Pay\AwxKlarna\Payment\AwxKlarna') ?>
+
+<body data-gr-c-s-loaded="true" cz-shortcut-listen="true">
+    You will be redirected to the Klarna website in a few seconds.
+
+
+    <form action="{{ $awxKlarnaStandard->getAwxKlarnaUrl() }}" id="klarna_standard_checkout" method="POST">
+        <input value="Click here if you are not redirected within 10 seconds..." type="submit">
+        <input type="hidden" name="_token" value="{{ csrf_token() }}">
+        <input type="hidden" name="orderId" value="{{ $order->id }}">
+    </form>
+
+    <script type="text/javascript">
+        document.getElementById("klarna_standard_checkout").submit();
+    </script>
+</body>