Quellcode durchsuchen

Add Docker and Stripe (#73)

Colette Contreras vor 4 Jahren
Ursprung
Commit
f16c2fb114

+ 28 - 0
.github/workflows/build-image.yml

@@ -0,0 +1,28 @@
+name: Docker Build And Push To Docker Hub
+
+on:
+  push:
+    branches:
+#      - master
+    tags:
+      - v*
+
+jobs:
+  build:
+    name: Build Proxy Panel
+    runs-on: ubuntu-18.04
+    steps:
+      - name: Git Checkout Code
+        uses: actions/checkout@v1
+        id: git_checkout
+
+      - name: Build and push Docker images
+        uses: docker/build-push-action@v1
+        with:
+          push: true
+          username: ${{ secrets.DOCKER_USERNAME }}
+          password: ${{ secrets.DOCKER_PASSWORD }}
+          repository: v2cc/proxypanel
+          tag_with_ref: true
+          tag_with_sha: true
+          tags: latest

+ 32 - 0
Dockerfile

@@ -0,0 +1,32 @@
+FROM v2cc/proxypanel-base
+
+# Give execution rights on the cron job
+RUN mkdir /etc/app_crontab \
+# Copy or create your cron file named crontab into the root directory crontab
+ && chown -R root /etc/app_crontab && chmod -R 0644 /etc/app_crontab
+COPY docker/crontab /etc/app_crontab/crontab
+# Apply cron job
+RUN crontab /etc/app_crontab/crontab
+
+# System V Init scripts
+COPY docker/init.d/caddy /etc/init.d/
+COPY docker/init.d/queue-worker /etc/init.d/
+COPY docker/entrypoint.sh /etc/
+COPY docker/wait-for-it.sh /etc/
+COPY docker/Caddyfile /etc/caddy/
+
+RUN chmod a+x /etc/init.d/* /etc/entrypoint.sh /etc/wait-for-it.sh
+
+COPY --chown=www-data:www-data . /www/wwwroot/proxypanel
+
+WORKDIR /www/wwwroot/proxypanel
+
+RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
+    && php -r "if (hash_file('sha384', 'composer-setup.php') === 'c31c1e292ad7be5f49291169c0ac8f683499edddcfd4e42232982d0fd193004208a58ff6f353fde0012d35fdd72bc394') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
+    && php composer-setup.php \
+    && php -r "unlink('composer-setup.php');" \
+    && php composer.phar global require hirak/prestissimo \
+    && php composer.phar install --no-ansi --no-dev --no-interaction --no-plugins --no-progress --no-scripts --no-suggest --optimize-autoloader \
+    && php artisan vendor:publish -n
+
+CMD ["/etc/entrypoint.sh"]

+ 5 - 0
app/Http/Controllers/Admin/SystemController.php

@@ -135,6 +135,11 @@ class SystemController extends Controller
                         return Response::json(['status' => 'fail', 'message' => '请先设置【PayPal】必要参数']);
                     }
                     break;
+                case 'stripe':
+                    if (! sysConfig('stripe_public_key') || ! sysConfig('stripe_secret_key')) {
+                        return Response::json(['status' => 'fail', 'message' => '请先设置【Stripe】必要参数']);
+                    }
+                    break;
                 default:
                     return Response::json(['status' => 'fail', 'message' => '未知支付渠道']);
             }

+ 140 - 0
app/Http/Controllers/Gateway/Stripe.php

@@ -0,0 +1,140 @@
+<?php
+
+namespace App\Http\Controllers\Gateway;
+
+use App\Models\Payment;
+use Auth;
+use Exception;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Log;
+use Response;
+use Stripe\Checkout\Session;
+use Stripe\Webhook;
+
+class Stripe extends AbstractPayment
+{
+    public function __construct()
+    {
+        \Stripe\Stripe::setApiKey(sysConfig('stripe_secret_key'));
+    }
+
+    public function purchase($request): JsonResponse
+    {
+        $payment = $this->creatNewPayment(Auth::id(), $request->input('id'), $request->input('amount'));
+
+        $data = $this->getCheckoutSessionData($payment->trade_no, $payment->amount);
+
+        try {
+            $session = Session::create($data);
+
+            $url = route('stripe-checkout', ['session_id' => $session->id]);
+            $payment->update(['url' => $url]);
+
+            return Response::json(['status' => 'success', 'url' => $url, 'message' => '创建订单成功!']);
+        } catch (Exception $e) {
+            Log::error('【Stripe】错误: '.$e->getMessage());
+            exit;
+        }
+    }
+
+    protected function getCheckoutSessionData(string $tradeNo, int $amount): array
+    {
+        $unitAmount = $amount * 100;
+
+        return [
+            'payment_method_types' => ['card'],
+            'line_items' => [[
+                'price_data' => [
+                    'currency' => 'usd',
+                    'product_data' => [
+                        'name' => sysConfig('subject_name') ?: sysConfig('website_name'),
+                    ],
+                    'unit_amount' => $unitAmount,
+                ],
+                'quantity' => 1,
+            ]],
+            'mode' => 'payment',
+            'success_url'          => sysConfig('website_url').'/invoices',
+            'cancel_url'          => sysConfig('website_url').'/invoices',
+            'client_reference_id' => $tradeNo,
+            'customer_email' => Auth::getUser()->email,
+        ];
+    }
+
+    // redirect to Stripe Payment url
+    public function redirectPage($session_id, request $request)
+    {
+        return view('user.stripe-checkout', ['session_id' => $session_id]);
+    }
+
+    // url = '/callback/notify?method=stripe'
+    public function notify($request): void
+    {
+        $sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'];
+        $endpointSecret = sysConfig('stripe_signing_secret');
+        $event = null;
+        $payload = @file_get_contents('php://input');
+
+        try {
+            $event = Webhook::constructEvent($payload, $sigHeader, $endpointSecret);
+        } catch (\UnexpectedValueException $e) {
+            // Invalid payload
+            http_response_code(400);
+            exit();
+        } catch (\Stripe\Exception\SignatureVerificationException $e) {
+            // Invalid signature
+            http_response_code(400);
+            exit();
+        }
+
+        Log::info('Passed signature verification!');
+        switch ($event->type) {
+            case 'checkout.session.completed':
+
+                /* @var $session Session */
+                $session = $event->data->object;
+
+                // Check if the order is paid (e.g., from a card payment)
+                //
+                // A delayed notification payment will have an `unpaid` status, as
+                // you're still waiting for funds to be transferred from the customer's
+                // account.
+                if ($session->payment_status == 'paid') {
+                    // Fulfill the purchase
+                    $this->fulfillOrder($session);
+                }
+                break;
+            case 'checkout.session.async_payment_succeeded':
+                $session = $event->data->object;
+                // Fulfill the purchase
+                $this->fulfillOrder($session);
+                break;
+            case 'checkout.session.async_payment_failed':
+                $session = $event->data->object;
+                // Send an email to the customer asking them to retry their order
+                $this->failedPayment($session);
+                break;
+        }
+
+        http_response_code(200);
+        exit();
+    }
+
+    public function fulfillOrder(Session $session)
+    {
+        $payment = Payment::whereTradeNo($session->client_reference_id)->first();
+        if ($payment) {
+            $payment->order->update(['status' => 2]);
+        }
+    }
+
+    // 未支付成功则关闭订单
+    public function failedPayment(Session $session)
+    {
+        $payment = Payment::whereTradeNo($session->client_reference_id)->first();
+        if ($payment) {
+            $payment->order->update(['status' => -1]);
+        }
+    }
+}

