1. 支付 DEMO
2. H5
wenjuan
鹿和sa0ChunLuyu 1 year ago
parent c3a26c2440
commit 4f45b66fdc

@ -21,6 +21,7 @@ class ApiMapController extends Controller
{ {
$base_url = env('APP_URL'); $base_url = env('APP_URL');
return [ return [
'DemoPay' => $base_url . '/api/Demo/pay',
'ApiMapTest' => $base_url . '/api/ApiMap/test', 'ApiMapTest' => $base_url . '/api/ApiMap/test',
]; ];
} }

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Controllers\API\WeChatPayController;
use Illuminate\Http\Request;
class DemoController extends Controller
{
public function pay_back()
{
$input = file_get_contents('php://input');
file_put_contents(base_path() . '/storage/app/pay/pay_back.txt', urldecode($input));
return [
'code' => 'SUCCESS',
'message' => '成功',
];
}
public function pay(Request $request)
{
$openid = $request->post('openid');
$total = $request->post('total');
$wcp = new WeChatPayController();
$wcp->builder([
'appid' => 'wx0d92d2990ec16a55',
'pem_path' => base_path() . '/storage/app/pay/key.pem',
'cer_path' => base_path() . '/storage/app/pay/crt.pem',
'cer_num' => '3CE37188EBCFBBEB800B0E1C69B360F05A3E80CD',
'mchid' => '1638739772',
'v3' => 'AVPV7NxK8cC2RvRrrwdTqUG9YbQXQe3w',
]);
$out_trade_no = date('Ymd') . time() . rand(10, 99);
$pay = $wcp->create([
'description' => '体检预约',
'out_trade_no' => $out_trade_no,
'notify_url' => 'https://api.hainan2024.sa0.online/api/Demo/pay_back',
'total' => $total,
'openid' => $openid
]);
return \Yz::Return(true, '获取成功', [
'pay' => $pay
]);
}
}

@ -0,0 +1,174 @@
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use WeChatPay\Builder;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;
class WeChatPayController extends Controller
{
public static $mp_instance = false;
public static $mp_config = false;
public function callback($input, $header, $apiv3Key, $pem)
{
$inWechatpaySignature = $header['wechatpay-signature'][0];
$inWechatpayTimestamp = $header['wechatpay-timestamp'][0];
$inWechatpaySerial = $header['wechatpay-serial'][0];
$inWechatpayNonce = $header['wechatpay-nonce'][0];
$inBody = $input;
$platformPublicKeyInstance = Rsa::from($pem, Rsa::KEY_TYPE_PUBLIC);
$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
// $verifiedStatus = Rsa::verify(
// Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
// $inWechatpaySignature,
// $platformPublicKeyInstance
// );
// if ($timeOffsetStatus && $verifiedStatus) {
if ($timeOffsetStatus) {
$inBodyArray = (array)json_decode($inBody, true);
['resource' => [
'ciphertext' => $ciphertext,
'nonce' => $nonce,
'associated_data' => $aad
]] = $inBodyArray;
$inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
return (array)json_decode($inBodyResource, true);
} else {
return false;
}
}
public function builder($config)
{
self::$mp_config = $config;
$merchantPrivateKeyFilePath = 'file://' . self::$mp_config['pem_path'];
$platformCertificateFilePath = 'file://' . self::$mp_config['cer_path'];
$merchantId = self::$mp_config['mchid'];
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
self::$mp_config['pem_key'] = $merchantPrivateKeyInstance;
$merchantCertificateSerial = self::$mp_config['cer_num'];
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
$platformCertificateSerial = self::$mp_config['v3'];
self::$mp_instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
}
public function refund($config)
{
$res = false;
try {
$resp = self::$mp_instance
->v3->refund->domestic->refunds
->post([
'json' => [
'transaction_id' => $config['transaction_id'],
'out_refund_no' => $config['out_refund_no'],
'amount' => [
'refund' => $config['total'],
'total' => $config['total'],
'currency' => 'CNY',
],
],
]);
$res = json_decode($resp->getBody(), true);
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
$res = json_decode($r->getBody(), true);
}
}
return $res;
}
public function create($config)
{
$res = false;
try {
$post_data = [
'appid' => self::$mp_config['appid'],
'mchid' => self::$mp_config['mchid'],
'description' => $config['description'],
'out_trade_no' => $config['out_trade_no'],
'notify_url' => $config['notify_url'],
'amount' => [
'total' => $config['total'],
],
'payer' => [
'openid' => $config['openid']
]
];
$resp = self::$mp_instance
->v3->pay->transactions->jsapi
->post([
'json' => $post_data,
]);
$res = json_decode($resp->getBody(), true);
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
$res = json_decode($r->getBody(), true);
}
}
$params = [
'appId' => self::$mp_config['appid'],
'timeStamp' => (string)time(),
'nonceStr' => self::nonce(),
'package' => 'prepay_id=' . $res['prepay_id'],
];
$params += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
self::$mp_config['pem_key']
), 'signType' => 'RSA'];
return [
'appid' => $params['appId'],
'timestamp' => $params['timeStamp'],
'nonce_str' => $params['nonceStr'],
'package' => $params['package'],
'pay_sign' => $params['paySign'],
'sign_type' => $params['signType'],
];
}
public function check($out_trade_no)
{
$res = false;
try {
$resp = self::$mp_instance
->v3->pay->transactions->outTradeNo->_out_trade_no_
->get([
'query' => ['mchid' => self::$mp_config['mchid']],
'out_trade_no' => (string)$out_trade_no,
]);
$res = json_decode($resp->getBody(), true);
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
$res = json_decode($r->getBody(), true);
}
}
return $res;
}
public static function nonce($l = 16)
{
$charts = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz0123456789";
$max = strlen($charts) - 1;
$noncestr = "";
for ($i = 0; $i < $l; $i++) {
$noncestr .= $charts[rand(0, $max)];
}
return $noncestr;
}
}

