Add NotifyPayment API.

This commit is contained in:
Yang Luo 2022-03-07 00:33:45 +08:00
parent bf5d4eea48
commit a4fc04474e
17 changed files with 215 additions and 105 deletions

View File

@ -16,10 +16,12 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination" "github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay/alipay"
) )
// GetPayments // GetPayments
@ -122,12 +124,18 @@ func (c *ApiController) DeletePayment() {
// @Success 200 {object} controllers.Response The Response object // @Success 200 {object} controllers.Response The Response object
// @router /notify-payment [post] // @router /notify-payment [post]
func (c *ApiController) NotifyPayment() { func (c *ApiController) NotifyPayment() {
var payment object.Payment bm, err := alipay.ParseNotifyToBodyMap(c.Ctx.Request)
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payment)
if err != nil { if err != nil {
panic(err) panic(err)
} }
c.Data["json"] = wrapActionResponse(object.NotifyPayment("111", "222")) ok := object.NotifyPayment(bm)
c.ServeJSON() if ok {
_, err = c.Ctx.ResponseWriter.Write([]byte("success"))
if err != nil {
panic(err)
}
} else {
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
}
} }

View File

@ -18,6 +18,8 @@ import (
"fmt" "fmt"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay"
"xorm.io/core" "xorm.io/core"
) )
@ -27,13 +29,14 @@ type Payment struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"` DisplayName string `xorm:"varchar(100)" json:"displayName"`
Provider string `xorm:"varchar(100)" json:"provider"` Provider string `xorm:"varchar(100)" json:"provider"`
Type string `xorm:"varchar(100)" json:"type"` Type string `xorm:"varchar(100)" json:"type"`
Organization string `xorm:"varchar(100)" json:"organization"` Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"` User string `xorm:"varchar(100)" json:"user"`
Good string `xorm:"varchar(100)" json:"good"` ProductId string `xorm:"varchar(100)" json:"productId"`
Amount string `xorm:"varchar(100)" json:"amount"` ProductName string `xorm:"varchar(100)" json:"productName"`
Currency string `xorm:"varchar(100)" json:"currency"` Price float64 `json:"price"`
Currency string `xorm:"varchar(100)" json:"currency"`
State string `xorm:"varchar(100)" json:"state"` State string `xorm:"varchar(100)" json:"state"`
} }
@ -124,16 +127,58 @@ func DeletePayment(payment *Payment) bool {
return affected != 0 return affected != 0
} }
func NotifyPayment(id string, state string) bool { func NotifyPayment(bm gopay.BodyMap) bool {
owner, name := util.GetOwnerAndNameFromId(id) owner := "admin"
payment := getPayment(owner, name) productName := bm.Get("subject")
if payment == nil { paymentId := bm.Get("out_trade_no")
return false priceString := bm.Get("total_amount")
price := util.ParseFloat(priceString)
productId := bm.Get("productId")
providerId := bm.Get("providerId")
product := getProduct(owner, productId)
if product == nil {
panic(fmt.Errorf("the product: %s does not exist", productId))
} }
payment.State = state if productName != product.DisplayName {
panic(fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", productName, product.DisplayName))
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(payment) if price != product.Price {
panic(fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", price, product.Price))
}
payment := getPayment(owner, paymentId)
if payment == nil {
panic(fmt.Errorf("the payment: %s does not exist", paymentId))
}
provider, err := product.getProvider(providerId)
if err != nil {
panic(err)
}
cert := getCert(owner, provider.Cert)
if cert == nil {
panic(fmt.Errorf("the cert: %s does not exist", provider.Cert))
}
ok, err := alipay.VerifySignWithCert(cert.AuthorityPublicKey, bm)
if err != nil {
panic(err)
}
if ok {
payment.State = "Paid"
} else {
if cert == nil {
panic(fmt.Errorf("VerifySignWithCert() failed: %v", ok))
}
//payment.State = "Failed"
}
affected, err := adapter.Engine.ID(core.PK{owner, paymentId}).AllCols().Update(payment)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -139,19 +139,28 @@ func (product *Product) isValidProvider(provider *Provider) bool {
return false return false
} }
func (product *Product) getProvider(providerId string) (*Provider, error) {
provider := getProvider(product.Owner, providerId)
if provider == nil {
return nil, fmt.Errorf("the payment provider: %s does not exist", providerId)
}
if !product.isValidProvider(provider) {
return nil, fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerId, product.Name)
}
return provider, nil
}
func BuyProduct(id string, providerId string, host string) (string, error) { func BuyProduct(id string, providerId string, host string) (string, error) {
product := GetProduct(id) product := GetProduct(id)
if product == nil { if product == nil {
return "", fmt.Errorf("the product: %s does not exist", id) return "", fmt.Errorf("the product: %s does not exist", id)
} }
provider := getProvider(product.Owner, providerId) provider, err := product.getProvider(providerId)
if provider == nil { if err != nil {
return "", fmt.Errorf("the payment provider: %s does not exist", providerId) return "", err
}
if !product.isValidProvider(provider) {
return "", fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerId, id)
} }
cert := getCert(product.Owner, provider.Cert) cert := getCert(product.Owner, provider.Cert)
@ -170,6 +179,6 @@ func BuyProduct(id string, providerId string, host string) (string, error) {
returnUrl := fmt.Sprintf("%s/payments/%s", originFrontend, paymentId) returnUrl := fmt.Sprintf("%s/payments/%s", originFrontend, paymentId)
notifyUrl := fmt.Sprintf("%s/api/notify-payment", originBackend) notifyUrl := fmt.Sprintf("%s/api/notify-payment", originBackend)
payUrl, err := pProvider.Pay(product.DisplayName, paymentId, product.Price, returnUrl, notifyUrl) payUrl, err := pProvider.Pay(product.DisplayName, product.Name, provider.Name, paymentId, product.Price, returnUrl, notifyUrl)
return payUrl, err return payUrl, err
} }

View File

@ -32,7 +32,7 @@ func TestProvider(t *testing.T) {
paymentId := util.GenerateTimeId() paymentId := util.GenerateTimeId()
returnUrl := "" returnUrl := ""
notifyUrl := "" notifyUrl := ""
payUrl, err := pProvider.Pay(product.DisplayName, paymentId, product.Price, returnUrl, notifyUrl) payUrl, err := pProvider.Pay(product.DisplayName, product.Name, provider.Name, paymentId, product.Price, returnUrl, notifyUrl)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -44,7 +44,7 @@ func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey s
return pp return pp
} }
func (pp *AlipayPaymentProvider) Pay(productName string, paymentId string, price float64, returnUrl string, notifyUrl string) (string, error) { func (pp *AlipayPaymentProvider) Pay(productName string, productId string, providerId string, paymentId string, price float64, returnUrl string, notifyUrl string) (string, error) {
pp.Client.DebugSwitch = gopay.DebugOn pp.Client.DebugSwitch = gopay.DebugOn
priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".") priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".")
@ -53,10 +53,12 @@ func (pp *AlipayPaymentProvider) Pay(productName string, paymentId string, price
bm.Set("subject", productName) bm.Set("subject", productName)
bm.Set("out_trade_no", paymentId) bm.Set("out_trade_no", paymentId)
bm.Set("total_amount", priceString) bm.Set("total_amount", priceString)
bm.Set("return_url", returnUrl) bm.Set("return_url", returnUrl)
bm.Set("notify_url", notifyUrl) bm.Set("notify_url", notifyUrl)
bm.Set("productId", productId)
bm.Set("providerId", productId)
payUrl, err := pp.Client.TradePagePay(context.Background(), bm) payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
if err != nil { if err != nil {
return "", err return "", err

View File

@ -15,7 +15,7 @@
package pp package pp
type PaymentProvider interface { type PaymentProvider interface {
Pay(productName string, paymentId string, price float64, returnUrl string, notifyUrl string) (string, error) Pay(productName string, productId string, providerId string, paymentId string, price float64, returnUrl string, notifyUrl string) (string, error)
} }
func GetPaymentProvider(typ string, appId string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider { func GetPaymentProvider(typ string, appId string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {

View File

@ -42,6 +42,15 @@ func ParseInt(s string) int {
return i return i
} }
func ParseFloat(s string) float64 {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
panic(err)
}
return f
}
func ParseBool(s string) bool { func ParseBool(s string) bool {
i := ParseInt(s) i := ParseInt(s)
return i != 0 return i != 0

View File

@ -112,7 +112,7 @@ class PaymentEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Type"), i18next.t("payment:Type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.payment.type} onChange={e => { <Input value={this.state.payment.type} onChange={e => {
@ -122,20 +122,20 @@ class PaymentEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("payment:Good"), i18next.t("payment:Good - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Product"), i18next.t("payment:Product - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.payment.good} onChange={e => { <Input value={this.state.payment.productName} onChange={e => {
// this.updatePaymentField('good', e.target.value); // this.updatePaymentField('productName', e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("payment:Amount"), i18next.t("payment:Amount - Tooltip"))} : {Setting.getLabel(i18next.t("payment:Price"), i18next.t("payment:Price - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.payment.amount} onChange={e => { <Input value={this.state.payment.price} onChange={e => {
// this.updatePaymentField('amount', e.target.value); // this.updatePaymentField('amount', e.target.value);
}} /> }} />
</Col> </Col>

View File

@ -34,8 +34,9 @@ class PaymentListPage extends BaseListPage {
type: "PayPal", type: "PayPal",
organization: "built-in", organization: "built-in",
user: "admin", user: "admin",
good: "A notebook computer", productId: "computer-1",
amount: "300", productName: "A notebook computer",
price: 300.00,
currency: "USD", currency: "USD",
state: "Paid", state: "Paid",
} }
@ -151,7 +152,7 @@ class PaymentListPage extends BaseListPage {
} }
}, },
{ {
title: i18next.t("provider:Type"), title: i18next.t("payment:Type"),
dataIndex: 'type', dataIndex: 'type',
key: 'type', key: 'type',
width: '110px', width: '110px',
@ -165,20 +166,20 @@ class PaymentListPage extends BaseListPage {
} }
}, },
{ {
title: i18next.t("payment:Good"), title: i18next.t("payment:Product"),
dataIndex: 'good', dataIndex: 'productName',
key: 'good', key: 'productName',
width: '160px', width: '160px',
sorter: true, sorter: true,
...this.getColumnSearchProps('good'), ...this.getColumnSearchProps('productName'),
}, },
{ {
title: i18next.t("payment:Amount"), title: i18next.t("payment:Price"),
dataIndex: 'amount', dataIndex: 'price',
key: 'amount', key: 'price',
width: '120px', width: '120px',
sorter: true, sorter: true,
...this.getColumnSearchProps('amount'), ...this.getColumnSearchProps('price'),
}, },
{ {
title: i18next.t("payment:Currency"), title: i18next.t("payment:Currency"),

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Descriptions} from "antd"; import {Button, Descriptions, Spin} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import * as ProductBackend from "./backend/ProductBackend"; import * as ProductBackend from "./backend/ProductBackend";
import * as ProviderBackend from "./backend/ProviderBackend"; import * as ProviderBackend from "./backend/ProviderBackend";
@ -28,6 +28,7 @@ class ProductBuyPage extends React.Component {
productName: props.match?.params.productName, productName: props.match?.params.productName,
product: null, product: null,
providers: [], providers: [],
isPlacingOrder: false,
}; };
} }
@ -109,16 +110,21 @@ class ProductBuyPage extends React.Component {
} }
buyProduct(product, provider) { buyProduct(product, provider) {
this.setState({
isPlacingOrder: true,
});
ProductBackend.buyProduct(this.state.product.owner, this.state.productName, provider.name) ProductBackend.buyProduct(this.state.product.owner, this.state.productName, provider.name)
.then((res) => { .then((res) => {
if (res.msg === "") { if (res.msg === "") {
const payUrl = res.data; const payUrl = res.data;
Setting.goToLink(payUrl); Setting.goToLink(payUrl);
this.setState({
productName: this.state.product.name,
});
} else { } else {
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
this.setState({
isPlacingOrder: false,
});
} }
}) })
.catch(error => { .catch(error => {
@ -182,31 +188,33 @@ class ProductBuyPage extends React.Component {
return ( return (
<div> <div>
<Descriptions title={i18next.t("product:Buy Product")} bordered> <Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} >
<Descriptions.Item label={i18next.t("general:Name")} span={3}> <Descriptions title={i18next.t("product:Buy Product")} bordered>
<Descriptions.Item label={i18next.t("general:Name")} span={3}>
<span style={{fontSize: 28}}> <span style={{fontSize: 28}}>
{product?.displayName} {product?.displayName}
</span> </span>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Detail")}><span style={{fontSize: 16}}>{product?.detail}</span></Descriptions.Item> <Descriptions.Item label={i18next.t("product:Detail")}><span style={{fontSize: 16}}>{product?.detail}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Tag")}><span style={{fontSize: 16}}>{product?.tag}</span></Descriptions.Item> <Descriptions.Item label={i18next.t("product:Tag")}><span style={{fontSize: 16}}>{product?.tag}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item> <Descriptions.Item label={i18next.t("product:SKU")}><span style={{fontSize: 16}}>{product?.name}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Image")} span={3}> <Descriptions.Item label={i18next.t("product:Image")} span={3}>
<img src={product?.image} alt={product?.image} height={90} style={{marginBottom: '20px'}}/> <img src={product?.image} alt={product?.image} height={90} style={{marginBottom: '20px'}}/>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Price")}> <Descriptions.Item label={i18next.t("product:Price")}>
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}> <span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
{`${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`} {`${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`}
</span> </span>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Quantity")}><span style={{fontSize: 16}}>{product?.quantity}</span></Descriptions.Item> <Descriptions.Item label={i18next.t("product:Quantity")}><span style={{fontSize: 16}}>{product?.quantity}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Sold")}><span style={{fontSize: 16}}>{product?.sold}</span></Descriptions.Item> <Descriptions.Item label={i18next.t("product:Sold")}><span style={{fontSize: 16}}>{product?.sold}</span></Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Pay")} span={3}> <Descriptions.Item label={i18next.t("product:Pay")} span={3}>
{ {
this.renderPay(product) this.renderPay(product)
} }
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
</Spin>
</div> </div>
) )
} }

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier" "Website URL - Tooltip": "Unique string-style identifier"
}, },
"payment": { "payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency", "Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip", "Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment", "Edit Payment": "Edit Payment",
"Good": "Good", "New Payment": "New Payment",
"Good - Tooltip": "Good - Tooltip", "Price": "Price",
"New Payment": "New Payment" "Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
}, },
"permission": { "permission": {
"Actions": "Aktionen", "Actions": "Aktionen",
@ -274,6 +276,7 @@
}, },
"product": { "product": {
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY", "CNY": "CNY",
"Currency": "Currency", "Currency": "Currency",
@ -288,6 +291,7 @@
"Payment providers": "Payment providers", "Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip", "Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal", "Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price", "Price": "Price",
"Price - Tooltip": "Price - Tooltip", "Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity", "Quantity": "Quantity",

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "Website URL - Tooltip" "Website URL - Tooltip": "Website URL - Tooltip"
}, },
"payment": { "payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency", "Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip", "Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment", "Edit Payment": "Edit Payment",
"Good": "Good", "New Payment": "New Payment",
"Good - Tooltip": "Good - Tooltip", "Price": "Price",
"New Payment": "New Payment" "Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
}, },
"permission": { "permission": {
"Actions": "Actions", "Actions": "Actions",
@ -274,6 +276,7 @@
}, },
"product": { "product": {
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY", "CNY": "CNY",
"Currency": "Currency", "Currency": "Currency",
@ -288,6 +291,7 @@
"Payment providers": "Payment providers", "Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip", "Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal", "Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price", "Price": "Price",
"Price - Tooltip": "Price - Tooltip", "Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity", "Quantity": "Quantity",

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier" "Website URL - Tooltip": "Unique string-style identifier"
}, },
"payment": { "payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency", "Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip", "Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment", "Edit Payment": "Edit Payment",
"Good": "Good", "New Payment": "New Payment",
"Good - Tooltip": "Good - Tooltip", "Price": "Price",
"New Payment": "New Payment" "Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
}, },
"permission": { "permission": {
"Actions": "Actions", "Actions": "Actions",
@ -274,6 +276,7 @@
}, },
"product": { "product": {
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY", "CNY": "CNY",
"Currency": "Currency", "Currency": "Currency",
@ -288,6 +291,7 @@
"Payment providers": "Payment providers", "Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip", "Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal", "Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price", "Price": "Price",
"Price - Tooltip": "Price - Tooltip", "Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity", "Quantity": "Quantity",

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier" "Website URL - Tooltip": "Unique string-style identifier"
}, },
"payment": { "payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency", "Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip", "Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment", "Edit Payment": "Edit Payment",
"Good": "Good", "New Payment": "New Payment",
"Good - Tooltip": "Good - Tooltip", "Price": "Price",
"New Payment": "New Payment" "Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
}, },
"permission": { "permission": {
"Actions": "アクション", "Actions": "アクション",
@ -274,6 +276,7 @@
}, },
"product": { "product": {
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY", "CNY": "CNY",
"Currency": "Currency", "Currency": "Currency",
@ -288,6 +291,7 @@
"Payment providers": "Payment providers", "Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip", "Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal", "Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price", "Price": "Price",
"Price - Tooltip": "Price - Tooltip", "Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity", "Quantity": "Quantity",

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier" "Website URL - Tooltip": "Unique string-style identifier"
}, },
"payment": { "payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency", "Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip", "Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment", "Edit Payment": "Edit Payment",
"Good": "Good", "New Payment": "New Payment",
"Good - Tooltip": "Good - Tooltip", "Price": "Price",
"New Payment": "New Payment" "Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
}, },
"permission": { "permission": {
"Actions": "Actions", "Actions": "Actions",
@ -274,6 +276,7 @@
}, },
"product": { "product": {
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY", "CNY": "CNY",
"Currency": "Currency", "Currency": "Currency",
@ -288,6 +291,7 @@
"Payment providers": "Payment providers", "Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip", "Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal", "Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price", "Price": "Price",
"Price - Tooltip": "Price - Tooltip", "Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity", "Quantity": "Quantity",

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "Unique string-style identifier" "Website URL - Tooltip": "Unique string-style identifier"
}, },
"payment": { "payment": {
"Amount": "Amount",
"Amount - Tooltip": "Amount - Tooltip",
"Currency": "Currency", "Currency": "Currency",
"Currency - Tooltip": "Currency - Tooltip", "Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment", "Edit Payment": "Edit Payment",
"Good": "Good", "New Payment": "New Payment",
"Good - Tooltip": "Good - Tooltip", "Price": "Price",
"New Payment": "New Payment" "Price - Tooltip": "Price - Tooltip",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
}, },
"permission": { "permission": {
"Actions": "Действия", "Actions": "Действия",
@ -274,6 +276,7 @@
}, },
"product": { "product": {
"Alipay": "Alipay", "Alipay": "Alipay",
"Buy": "Buy",
"Buy Product": "Buy Product", "Buy Product": "Buy Product",
"CNY": "CNY", "CNY": "CNY",
"Currency": "Currency", "Currency": "Currency",
@ -288,6 +291,7 @@
"Payment providers": "Payment providers", "Payment providers": "Payment providers",
"Payment providers - Tooltip": "Payment providers - Tooltip", "Payment providers - Tooltip": "Payment providers - Tooltip",
"Paypal": "Paypal", "Paypal": "Paypal",
"Placing order...": "Placing order...",
"Price": "Price", "Price": "Price",
"Price - Tooltip": "Price - Tooltip", "Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity", "Quantity": "Quantity",

View File

@ -252,14 +252,16 @@
"Website URL - Tooltip": "网页地址" "Website URL - Tooltip": "网页地址"
}, },
"payment": { "payment": {
"Amount": "金额",
"Amount - Tooltip": "付款的金额",
"Currency": "币种", "Currency": "币种",
"Currency - Tooltip": "如USD美元CNY人民币等", "Currency - Tooltip": "如USD美元CNY人民币等",
"Edit Payment": "编辑付款", "Edit Payment": "编辑付款",
"Good": "商品", "New Payment": "添加付款",
"Good - Tooltip": "购买的商品名称", "Price": "价格",
"New Payment": "添加付款" "Price - Tooltip": "商品价格",
"Product": "商品",
"Product - Tooltip": "商品名称",
"Type": "支付方式",
"Type - Tooltip": "商品购买时的支付方式"
}, },
"permission": { "permission": {
"Actions": "动作", "Actions": "动作",
@ -274,6 +276,7 @@
}, },
"product": { "product": {
"Alipay": "支付宝", "Alipay": "支付宝",
"Buy": "购买",
"Buy Product": "购买商品", "Buy Product": "购买商品",
"CNY": "人民币", "CNY": "人民币",
"Currency": "币种", "Currency": "币种",
@ -288,6 +291,7 @@
"Payment providers": "支付提供商", "Payment providers": "支付提供商",
"Payment providers - Tooltip": "支付提供商 - 工具提示", "Payment providers - Tooltip": "支付提供商 - 工具提示",
"Paypal": "Paypal", "Paypal": "Paypal",
"Placing order...": "正在下单...",
"Price": "价格", "Price": "价格",
"Price - Tooltip": "价格 - 工具提示", "Price - Tooltip": "价格 - 工具提示",
"Quantity": "库存", "Quantity": "库存",