+ 3 - 0
app/Http/Controllers/PaymentController.php

@@ -10,6 +10,7 @@ use App\Http\Controllers\Gateway\F2Fpay;
 use App\Http\Controllers\Gateway\Local;
 use App\Http\Controllers\Gateway\PayJs;
 use App\Http\Controllers\Gateway\PayPal;
+use App\Http\Controllers\Gateway\Stripe;
 use App\Models\Coupon;
 use App\Models\Goods;
 use App\Models\Order;
@@ -57,6 +58,8 @@ class PaymentController extends Controller
                 return new PayPal();
             case 'epay':
                 return new EPay();
+            case 'stripe':
+                return new Stripe();
             default:
                 Log::error('未知支付:'.self::$method);
 

+ 9 - 0
app/Models/Order.php

@@ -145,6 +145,9 @@ class Order extends Model
             case 5:
                 $pay_type_label = 'PayPal';
                 break;
+            case 6:
+                $pay_type_label = 'Stripe';
+                break;
             default:
                 $pay_type_label = '';
         }
@@ -170,6 +173,9 @@ class Order extends Model
             case 5:
                 $pay_type_icon = $base_path.'paypal.png';
                 break;
+            case 6:
+                $pay_type_icon = $base_path.'stripe.png';
+                break;
             case 0:
             case 4:
             default:
