mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-04 05:10:19 +08:00
feat: support wechat pay (#2312)
* feat: support wechat pay * feat: support wechat pay * feat: update wechatpay.go * feat: add router /qrcode
This commit is contained in:
@ -664,7 +664,8 @@ class App extends Component {
|
||||
window.location.pathname.startsWith("/cas") ||
|
||||
window.location.pathname.startsWith("/auto-signup") ||
|
||||
window.location.pathname.startsWith("/select-plan") ||
|
||||
window.location.pathname.startsWith("/buy-plan");
|
||||
window.location.pathname.startsWith("/buy-plan") ||
|
||||
window.location.pathname.startsWith("/qrcode") ;
|
||||
}
|
||||
|
||||
renderPage() {
|
||||
|
@ -31,6 +31,7 @@ import CasLogout from "./auth/CasLogout";
|
||||
import {authConfig} from "./auth/Auth";
|
||||
import ProductBuyPage from "./ProductBuyPage";
|
||||
import PaymentResultPage from "./PaymentResultPage";
|
||||
import QrCodePage from "./QrCodePage";
|
||||
|
||||
class EntryPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -113,6 +114,7 @@ class EntryPage extends React.Component {
|
||||
<Route exact path="/select-plan/:owner/:pricingName" render={(props) => <PricingPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||
<Route exact path="/buy-plan/:owner/:pricingName" render={(props) => <ProductBuyPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||
<Route exact path="/buy-plan/:owner/:pricingName/result" render={(props) => <PaymentResultPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||
<Route exact path="/qrcode/:owner/:paymentName" render={(props) => <QrCodePage {...this.props} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
|
@ -13,8 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Descriptions, Modal, Spin} from "antd";
|
||||
import {CheckCircleTwoTone} from "@ant-design/icons";
|
||||
import {Button, Descriptions, Spin} from "antd";
|
||||
import i18next from "i18next";
|
||||
import * as ProductBackend from "./backend/ProductBackend";
|
||||
import * as PlanBackend from "./backend/PlanBackend";
|
||||
@ -36,7 +35,6 @@ class ProductBuyPage extends React.Component {
|
||||
pricing: props?.pricing ?? null,
|
||||
plan: null,
|
||||
isPlacingOrder: false,
|
||||
qrCodeModalProvider: null,
|
||||
};
|
||||
}
|
||||
|
||||
@ -130,13 +128,6 @@ class ProductBuyPage extends React.Component {
|
||||
}
|
||||
|
||||
buyProduct(product, provider) {
|
||||
if (provider.clientId.startsWith("http")) {
|
||||
this.setState({
|
||||
qrCodeModalProvider: provider,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isPlacingOrder: true,
|
||||
});
|
||||
@ -144,7 +135,11 @@ class ProductBuyPage extends React.Component {
|
||||
ProductBackend.buyProduct(product.owner, product.name, provider.name, this.state.pricingName ?? "", this.state.planName ?? "", this.state.userName ?? "")
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const payUrl = res.data;
|
||||
const payment = res.data;
|
||||
let payUrl = payment.payUrl;
|
||||
if (provider.type === "WeChat Pay") {
|
||||
payUrl = `/qrcode/${payment.owner}/${payment.name}?providerName=${provider.name}&payUrl=${encodeURI(payment.payUrl)}&successUrl=${encodeURI(payment.successUrl)}`;
|
||||
}
|
||||
Setting.goToLink(payUrl);
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||
@ -159,45 +154,6 @@ class ProductBuyPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
renderQrCodeModal() {
|
||||
if (this.state.qrCodeModalProvider === undefined || this.state.qrCodeModalProvider === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal title={
|
||||
<div>
|
||||
<CheckCircleTwoTone twoToneColor="rgb(45,120,213)" />
|
||||
{" " + i18next.t("product:Please scan the QR code to pay")}
|
||||
</div>
|
||||
}
|
||||
open={this.state.qrCodeModalProvider !== undefined && this.state.qrCodeModalProvider !== null}
|
||||
onOk={() => {
|
||||
Setting.goToLink(this.state.product.returnUrl);
|
||||
}}
|
||||
onCancel={() => {
|
||||
this.setState({
|
||||
qrCodeModalProvider: null,
|
||||
});
|
||||
}}
|
||||
okText={i18next.t("product:I have completed the payment")}
|
||||
cancelText={i18next.t("general:Cancel")}>
|
||||
<p key={this.state.qrCodeModalProvider?.name}>
|
||||
{
|
||||
i18next.t("product:Please provide your username in the remark")
|
||||
}
|
||||
:
|
||||
{
|
||||
Setting.getTag("default", this.props.account.name)
|
||||
}
|
||||
<br />
|
||||
<br />
|
||||
<img src={this.state.qrCodeModalProvider?.clientId} alt={this.state.qrCodeModalProvider?.name} width={"472px"} style={{marginBottom: "20px"}} />
|
||||
</p>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
getPayButton(provider) {
|
||||
let text = provider.type;
|
||||
if (provider.type === "Dummy") {
|
||||
@ -290,9 +246,6 @@ class ProductBuyPage extends React.Component {
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Spin>
|
||||
{
|
||||
this.renderQrCodeModal()
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ import React from "react";
|
||||
import {Button, Card, Checkbox, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||
import {LinkOutlined} from "@ant-design/icons";
|
||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as CertBackend from "./backend/CertBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import i18next from "i18next";
|
||||
import {authConfig} from "./auth/Auth";
|
||||
@ -24,7 +26,6 @@ import * as ProviderNotification from "./common/TestNotificationWidget";
|
||||
import * as ProviderEditTestSms from "./common/TestSmsWidget";
|
||||
import copy from "copy-to-clipboard";
|
||||
import {CaptchaPreview} from "./common/CaptchaPreview";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
|
||||
import * as Web3Auth from "./auth/Web3Auth";
|
||||
|
||||
@ -39,6 +40,7 @@ class ProviderEditPage extends React.Component {
|
||||
providerName: props.match.params.providerName,
|
||||
owner: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||
provider: null,
|
||||
certs: [],
|
||||
organizations: [],
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
};
|
||||
@ -47,6 +49,7 @@ class ProviderEditPage extends React.Component {
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getOrganizations();
|
||||
this.getProvider();
|
||||
this.getCerts(this.state.owner);
|
||||
}
|
||||
|
||||
getProvider() {
|
||||
@ -80,6 +83,17 @@ class ProviderEditPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getCerts(owner) {
|
||||
CertBackend.getCerts(owner)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
certs: res.data || [],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
parseProviderField(key, value) {
|
||||
if (["port"].includes(key)) {
|
||||
value = Setting.myParseInt(value);
|
||||
@ -91,6 +105,11 @@ class ProviderEditPage extends React.Component {
|
||||
value = this.parseProviderField(key, value);
|
||||
|
||||
const provider = this.state.provider;
|
||||
if (key === "owner" && provider["owner"] !== value) {
|
||||
// the provider change the owner, reset the cert
|
||||
provider["cert"] = "";
|
||||
this.getCerts(value);
|
||||
}
|
||||
provider[key] = value;
|
||||
this.setState({
|
||||
provider: provider,
|
||||
@ -1076,9 +1095,11 @@ class ProviderEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.cert} onChange={e => {
|
||||
this.updateProviderField("cert", e.target.value);
|
||||
}} />
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.cert} onChange={(value => {this.updateProviderField("cert", value);})}>
|
||||
{
|
||||
this.state.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null
|
||||
|
135
web/src/QrCodePage.js
Normal file
135
web/src/QrCodePage.js
Normal file
@ -0,0 +1,135 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import QRCode from "qrcode.react";
|
||||
import {Button, Col, Row} from "antd";
|
||||
import * as PaymentBackend from "./backend/PaymentBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import * as ProviderBackend from "./backend/ProviderBackend";
|
||||
import i18next from "i18next";
|
||||
|
||||
class QrCodePage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
this.state = {
|
||||
classes: props,
|
||||
owner: props.owner ?? (props.match?.params?.owner ?? null),
|
||||
paymentName: props.paymentName ?? (props.match?.params?.paymentName ?? null),
|
||||
providerName: props.providerName ?? params.get("providerName"),
|
||||
payUrl: props.payUrl ?? params.get("payUrl"),
|
||||
successUrl: props.successUrl ?? params.get("successUrl"),
|
||||
provider: props.provider ?? null,
|
||||
payment: null,
|
||||
timer: null,
|
||||
};
|
||||
}
|
||||
|
||||
async getProvider() {
|
||||
if (!this.state.owner || !this.state.providerName) {
|
||||
return ;
|
||||
}
|
||||
try {
|
||||
const res = await ProviderBackend.getProvider(this.state.owner, this.state.providerName);
|
||||
if (res.status !== "ok") {
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
const provider = res.data;
|
||||
this.setState({
|
||||
provider: provider,
|
||||
});
|
||||
} catch (err) {
|
||||
Setting.showMessage("error", err.message);
|
||||
return ;
|
||||
}
|
||||
}
|
||||
|
||||
setNotifyTask() {
|
||||
if (!this.state.owner || !this.state.paymentName) {
|
||||
return ;
|
||||
}
|
||||
|
||||
const notifyTask = async() => {
|
||||
try {
|
||||
const res = await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
||||
if (res.status !== "ok") {
|
||||
throw new Error(res.msg);
|
||||
}
|
||||
const payment = res.data;
|
||||
if (payment.state !== "Created") {
|
||||
Setting.goToLink(this.state.successUrl);
|
||||
}
|
||||
} catch (err) {
|
||||
Setting.showMessage("error", err.message);
|
||||
return ;
|
||||
}
|
||||
};
|
||||
|
||||
this.setState({
|
||||
timer: setTimeout(async() => {
|
||||
await notifyTask();
|
||||
this.setNotifyTask();
|
||||
}, 2000),
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.onUpdateApplication) {
|
||||
this.props.onUpdateApplication(null);
|
||||
}
|
||||
this.getProvider();
|
||||
this.setNotifyTask();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.state.timer);
|
||||
}
|
||||
|
||||
renderProviderInfo(provider) {
|
||||
if (!provider) {
|
||||
return null;
|
||||
}
|
||||
const text = i18next.t(`product:${provider.type}`);
|
||||
return (
|
||||
<Button style={{height: "50px", borderWidth: "2px"}} shape="round" icon={
|
||||
<img style={{marginRight: "10px"}} width={36} height={36} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} />
|
||||
} size={"large"} >
|
||||
{
|
||||
text
|
||||
}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.payUrl || !this.state.successUrl || !this.state.owner || !this.state.paymentName) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="login-content">
|
||||
<Col>
|
||||
<Row style={{justifyContent: "center"}}>
|
||||
{this.renderProviderInfo(this.state.provider)}
|
||||
</Row>
|
||||
<Row style={{marginTop: "10px", justifyContent: "center"}}>
|
||||
<QRCode value={this.state.payUrl} size={this.props.size ?? 200} />
|
||||
</Row>
|
||||
</Col>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default QrCodePage;
|
Reference in New Issue
Block a user