Documentation
¶
Overview ¶
Package native implements the payment package's v2 routes in the package itself, on the kernel's v2_payment_gateway, v2_payment_record and v2_payment tables adopted in place (kernel.storage.adopt). Legacy handlers and native routes share the tables, so a route can switch between them at any time. Responses are byte-compatible with the legacy handlers (internal/tests/paymentcompat).
The package owns the payment gateways and so holds their secrets (merchant keys, webhook secrets) in v2_payment_gateway; its administrator answers show them as a placeholder, as the kernel's do. An order is read through the kernel view kapi_order_billing_v1 (its buyer, total and status) only: a payment is created for the caller's own pending order and its exact total.
The four callback routes (the gateway callback, the x402 callback, the Stripe and PayPal webhooks) verify the provider's signature with the gateway's secrets, as the kernel's do (signatures.go). A paid callback then takes two steps (callbacks.go, docs/architecture/order-service.md):
- the package marks its payment record paid and adds it to its gateway's statistics, in one transaction on its own tables;
- the kernel applies the paid record to its order (KernelOrder.CompleteOrderPayment, kernel.order.complete.v1): it re-checks that the record pays the order, marks the order paid and completes it, in one transaction, once per trade number.
Every repeat of a callback whose record is already paid takes step 2 again, so a failure between the steps converges on the provider's next delivery; an order is never paid without a paid record. Without a bridge connection the callbacks have no native handler and stay legacy.
Index ¶
- Constants
- type BillingOrder
- type CreateGatewayRequest
- type CreatePaymentRequest
- type OrderCompleter
- type Payment
- type PaymentChannel
- type PaymentGateway
- type PaymentRecord
- type Service
- func (s *Service) AdminCreateGateway(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) AdminDeleteGateway(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) AdminGateways(ctx context.Context, _ pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) AdminRecords(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) AdminStats(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) AdminToggleGateway(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) AdminUpdateGateway(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) FiatCreatePayment(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) Handlers() map[string]pluginhostsdk.NativeHandler
- func (s *Service) PayPalWebhook(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) PaymentCallback(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) PaymentMethods(ctx context.Context, _ pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) PublicPaymentStatus(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) StripeWebhook(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) UserChannels(ctx context.Context, _ pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) UserCreatePayment(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) UserPaymentStatus(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) UserRecords(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) X402Callback(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) X402CheckPayment(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- func (s *Service) X402CreatePayment(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
- type UpdateGatewayRequest
Constants ¶
const ( CallbackRouteID = "payment.callback" X402CallbackRouteID = "payment.payment.x402.callback.post" StripeWebhookRouteID = "payment.payment.stripe.webhook.post" PayPalWebhookRouteID = "payment.payment.paypal.webhook.post" )
The callback routes' ids.
const ( PaymentStatusPending = 0 PaymentStatusPaid = 1 PaymentStatusCancelled = 2 PaymentStatusRefunded = 3 PaymentStatusExpired = 4 )
Payment statuses, as the kernel's model.PaymentStatus* constants.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BillingOrder ¶
type BillingOrder struct {
ID uint `gorm:"primaryKey"`
UserID uint
TotalAmount int64 // cents
Status int
}
BillingOrder is a row of kapi_order_billing_v1: what a payment needs of an order.
func (BillingOrder) TableName ¶
func (BillingOrder) TableName() string
TableName is the kernel view.
type CreateGatewayRequest ¶
type CreateGatewayRequest struct {
Name string `json:"name" binding:"required"`
Type string `json:"type" binding:"required,oneof=alipay wechat stripe usdt epay"`
Icon string `json:"icon"`
Config any `json:"config"`
FeeRate float64 `json:"fee_rate"`
FeeFixed float64 `json:"fee_fixed"`
MinAmount float64 `json:"min_amount"`
MaxAmount float64 `json:"max_amount"`
Sort int `json:"sort"`
Description string `json:"description"`
}
CreateGatewayRequest is the kernel's request type of the same name; the name appears in binding errors.
type CreatePaymentRequest ¶
type CreatePaymentRequest struct {
GatewayID uint `json:"gateway_id" binding:"required"`
Amount float64 `json:"amount" binding:"required,min=1"`
OrderID *uint `json:"order_id"`
}
CreatePaymentRequest is the kernel's request type of the same name; the name appears in binding errors.
type OrderCompleter ¶
type OrderCompleter interface {
CompleteOrderPayment(ctx context.Context, in *kernelorderv1.CompleteOrderPaymentRequest, opts ...grpc.CallOption) (*kernelorderv1.CompleteOrderPaymentResponse, error)
}
OrderCompleter is the part of KernelOrder the callbacks call.
type Payment ¶
type Payment struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"size:100" json:"name"`
Icon *string `gorm:"size:255" json:"icon"`
Method string `gorm:"size:20" json:"method"`
Provider string `gorm:"size:50" json:"provider"`
Config string `gorm:"type:text" json:"config"`
Notify *string `gorm:"size:255" json:"notify"`
Sort int `gorm:"default:0" json:"sort"`
Enable int `gorm:"default:1" json:"enable"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Payment is a v2_payment row: the older payment method configuration the method list reads.
type PaymentChannel ¶
type PaymentChannel struct {
ID uint `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Icon string `json:"icon"`
MinAmount float64 `json:"min_amount"`
MaxAmount float64 `json:"max_amount"`
FeeRate float64 `json:"fee_rate"`
FeeFixed float64 `json:"fee_fixed"`
Description string `json:"description"`
}
PaymentChannel is a gateway as users see it.
type PaymentGateway ¶
type PaymentGateway struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"size:100;not null" json:"name"`
Type string `gorm:"size:20;not null" json:"type"` // alipay/wechat/stripe/usdt/epay
Enabled bool `gorm:"default:false" json:"enabled"`
Icon string `gorm:"size:255" json:"icon"`
// Config is the gateway's JSON configuration, secrets included.
Config string `gorm:"type:text" json:"config"`
FeeRate float64 `gorm:"default:0" json:"fee_rate"`
FeeFixed float64 `gorm:"default:0" json:"fee_fixed"`
MinAmount float64 `gorm:"default:1" json:"min_amount"`
MaxAmount float64 `gorm:"default:10000" json:"max_amount"`
Sort int `gorm:"default:0" json:"sort"`
Description string `gorm:"size:500" json:"description"`
TotalOrders int64 `json:"total_orders"`
TotalAmount float64 `json:"total_amount"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
PaymentGateway is a v2_payment_gateway row. Its fields, tags and type name match the kernel model, so answers are the same.
func (PaymentGateway) TableName ¶
func (PaymentGateway) TableName() string
TableName is the adopted kernel table.
type PaymentRecord ¶
type PaymentRecord struct {
ID uint `gorm:"primaryKey" json:"id"`
TradeNo string `gorm:"size:64;uniqueIndex" json:"trade_no"`
GatewayID uint `json:"gateway_id"`
GatewayType string `json:"gateway_type"`
GatewayTradeNo string `gorm:"size:100" json:"gateway_trade_no"`
Provider string `gorm:"size:50" json:"provider"`
UserID uint `json:"user_id"`
Amount float64 `json:"amount"`
FeeAmount float64 `json:"fee_amount"`
ActualAmount float64 `json:"actual_amount"`
Currency string `gorm:"size:10;default:'CNY'" json:"currency"`
Status int `json:"status"`
PaidAt *time.Time `json:"paid_at"`
CancelledAt *time.Time `json:"cancelled_at"`
RefundedAt *time.Time `json:"refunded_at"`
NotifyData string `gorm:"type:text" json:"notify_data"`
ClientIP string `gorm:"size:50" json:"client_ip"`
TxHash *string `gorm:"size:255" json:"tx_hash"`
WalletAddress *string `gorm:"size:255" json:"wallet_address"`
Network *string `gorm:"size:50" json:"network"`
OrderID *uint `json:"order_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
PaymentRecord is a v2_payment_record row, with the kernel model's fields and tags.
func (PaymentRecord) TableName ¶
func (PaymentRecord) TableName() string
TableName is the adopted kernel table.
type Service ¶
type Service struct {
// Open returns the package's storage connection, on which the adopted
// tables and the granted view are visible.
Open func(ctx context.Context) (*gorm.DB, error)
// Orders is the kernel's KernelOrder; without it the callbacks have no
// native handler and stay legacy.
Orders OrderCompleter
// HTTPClient calls PayPal's API to verify a webhook delivery; nil uses
// a client with the kernel's 10-second timeout.
HTTPClient *http.Client
// Now defaults to time.Now.
Now func() time.Time
}
Service holds what the native routes need.
func (*Service) AdminCreateGateway ¶
func (s *Service) AdminCreateGateway(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
AdminCreateGateway is POST /api/v2/admin/payment/gateways. A new gateway is disabled; a secret sent as the placeholder is stored empty.
func (*Service) AdminDeleteGateway ¶
func (s *Service) AdminDeleteGateway(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
AdminDeleteGateway is DELETE /api/v2/admin/payment/gateways/:id; deleting a gateway that does not exist succeeds.
func (*Service) AdminGateways ¶
func (s *Service) AdminGateways(ctx context.Context, _ pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
AdminGateways is GET /api/v2/admin/payment/gateways, secrets redacted.
func (*Service) AdminRecords ¶
func (s *Service) AdminRecords(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
AdminRecords is GET /api/v2/admin/payment/records: newest first, filtered by status (a number or its name) and gateway type.
func (*Service) AdminStats ¶
func (s *Service) AdminStats(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
AdminStats is GET /api/v2/admin/payment/stats for the dates start to end (default: the last 30 days), as the kernel's GetPaymentStats and PaymentGatewayService.GetStats; failed sums count as zero, as there.
func (*Service) AdminToggleGateway ¶
func (s *Service) AdminToggleGateway(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
AdminToggleGateway is POST /api/v2/admin/payment/gateways/:id/toggle: the state sent as "enabled", else the opposite of the current one. Only a gateway type with a live callback may be enabled.
func (*Service) AdminUpdateGateway ¶
func (s *Service) AdminUpdateGateway(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
AdminUpdateGateway is PUT /api/v2/admin/payment/gateways/:id: the fields sent replace the stored ones, a secret sent as the placeholder keeps its stored value, and the whole row is saved.
func (*Service) FiatCreatePayment ¶
func (s *Service) FiatCreatePayment(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
FiatCreatePayment is POST /api/v2/payment/fiat/create. The caller's order is validated as in the kernel, then a Stripe or PayPal payment is refused: neither provider's checkout is implemented, and an earlier version answered a simulated checkout link and stored a pending payment record that nothing could ever pay. No payment record is created.
func (*Service) Handlers ¶
func (s *Service) Handlers() map[string]pluginhostsdk.NativeHandler
Handlers returns the native handlers by route id.
func (*Service) PayPalWebhook ¶
func (s *Service) PayPalWebhook(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
PayPalWebhook is POST /api/v2/payment/paypal/webhook: a PayPal event that PayPal's API confirms. Only a completed capture pays (#74): it marks the payment paid when its amount and currency match, and completes its order; a denied capture or a processing order cancels the payment.
func (*Service) PaymentCallback ¶
func (s *Service) PaymentCallback(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
PaymentCallback is POST /api/v2/payment/callback/:type: a registered gateway's signed notification (EPay's, in its query) marks the payment paid and completes its order. It answers "success", or "fail" with 400.
func (*Service) PaymentMethods ¶
func (s *Service) PaymentMethods(ctx context.Context, _ pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
PaymentMethods is GET /api/v2/payment/methods: the fixed method list, each enabled when an enabled v2_payment row names its provider.
func (*Service) PublicPaymentStatus ¶
func (s *Service) PublicPaymentStatus(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
PublicPaymentStatus is GET /api/v2/payment/status/:trade_no, which needs no login: the trade number is the payment's reference.
func (*Service) StripeWebhook ¶
func (s *Service) StripeWebhook(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
StripeWebhook is POST /api/v2/payment/stripe/webhook: a Stripe event, signed with the gateway's webhook secret within five minutes. A completed checkout marks the payment paid and completes its order; an expired or failed one ends the payment.
func (*Service) UserChannels ¶
func (s *Service) UserChannels(ctx context.Context, _ pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
UserChannels is GET /api/v2/user/payment/channels: the enabled gateways, without their configuration.
func (*Service) UserCreatePayment ¶
func (s *Service) UserCreatePayment(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
UserCreatePayment is POST /api/v2/user/payment/create: a payment through an enabled gateway, within its limits, with its fee. A payment for an order pays the caller's own pending order in full.
func (*Service) UserPaymentStatus ¶
func (s *Service) UserPaymentStatus(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
UserPaymentStatus is GET /api/v2/user/payment/status/:trade_no for the caller's own payment; another user's is answered as missing.
func (*Service) UserRecords ¶
func (s *Service) UserRecords(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
UserRecords is GET /api/v2/user/payment/records: the caller's records, newest first. The page is not clamped, as in the kernel.
func (*Service) X402Callback ¶
func (s *Service) X402Callback(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
X402Callback is POST /api/v2/payment/x402/callback: the confirmation service's signed report on a crypto payment. A confirmed transfer of the payment's token and amount marks it paid and completes its order; a failed one cancels the payment.
func (*Service) X402CheckPayment ¶
func (s *Service) X402CheckPayment(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
X402CheckPayment is GET /api/v2/payment/x402/check/:id: the caller's payment by trade number, else by numeric id; another user's is answered as missing.
func (*Service) X402CreatePayment ¶
func (s *Service) X402CreatePayment(ctx context.Context, request pluginhostsdk.NativeRequest) (pluginhostsdk.NativeResponse, error)
X402CreatePayment is POST /api/v2/payment/x402/create: a test-chain crypto payment of the caller's pending order, to a generated address.
type UpdateGatewayRequest ¶
type UpdateGatewayRequest struct {
Name string `json:"name"`
Type string `json:"type" binding:"omitempty,oneof=alipay wechat stripe usdt epay"`
Icon string `json:"icon"`
Config any `json:"config"`
FeeRate *float64 `json:"fee_rate"`
FeeFixed *float64 `json:"fee_fixed"`
MinAmount *float64 `json:"min_amount"`
MaxAmount *float64 `json:"max_amount"`
Sort *int `json:"sort"`
Description string `json:"description"`
}
UpdateGatewayRequest is the kernel's request type of the same name.