@@ -204,6 +210,9 @@ class Order extends Model
             case 'paypal':
                 $pay_way_label = 'PayPal';
                 break;
+            case 'stripe':
+                $pay_way_label = 'Stripe';
+                break;
             default:
                 $pay_way_label = '未知';
         }

+ 1 - 0
composer.json

@@ -30,6 +30,7 @@
     "riverslei/payment": "^5.1",
     "spatie/laravel-permission": "^3.16",
     "srmklive/paypal": "^1.7",
+    "stripe/stripe-php": "^7.61",
     "xhat/payjs": "^1.4",
     "zbrettonye/geetest": "^1.2",
     "zbrettonye/hcaptcha": "^1.0",

+ 58 - 1
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "ee9797167cada5b52c4069a15fd4abaa",
+    "content-hash": "04ef064a952d8635e2fbfa09ff022d67",
     "packages": [
         {
             "name": "asm89/stack-cors",
@@ -3744,6 +3744,63 @@
             ],
             "time": "2020-09-03T07:50:08+00:00"
         },
+        {
+            "name": "stripe/stripe-php",
+            "version": "v7.61.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/stripe/stripe-php.git",
+                "reference": "51c6cd18cb51740101c940a3fefc876ef7cd8cae"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/stripe/stripe-php/zipball/51c6cd18cb51740101c940a3fefc876ef7cd8cae",
+                "reference": "51c6cd18cb51740101c940a3fefc876ef7cd8cae",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*",
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "php": ">=5.6.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "2.16.1",
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5.7",
+                "squizlabs/php_codesniffer": "^3.3",
+                "symfony/process": "~3.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Stripe\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Stripe and contributors",
+                    "homepage": "https://github.com/stripe/stripe-php/contributors"
+                }
+            ],
+            "description": "Stripe PHP Library",
+            "homepage": "https://stripe.com/",
+            "keywords": [
+                "api",
+                "payment processing",
+                "stripe"
+            ],
+            "time": "2020-10-20T20:01:45+00:00"
+        },
         {
             "name": "swiftmailer/swiftmailer",
             "version": "v6.2.3",

+ 35 - 0
database/migrations/2020_09_24_184434_add_strip_config.php

@@ -0,0 +1,35 @@
+<?php
+
+use App\Models\Config;
+use Illuminate\Database\Migrations\Migration;
+
+class AddStripConfig extends Migration
+{
+    protected $configs = [
+        'stripe_public_key',
+        'stripe_secret_key',
+        'stripe_signing_secret',
+    ];
+
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        foreach ($this->configs as $config) {
+            Config::insert(['name' => $config]);
+        }
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Config::destroy($this->configs);
+    }
+}

+ 7 - 0
docker/Caddyfile

@@ -0,0 +1,7 @@
+{$SITE_ADDRESS}
+
+root * /www/wwwroot/proxypanel/public
+
+php_fastcgi 127.0.0.1:9000
+
+file_server

+ 1 - 0
docker/crontab

@@ -0,0 +1 @@
+* * * * * /usr/bin/sudo -u "www-data" /usr/local/bin/php /www/wwwroot/proxypanel/artisan schedule:run > /proc/1/fd/1 2>&1

+ 20 - 0
docker/entrypoint.sh