@ -1,4 +1,5 @@
<?php <?php
use Illuminate\Http\Exceptions\HttpResponseException;
class Yz{ class Yz{
public static function echo($data=[],$code=200){ public static function echo($data=[],$code=200){
$result=array(); $result=array();
@ -26,5 +27,10 @@ class Yz{
$result['data']=$data; $result['data']=$data;
return $result; return $result;
} }
public static function debug($data)
{
throw new HttpResponseException(response()->json(['status' => true, 'data' => $data])->setEncodingOptions(JSON_UNESCAPED_UNICODE));
}
} }

@ -1,65 +1,69 @@
{ {
"name": "laravel/laravel", "name": "laravel/laravel",
"type": "project", "type": "project",
"description": "The Laravel Framework.", "description": "The Laravel Framework.",
"keywords": ["framework", "laravel"], "keywords": [
"license": "MIT", "framework",
"require": { "laravel"
"php": "^7.3|^8.0", ],
"fruitcake/laravel-cors": "^2.0", "license": "MIT",
"guzzlehttp/guzzle": "^7.0.1", "require": {
"laravel/framework": "^8.75", "php": "^7.3|^8.0",
"laravel/sanctum": "^2.11", "fruitcake/laravel-cors": "^2.0",
"laravel/tinker": "^2.5" "guzzlehttp/guzzle": "^7.0.1",
}, "laravel/framework": "^8.75",
"require-dev": { "laravel/sanctum": "^2.11",
"facade/ignition": "^2.5", "laravel/tinker": "^2.5",
"fakerphp/faker": "^1.9.1", "wechatpay/wechatpay": "^1.4"
"laravel/sail": "^1.0.1", },
"mockery/mockery": "^1.4.4", "require-dev": {
"nunomaduro/collision": "^5.10", "facade/ignition": "^2.5",
"phpunit/phpunit": "^9.5.10" "fakerphp/faker": "^1.9.1",
}, "laravel/sail": "^1.0.1",
"autoload": { "mockery/mockery": "^1.4.4",
"classmap": [ "nunomaduro/collision": "^5.10",
"app/Lib" "phpunit/phpunit": "^9.5.10"
], },
"psr-4": { "autoload": {
"App\\": "app/", "classmap": [
"Database\\Factories\\": "database/factories/", "app/Lib"
"Database\\Seeders\\": "database/seeders/" ],
} "psr-4": {
}, "App\\": "app/",
"autoload-dev": { "Database\\Factories\\": "database/factories/",
"psr-4": { "Database\\Seeders\\": "database/seeders/"
"Tests\\": "tests/" }
} },
}, "autoload-dev": {
"scripts": { "psr-4": {
"post-autoload-dump": [ "Tests\\": "tests/"
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", }
"@php artisan package:discover --ansi" },
], "scripts": {
"post-update-cmd": [ "post-autoload-dump": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force" "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
], "@php artisan package:discover --ansi"
"post-root-package-install": [ ],
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" "post-update-cmd": [
], "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
"post-create-project-cmd": [ ],
"@php artisan key:generate --ansi" "post-root-package-install": [
] "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
}, ],
"extra": { "post-create-project-cmd": [
"laravel": { "@php artisan key:generate --ansi"
"dont-discover": [] ]
} },
}, "extra": {
"config": { "laravel": {
"optimize-autoloader": true, "dont-discover": []
"preferred-install": "dist", }
"sort-packages": true },
}, "config": {
"minimum-stability": "dev", "optimize-autoloader": true,
"prefer-stable": true "preferred-install": "dist",
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
} }

1601
Laravel/composer.lock generated

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
import{$ as a,g as t,e,s as i,f as n,h as o}from"./index-DqaBDANc.js";const s=a.token;function l(){return t(s)}const d=e("counter",{state:()=>({api_map:{},count:0,loading:0}),actions:{loadingStart(){this.loading++},loadingDone(){this.loading--,this.loading<0&&(this.loading=0)}}}),p=async({url:a,data:t={}},e)=>{const s=d();let p={};if("delete_token"in e&&e.delete_token)p.Authorization&&delete p.Authorization;else{const a=l()?l():"";p.Authorization="Bearer "+a}"delete_appid"in e&&e.delete_appid?t.UNIAPP_APPID&&delete t.UNIAPP_APPID:t.UNIAPP_APPID=e.appid,"delete_apptype"in e&&e.delete_apptype?t.UNIAPP_APPTYPE&&delete t.UNIAPP_APPTYPE:t.UNIAPP_APPTYPE=e.app_type,e.loading&&(s.loadingStart(),1===s.loading&&i({title:e.loading_text}));const r=await n({url:a,method:"POST",data:t,header:p});return e.loading&&(s.loadingDone(),0===s.loading&&o()),r&&""!=r.data?r.data:(uni.$lu.toast("请求发生错误"),!1)};a.config.api_map_url,a.config.base_assets_url;const r=async(t,e={},i={})=>{const n={...a,...i},o=d();if(!(t in o.api_map)){const a=await p({url:n.config.api_map_url},n);if(!a.status)return uni.$lu.toast("获取接口失败"),!1;o.api_map=a.data.list}return t in o.api_map?await p({url:o.api_map[t],data:e},n):(uni.$lu.toast(`接口不存在 [${t}]`),!1)},u=(t,e,i={},n=(()=>{}))=>{if(t){const o={...a,...i};if(t.status!=o.success_code)return uni.$lu.toast(t.message),void n();e()}};export{r as $,u as a};

@ -0,0 +1 @@
import{c as a,w as s,i as o,o as e,a as t,b as n}from"./index-DqaBDANc.js";import"./index.BjiQWzit.js";const r={__name:"combo",setup:r=>(r,l)=>{const m=o;return e(),a(m,null,{default:s((()=>[t(" Combo "),n(m,{class:"blank_wrapper"})])),_:1})}};export{r as default};

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
import{c as a,w as s,i as e,o as n,a as d,b as t,n as l,d as r}from"./index-DqaBDANc.js";import"./index.BjiQWzit.js";const i={__name:"index",setup:i=>(i,o)=>{const p=r,u=e;return n(),a(u,null,{default:s((()=>[d(" Index "),t(p,{onClick:o[0]||(o[0]=a=>{l({url:"/pages/main/dev/dev"})})},{default:s((()=>[d("开发测试")])),_:1}),t(u,{class:"blank_wrapper"})])),_:1})}};export{i as default};

@ -0,0 +1 @@
import{c as a,w as s,i as r,o as e,a as t,b as n}from"./index-DqaBDANc.js";import"./index.BjiQWzit.js";const o={__name:"order",setup:o=>(o,d)=>{const l=r;return e(),a(l,null,{default:s((()=>[t(" Order "),n(l,{class:"blank_wrapper"})])),_:1})}};export{o as default};

File diff suppressed because one or more lines are too long

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="/h5/assets/uni.943b8fef.css">
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script><title>海南现代妇女儿童医院</title>
<!--preload-links-->
<!--app-context-->
<script type="module" crossorigin src="/h5/assets/index-DqaBDANc.js"></script>
<link rel="stylesheet" crossorigin href="/h5/assets/index-BPRTVkjj.css">
</head>
<body>
<div id="app"><!--app-html--></div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

@ -0,0 +1,20 @@
@font-face {
font-family: "customicons"; /* Project id 2878519 */
src:url('/static/customicons.ttf') format('truetype');
}
.customicons {
font-family: "customicons" !important;
}
.youxi:before {
content: "\e60e";
}
.wenjian:before {
content: "\e60f";
}
.zhuanfa:before {
content: "\e610";
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

@ -2,5 +2,8 @@
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::any("/api/Demo/pay_back", [\App\Http\Controllers\API\DemoController::class, 'pay_back']);
Route::any("/api/Demo/pay", [\App\Http\Controllers\API\DemoController::class, 'pay']);
Route::any("/api/ApiMap/test", [\App\Http\Controllers\API\ApiMapController::class, 'test']); Route::any("/api/ApiMap/test", [\App\Http\Controllers\API\ApiMapController::class, 'test']);
Route::post("/api/ApiMap/{type}", [\App\Http\Controllers\API\ApiMapController::class, 'list']); Route::post("/api/ApiMap/{type}", [\App\Http\Controllers\API\ApiMapController::class, 'list']);

@ -26,7 +26,7 @@ export const $api = async (url_key, data = {}, opt = {}) => {
const api_map = await $post({ const api_map = await $post({
url: opt_data.config.api_map_url url: opt_data.config.api_map_url
}, opt_data) }, opt_data)
if (api_map.code !== 200) { if (!api_map.status) {
uni.$lu.toast('获取接口失败') uni.$lu.toast('获取接口失败')
return false return false
} }
@ -58,7 +58,7 @@ export const $response = (response, then, opt = {}, error = () => {}) => {
...$config, ...$config,
...opt, ...opt,
} }
if (response.code != opt_data.success_code) { if (response.status != opt_data.success_code) {
uni.$lu.toast(response.message); uni.$lu.toast(response.message);
error() error()
return return

@ -6,7 +6,7 @@ const config = {
uni.$config = JSON.parse(JSON.stringify(config)) uni.$config = JSON.parse(JSON.stringify(config))
export default { export default {
title: '海南现代妇女儿童医院', title: '海南现代妇女儿童医院',
success_code: 200, success_code: true,
loading: false, loading: false,
loading_text: '网络请求中,请稍等', loading_text: '网络请求中,请稍等',
error_message: '网络请求发生错误', error_message: '网络请求发生错误',

@ -8,6 +8,7 @@
ref ref
} from 'vue' } from 'vue'
import { import {
$api,
$response $response
} from '@/api' } from '@/api'
import { import {
@ -15,12 +16,14 @@
} from '@dcloudio/uni-app' } from '@dcloudio/uni-app'
import JWeixin from "weixin-js-sdk" import JWeixin from "weixin-js-sdk"
const latitude = ref('39.867671')
const longitude = ref('119.514223')
const mapClick = () => { const mapClick = () => {
let url = '/pages/main/map/map?' let url = '/pages/main/map/map?'
let query_arr = [] let query_arr = []
query_arr.push(`type=gcj02`) query_arr.push(`type=gcj02`)
query_arr.push(`latitude=39.867671`) query_arr.push(`latitude=${latitude.value}`)
query_arr.push(`longitude=119.514223`) query_arr.push(`longitude=${longitude.value}`)
query_arr.push(`scale=18`) query_arr.push(`scale=18`)
query_arr.push(`name=${encodeURIComponent('测试地址')}`) query_arr.push(`name=${encodeURIComponent('测试地址')}`)
query_arr.push(`address=${encodeURIComponent('测试地址测试地址123 测试地址')}`) query_arr.push(`address=${encodeURIComponent('测试地址测试地址123 测试地址')}`)
@ -29,11 +32,45 @@
url: url, url: url,
}); });
} }
const total = ref('1')
const openid = ref('ourv44tbkA8yLB8X60GhmvhetVTM')
const payClick = async () => {
let total_number = Number(total.value)
if (!total_number) uni.$lu.toast('请输入正确金额')
const response = await $api('DemoPay', {
total: total_number,
openid: openid.value,
})
$response(response, () => {
let p = JSON.stringify({
...response.data.pay,
url: 'https://h5.hainan2024.ziqian.online/h5/#/pages/main/dev/dev'
})
let url = '/pages/main/pay/pay?'
let query_arr = []
query_arr.push(`p=${encodeURIComponent(p)}`)
url = url + query_arr.join('&')
JWeixin.miniProgram.navigateTo({
url: url,
});
})
}
</script> </script>
<template> <template>
<view> <view>
<view> <view>
<button @click="mapClick()"></button> <view>
<input class="uni-input" v-model="latitude" />
<input class="uni-input" v-model="longitude" />
<button @click="mapClick()"></button>
</view>
<view>
<input class="uni-input" v-model="total" />
<input class="uni-input" v-model="openid" />
<button @click="payClick()"></button>
</view>
</view> </view>
<view class="blank_wrapper"></view> <view class="blank_wrapper"></view>
</view> </view>

Loading…
Cancel
Save