Compare commits

...

3 Commits

Author SHA1 Message Date
16f427d4d7 feat: Remove useless dependencies
Signed-off-by: MRGUOKING <420919469@qq.com>

Remove useless dependencies

Signed-off-by: MRGUOKING <420919469@qq.com>
2021-09-15 00:13:43 +08:00
6ad8f9fa0b feat: Add PayPal pay function
Signed-off-by: MRGUOKING <420919469@qq.com>

Add PayPal pay function

Signed-off-by: MRGUOKING <420919469@qq.com>
2021-09-15 00:00:56 +08:00
b93a29fbc6 Improve language code. 2021-09-14 01:22:13 +08:00
24 changed files with 970 additions and 87 deletions

View File

@ -93,6 +93,9 @@ p, *, *, POST, /api/send-verification-code, *, *
p, *, *, GET, /api/get-human-check, *, *
p, *, *, POST, /api/reset-email-or-phone, *, *
p, *, *, POST, /api/upload-resource, *, *
p, *, *, POST, /api/paypal, *, *
p, *, *, GET, /api/success-pay, *, *
p, *, *, GET, /api/get-application-clientId, *, *
`
sa := stringadapter.NewAdapter(ruleText)

View File

@ -16,6 +16,7 @@ package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
)
@ -116,3 +117,10 @@ func (c *ApiController) DeleteApplication() {
c.Data["json"] = wrapActionResponse(object.DeleteApplication(&application))
c.ServeJSON()
}
func (c *ApiController) GetApplicationByClientId() {
clientId := c.Input().Get("clientId")
c.Data["json"] = object.GetApplicationByClientId(clientId)
c.ServeJSON()
}

45
controllers/payment.go Normal file
View File

@ -0,0 +1,45 @@
package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/payment"
)
func (c *ApiController) PaypalPay() {
clientId := c.Input().Get("clientId")
redirectUri := c.Input().Get("redirectUri")
var payItem object.PayItem
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payItem)
if err != nil {
panic(err)
}
msg := payment.Paypal(payItem, clientId, redirectUri)
c.Data["json"] = msg
c.ServeJSON()
}
func (c *ApiController) GetPayments() {
c.Data["json"] = object.GetPayments()
c.ServeJSON()
}
func (c *ApiController) DeletePayment() {
var payment object.Payment
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payment)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
c.ServeJSON()
}
func (c *ApiController) SuccessPay() {
token := c.Input().Get("paymentId")
c.Data["json"] = payment.SuccessPay(token)
c.ServeJSON()
}

3
go.mod
View File

@ -18,6 +18,7 @@ require (
github.com/google/uuid v1.2.0
github.com/jinzhu/configor v1.2.1 // indirect
github.com/mileusna/crontab v1.0.1
github.com/plutov/paypal/v4 v4.3.7
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
github.com/satori/go.uuid v1.2.0 // indirect
@ -30,5 +31,5 @@ require (
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
xorm.io/core v0.7.2
xorm.io/xorm v1.0.3
xorm.io/xorm v1.0.6
)

6
go.sum
View File

@ -256,6 +256,8 @@ github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHu
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/plutov/paypal/v4 v4.3.7 h1:wPvhAJ3RkDkV+UDrGX/UivXAl5JEPOOJuzsdgnTMJHc=
github.com/plutov/paypal/v4 v4.3.7/go.mod h1:D56boafCRGcF/fEM0w282kj0fCDKIyrwOPX/Te1jCmw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@ -300,6 +302,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
@ -622,5 +625,6 @@ xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
xorm.io/xorm v1.0.3 h1:3dALAohvINu2mfEix5a5x5ZmSVGSljinoSGgvGbaZp0=
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
xorm.io/xorm v1.0.6 h1:7eco1c8QUpGz+3dztpLDj9gU1bTiQdFC/KtmPaLxUJk=
xorm.io/xorm v1.0.6/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=

View File

@ -147,4 +147,9 @@ func (a *Adapter) createTable() {
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Payment))
if err != nil {
panic(err)
}
}

View File

@ -46,6 +46,10 @@ func (application *Application) GetStorageProvider() *Provider {
return application.GetProviderByCategory("Storage")
}
func (application *Application) GetPayProvider() *Provider {
return application.GetProviderByCategory("Pay")
}
func (application *Application) getSignupItem(itemName string) *SignupItem {
for _, signupItem := range application.SignupItems {
if signupItem.Name == itemName {

8
object/pay_item.go Normal file
View File

@ -0,0 +1,8 @@
package object
type PayItem struct {
Invoice string `json:"invoice"`
Price string `json:"price"`
Description string `json:"description"`
Currency string `json:"currency"`
}

74
object/payment.go Normal file
View File

@ -0,0 +1,74 @@
package object
import (
"github.com/plutov/paypal/v4"
"xorm.io/core"
)
type Payment struct {
Id string `xorm:"varchar(100) notnull pk" json:"id"`
Invoice string `xorm:"varchar(100)" json:"invoice"`
Application string `xorm:"varchar(100)" json:"application"`
PayItem PayItem `xorm:"json varchar(1000)" json:"pay_item"`
Payer *paypal.PayerWithNameAndPhone `xorm:"json varchar(1000)" json:"payer"`
Purchase []paypal.CapturedPurchaseUnit `xorm:"varchar(10000)" json:"purchase"`
Status string `xorm:"varchar(100)" json:"status"`
CreateTime string `xorm:"varchar(100) created" json:"create_time"`
UpdateTime string `xorm:"varchar(100) updated" json:"update_time"`
Callback string `xorm:"varchar(1000)" json:"callback"`
}
func AddPayment(pay *Payment) bool {
affected, err := adapter.Engine.Insert(pay)
if err != nil {
panic(err)
}
return affected != 0
}
func GetPayments() []*Payment {
pays := []*Payment{}
err := adapter.Engine.Desc("create_time").Find(&pays)
if err != nil {
panic(err)
}
return pays
}
func GetPayment(id string) *Payment {
pay := Payment{Id: id}
existed, err := adapter.Engine.Get(&pay)
if err != nil {
panic(err)
}
if existed {
return &pay
} else {
return nil
}
}
func DeletePayment(payment *Payment) bool {
affected, err := adapter.Engine.ID(core.PK{payment.Id}).Delete(&Payment{})
if err != nil {
panic(err)
}
return affected != 0
}
func UpdatePay(id string, pay *Payment) bool {
if GetPayment(id) == nil {
return false
}
affected, err := adapter.Engine.ID(core.PK{id}).AllCols().Update(pay)
if err != nil {
panic(err)
}
return affected != 0
}

105
payment/paypal.go Normal file
View File

@ -0,0 +1,105 @@
package payment
import (
"context"
"fmt"
"time"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/object"
"github.com/plutov/paypal/v4"
)
var client = GetClient()
func GetClient() *paypal.Client {
c, err := paypal.NewClient(beego.AppConfig.String("paypalClientId"), beego.AppConfig.String("paypalSecret"), paypal.APIBaseSandBox)
if err != nil {
panic(err)
}
return c
}
func Paypal(payItem object.PayItem, clientId string, redirectUri string) string {
application := object.GetApplicationByClientId(clientId)
if application == nil {
return "Invalid client_id"
}
applicationName := fmt.Sprintf("%s/%s", application.Owner, application.Name)
if payItem.Currency == "" {
payItem.Currency = "USD"
}
_, err := client.GetAccessToken(context.Background())
if err != nil {
panic(err)
}
appContext := &paypal.ApplicationContext{
ReturnURL: "http://localhost:7001/pay/success", //回调链接
CancelURL: "https://www.baidu.com",
}
purchaseUnits := make([]paypal.PurchaseUnitRequest, 1)
purchaseUnits[0] = paypal.PurchaseUnitRequest{
Amount: &paypal.PurchaseUnitAmount{
Currency: payItem.Currency, //收款类型
Value: payItem.Price, //收款数量
},
InvoiceID: payItem.Invoice,
Description: payItem.Description,
}
order, err := client.CreateOrder(context.Background(),
paypal.OrderIntentCapture,
purchaseUnits,
&paypal.CreateOrderPayer{},
appContext)
if err != nil {
panic(err)
}
newPay := object.Payment{
Id: order.ID,
Invoice: payItem.Invoice,
PayItem: payItem,
Application: applicationName,
Status: order.Status,
Callback: redirectUri,
}
success := object.AddPayment(&newPay)
if success {
links := order.Links
for _, link := range links {
fmt.Println(link.Rel)
if link.Rel == "approve" {
return link.Href
}
}
}
return "Add Order to Database false"
}
func SuccessPay(token string) string {
_, err := client.GetAccessToken(context.Background())
if err != nil {
panic(err)
}
captureOrder, err := client.CaptureOrder(context.Background(), token, paypal.CaptureOrderRequest{})
if err != nil {
panic(err)
}
pay := object.GetPayment(captureOrder.ID)
pay.Purchase = captureOrder.PurchaseUnits
pay.Payer = captureOrder.Payer
pay.UpdateTime = time.Now().String()
pay.Status = captureOrder.Status
object.UpdatePay(captureOrder.ID, pay)
if captureOrder.Status == "COMPLETED" {
return fmt.Sprintf("%s?paymentId=%s", pay.Callback, token)
}
return ""
}

View File

@ -49,6 +49,7 @@ func initAPI() {
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
beego.Router("/api/get-application-clientId", &controllers.ApiController{}, "Get:GetApplicationByClientId")
beego.Router("/api/update-organization", &controllers.ApiController{}, "POST:UpdateOrganization")
beego.Router("/api/add-organization", &controllers.ApiController{}, "POST:AddOrganization")
beego.Router("/api/delete-organization", &controllers.ApiController{}, "POST:DeleteOrganization")
@ -108,4 +109,9 @@ func initAPI() {
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
beego.Router("/api/paypal", &controllers.ApiController{}, "POST:PaypalPay")
beego.Router("/api/success-pay", &controllers.ApiController{}, "GET:SuccessPay")
beego.Router("/api/get-payments", &controllers.ApiController{}, "Get:GetPayments")
beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment")
}

View File

@ -50,6 +50,9 @@ import AuthCallback from "./auth/AuthCallback";
import SelectLanguageBox from './SelectLanguageBox';
import i18next from 'i18next';
import PromptPage from "./auth/PromptPage";
import PaySuccessCallback from "./PaySuccessCallback";
import Pay from "./Pay";
import PaymentListPage from "./PaymentListPage";
const { Header, Footer } = Layout;
@ -72,7 +75,6 @@ class App extends Component {
}
UNSAFE_componentWillMount() {
Setting.setLanguage();
this.updateMenuKey();
this.getAccount();
}
@ -113,6 +115,8 @@ class App extends Component {
this.setState({ selectedMenuKey: '/login' });
} else if (uri.includes('/result')) {
this.setState({ selectedMenuKey: '/result' });
}else if (uri.includes('/payment')) {
this.setState({ selectedMenuKey: '/payment' });
} else {
this.setState({ selectedMenuKey: -1 });
}
@ -139,6 +143,13 @@ class App extends Component {
return location.toString().replace(location.search, "");
}
setLanguage(account) {
let language = account?.language;
if (language !== "" && language !== i18next.language) {
Setting.setLanguage(language);
}
}
getAccount() {
let query = this.getAccessTokenParam();
if (query === "") {
@ -153,6 +164,8 @@ class App extends Component {
if (res.status === "ok") {
account = res.data;
account.organization = res.data2;
this.setLanguage(account);
} else {
if (res.msg !== "Please sign in first") {
Setting.showMessage("error", `Failed to sign in: ${res.msg}`);
@ -346,6 +359,13 @@ class App extends Component {
</Link>
</Menu.Item>
);
res.push(
<Menu.Item key="/payment">
<Link to="/payment">
{"Payment"}
</Link>
</Menu.Item>
)
}
res.push(
@ -408,6 +428,9 @@ class App extends Component {
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)}/>
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
<Route exact path="/payment" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
<Route exact path="/pay" render={(props) => <Pay account={this.state.account} {...props}/>}/>
<Route exact path="/pay/success" render={(props) => <PaySuccessCallback account={this.state.account} {...props}/>}/>
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>
@ -420,31 +443,35 @@ class App extends Component {
return (
<div style={{display: 'flex', flex: 'auto',width:"100%",flexDirection: 'column'}}>
<Layout style={{display: 'flex', alignItems: 'stretch'}}>
<Header style={{ padding: '0', marginBottom: '3px'}}>
{
Setting.isMobile() ? null : (
<Link to={"/"}>
<div className="logo" />
</Link>
)
}
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{ lineHeight: '64px'}}
>
{window.location.pathname.indexOf("/pay") != -1 && window.location.pathname.indexOf("/payment") == -1 ?
null :
(<Header style={{ padding: '0', marginBottom: '3px'}}>
{
this.renderMenu()
Setting.isMobile() ? null : (
<Link to={"/"}>
<div className="logo" />
</Link>
)
}
<div style = {{float: 'right'}}>
{
this.renderAccount()
}
<SelectLanguageBox/>
</div>
</Menu>
</Header>
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{ lineHeight: '64px'}}
>
{
this.renderMenu()
}
<div style = {{float: 'right'}}>
{
this.renderAccount()
}
<SelectLanguageBox/>
</div>
</Menu>
</Header>)
}
<Layout style={{backgroundColor: "#f5f5f5", alignItems: 'stretch'}}>
<Card className="content-warp-card">
{
@ -458,31 +485,34 @@ class App extends Component {
} else {
return(
<div>
<Header style={{ padding: '0', marginBottom: '3px'}}>
{
Setting.isMobile() ? null : (
<Link to={"/"}>
<div className="logo" />
</Link>
)
{window.location.pathname.indexOf("/pay") != -1 && window.location.pathname.indexOf("/payment") == -1 ?
null :
<Header style={{ padding: '0', marginBottom: '3px'}}>
{
Setting.isMobile() ? null : (
<Link to={"/"}>
<div className="logo" />
</Link>
)
}
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{ lineHeight: '64px' }}
>
{
this.renderMenu()
}
<div style = {{float: 'right'}}>
{
this.renderAccount()
}
<SelectLanguageBox/>
</div>
</Menu>
</Header>
}
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{ lineHeight: '64px' }}
>
{
this.renderMenu()
}
<div style = {{float: 'right'}}>
{
this.renderAccount()
}
<SelectLanguageBox/>
</div>
</Menu>
</Header>
{
this.renderRouter()
}

View File

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
export const ShowGithubCorner = false
export const ShowGithubCorner = false;
export const GithubRepo = "https://github.com/casbin/casdoor";
export const GithubRepo = "https://github.com/casbin/casdoor"
export const ForceLanguage = "en";
export const DefaultLanguage = "en";

View File

@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import * as Conf from "./common/Conf"
import * as Conf from "./Conf";
import GithubCorner from "react-github-corner";
class CustomGithubCorner extends React.Component {

176
web/src/Pay.js Normal file
View File

@ -0,0 +1,176 @@
// Copyright 2021 The casbin 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 {Button, Result, Spin, Form, Input,} from "antd";
import {withRouter} from "react-router-dom";
import * as PaymentBackend from "./backend/PaymentBackend"
import * as ApplicationBackend from "./backend/ApplicationBackend"
class Pay extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: null,
classes: props,
clientId: this.GetQueryString("clientId"),
invoice: this.GetQueryString("invoice"),
price: this.GetQueryString("price"),
currency: this.GetQueryString("currency"),
description: this.GetQueryString("description"),
redirectUri: this.GetQueryString("redirectUri"),
waiting: false,
applicationName: "",
wrongMsg: "",
spinMsg: "正在生成订单..."
};
}
UNSAFE_componentWillMount() {
this.getApplication();
}
getApplication(){
ApplicationBackend.getApplicationByClientId(this.state.clientId).then(res => {
if(res == null){
this.setState({
wrongMsg: "Invalid clientID"
})
}else {
console.log("res")
console.log(res)
this.setState({
applicationName : res.displayName
})
}
})
}
GetQueryString(key){
let reg = new RegExp("(^|&)"+ key +"=([^&]*)(&|$)");
let r = window.location.search.substr(1).match(reg);
if(r!=null)return unescape(r[2]); return null;
}
getCurrencyString(currency){
if(currency === "USD"){
return "$"
}else if (currency === "CNY"){
return "¥"
}else if (currency === "EUR"){
return "€"
}
return "$"
}
pay(){
let payItem = {
invoice: this.state.invoice,
price : this.state.price,
currency : this.state.currency,
description : this.state.description
}
this.setState({
waiting: true
})
PaymentBackend.PaypalPal(payItem, this.state.clientId, this.state.redirectUri).then(res => {
console.log(res)
if (res.indexOf("http") !== -1){
this.setState({
spinMsg: "前往支付页面"
})
window.location.replace(res);
}
else {
this.setState({
wrongMsg : res
})
}
})
}
render() {
const data = [
{
title: 'Invoice',
},
{
title: 'Amount',
},
{
title: 'Description',
}
];
return (
<div>
{
(this.state.wrongMsg === "") ?
(<div style={{textAlign: "center"}}>
{
(this.state.waiting) ? (
<Spin size="large" tip={this.state.spinMsg} style={{paddingTop: "10%"}} />
) : (
<div style={{display: "inline"}}>
<p style={{fontSize: 20}}>{`尊敬的用户,您正在通过 casdoor 向 ${this.state.applicationName} 进行付款,请确认`}</p>
<Form
size="large"
name="pay"
labelCol={{ span: 8 }}
wrapperCol={{ span: 8 }}
initialValues={{ remember: true }}
autoComplete="off"
>
<Form.Item
label="Invoice"
>
<Input style={{color: "black"}} value={this.state.invoice} bordered={false} disabled />
</Form.Item>
<Form.Item
label="Account"
>
<Input style={{color: "black"}} disabled bordered={false} value={`${this.getCurrencyString(this.state.currency)} ${this.state.price}`}/>
</Form.Item>
<Form.Item
label="Description"
>
<Input style={{color: "black"}} bordered={false} disabled value={`${this.state.description}`}/>
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 8 }}>
<Button size="large" type="primary" onClick={() => this.pay()}>
确认付款
</Button>
</Form.Item>
</Form>
</div>
)
}
</div>) :
( <div style={{display: "inline"}}>
<Result
status="error"
title="Pay Error"
subTitle={this.state.wrongMsg}
/>
</div>)
}
</div>
);
}
}
export default withRouter(Pay);

View File

@ -0,0 +1,71 @@
// Copyright 2021 The casbin 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 {Button, Result, Spin} from "antd";
import {withRouter} from "react-router-dom";
import * as PaymentBackend from "./backend/PaymentBackend"
class PaySuccessCallback extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
msg: null,
paymentId: this.GetQueryString("token"),
payerId: this.GetQueryString("PayerID")
};
}
// eslint-disable-next-line react/no-deprecated
componentWillMount() {
PaymentBackend.PaySuccess(this.state.paymentId, this.state.payerId).then(res => {
window.location.href = res
})
}
GetQueryString(key){
let reg = new RegExp("(^|&)"+ key +"=([^&]*)(&|$)");
let r = window.location.search.substr(1).match(reg);
if(r!=null)return unescape(r[2]); return null;
}
render() {
return (
<div style={{textAlign: "center"}}>
{
(this.state.msg === null) ? (
<Spin size="large" tip="支付完成,正在跳转..." style={{paddingTop: "10%"}} />
) : (
<div style={{display: "inline"}}>
<Result
status="error"
title="Login Error"
subTitle={this.state.msg}
extra={[
<Button type="primary" key="details">
Details
</Button>,
<Button key="help">Help</Button>,
]}
/>
</div>
)
}
</div>
);
}
}
export default withRouter(PaySuccessCallback);

208
web/src/PaymentListPage.js Normal file
View File

@ -0,0 +1,208 @@
// Copyright 2021 The casbin 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 {Link} from "react-router-dom";
import {Button, Popconfirm, Table} from 'antd';
import moment from "moment";
import * as Setting from "./Setting";
import * as PaymentBackend from "./backend/PaymentBackend";
import i18next from "i18next";
class PaymentListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
payments: [],
};
}
UNSAFE_componentWillMount() {
this.getPayments();
}
getPayments() {
PaymentBackend.getPayments().then(res => {
this.setState({
payments: res
})
})
}
deletePayment(i) {
PaymentBackend.deletePayment(this.state.payments[i]).then(res => {
Setting.showMessage("success", `Payment deleted successfully`);
this.setState({
payments: Setting.deleteRow(this.state.payments, i),
});
}).catch(error => {
Setting.showMessage("error", `Payment failed to delete: ${error}`);
});
}
renderTable(payments) {
const columns = [
{
title: "Id",
dataIndex: 'id',
key: 'id',
width: (Setting.isMobile()) ? "100px" : "170px",
fixed: 'left',
sorter: (a, b) => a.id.localeCompare(b.id),
},
{
title: "invoice",
dataIndex: 'invoice',
key: 'invoice',
width: '150px',
sorter: (a, b) => a.invoice.localeCompare(b.invoice),
},
{
title: "Application",
dataIndex: 'application',
key: 'application',
width: '120px',
sorter: (a, b) => a.application.localeCompare(b.application),
render: (text, record, index) => {
return (
<Link to={`/applications/${text.substring(text.indexOf("/")+1)}`}>
{text}
</Link>
)
}
},
{
title: "Amount",
dataIndex: 'pay_item',
key: 'pay_item',
width: '150px',
ellipsis: true,
render: (text, record, index) => {
return (
`amount: ${text.currency} ${text.price}`
)
}
},
{
title: "Status",
dataIndex: 'status',
key: 'status',
width: '120px',
sorter: (a, b) => a.status.localeCompare(b.status),
},
{
title: "Payer",
dataIndex: 'payer',
key: 'payer',
width: '120px',
render: (text, record, index) => {
return (
text === null ? "" : (
`${text?.name.surname} ${text?.name.given_name}`
)
)
}
},
{
title: "Payer Email",
dataIndex: 'payer',
key: 'payer',
width: '160px',
ellipsis: true,
render: (text, record, index) => {
return (
text === null ? "" : text.email_address
)
}
},
{
title: "Create_time",
dataIndex: 'create_time',
key: 'create_time',
width: '150px',
sorter: (a, b) => a.create_time.localeCompare(b.create_time),
},
{
title: "update_time",
dataIndex: 'update_time',
key: 'update_time',
width: '150px',
sorter: (a, b) => a.update_time.localeCompare(b.update_time),
},
{
title: "description",
dataIndex: 'pay_item',
key: 'pay_item',
ellipsis: true,
width: '150px',
render: (text, record, index) => {
return (
text.description
)
}
},
{
title: "Callback",
dataIndex: 'callback',
key: 'callback',
width: '100px',
},
{
title: i18next.t("general:Action"),
dataIndex: '',
key: 'op',
width: '80px',
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return (
<div>
<Popconfirm
title={`Sure to delete this payment ?`}
onConfirm={() => this.deletePayment(index)}
>
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
)
}
},
];
return (
<div>
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={payments} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => (
<div>
{"Payments"}&nbsp;&nbsp;&nbsp;&nbsp;
</div>
)}
loading={payments === null}
/>
</div>
);
}
render() {
return (
<div>
{
this.renderTable(this.state.payments)
}
</div>
);
}
}
export default PaymentListPage;

View File

@ -102,6 +102,12 @@ class ProviderEditPage extends React.Component {
{id: 'Aliyun OSS', name: 'Aliyun OSS'},
]
);
} else if (provider.category === "Payment") {
return (
[
{id: 'Paypal', name: 'PayPay'}
]
)
} else {
return [];
}
@ -201,6 +207,8 @@ class ProviderEditPage extends React.Component {
} else if (value === "Storage") {
this.updateProviderField('type', 'Local File System');
this.updateProviderField('domain', Setting.getFullServerUrl());
} else if (value === "Pay") {
this.updateProviderField('type', 'Paypal')
}
})}>
{
@ -209,6 +217,7 @@ class ProviderEditPage extends React.Component {
{id: 'Email', name: 'Email'},
{id: 'SMS', name: 'SMS'},
{id: 'Storage', name: 'Storage'},
{id: 'Payment', name: 'Payment'}
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
}
</Select>

View File

@ -151,6 +151,7 @@ class ProviderTable extends React.Component {
)
}
},
// {
// title: i18next.t("provider:alertType"),
// dataIndex: 'alertType',

View File

@ -30,13 +30,11 @@ class SelectLanguageBox extends React.Component {
};
}
onClick(e) {
Setting.changeLanguage(e.key);
};
render() {
const menu = (
<Menu onClick={this.onClick.bind(this)}>
<Menu onClick={(e) => {
Setting.changeLanguage(e.key);
}}>
<Menu.Item key="en" icon={<IconFont type="icon-en" />}>English</Menu.Item>
<Menu.Item key="zh" icon={<IconFont type="icon-zh" />}>简体中文</Menu.Item>
<Menu.Item key="fr" icon={<IconFont type="icon-fr" />}>Français</Menu.Item>

View File

@ -21,6 +21,7 @@ import i18next from "i18next";
import copy from "copy-to-clipboard";
import {authConfig} from "./auth/Auth";
import {Helmet} from "react-helmet";
import moment from "moment";
export let ServerUrl = "";
@ -308,20 +309,43 @@ export function getAvatarColor(s) {
return colorList[random % 4];
}
export function setLanguage() {
let language = localStorage.getItem('language');
if (language === undefined) {
language = "en"
}
i18next.changeLanguage(language)
export function setLanguage(language) {
localStorage.setItem("language", language);
changeMomentLanguage(language);
i18next.changeLanguage(language);
}
export function changeLanguage(language) {
localStorage.setItem("language", language)
i18next.changeLanguage(language)
localStorage.setItem("language", language);
changeMomentLanguage(language);
i18next.changeLanguage(language);
window.location.reload(true);
}
export function changeMomentLanguage(language) {
return;
if (language === "zh") {
moment.locale("zh", {
relativeTime: {
future: "%s内",
past: "%s前",
s: "几秒",
ss: "%d秒",
m: "1分钟",
mm: "%d分钟",
h: "1小时",
hh: "%d小时",
d: "1天",
dd: "%d天",
M: "1个月",
MM: "%d个月",
y: "1年",
yy: "%d年",
},
});
}
}
export function getClickable(text) {
return (
// eslint-disable-next-line jsx-a11y/anchor-is-valid

View File

@ -62,3 +62,10 @@ export function deleteApplication(application) {
body: JSON.stringify(newApplication),
}).then(res => res.json());
}
export function getApplicationByClientId(clientId){
return fetch(`${Setting.ServerUrl}/api/get-application-clientId?clientId=${clientId}`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}

View File

@ -0,0 +1,47 @@
// Copyright 2021 The casbin 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 * as Setting from "../Setting";
export function PaySuccess(paymentId, payerID){
return fetch(`${Setting.ServerUrl}/api/success-pay?paymentId=${paymentId}&payerId=${payerID}`, {
method: "GET",
credentials: "include",
}).then(res => res.json())
}
export function PaypalPal(payItem,clientId, redirectUri) {
return fetch(`http://localhost:8000/api/paypal?clientId=${clientId}&redirectUri=${redirectUri}`, {
method: "POST",
credentials: "include",
body: JSON.stringify(payItem),
}).then(res => res.json());
}
export function getPayments(){
return fetch(`${Setting.ServerUrl}/api/get-payments?`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}
export function deletePayment(payment){
let newPayment = Setting.deepCopy(payment);
return fetch(`${Setting.ServerUrl}/api/delete-payment`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newPayment),
}).then(res => res.json());
}