@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+# turn on bash's job control
+set -m
+
+# export env from .env
+export "$(grep DB_HOST .env)" \
+&& export "$(grep DB_PORT .env)" \
+&& /etc/wait-for-it.sh $DB_HOST:$DB_PORT -t 45
+
+chmod -R 777 storage
+sudo -u "www-data" mkdir -p storage/framework/{cache,sessions,testing,views}
+
+service queue-worker start
+service caddy start
+service cron start
+
+/usr/local/bin/php artisan --force migrate
+
+php-fpm

+ 102 - 0
docker/init.d/caddy

@@ -0,0 +1,102 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:
+# Required-Start:    $remote_fs $syslog
+# Required-Stop:     $remote_fs $syslog
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Start daemon at boot time
+# Description:       Enable service provided by daemon.
+### END INIT INFO
+
+dir="/"
+cmd="/usr/bin/caddy run --envfile /www/wwwroot/proxypanel/.env --config /etc/caddy/Caddyfile"
+user="root"
+
+name=`basename $0`
+
+pid_file="/var/run/$name.pid"
+#stdout_log="/var/log/$name.log"
+#stderr_log="/var/log/$name.err"
+stdout_log="/proc/self/fd/1"
+stderr_log="/proc/self/fd/2"
+
+get_pid() {
+    cat "$pid_file"
+}
+
+is_running() {
+    [ -f "$pid_file" ] && ps -p `get_pid` > /dev/null 2>&1
+}
+
+case "$1" in
+    start)
+    if is_running; then
+        echo "Already started"
+    else
+        echo "Starting $name"
+        cd "$dir"
+        if [ -z "$user" ]; then
+            sudo $cmd >> "$stdout_log" 2>> "$stderr_log" &
+        else
+            sudo -u "$user" $cmd >> "$stdout_log" 2>> "$stderr_log" &
+        fi
+        echo $! > "$pid_file"
+        if ! is_running; then
+            echo "Unable to start, see $stdout_log and $stderr_log"
+            exit 1
+        fi
+    fi
+    ;;
+    stop)
+    if is_running; then
+        echo -n "Stopping $name.."
+        kill `get_pid`
+        for i in 1 2 3 4 5 6 7 8 9 10
+        # for i in `seq 10`
+        do
+            if ! is_running; then
+                break
+            fi
+
+            echo -n "."
+            sleep 1
+        done
+        echo
+
+        if is_running; then
+            echo "Not stopped; may still be shutting down or shutdown may have failed"
+            exit 1
+        else
+            echo "Stopped"
+            if [ -f "$pid_file" ]; then
+                rm "$pid_file"
+            fi
+        fi
+    else
+        echo "Not running"
+    fi
+    ;;
+    restart)
+    $0 stop
+    if is_running; then
+        echo "Unable to stop, will not attempt to start"
+        exit 1
+    fi
+    $0 start
+    ;;
+    status)
+    if is_running; then
+        echo "Running"
+    else
+        echo "Stopped"
+        exit 1
+    fi
+    ;;
+    *)
+    echo "Usage: $0 {start|stop|restart|status}"
+    exit 1
+    ;;
+esac
+
+exit 0

+ 101 - 0
docker/init.d/queue-worker

