package paypal import ( "context" "errors" "sync" ) type ( // App端需要的关键参数(示例:返回订单ID与跳转链接) AppPayParams struct { OrderID string `json:"order_id"` ApproveLink string `json:"approve_link"` } ISys interface { GenerateOrderNo(prefix string) string CreateAppOrder(ctx context.Context, outTradeNo string, totalFee int64, description string, return_url string, cancel_url string) (result string, err error) CaptureOrder(ctx context.Context, orderID string) (result string, err error) VerifyWebhook(headers map[string]string, body []byte) (bool, error) } ) // defsys 由 OnInit(启动) 与 Reload(运行时热更) 写、各包级函数读,故必须加锁: // 无锁并发读写接口值是 data race。 var ( mu sync.RWMutex defsys ISys ) // ErrNotInited PayPal 未初始化。OnInit 失败时调用方可选择降级;此时包级函数返回本错误,而不是 panic。 var ErrNotInited = errors.New("paypal 未初始化:配置缺失或凭据加载失败") func get() ISys { mu.RLock() defer mu.RUnlock() return defsys } func set(sys ISys) { mu.Lock() defsys = sys mu.Unlock() } func OnInit(config map[string]interface{}, option ...Option) error { sys, err := newSys(newOptions(config, option...)) if err != nil { // 出错时不要给 defsys 赋值,避免「非 nil 接口 + nil 指针」让守卫失效。 return err } set(sys) return nil } // Reload 运行时热替换 PayPal 客户端(console 后台改配置后由业务侧调用)。 // 配置不合法时返回 error 且**保留原客户端不动**;启动时未初始化的场景下, // 后台首次配好凭据即经此启用,无需重启。 func Reload(config map[string]interface{}, option ...Option) error { sys, err := newSys(newOptions(config, option...)) if err != nil { return err } set(sys) return nil } func NewSys(option ...Option) (ISys, error) { sys, err := newSys(newOptionsByOption(option...)) if err != nil { return nil, err } return sys, nil } func GenerateOrderNo(prefix string) string { sys := get() if sys == nil { return "" } return sys.GenerateOrderNo(prefix) } func CreateAppOrder(ctx context.Context, outTradeNo string, totalFee int64, description string, return_url string, cancel_url string) (result string, err error) { sys := get() if sys == nil { return "", ErrNotInited } return sys.CreateAppOrder(ctx, outTradeNo, totalFee, description, return_url, cancel_url) } func CaptureOrder(ctx context.Context, orderID string) (result string, err error) { sys := get() if sys == nil { return "", ErrNotInited } return sys.CaptureOrder(ctx, orderID) } func VerifyWebhook(headers map[string]string, body []byte) (bool, error) { sys := get() if sys == nil { return false, ErrNotInited } return sys.VerifyWebhook(headers, body) }