fix(payment): restore public resume and result flows
This commit is contained in:
@@ -379,16 +379,13 @@ func (s *PaymentService) invokeProvider(ctx context.Context, order *dbent.Paymen
|
||||
}
|
||||
subject := s.buildPaymentSubject(plan, limitAmount, cfg)
|
||||
outTradeNo := order.OutTradeNo
|
||||
canonicalReturnURL, err := CanonicalizeReturnURL(req.ReturnURL, req.SrcHost)
|
||||
canonicalReturnURL, err := CanonicalizeReturnURL(req.ReturnURL, req.SrcHost, req.SrcURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resumeToken := ""
|
||||
if resume := s.paymentResume(); resume != nil {
|
||||
if canonicalReturnURL != "" {
|
||||
if err := resume.ensureSigningKey(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if canonicalReturnURL != "" && resume.isSigningConfigured() {
|
||||
resumeToken, err = resume.CreateToken(ResumeTokenClaims{
|
||||
OrderID: order.ID,
|
||||
UserID: order.UserID,
|
||||
@@ -402,7 +399,7 @@ func (s *PaymentService) invokeProvider(ctx context.Context, order *dbent.Paymen
|
||||
}
|
||||
}
|
||||
}
|
||||
providerReturnURL, err := buildPaymentReturnURL(canonicalReturnURL, order.ID, resumeToken)
|
||||
providerReturnURL, err := buildPaymentReturnURL(canonicalReturnURL, order.ID, outTradeNo, resumeToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ func visibleMethodSourceSettingKey(method string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func CanonicalizeReturnURL(raw string, srcHost string) (string, error) {
|
||||
func CanonicalizeReturnURL(raw string, srcHost string, srcURL string) (string, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return "", nil
|
||||
@@ -228,13 +228,29 @@ func CanonicalizeReturnURL(raw string, srcHost string) (string, error) {
|
||||
if parsed.Path != paymentResultReturnPath {
|
||||
return "", infraerrors.BadRequest("INVALID_RETURN_URL", "return_url must target the canonical internal payment result page")
|
||||
}
|
||||
if !sameOriginHost(parsed.Host, srcHost) {
|
||||
return "", infraerrors.BadRequest("INVALID_RETURN_URL", "return_url must use the same host as the current site")
|
||||
if !allowedReturnURLHost(parsed.Host, srcHost, srcURL) {
|
||||
return "", infraerrors.BadRequest("INVALID_RETURN_URL", "return_url must use the same host as the current site or browser origin")
|
||||
}
|
||||
return parsed.String(), nil
|
||||
}
|
||||
|
||||
func buildPaymentReturnURL(base string, orderID int64, resumeToken string) (string, error) {
|
||||
func allowedReturnURLHost(returnURLHost string, requestHost string, refererURL string) bool {
|
||||
if sameOriginHost(returnURLHost, requestHost) {
|
||||
return true
|
||||
}
|
||||
|
||||
refererURL = strings.TrimSpace(refererURL)
|
||||
if refererURL == "" {
|
||||
return false
|
||||
}
|
||||
parsedReferer, err := url.Parse(refererURL)
|
||||
if err != nil || parsedReferer.Host == "" {
|
||||
return false
|
||||
}
|
||||
return sameOriginHost(returnURLHost, parsedReferer.Host)
|
||||
}
|
||||
|
||||
func buildPaymentReturnURL(base string, orderID int64, outTradeNo string, resumeToken string) (string, error) {
|
||||
canonical := strings.TrimSpace(base)
|
||||
if canonical == "" {
|
||||
return "", nil
|
||||
@@ -253,6 +269,9 @@ func buildPaymentReturnURL(base string, orderID int64, resumeToken string) (stri
|
||||
if orderID > 0 {
|
||||
query.Set("order_id", strconv.FormatInt(orderID, 10))
|
||||
}
|
||||
if strings.TrimSpace(outTradeNo) != "" {
|
||||
query.Set("out_trade_no", strings.TrimSpace(outTradeNo))
|
||||
}
|
||||
if strings.TrimSpace(resumeToken) != "" {
|
||||
query.Set("resume_token", strings.TrimSpace(resumeToken))
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func TestNormalizePaymentSource(t *testing.T) {
|
||||
func TestCanonicalizeReturnURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := CanonicalizeReturnURL("https://example.com/payment/result?b=2#a", "example.com")
|
||||
got, err := CanonicalizeReturnURL("https://example.com/payment/result?b=2#a", "example.com", "")
|
||||
if err != nil {
|
||||
t.Fatalf("CanonicalizeReturnURL returned error: %v", err)
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func TestCanonicalizeReturnURL(t *testing.T) {
|
||||
func TestCanonicalizeReturnURLRejectsRelativeURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := CanonicalizeReturnURL("/payment/result", "example.com"); err == nil {
|
||||
if _, err := CanonicalizeReturnURL("/payment/result", "example.com", ""); err == nil {
|
||||
t.Fatal("CanonicalizeReturnURL should reject relative URLs")
|
||||
}
|
||||
}
|
||||
@@ -84,15 +84,31 @@ func TestCanonicalizeReturnURLRejectsRelativeURL(t *testing.T) {
|
||||
func TestCanonicalizeReturnURLRejectsExternalHost(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := CanonicalizeReturnURL("https://evil.example/payment/result", "app.example.com"); err == nil {
|
||||
if _, err := CanonicalizeReturnURL("https://evil.example/payment/result", "app.example.com", ""); err == nil {
|
||||
t.Fatal("CanonicalizeReturnURL should reject external hosts")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalizeReturnURLAllowsConfiguredFrontendHost(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := CanonicalizeReturnURL(
|
||||
"https://app.example.com/payment/result?from=checkout",
|
||||
"api.example.com",
|
||||
"https://app.example.com/purchase",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("CanonicalizeReturnURL returned error: %v", err)
|
||||
}
|
||||
if got != "https://app.example.com/payment/result?from=checkout" {
|
||||
t.Fatalf("CanonicalizeReturnURL = %q, want %q", got, "https://app.example.com/payment/result?from=checkout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalizeReturnURLRejectsNonCanonicalPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := CanonicalizeReturnURL("https://app.example.com/orders/42", "app.example.com"); err == nil {
|
||||
if _, err := CanonicalizeReturnURL("https://app.example.com/orders/42", "app.example.com", ""); err == nil {
|
||||
t.Fatal("CanonicalizeReturnURL should reject non-canonical result paths")
|
||||
}
|
||||
}
|
||||
@@ -100,7 +116,7 @@ func TestCanonicalizeReturnURLRejectsNonCanonicalPath(t *testing.T) {
|
||||
func TestBuildPaymentReturnURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := buildPaymentReturnURL("https://example.com/payment/result?from=checkout#fragment", 42, "resume-token")
|
||||
got, err := buildPaymentReturnURL("https://example.com/payment/result?from=checkout#fragment", 42, "sub2_42", "resume-token")
|
||||
if err != nil {
|
||||
t.Fatalf("buildPaymentReturnURL returned error: %v", err)
|
||||
}
|
||||
@@ -119,6 +135,9 @@ func TestBuildPaymentReturnURL(t *testing.T) {
|
||||
if query.Get("order_id") != strconv.FormatInt(42, 10) {
|
||||
t.Fatalf("order_id = %q", query.Get("order_id"))
|
||||
}
|
||||
if query.Get("out_trade_no") != "sub2_42" {
|
||||
t.Fatalf("out_trade_no = %q", query.Get("out_trade_no"))
|
||||
}
|
||||
if query.Get("resume_token") != "resume-token" {
|
||||
t.Fatalf("resume_token = %q", query.Get("resume_token"))
|
||||
}
|
||||
@@ -127,10 +146,34 @@ func TestBuildPaymentReturnURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPaymentReturnURLWithoutResumeTokenStillIncludesOutTradeNo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := buildPaymentReturnURL("https://example.com/payment/result", 42, "sub2_42", "")
|
||||
if err != nil {
|
||||
t.Fatalf("buildPaymentReturnURL returned error: %v", err)
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(got)
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse returned error: %v", err)
|
||||
}
|
||||
query := parsed.Query()
|
||||
if query.Get("order_id") != "42" {
|
||||
t.Fatalf("order_id = %q", query.Get("order_id"))
|
||||
}
|
||||
if query.Get("out_trade_no") != "sub2_42" {
|
||||
t.Fatalf("out_trade_no = %q", query.Get("out_trade_no"))
|
||||
}
|
||||
if query.Get("resume_token") != "" {
|
||||
t.Fatalf("resume_token = %q, want empty", query.Get("resume_token"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPaymentReturnURLEmptyBase(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := buildPaymentReturnURL("", 42, "resume-token")
|
||||
got, err := buildPaymentReturnURL("", 42, "sub2_42", "resume-token")
|
||||
if err != nil {
|
||||
t.Fatalf("buildPaymentReturnURL returned error: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user