@@ -0,0 +1,101 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:
+# Required-Start:    $remote_fs $syslog
+# Required-Stop:     $remote_fs $syslog
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Start daemon at boot time
+# Description:       Enable service provided by daemon.
+### END INIT INFO
+
+dir="/www/wwwroot/proxypanel"
+cmd="php artisan queue:work redis --daemon --queue=default --timeout=60 --tries=3 -vvv"
+user="www-data"
+
+name=`basename $0`
+pid_file="/var/run/$name.pid"
+#stdout_log="/var/log/$name.log"
+#stderr_log="/var/log/$name.err"
+stdout_log="/proc/self/fd/1"
+stderr_log="/proc/self/fd/2"
+
+get_pid() {
+    cat "$pid_file"
+}
+
+is_running() {
+    [ -f "$pid_file" ] && ps -p `get_pid` > /dev/null 2>&1
+}
+
+case "$1" in
+    start)
+    if is_running; then
+        echo "Already started"
+    else
+        echo "Starting $name"
+        cd "$dir"
+        if [ -z "$user" ]; then
+            sudo $cmd >> "$stdout_log" 2>> "$stderr_log" &
+        else
+            sudo -u "$user" $cmd >> "$stdout_log" 2>> "$stderr_log" &
+        fi
+        echo $! > "$pid_file"
+        if ! is_running; then
+            echo "Unable to start, see $stdout_log and $stderr_log"
+            exit 1
+        fi
+    fi
+    ;;
+    stop)
+    if is_running; then
+        echo -n "Stopping $name.."
+        kill `get_pid`
+        for i in 1 2 3 4 5 6 7 8 9 10
+        # for i in `seq 10`
+        do
+            if ! is_running; then
+                break
+            fi
+
+            echo -n "."
+            sleep 1
+        done
+        echo
+
+        if is_running; then
+            echo "Not stopped; may still be shutting down or shutdown may have failed"
+            exit 1
+        else
+            echo "Stopped"
+            if [ -f "$pid_file" ]; then
+                rm "$pid_file"
+            fi
+        fi
+    else
+        echo "Not running"
+    fi
+    ;;
+    restart)
+    $0 stop
+    if is_running; then
+        echo "Unable to stop, will not attempt to start"
+        exit 1
+    fi
+    $0 start
+    ;;
+    status)
+    if is_running; then
+        echo "Running"
+    else
+        echo "Stopped"
+        exit 1
+    fi
+    ;;
+    *)
+    echo "Usage: $0 {start|stop|restart|status}"
+    exit 1
+    ;;
+esac
+
+exit 0

+ 183 - 0
docker/wait-for-it.sh

