226 lines
5.9 KiB
Go
226 lines
5.9 KiB
Go
package payment
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
bizpayment "kra/internal/biz/payment"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/go-pay/gopay"
|
|
"github.com/go-pay/gopay/douyin"
|
|
)
|
|
|
|
// The adapters in this package intentionally keep GoPay request/response
|
|
// models below the data boundary. The business layer only sees PaymentResult.
|
|
|
|
func callbackRequest(callback *bizpayment.PaymentCallback) (*http.Request, error) {
|
|
request, err := http.NewRequest(http.MethodPost, "http://payment-callback.local", bytes.NewReader(callback.Body))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for key, value := range callback.Headers {
|
|
request.Header.Set(key, value)
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
func mergeGoPayExtras(dst gopay.BodyMap, src map[string]any, protected ...string) {
|
|
protectedKeys := make(map[string]struct{}, len(protected))
|
|
for _, key := range protected {
|
|
protectedKeys[key] = struct{}{}
|
|
}
|
|
for key, value := range src {
|
|
if _, ok := protectedKeys[key]; ok {
|
|
continue
|
|
}
|
|
dst[key] = value
|
|
}
|
|
}
|
|
|
|
func mergeGoPayConfigExtras(dst gopay.BodyMap, config map[string]any, key string, protected ...string) {
|
|
extra, ok := config[key].(map[string]any)
|
|
if !ok || extra == nil {
|
|
return
|
|
}
|
|
mergeGoPayExtras(dst, extra, protected...)
|
|
}
|
|
|
|
func anyConfig(c map[string]any, keys ...string) any {
|
|
for _, key := range keys {
|
|
if value, ok := c[key]; ok && fmt.Sprint(value) != "" {
|
|
return value
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func firstAnyOr(values map[string]any, fallback string, keys ...string) string {
|
|
if value := firstAny(values, keys...); value != "" {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func mustJSON(value any) []byte {
|
|
encoded, _ := json.Marshal(value)
|
|
return encoded
|
|
}
|
|
|
|
func douyinError(rsp any) error {
|
|
return fmt.Errorf("抖音支付请求失败: %v", rsp)
|
|
}
|
|
|
|
func qqOK(returnCode, resultCode string) bool {
|
|
return strings.EqualFold(strings.TrimSpace(returnCode), "SUCCESS") && strings.EqualFold(strings.TrimSpace(resultCode), "SUCCESS")
|
|
}
|
|
|
|
// saobeiOK accepts both the documented numeric success code (01) and the
|
|
// textual SUCCESS form used by some gateway test/proxy responses. Saobei's
|
|
// communication and business codes are independent, so both must explicitly
|
|
// report success.
|
|
func saobeiOK(returnCode, resultCode string) bool {
|
|
return saobeiSuccessCode(returnCode) && saobeiSuccessCode(resultCode)
|
|
}
|
|
|
|
func saobeiSuccessCode(code string) bool {
|
|
code = strings.TrimSpace(code)
|
|
return strings.EqualFold(code, "01") || strings.EqualFold(code, "SUCCESS")
|
|
}
|
|
|
|
func saobeiProcessingOK(returnCode, resultCode string) bool {
|
|
return saobeiSuccessCode(returnCode) && strings.TrimSpace(resultCode) == "03"
|
|
}
|
|
|
|
func saobeiResultOK(returnCode, resultCode string) bool {
|
|
return saobeiOK(returnCode, resultCode) || saobeiProcessingOK(returnCode, resultCode)
|
|
}
|
|
|
|
func lakalaResponseOK(returnCode, resultCode string, accepted ...string) bool {
|
|
if !strings.EqualFold(strings.TrimSpace(returnCode), "SUCCESS") {
|
|
return false
|
|
}
|
|
resultCode = strings.ToUpper(strings.TrimSpace(resultCode))
|
|
if resultCode == "" {
|
|
return false
|
|
}
|
|
for _, code := range accepted {
|
|
if resultCode == strings.ToUpper(strings.TrimSpace(code)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func lakalaCreateResponseOK(returnCode, resultCode string) bool {
|
|
return lakalaResponseOK(returnCode, resultCode, "SUCCESS", "EXISTS")
|
|
}
|
|
|
|
func lakalaRetailCreateResponseOK(returnCode, resultCode string) bool {
|
|
return lakalaResponseOK(returnCode, resultCode, "PAYING", "PAY_SUCCESS")
|
|
}
|
|
|
|
func lakalaQueryResponseOK(returnCode, resultCode string) bool {
|
|
return lakalaResponseOK(returnCode, resultCode,
|
|
"PAYING", "CREATE_FAIL", "CLOSED", "PAY_FAIL", "PAY_SUCCESS", "PARTIAL_REFUND", "FULL_REFUND")
|
|
}
|
|
|
|
func lakalaRefundResponseOK(returnCode, resultCode string) bool {
|
|
return lakalaResponseOK(returnCode, resultCode,
|
|
"WAITING", "CREATE_FAILED", "SUCCESS", "FAILED", "FINISHED", "CHANGE")
|
|
}
|
|
|
|
func rspMap(value any) map[string]string {
|
|
encoded, _ := json.Marshal(value)
|
|
var object map[string]any
|
|
_ = json.Unmarshal(encoded, &object)
|
|
result := make(map[string]string, len(object))
|
|
for key, val := range object {
|
|
result[key] = fmt.Sprint(val)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func normalizeDouyinState(state string) string {
|
|
switch strings.ToUpper(strings.TrimSpace(state)) {
|
|
case douyin.TradeStateSuccess:
|
|
return "success"
|
|
case douyin.TradeStatePayErr:
|
|
return "failed"
|
|
case douyin.TradeStateClosed:
|
|
return "failed"
|
|
default:
|
|
return "pending"
|
|
}
|
|
}
|
|
|
|
func normalizeRefundState(state string) string {
|
|
switch strings.ToUpper(strings.TrimSpace(state)) {
|
|
case douyin.RefundStatusSuccess, "REFUNDED":
|
|
return "success"
|
|
case douyin.RefundStatusClosed, douyin.RefundStatusAbnormal, "FAILED":
|
|
return "failed"
|
|
default:
|
|
return "pending"
|
|
}
|
|
}
|
|
|
|
func normalizeQQState(state string) string {
|
|
switch strings.ToUpper(strings.TrimSpace(state)) {
|
|
case "SUCCESS":
|
|
return "success"
|
|
case "REFUND", "CLOSED", "PAYERROR", "REVOKED", "NOPAY":
|
|
return "failed"
|
|
default:
|
|
return "pending"
|
|
}
|
|
}
|
|
|
|
func normalizeAllinState(state string) string {
|
|
state = strings.ToUpper(strings.TrimSpace(state))
|
|
switch state {
|
|
case "SUCCESS", "0000":
|
|
return "success"
|
|
case "FAIL", "FAILED":
|
|
return "failed"
|
|
}
|
|
if len(state) == 4 && state >= "3000" && state <= "3999" {
|
|
return "failed"
|
|
}
|
|
return "pending"
|
|
}
|
|
|
|
func normalizeLakalaState(state string) string {
|
|
switch strings.ToUpper(strings.TrimSpace(state)) {
|
|
case "SUCCESS", "PAID", "PAY_SUCCESS", "1":
|
|
return "success"
|
|
case "CLOSED", "FAILED", "FAIL", "CANCELLED", "CREATE_FAIL", "PAY_FAIL", "PARTIAL_REFUND", "FULL_REFUND":
|
|
return "failed"
|
|
default:
|
|
return "pending"
|
|
}
|
|
}
|
|
|
|
func normalizeLakalaRefundState(state string) string {
|
|
switch strings.ToUpper(strings.TrimSpace(state)) {
|
|
case "SUCCESS", "FINISHED":
|
|
return "success"
|
|
case "CREATE_FAILED", "FAILED", "CHANGE":
|
|
return "failed"
|
|
default:
|
|
return "pending"
|
|
}
|
|
}
|
|
|
|
func normalizeSaobeiState(state string) string {
|
|
switch strings.ToUpper(strings.TrimSpace(state)) {
|
|
case "SUCCESS":
|
|
return "success"
|
|
case "CLOSED", "PAYERROR", "REVOKED", "FAILED", "NOPAY":
|
|
return "failed"
|
|
default:
|
|
return "pending"
|
|
}
|
|
}
|