Quellcode durchsuchen

add dujiaoka for v2.0.3

Stille vor 3 Jahren
Ursprung
Commit
d8aa26ea8a

+ 31 - 0
dujiaoka/Dockerfile

@@ -0,0 +1,31 @@
+# FROM webdevops/php-nginx:7.4-alpine # ⬅️ X86_64  ⬇️ ARM64
+FROM ioiox/php-nginx:7.4-alpine-arm64
+LABEL maintainer="[email protected]"
+
+ENV INSTALL=true
+ENV MODIFY=false
+ENV VERSION=2.0.3
+
+WORKDIR /
+
+RUN wget https://github.com/assimon/dujiaoka/releases/download/${VERSION}/${VERSION}-Antibody.tar.gz \
+  && tar zxvf ${VERSION}-Antibody.tar.gz \
+  && rm -rf ${VERSION}-Antibody.tar.gz && mv dujiaoka_build dujiaoka
+
+COPY ./conf/default.conf /opt/docker/etc/nginx/vhost.conf
+COPY ./conf/dujiao.conf /opt/docker/etc/supervisor.d/
+COPY ./modify /dujiaoka/modify
+COPY start.sh /
+
+WORKDIR /dujiaoka
+
+RUN set -xe \
+    && composer install -vvv \
+    && chmod +x /start.sh \
+    && chown -R application:application /dujiaoka/ \
+    && chmod -R 0755 /dujiaoka/ \
+    && mv /dujiaoka/storage /dujiaoka/storage_bak \
+    && sed -i "s?\$proxies;?\$proxies=\'\*\*\';?" /dujiaoka/app/Http/Middleware/TrustProxies.php \
+    && rm -rf /root/.composer/cache/ /tmp/*
+
+CMD /start.sh

+ 8 - 0
dujiaoka/README.md

@@ -0,0 +1,8 @@
+# dujiaoka
+
+## 简介
+待优化中...
+
+## 链接
+- [Apocalypsor/dujiaoka-docker](https://github.com/Apocalypsor/dujiaoka-docker)
+- [如何优雅地搭建自己的发卡站](https://blog.dov.moe/posts/49102/)

+ 30 - 0
dujiaoka/conf/default.conf

@@ -0,0 +1,30 @@
+server {
+    listen 80;
+    server_name _;
+    root /dujiaoka/public;
+
+    add_header X-Frame-Options "SAMEORIGIN";
+    add_header X-XSS-Protection "1; mode=block";
+    add_header X-Content-Type-Options "nosniff";
+
+    index index.html index.htm index.php;
+
+    charset utf-8;
+
+    location / {
+        try_files $uri $uri/ /index.php$is_args$query_string;  
+    }
+
+    location = /favicon.ico { access_log off; log_not_found off; }
+    location = /robots.txt  { access_log off; log_not_found off; }
+
+    error_page 404 /index.php;
+
+    location ~ \.php$ {
+        fastcgi_split_path_info ^(.+\.php)(/.+)$;
+        fastcgi_pass 127.0.0.1:9000;
+        fastcgi_index index.php;
+        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
+        include fastcgi_params;
+    }
+}

+ 12 - 0
dujiaoka/conf/dujiao.conf

@@ -0,0 +1,12 @@
+[program:dujiaoka-worker]
+process_name=%(program_name)s_%(process_num)02d
+command=php /dujiaoka/artisan queue:work --tries=3
+directory=/dujiaoka
+autostart=true
+autorestart=true
+startsecs=3
+startretries=3
+user=root
+priority=999
+numprocs=1
+stdout_logfile=/tmp/dujiao.log

+ 467 - 0
dujiaoka/modify/StripeController.php

@@ -0,0 +1,467 @@
+<?php
+
+namespace App\Http\Controllers\Pay;
+
+use App\Exceptions\RuleValidationException;
+use App\Http\Controllers\PayController;
+use Illuminate\Http\Request;
+use GuzzleHttp\Client;
+use Illuminate\Support\Facades\Redis;
+use URL;
+
+class StripeController extends PayController
+{
+
+    public function gateway(string $payway, string $orderSN)
+    {
+
+
+        // 加载网关
+        $this->loadGateWay($orderSN, $payway);
+        //构造要请求的参数数组,无需改动
+        switch ($payway) {
+            case 'wx':
+            case 'alipay':
+            default:
+                try {
+                    \Stripe\Stripe::setApiKey($this->payGateway->merchant_id);
+                    $amount = bcmul($this->order->actual_price, 100, 2);
+                    $price = $this->order->actual_price;
+                    $gbp = bcmul($this->getGbpCurrency($this->order->actual_price), 100, 2);
+                    $orderid = $this->order->order_sn;
+                    $pk = $this->payGateway->merchant_id;
+                    $return_url = site_url() . $this->payGateway->pay_handleroute . '/return_url/?orderid=' . $this->order->order_sn;
+                    $html = "<html class=\"js cssanimations\">
+<head lang=\"en\">
+    <meta charset=\"UTF-8\">
+    <title>收银台</title>
+    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">
+    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
+    <meta name=\"format-detection\" content=\"telephone=no\">
+    <meta name=\"renderer\" content=\"webkit\">
+    <meta http-equiv=\"Cache-Control\" content=\"no-siteapp\">
+    <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/[email protected]/dist/css/amazeui.min.css\">
+    <script src=\"https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js\"></script>
+    <script src=\"https://cdn.jsdelivr.net/npm/[email protected]/jquery.qrcode.min.js\"></script>
+    <script src=\"https://cdn.jsdelivr.net/npm/[email protected]/dist/js/amazeui.min.js\"></script>
+    <script src=\"https://js.stripe.com/v3/\"></script>
+    <style>
+        @media only screen and (min-width: 641px) {
+            .am-offcanvas {
+                display: block;
+                position: static;
+                background: none;
+            }
+            .am-offcanvas-bar {
+                position: static;
+                width: auto;
+                background: none;
+                -webkit-transform: translate3d(0, 0, 0);
+                -ms-transform: translate3d(0, 0, 0);
+                transform: translate3d(0, 0, 0);
+            }
+            .am-offcanvas-bar:after {
+                content: none;
+            }
+        }
+        @media only screen and (max-width: 640px) {
+            .am-offcanvas-bar .am-nav > li > a {
+                color: #ccc;
+                border-radius: 0;
+                border-top: 1px solid rgba(0, 0, 0, .3);
+                box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05)
+            }
+            .am-offcanvas-bar .am-nav > li > a:hover {
+                background: #404040;
+                color: #fff
+            }
+            .am-offcanvas-bar .am-nav > li.am-nav-header {
+                color: #777;
+                background: #404040;
+                box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
+                text-shadow: 0 1px 0 rgba(0, 0, 0, .5);
+                border-top: 1px solid rgba(0, 0, 0, .3);
+                font-weight: 400;
+                font-size: 75%
+            }
+            .am-offcanvas-bar .am-nav > li.am-active > a {
+                background: #1a1a1a;
+                color: #fff;
+                box-shadow: inset 0 1px 3px rgba(0, 0, 0, .3)
+            }
+            .am-offcanvas-bar .am-nav > li + li {
+                margin-top: 0;
+            }
+        }
+        .my-head {
+            margin-top: 40px;
+            text-align: center;
+        }
+        .am-tab-panel {
+            text-align: center;
+            margin-top: 50px;
+            margin-bottom: 50px;
+        }
+        .my-footer {
+            border-top: 1px solid #eeeeee;
+            padding: 10px 0;
+            margin-top: 10px;
+            text-align: center;
+        }
+        .panel-title {
+            display: inline;
+            font-weight: bold;
+        }
+        .display-table {
+            display: table;
+        }
+        .display-tr {
+            display: table-row;
+        }
+        .display-td {
+            display: table-cell;
+            vertical-align: middle;
+            width: 61%;
+        }
+        .StripeElement {
+            box-sizing: border-box;
+            height: 40px;
+            padding: 10px 12px;
+            border: 1px solid transparent;
+            border-radius: 4px;
+            background-color: white;
+            box-shadow: 0 1px 3px 0 #e6ebf1;
+            -webkit-transition: box-shadow 150ms ease;
+            transition: box-shadow 150ms ease;
+        }
+        .StripeElement--focus {
+            box-shadow: 0 1px 3px 0 #cfd7df;
+        }
+        .StripeElement--invalid {
+            border-color: #fa755a;
+        }
+        .StripeElement--webkit-autofill {
+            background-color: #fefde5 !important;
+        }
+        .form-row {
+            width: 70%;
+            float: left;
+        }
+        .wrapper {
+            width: 670px;
+            margin: 0 auto;
+        }
+        label {
+            font-weight: 500;
+            font-size: 14px;
+            display: block;
+            margin-bottom: 8px;
+        }
+        .button {
+            border: none;
+            border-radius: 4px;
+            outline: none;
+            text-decoration: none;
+            color: #fff;
+            background: #32325d;
+            white-space: nowrap;
+            display: inline-block;
+            height: 40px;
+            line-height: 40px;
+            padding: 0 14px;
+            box-shadow: 0 4px 6px rgba(50, 50, 93, .11), 0 1px 3px rgba(0, 0, 0, .08);
+            border-radius: 4px;
+            font-size: 16px;
+            font-weight: 600;
+            letter-spacing: 0.025em;
+            text-decoration: none;
+            -webkit-transition: all 150ms ease;
+            transition: all 150ms ease;
+            margin-top: 10px;
+        }
+    </style>
+</head>
+<body>
+<header class=\"am-g my-head\">
+    <div class=\"am-u-sm-12 am-article\">
+        <h1 class=\"am-article-title\">收银台</h1>
+    </div>
+</header>
+<hr class=\"am-article-divider\">
+<div class=\"am-container\">
+    <h2>付款信息
+        <div class=\"am-topbar-right\">¥{$price}</div>
+    </h2>
+    <p><small>订单编号:$orderid</small></p>
+    <div class=\"am-tabs\" data-am-tabs=\"\">
+        <ul class=\"am-tabs-nav am-nav am-nav-tabs\">
+            <li class=\"am-active\"><a href=\"#alipay\">Alipay 支付宝</a></li>
+            <li class=\"request-card-pay\"><a href=\"#cardpay\">银行卡支付</a></li>
+        </ul>
+        <div class=\"am-tabs-bd am-tabs-bd-ofv\"
+             style=\"touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\">
+            <div class=\"am-tab-panel am-active\" id=\"alipay\">
+                <a class=\"am-btn am-btn-lg am-btn-warning am-btn-primary\" id=\"alipaybtn\" href=\"#\">进入支付宝付款</a>
+                <p></p>
+            </div>
+            <div class=\"am-tab-panel am-fade\" id=\"cardpay\">
+                <div class=\"text-align:center; margin:0 auto; width:60%\">
+                <div class=\"wrapper cardpay_content\" style=\"max-width:500px\">
+                <div class=\"am-alert am-alert-danger\" style=\"display:none\">支付失败,请更换卡片或检查输入信息</div>
+                    <form action=\"/pay/stripe/charge\" method=\"post\" id=\"payment-form\">
+                        <div class=\"form-row\">
+                            <label for=\"card-element\">
+                                <p class='am-alert am-alert-secondary'>借记卡或信用卡</p>
+                            </label>
+                            <div id=\"card-element\">
+                                <!-- A Stripe Element will be inserted here. -->
+                            </div>
+                            <!-- Used to display form errors. -->
+                            <div id=\"card-errors\" role=\"alert\"></div>
+                        </div>
+                            <div class=\"form-row\">
+                            <button class=\"button\">支付</button>
+                        </div>
+                    </form>
+                </div>
+                 </div>
+            </div>
+        </div>
+    </div>
+</div>
+</div>
+<script>
+    var stripe = Stripe('$pk');
+    var source = '';
+    // Create a Stripe client.
+    // Create an instance of Elements.
+    var elements = stripe.elements();
+    // Custom styling can be passed to options when creating an Element.
+    // (Note that this demo uses a wider set of styles than the guide below.)
+    var style = {
+        base: {
+            color: '#32325d',
+            fontFamily: '\"Helvetica Neue\", Helvetica, sans-serif',
+            fontSmoothing: 'antialiased',
+            fontSize: '16px',
+            '::placeholder': {
+                color: '#aab7c4'
+            }
+        },
+        invalid: {
+            color: '#fa755a',
+            iconColor: '#fa755a'
+        }
+    };
+    // Create an instance of the card Element.
+    var card = elements.create('card', {style: style});
+    // Add an instance of the card Element into the `card-element` <div>.
+    card.mount('#card-element');
+    // Handle real-time validation errors from the card Element.
+    card.on('change', function (event) {
+        var displayError = document.getElementById('card-errors');
+        if (event.error) {
+            displayError.textContent = event.error.message;
+        } else {
+            displayError.textContent = '';
+        }
+    });
+    // Handle form submission.
+    var form = document.getElementById('payment-form');
+    form.addEventListener('submit', function (event) {
+        event.preventDefault();
+        $(\".button\").attr(\"disabled\",\"true\");
+        $(\".button\").html(\"请稍后\");
+        stripe.createToken(card).then(function (result) {
+            if (result.error) {
+                // Inform the user if there was an error.
+                var errorElement = document.getElementById('card-errors');
+                errorElement.textContent = result.error.message;
+            } else {
+                // Send the token to your server.
+                stripeTokenHandler(result.token);
+            }
+        });
+    });
+    // Submit the form with the token ID.
+    function stripeTokenHandler(token) {
+        // Insert the token ID into the form so it gets submitted to the server
+        var form = document.getElementById('payment-form');
+        var hiddenInput = document.createElement('input');
+        var hiddenInput1 = document.createElement('input');
+        hiddenInput.setAttribute('type', 'hidden');
+        hiddenInput.setAttribute('name', 'stripeToken');
+        hiddenInput.setAttribute('value', token.id);
+        hiddenInput1.setAttribute('type', 'hidden');
+        hiddenInput1.setAttribute('name', 'orderid');
+        hiddenInput1.setAttribute('value', '$orderid');
+        form.appendChild(hiddenInput);
+        form.appendChild(hiddenInput1);
+        // Submit the form
+        //form.submit();
+        $.ajax({
+            url: '/pay/stripe/charge/?orderid=$orderid&stripeToken=' + token.id,
+            type: 'GET',
+            success: function (result) {
+                if (result == \"success\") {
+                    $(\".cardpay_content\").html(\"\");
+                    $(\".cardpay_content\").html(\"<p class='am-alert am-alert-success'>支付成功,正在跳转页面</p>\");
+                    window.setTimeout(function () {
+                        location.href = \"/detail-order-sn/$orderid\"
+                    }, 800);
+                } else {
+                    $(\".am-alert\").show();
+                    $(\".button\").removeAttr(\"disabled\");
+                    $(\".button\").html(\"支付\");
+                    setTimeout(\" $('.am-alert').hide();\", 3000);
+                }
+            }
+        });
+    }
+    (function () {
+        stripe.createSource({
+            type: 'alipay',
+            amount: $gbp,
+            currency: 'gbp',
+            // 这里你需要渲染出一些用户的信息,不然后期没法知道是谁在付钱
+            owner: {
+                name: '$orderid',
+            },
+            redirect: {
+                return_url: '$return_url',
+            },
+        }).then(function (result) {
+            $(\"#alipaybtn\").attr(\"href\", result.source.redirect.url);
+        });
+    })();
+    function paymentcheck() {
+        $.ajax({
+            url: '/pay/stripe/check/?orderid=$orderid&source=' + source,
+            type: 'GET',
+            success: function (result) {
+                if (result == \"success\") {
+                    window.setTimeout(function () {
+                        location.href = \"/detail-order-sn/$orderid\"
+                    }, 800);
+                } else {
+                    setTimeout(\"paymentcheck()\", 1000);
+                }
+            }
+        });
+    };
+</script>
+</body>
+</html>";
+
+                    return $html;
+                } catch (\Exception $e) {
+                    throw new RuleValidationException(__('dujiaoka.prompt.abnormal_payment_channel') . $e->getMessage());
+                }
+                break;
+        }
+    }
+
+    public function returnUrl(Request $request)
+    {
+
+        $data = $request->all();
+        $cacheord = $this->orderService->detailOrderSN($data['orderid']);
+        if (!$cacheord) {
+            return redirect(url('detail-order-sn', ['orderSN' => $data['orderid']]));
+        }
+        $payGateway = $this->payService->detail($cacheord->pay_id);
+        \Stripe\Stripe::setApiKey($payGateway -> merchant_pem);
+        $source_object = \Stripe\Source::retrieve($data['source']);
+        //die($source_object);
+        if ($source_object->status == 'chargeable') {
+            \Stripe\Charge::create([
+                'amount' => $source_object->amount,
+                'currency' => $source_object->currency,
+                'source' => $data['source'],
+            ]);
+            if ($source_object->owner->name == $data['orderid']) {
+                $this->orderProcessService->completedOrder($data['orderid'], $cacheord->actual_price, $source_object->id);
+            }
+        }
+        return redirect(url('detail-order-sn', ['orderSN' => $data['orderid']]));
+    }
+
+    public function check(Request $request)
+    {
+
+        $data = $request->all();
+        $cacheord = $this->orderService->detailOrderSN($data['orderid']);
+        if (!$cacheord) {
+            //可能已异步回调成功,跳转
+            return 'fail';
+        } else {
+            $payGateway = $this->payService->detail($cacheord->pay_id);
+            \Stripe\Stripe::setApiKey($payGateway -> merchant_pem);
+            $source_object = \Stripe\Source::retrieve($data['source']);
+            if ($source_object->status == 'chargeable') {
+                \Stripe\Charge::create([
+                    'amount' => $source_object->amount,
+                    'currency' => $source_object->currency,
+                    'source' => $data['source'],
+                ]);
+            }
+            if ($source_object->status == 'consumed' && $source_object->owner->name == $data['orderid']) {
+                $this->orderProcessService->completedOrder($data['orderid'], $cacheord->actual_price, $source_object->id);
+                return 'success';
+            } else {
+                return 'fail';
+            }
+        }
+
+    }
+
+    public function charge(Request $request)
+    {
+        $data = $request->all();
+        $cacheord = $this->orderService->detailOrderSN($data['orderid']);
+        if (!$cacheord) {
+            //可能已异步回调成功,跳转
+            return 'fail';
+        } else {
+            try {
+                $payGateway = $this->payService->detail($cacheord->pay_id);
+                \Stripe\Stripe::setApiKey($payGateway -> merchant_pem);
+                $result = \Stripe\Charge::create([
+                    'amount' => bcmul($this->getGbpCurrency($cacheord->actual_price), 100,0),
+                    'currency' => 'gbp',
+                    'source' => $data['stripeToken'],
+                ]);
+                if ($result->status == 'succeeded') {
+                    $this->orderProcessService->completedOrder($data['orderid'], $cacheord->actual_price, $data['stripeToken']);
+                    return 'success';
+                }
+                return $result;
+            } catch (\Exception $e) {
+                return $e->getMessage();
+            }
+        }
+    }
+    
+
+    /**
+     * 根据RMB获取英镑
+     * @param $cny
+     * @return float|int
+     * @throws \Exception
+     */
+
+    public function getGbpCurrency($cny)
+    {
+        $client = new Client();
+        $res = $client->get('https://api.dov.moe/exchange?src=cny&dst=gbp');
+        $fxrate = json_decode($res->getBody(), true);
+        if ($fxrate['code'] != 1) {
+            $dfFxrate = 0.12;
+        } else {
+            $dfFxrate = $fxrate['data']['value'] * 1.029;
+        }
+        return bcmul($cny , $dfFxrate, 2) + 0.2;
+    }
+
+
+}

+ 1 - 0
dujiaoka/start-hook.sh

@@ -0,0 +1 @@
+#!/bin/sh

+ 25 - 0
dujiaoka/start.sh

@@ -0,0 +1,25 @@
+#!/bin/sh
+
+if [ -f "/dujiaoka/.env" ]; then
+    if [ ! -d "./storage/app" ]; then
+        mv -n storage_bak/* storage/
+    fi
+    if [ "$INSTALL" != "true" ]; then
+        echo "ok" > install.lock
+    fi
+    if [ "$MODIFY" != "false" ]; then
+        mv ./modify/StripeController.php /dujiaoka/app/Http/Controllers/Pay/StripeController.php
+    fi
+
+    bash /dujiaoka/start-hook.sh
+
+    chmod -R 777 storage
+
+    php artisan clear-compiled
+    php artisan optimize
+    php artisan migrate
+
+    supervisord
+else
+    echo "配置文件不存在,请根据文档修改配置文件!"
+fi