forked from casdoor/casdoor
feat: add Polar payment provider (#4595)
This commit is contained in:
4
go.mod
4
go.mod
@@ -43,6 +43,7 @@ require (
|
||||
github.com/markbates/goth v1.79.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/nyaruka/phonenumbers v1.2.2
|
||||
github.com/polarsource/polar-go v0.12.0
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.11.1
|
||||
github.com/prometheus/client_model v0.4.0
|
||||
@@ -53,7 +54,7 @@ require (
|
||||
github.com/sendgrid/sendgrid-go v3.14.0+incompatible
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/stripe/stripe-go/v74 v74.29.0
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
github.com/thanhpk/randstr v1.0.4
|
||||
@@ -197,6 +198,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||
github.com/slack-go/slack v0.12.3 // indirect
|
||||
github.com/spyzhov/ajson v0.8.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -788,6 +788,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
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/polarsource/polar-go v0.12.0 h1:um+6ftOPUMg2TQq9Kv/6fKGBOAl7dOc2YiDdx4Bb0y8=
|
||||
github.com/polarsource/polar-go v0.12.0/go.mod h1:FB11Q4m2n3wIk6l/POOkz0MVOUx1o0Yt4Y97MnQfe0c=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
@@ -894,6 +896,8 @@ github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJ
|
||||
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM=
|
||||
github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spyzhov/ajson v0.8.0 h1:sFXyMbi4Y/BKjrsfkUZHSjA2JM1184enheSjjoT/zCc=
|
||||
github.com/spyzhov/ajson v0.8.0/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=
|
||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
@@ -916,8 +920,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stripe/stripe-go/v74 v74.29.0 h1:ffJ+1Ta1Ccg7yDDz+SfjixX0KizEEJ/wNVRoFYkdwFY=
|
||||
github.com/stripe/stripe-go/v74 v74.29.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
|
||||
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
|
||||
@@ -345,6 +345,12 @@ func GetPaymentProvider(p *Provider) (pp.PaymentProvider, error) {
|
||||
return nil, err
|
||||
}
|
||||
return pp, nil
|
||||
} else if typ == "Polar" {
|
||||
pp, err := pp.NewPolarPaymentProvider(p.ClientSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pp, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||
}
|
||||
|
||||
157
pp/polar.go
Normal file
157
pp/polar.go
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
package pp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
polargo "github.com/polarsource/polar-go"
|
||||
"github.com/polarsource/polar-go/models/components"
|
||||
)
|
||||
|
||||
type PolarPaymentProvider struct {
|
||||
Client *polargo.Polar
|
||||
}
|
||||
|
||||
func NewPolarPaymentProvider(accessToken string) (*PolarPaymentProvider, error) {
|
||||
client := polargo.New(
|
||||
polargo.WithSecurity(accessToken),
|
||||
)
|
||||
|
||||
pp := &PolarPaymentProvider{
|
||||
Client: client,
|
||||
}
|
||||
return pp, nil
|
||||
}
|
||||
|
||||
func (pp *PolarPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Store product info in metadata for later retrieval
|
||||
description := joinAttachString([]string{r.ProductName, r.ProductDisplayName, r.ProviderName})
|
||||
|
||||
metadata := map[string]components.CheckoutCreateMetadata{
|
||||
"payment_name": components.CreateCheckoutCreateMetadataStr(r.PaymentName),
|
||||
"product_description": components.CreateCheckoutCreateMetadataStr(description),
|
||||
"product_name": components.CreateCheckoutCreateMetadataStr(r.ProductName),
|
||||
"product_display_name": components.CreateCheckoutCreateMetadataStr(r.ProductDisplayName),
|
||||
"provider_name": components.CreateCheckoutCreateMetadataStr(r.ProviderName),
|
||||
}
|
||||
|
||||
checkoutCreate := components.CheckoutCreate{
|
||||
CustomerName: polargo.Pointer(r.PayerName),
|
||||
CustomerEmail: polargo.Pointer(r.PayerEmail),
|
||||
SuccessURL: polargo.Pointer(r.ReturnUrl),
|
||||
Metadata: metadata,
|
||||
Amount: polargo.Pointer(priceFloat64ToInt64(r.Price)),
|
||||
}
|
||||
|
||||
res, err := pp.Client.Checkouts.Create(ctx, checkoutCreate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.Checkout == nil {
|
||||
return nil, fmt.Errorf("polar checkout response is nil")
|
||||
}
|
||||
|
||||
payResp := &PayResp{
|
||||
PayUrl: res.Checkout.URL,
|
||||
OrderId: res.Checkout.ID,
|
||||
}
|
||||
return payResp, nil
|
||||
}
|
||||
|
||||
func (pp *PolarPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get checkout session status
|
||||
res, err := pp.Client.Checkouts.Get(ctx, orderId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.Checkout == nil {
|
||||
return nil, fmt.Errorf("polar checkout not found for order: %s", orderId)
|
||||
}
|
||||
|
||||
checkout := res.Checkout
|
||||
|
||||
// Map Polar status to payment state
|
||||
switch checkout.Status {
|
||||
case components.CheckoutStatusOpen:
|
||||
return &NotifyResult{PaymentStatus: PaymentStateCreated}, nil
|
||||
case components.CheckoutStatusSucceeded:
|
||||
// Payment successful, continue to extract payment details below
|
||||
case components.CheckoutStatusConfirmed:
|
||||
// Payment confirmed but not yet succeeded
|
||||
return &NotifyResult{PaymentStatus: PaymentStateCreated}, nil
|
||||
case components.CheckoutStatusExpired:
|
||||
return &NotifyResult{PaymentStatus: PaymentStateTimeout}, nil
|
||||
case components.CheckoutStatusFailed:
|
||||
return &NotifyResult{PaymentStatus: PaymentStateError, NotifyMessage: "Payment failed"}, nil
|
||||
default:
|
||||
return &NotifyResult{PaymentStatus: PaymentStateError, NotifyMessage: fmt.Sprintf("unexpected polar checkout status: %v", checkout.Status)}, nil
|
||||
}
|
||||
|
||||
// Extract payment details from checkout for successful payment
|
||||
var (
|
||||
paymentName string
|
||||
productName string
|
||||
productDisplayName string
|
||||
providerName string
|
||||
)
|
||||
|
||||
if checkout.Metadata != nil {
|
||||
if v, ok := checkout.Metadata["payment_name"]; ok && v.Str != nil {
|
||||
paymentName = *v.Str
|
||||
}
|
||||
if v, ok := checkout.Metadata["product_name"]; ok && v.Str != nil {
|
||||
productName = *v.Str
|
||||
}
|
||||
if v, ok := checkout.Metadata["product_display_name"]; ok && v.Str != nil {
|
||||
productDisplayName = *v.Str
|
||||
}
|
||||
if v, ok := checkout.Metadata["provider_name"]; ok && v.Str != nil {
|
||||
providerName = *v.Str
|
||||
}
|
||||
}
|
||||
|
||||
return &NotifyResult{
|
||||
PaymentName: paymentName,
|
||||
PaymentStatus: PaymentStatePaid,
|
||||
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProviderName: providerName,
|
||||
|
||||
Price: priceInt64ToFloat64(checkout.TotalAmount),
|
||||
Currency: checkout.Currency,
|
||||
|
||||
OrderId: orderId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (pp *PolarPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (pp *PolarPaymentProvider) GetResponseError(err error) string {
|
||||
if err == nil {
|
||||
return "success"
|
||||
}
|
||||
return "fail"
|
||||
}
|
||||
@@ -288,6 +288,10 @@ export const OtherProviderInfo = {
|
||||
logo: `${StaticBaseUrl}/img/payment_gc.png`,
|
||||
url: "https://gc.org",
|
||||
},
|
||||
"Polar": {
|
||||
logo: `${StaticBaseUrl}/img/payment_polar.png`,
|
||||
url: "https://polar.sh/",
|
||||
},
|
||||
},
|
||||
Captcha: {
|
||||
"Default": {
|
||||
@@ -1262,6 +1266,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: "Stripe", name: "Stripe"},
|
||||
{id: "AirWallex", name: "AirWallex"},
|
||||
{id: "GC", name: "GC"},
|
||||
{id: "Polar", name: "Polar"},
|
||||
]);
|
||||
} else if (category === "Captcha") {
|
||||
return ([
|
||||
|
||||
Reference in New Issue
Block a user