View File

@ -12,38 +12,85 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import i18n from 'i18next'
import zh from './locales/zh/data.json'
import en from './locales/en/data.json'
import fr from './locales/fr/data.json'
import de from './locales/de/data.json'
import ko from './locales/ko/data.json'
import ru from './locales/ru/data.json'
import ja from './locales/ja/data.json'
import i18n from "i18next";
import zh from "./locales/zh/data.json";
import en from "./locales/en/data.json";
import fr from "./locales/fr/data.json";
import de from "./locales/de/data.json";
import ko from "./locales/ko/data.json";
import ru from "./locales/ru/data.json";
import ja from "./locales/ja/data.json";
import * as Conf from "./Conf";
import * as Setting from "./Setting";
const resources = {
en: en,
zh: zh,
fr: fr,
ja: ja,
de: de,
ko: ko,
ru: ru,
ja: ja,
};
i18n
.init({
lng: "en",
function initLanguage() {
let language = localStorage.getItem("language");
if (language === undefined || language == null) {
if (Conf.ForceLanguage !== "") {
language = Conf.ForceLanguage;
} else {
let userLanguage;
userLanguage = navigator.language;
switch (userLanguage) {
case "zh-CN":
language = "zh";
break;
case "zh":
language = "zh";
break;
case "en":
language = "en";
break;
case "en-US":
language = "en";
break;
case "fr":
language = "fr";
break;
case "de":
language = "de";
break;
case "ko":
language = "ko";
break;
case "ru":
language = "ru";
break;
case "ja":
language = "ja";
break;
default:
language = Conf.DefaultLanguage;
}
}
}
Setting.changeMomentLanguage(language);
resources: resources,
return language;
}
keySeparator: false,
i18n.init({
lng: initLanguage(),
interpolation: {
escapeValue: false
},
saveMissing: true,
})
resources: resources,
export default i18n;
keySeparator: false,
interpolation: {
escapeValue: false,
},
//debug: true,
saveMissing: true,
});
export default i18n;