@@ -0,0 +1,183 @@
+#!/usr/bin/env bash
+# Use this script to test if a given TCP host/port are available
+# source: https://github.com/vishnubob/wait-for-it/blob/master/wait-for-it.sh
+
+WAITFORIT_cmdname=${0##*/}
+
+echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
+
+usage()
+{
+    cat << USAGE >&2
+Usage:
+    $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
+    -h HOST | --host=HOST       Host or IP under test
+    -p PORT | --port=PORT       TCP port under test
+                                Alternatively, you specify the host and port as host:port
+    -s | --strict               Only execute subcommand if the test succeeds
+    -q | --quiet                Don't output any status messages
+    -t TIMEOUT | --timeout=TIMEOUT
+                                Timeout in seconds, zero for no timeout
+    -- COMMAND ARGS             Execute command with args after the test finishes
+USAGE
+    exit 1
+}
+
+wait_for()
+{
+    if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
+        echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
+    else
+        echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
+    fi
+    WAITFORIT_start_ts=$(date +%s)
+    while :
+    do
+        if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
+            nc -z $WAITFORIT_HOST $WAITFORIT_PORT
+            WAITFORIT_result=$?
+        else
+            (echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
+            WAITFORIT_result=$?
+        fi
+        if [[ $WAITFORIT_result -eq 0 ]]; then
+            WAITFORIT_end_ts=$(date +%s)
+            echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
+            break
+        fi
+        sleep 1
+    done
+    return $WAITFORIT_result
+}
+
+wait_for_wrapper()
+{
+    # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
+    if [[ $WAITFORIT_QUIET -eq 1 ]]; then
+        timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
+    else
+        timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
+    fi
+    WAITFORIT_PID=$!
+    trap "kill -INT -$WAITFORIT_PID" INT
+    wait $WAITFORIT_PID
+    WAITFORIT_RESULT=$?
+    if [[ $WAITFORIT_RESULT -ne 0 ]]; then
+        echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
+    fi
+    return $WAITFORIT_RESULT
+}
+
+# process arguments
+while [[ $# -gt 0 ]]
+do
+    case "$1" in
+        *:* )
+        WAITFORIT_hostport=(${1//:/ })
+        WAITFORIT_HOST=${WAITFORIT_hostport[0]}
+        WAITFORIT_PORT=${WAITFORIT_hostport[1]}
+        shift 1
+        ;;
+        --child)
+        WAITFORIT_CHILD=1
+        shift 1
+        ;;
+        -q | --quiet)
+        WAITFORIT_QUIET=1
+        shift 1
+        ;;
+        -s | --strict)
+        WAITFORIT_STRICT=1
+        shift 1
+        ;;
+        -h)
+        WAITFORIT_HOST="$2"
+        if [[ $WAITFORIT_HOST == "" ]]; then break; fi
+        shift 2
+        ;;
+        --host=*)
+        WAITFORIT_HOST="${1#*=}"
+        shift 1
+        ;;
+        -p)
+        WAITFORIT_PORT="$2"
+        if [[ $WAITFORIT_PORT == "" ]]; then break; fi
+        shift 2
+        ;;
+        --port=*)
+        WAITFORIT_PORT="${1#*=}"
+        shift 1
+        ;;
+        -t)
+        WAITFORIT_TIMEOUT="$2"
+        if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
+        shift 2
+        ;;
+        --timeout=*)
+        WAITFORIT_TIMEOUT="${1#*=}"
+        shift 1
+        ;;
+        --)
+        shift
+        WAITFORIT_CLI=("$@")
+        break
+        ;;
+        --help)
+        usage
+        ;;
+        *)
+        echoerr "Unknown argument: $1"
+        usage
+        ;;
+    esac
+done
+
+if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
+    echoerr "Error: you need to provide a host and port to test."
+    usage
+fi
+
+WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
+WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
+WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
+WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
+
+# Check to see if timeout is from busybox?
+WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
+WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
+
+WAITFORIT_BUSYTIMEFLAG=""
+if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
+    WAITFORIT_ISBUSY=1
+    # Check if busybox timeout uses -t flag
+    # (recent Alpine versions don't support -t anymore)
+    if timeout &>/dev/stdout | grep -q -e '-t '; then
+        WAITFORIT_BUSYTIMEFLAG="-t"
+    fi
+else
+    WAITFORIT_ISBUSY=0
+fi
+
+if [[ $WAITFORIT_CHILD -gt 0 ]]; then
+    wait_for
+    WAITFORIT_RESULT=$?
+    exit $WAITFORIT_RESULT
+else
+    if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
+        wait_for_wrapper
+        WAITFORIT_RESULT=$?
+    else
+        wait_for
+        WAITFORIT_RESULT=$?
+    fi
+fi
+
+if [[ $WAITFORIT_CLI != "" ]]; then
+    if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
+        echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
+        exit $WAITFORIT_RESULT
+    fi
+    exec "${WAITFORIT_CLI[@]}"
+else
+    exit $WAITFORIT_RESULT
+fi

+ 60 - 0
resources/views/admin/config/system.blade.php

@@ -1210,6 +1210,7 @@
                                                 <option value="">关闭</option>
                                                 <option value="bitpayx">麻瓜宝</option>
                                                 <option value="paypal">PayPal</option>
+                                                <option value="stripe">Stripe</option>
                                             </select>
                                         </div>
                                     </div>
@@ -1518,6 +1519,65 @@
                                             </div>
                                         </div>
                                     </div>
+                                    <hr />
+                                    <!-- Stripe begin -->
+                                    <div class="row pb-70">
+                                        <div class="form-group col-lg-2">
+                                            <label class="col-form-label">Stripe</label>
+                                        </div>
+                                        <div class="form-group col-lg-10">
+                                            <div class="row">
+                                                <label class="col-md-3 col-form-label" for="stripe_public_key">Public Key</label>
+                                                <div class="col-md-9">
+                                                    <div class="input-group">
+                                                        <input type="text" class="form-control" id="stripe_public_key" value="{{$stripe_public_key}}"/>
+                                                        <span class="input-group-append">
+                                                            <button class="btn btn-primary" type="button" onclick="update('stripe_public_key')">
+                                                                修改
+                                                            </button>
+                                                        </span>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="form-group col-lg-2">
+                                        </div>
+                                        <div class="form-group col-lg-10">
+                                            <div class="row">
+                                                <label class="col-md-3 col-form-label" for="stripe_secret_key">Secret Key</label>
+                                                <div class="col-md-9">
+                                                    <div class="input-group">
+                                                        <input type="text" class="form-control" id="stripe_secret_key" value="{{$stripe_secret_key}}"/>
+                                                        <span class="input-group-append">
+                                                            <button class="btn btn-primary" type="button" onclick="update('stripe_secret_key')">
+                                                                修改
+                                                            </button>
+                                                        </span>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="form-group col-lg-2">
+                                        </div>
+                                        <div class="form-group col-lg-10">
+                                            <div class="row">
+                                                <label class="col-md-3 col-form-label" for="stripe_secret_key">WebHook Signing secret</label>
+                                                <div class="col-md-9">
+                                                    <div class="input-group">
+                                                        <input type="text" class="form-control" id="stripe_signing_secret" value="{{$stripe_signing_secret}}"/>
+                                                        <span class="input-group-append">
+                                                            <button class="btn btn-primary" type="button" onclick="update('stripe_signing_secret')">
+                                                                修改
+                                                            </button>
+                                                        </span>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <!-- Stripe end -->
+                                    <hr />
+
                                     <div class="form-group col-lg-6" hidden>
                                         <div class="row">
                                             <label class="col-md-3 col-form-label" for="paypal_app_id">应用ID</label>

+ 12 - 0
resources/views/user/components/purchase.blade.php

@@ -22,4 +22,16 @@
     <button class="btn btn-flat" onclick="pay('{{sysConfig('is_otherPay')}}','5')">
         <img src="https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-150px.png" height="32px" alt="PayPal"/>
     </button>'
+@elseif(sysConfig('is_otherPay') == 'stripe')
+    <style>
+        .stripeLogo {
+            width: 70px;
+            height: 28px;
+            display: inline-block;
+            font-size: 18px;
+        }
+    </style>
+    <button class="btn btn-outline-{{config('theme.bg_color')}} mt-2" onclick="pay('{{sysConfig('is_otherPay')}}','6','{{ $goods->type }}','{{ $goods->id }}')">
+        <div class="stripeLogo">Stripe</div>
+    </button>
 @endif

+ 19 - 0
resources/views/user/stripe-checkout.blade.php

@@ -0,0 +1,19 @@
+@extends('user.layouts')
+
+@section('css')
+@endsection
+
+@section('content')
+    <div>
+        <p style="text-align: center">Redirecting to Stripe Checkout ...</p>
+    </div>
+@endsection
+
+@section('script')
+    <script src="https://js.stripe.com/v3/"></script>
+    <script>
+        let stripe = Stripe('{{ sysConfig('stripe_public_key') }}');
+        let redirectData = stripe.redirectToCheckout({ sessionId: '{{ $session_id  }}' });
+        console.log(redirectData);
+    </script>
+@endsection

+ 4 - 2
routes/web.php

@@ -1,11 +1,11 @@
 <?php
 
-if (env('APP_KEY')) {
+if (env('APP_KEY') && \Illuminate\Support\Facades\Schema::hasTable('config')) {
     Route::domain(sysConfig('subscribe_domain') ?: sysConfig('website_url'))
         ->get('s/{code}', 'User\SubscribeController@getSubscribeByCode')->name('sub'); // 节点订阅地址
 
     Route::domain(sysConfig('website_callback_url') ?: sysConfig('website_url'))
-        ->get('callback/notify', 'PaymentController@notify')->name('payment.notify'); //支付回调
+        ->any('callback/notify', 'PaymentController@notify')->name('payment.notify'); //支付回调
 }
 
 Route::get('callback/checkout', 'Gateway\PayPal@getCheckout')->name('paypal.checkout'); // 支付回调相关
@@ -63,6 +63,8 @@ Route::middleware(['isForbidden', 'isMaintenance', 'isLogin'])->group(function (
         Route::get('getStatus', 'PaymentController@getStatus')->name('orderStatus'); // 获取支付单状态
         Route::get('{trade_no}', 'PaymentController@detail')->name('orderDetail'); // 支付单详情
     });
+
+    Route::get('/stripe-checkout/{session_id}', 'Gateway\Stripe@redirectPage')->name('stripe-checkout'); // Stripe Checkout page
 });
 
 // 管理相关