peakchao

搜索

peakchao

peakchao

前端开发工程师 | Go 爱好者

联系方式

微信支付宝支付集成完全指南:Android、Web 与 Go 后端实战

peakchao 2025-12-13 05:45 15 次浏览 0 条评论

支付流程概述

支付时序图

无论是微信支付还是支付宝支付,核心流程都是类似的:

┌─────────────────────────────────────────────────────────────────────────────┐
│                            支付流程时序图                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   客户端              商户后端                支付平台                       │
│     │                    │                      │                           │
│     │ ─── 1.创建订单 ───►│                      │                           │
│     │                    │                      │                           │
│     │                    │ ─── 2.预下单请求 ───►│                           │
│     │                    │                      │                           │
│     │                    │ ◄─── 3.返回预支付 ───│                           │
│     │                    │       信息           │                           │
│     │                    │                      │                           │
│     │ ◄── 4.返回支付 ────│                      │                           │
│     │      参数          │                      │                           │
│     │                    │                      │                           │
│     │ ─── 5.调起支付 ─────────────────────────►│                           │
│     │      SDK/页面      │                      │                           │
│     │                    │                      │                           │
│     │ ◄── 6.支付结果 ─────────────────────────│                           │
│     │      (客户端)      │                      │                           │
│     │                    │                      │                           │
│     │                    │ ◄─── 7.异步通知 ────│                           │
│     │                    │      (服务端)        │                           │
│     │                    │                      │                           │
│     │                    │ ─── 8.返回成功 ─────►│                           │
│     │                    │                      │                           │
│     │ ◄── 9.确认结果 ────│                      │                           │
│     │                    │                      │                           │
└─────────────────────────────────────────────────────────────────────────────┘

微信支付 vs 支付宝支付

对比项微信支付支付宝支付
App支付需要 App ID + 商户号需要 App ID + 私钥
H5支付需要申请 H5 支付权限手机网站支付
签名算法RSA/HMAC-SHA256RSA2 (SHA256WithRSA)
异步通知XML/JSON 格式form 表单格式
证书管理API 证书 + 平台证书应用私钥 + 支付宝公钥

准备工作

微信支付准备

  1. 注册微信商户平台账号

  2. 获取必要参数

参数说明获取位置
appid应用/公众号 ID微信开放平台/公众平台
mchid商户号商户平台 - 账户中心
apiKeyAPI 密钥 (V2)商户平台 - API安全
apiV3KeyAPI V3 密钥商户平台 - API安全
serialNo证书序列号API 证书详情
privateKey商户私钥申请 API 证书时生成
  1. 下载 API 证书
    • 商户平台 → 账户中心 → API安全 → 申请API证书
    • 妥善保管 apiclient_key.pem(私钥)

支付宝支付准备

  1. 注册支付宝开放平台账号

  2. 获取必要参数

参数说明获取位置
appId应用 ID开放平台 - 应用详情
privateKey应用私钥本地生成后上传公钥
alipayPublicKey支付宝公钥开放平台 - 应用详情
signType签名类型推荐 RSA2
  1. 生成密钥对
# 生成 RSA2 私钥
openssl genrsa -out app_private_key.pem 2048

# 生成公钥(上传到支付宝开放平台)
openssl rsa -in app_private_key.pem -pubout -out app_public_key.pem

# 将私钥转换为 PKCS8 格式(Java/Kotlin 使用)
openssl pkcs8 -topk8 -inform PEM -in app_private_key.pem \
  -outform PEM -nocrypt -out app_private_key_pkcs8.pem

Go 后端实现

项目结构

payment-server/
├── main.go
├── config/
│   └── config.go
├── handlers/
│   ├── wechat.go
│   └── alipay.go
├── services/
│   ├── wechat_pay.go
│   └── alipay_pay.go
├── models/
│   └── order.go
└── utils/
    └── sign.go

配置文件

// config/config.go
package config

type PaymentConfig struct {
    // 微信支付配置
    Wechat WechatConfig `yaml:"wechat"`
    // 支付宝配置
    Alipay AlipayConfig `yaml:"alipay"`
}

type WechatConfig struct {
    AppID       string `yaml:"app_id"`       // 应用ID
    MchID       string `yaml:"mch_id"`       // 商户号
    APIKey      string `yaml:"api_key"`      // API密钥(V2)
    APIv3Key    string `yaml:"apiv3_key"`    // APIv3密钥
    SerialNo    string `yaml:"serial_no"`    // 证书序列号
    PrivateKey  string `yaml:"private_key"`  // 私钥路径
    NotifyURL   string `yaml:"notify_url"`   // 回调地址
}

type AlipayConfig struct {
    AppID           string `yaml:"app_id"`
    PrivateKey      string `yaml:"private_key"`       // 应用私钥
    AlipayPublicKey string `yaml:"alipay_public_key"` // 支付宝公钥
    NotifyURL       string `yaml:"notify_url"`
    ReturnURL       string `yaml:"return_url"`        // 同步跳转地址
    IsSandbox       bool   `yaml:"is_sandbox"`        // 是否沙箱环境
}

微信支付服务

// services/wechat_pay.go
package services

import (
    "context"
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "os"
    "time"

    "github.com/wechatpay-apiv3/wechatpay-go/core"
    "github.com/wechatpay-apiv3/wechatpay-go/core/option"
    "github.com/wechatpay-apiv3/wechatpay-go/services/payments/app"
    "github.com/wechatpay-apiv3/wechatpay-go/services/payments/h5"
    "github.com/wechatpay-apiv3/wechatpay-go/utils"
)

type WechatPayService struct {
    client    *core.Client
    appID     string
    mchID     string
    notifyURL string
}

// NewWechatPayService 创建微信支付服务实例
func NewWechatPayService(cfg *config.WechatConfig) (*WechatPayService, error) {
    // 读取私钥
    privateKey, err := utils.LoadPrivateKeyWithPath(cfg.PrivateKey)
    if err != nil {
        return nil, fmt.Errorf("加载私钥失败: %w", err)
    }

    // 创建客户端
    ctx := context.Background()
    client, err := core.NewClient(ctx,
        option.WithWechatPayAutoAuthCipher(
            cfg.MchID,
            cfg.SerialNo,
            privateKey,
            cfg.APIv3Key,
        ),
    )
    if err != nil {
        return nil, fmt.Errorf("创建微信支付客户端失败: %w", err)
    }

    return &WechatPayService{
        client:    client,
        appID:     cfg.AppID,
        mchID:     cfg.MchID,
        notifyURL: cfg.NotifyURL,
    }, nil
}

// CreateAppPayOrder 创建 App 支付订单
func (s *WechatPayService) CreateAppPayOrder(ctx context.Context, 
    orderNo string, amount int64, description string) (*AppPayResult, error) {
    
    svc := app.AppApiService{Client: s.client}
    
    resp, _, err := svc.Prepay(ctx, app.PrepayRequest{
        Appid:       core.String(s.appID),
        Mchid:       core.String(s.mchID),
        Description: core.String(description),
        OutTradeNo:  core.String(orderNo),
        NotifyUrl:   core.String(s.notifyURL),
        Amount: &app.Amount{
            Total:    core.Int64(amount), // 单位:分
            Currency: core.String("CNY"),
        },
    })
    if err != nil {
        return nil, fmt.Errorf("预下单失败: %w", err)
    }

    // 生成调起支付的参数
    prepayID := *resp.PrepayId
    timestamp := time.Now().Unix()
    nonceStr := generateNonceStr()
    
    // 签名
    signStr := fmt.Sprintf("%s\n%d\n%s\nprepay_id=%s\n",
        s.appID, timestamp, nonceStr, prepayID)
    
    sign, err := s.signWithPrivateKey(signStr)
    if err != nil {
        return nil, err
    }

    return &AppPayResult{
        AppID:     s.appID,
        PartnerID: s.mchID,
        PrepayID:  prepayID,
        Package:   "Sign=WXPay",
        NonceStr:  nonceStr,
        Timestamp: fmt.Sprintf("%d", timestamp),
        Sign:      sign,
    }, nil
}

// CreateH5PayOrder 创建 H5 支付订单
func (s *WechatPayService) CreateH5PayOrder(ctx context.Context,
    orderNo string, amount int64, description, clientIP string) (string, error) {
    
    svc := h5.H5ApiService{Client: s.client}
    
    resp, _, err := svc.Prepay(ctx, h5.PrepayRequest{
        Appid:       core.String(s.appID),
        Mchid:       core.String(s.mchID),
        Description: core.String(description),
        OutTradeNo:  core.String(orderNo),
        NotifyUrl:   core.String(s.notifyURL),
        Amount: &h5.Amount{
            Total:    core.Int64(amount),
            Currency: core.String("CNY"),
        },
        SceneInfo: &h5.SceneInfo{
            PayerClientIp: core.String(clientIP),
            H5Info: &h5.H5Info{
                Type: core.String("Wap"),
            },
        },
    })
    if err != nil {
        return "", fmt.Errorf("H5预下单失败: %w", err)
    }

    return *resp.H5Url, nil
}

// AppPayResult App支付结果
type AppPayResult struct {
    AppID     string `json:"appId"`
    PartnerID string `json:"partnerId"`
    PrepayID  string `json:"prepayId"`
    Package   string `json:"package"`
    NonceStr  string `json:"nonceStr"`
    Timestamp string `json:"timestamp"`
    Sign      string `json:"sign"`
}

支付宝服务

// services/alipay_pay.go
package services

import (
    "context"
    "fmt"

    "github.com/smartwalle/alipay/v3"
)

type AlipayService struct {
    client    *alipay.Client
    notifyURL string
    returnURL string
}

// NewAlipayService 创建支付宝支付服务实例
func NewAlipayService(cfg *config.AlipayConfig) (*AlipayService, error) {
    var client *alipay.Client
    var err error

    if cfg.IsSandbox {
        client, err = alipay.NewSandbox(cfg.AppID, cfg.PrivateKey, true)
    } else {
        client, err = alipay.New(cfg.AppID, cfg.PrivateKey, true)
    }
    if err != nil {
        return nil, fmt.Errorf("创建支付宝客户端失败: %w", err)
    }

    // 加载支付宝公钥
    if err = client.LoadAliPayPublicKey(cfg.AlipayPublicKey); err != nil {
        return nil, fmt.Errorf("加载支付宝公钥失败: %w", err)
    }

    return &AlipayService{
        client:    client,
        notifyURL: cfg.NotifyURL,
        returnURL: cfg.ReturnURL,
    }, nil
}

// CreateAppPayOrder 创建 App 支付订单
func (s *AlipayService) CreateAppPayOrder(ctx context.Context,
    orderNo string, amount string, subject string) (string, error) {
    
    p := alipay.TradeAppPay{
        Trade: alipay.Trade{
            Subject:     subject,
            OutTradeNo:  orderNo,
            TotalAmount: amount, // 单位:元,如 "0.01"
            ProductCode: "QUICK_MSECURITY_PAY",
            NotifyURL:   s.notifyURL,
        },
    }

    // 返回用于调起支付的订单字符串
    orderStr, err := s.client.TradeAppPay(p)
    if err != nil {
        return "", fmt.Errorf("创建App支付订单失败: %w", err)
    }

    return orderStr, nil
}

// CreateWapPayOrder 创建手机网站支付订单
func (s *AlipayService) CreateWapPayOrder(ctx context.Context,
    orderNo string, amount string, subject string) (string, error) {
    
    p := alipay.TradeWapPay{
        Trade: alipay.Trade{
            Subject:     subject,
            OutTradeNo:  orderNo,
            TotalAmount: amount,
            ProductCode: "QUICK_WAP_WAY",
            NotifyURL:   s.notifyURL,
            ReturnURL:   s.returnURL,
        },
    }

    // 返回支付页面 URL
    url, err := s.client.TradeWapPay(p)
    if err != nil {
        return "", fmt.Errorf("创建WAP支付订单失败: %w", err)
    }

    return url.String(), nil
}

// CreatePagePayOrder 创建电脑网站支付订单
func (s *AlipayService) CreatePagePayOrder(ctx context.Context,
    orderNo string, amount string, subject string) (string, error) {
    
    p := alipay.TradePagePay{
        Trade: alipay.Trade{
            Subject:     subject,
            OutTradeNo:  orderNo,
            TotalAmount: amount,
            ProductCode: "FAST_INSTANT_TRADE_PAY",
            NotifyURL:   s.notifyURL,
            ReturnURL:   s.returnURL,
        },
    }

    url, err := s.client.TradePagePay(p)
    if err != nil {
        return "", fmt.Errorf("创建PC支付订单失败: %w", err)
    }

    return url.String(), nil
}

// VerifyNotify 验证异步通知
func (s *AlipayService) VerifyNotify(values map[string][]string) (*alipay.Notification, error) {
    notification, err := s.client.DecodeNotification(values)
    if err != nil {
        return nil, fmt.Errorf("验证通知签名失败: %w", err)
    }
    return notification, nil
}

// QueryOrder 查询订单
func (s *AlipayService) QueryOrder(ctx context.Context, orderNo string) (*alipay.TradeQueryRsp, error) {
    p := alipay.TradeQuery{
        OutTradeNo: orderNo,
    }

    resp, err := s.client.TradeQuery(ctx, p)
    if err != nil {
        return nil, fmt.Errorf("查询订单失败: %w", err)
    }

    return resp, nil
}

HTTP 处理器

// handlers/payment.go
package handlers

import (
    "encoding/json"
    "io"
    "log"
    "net/http"
)

type PaymentHandler struct {
    wechatService *services.WechatPayService
    alipayService *services.AlipayService
}

// CreateOrderRequest 创建订单请求
type CreateOrderRequest struct {
    OrderNo     string `json:"orderNo"`
    Amount      int64  `json:"amount"`      // 金额(分)
    Description string `json:"description"`
    PayMethod   string `json:"payMethod"`   // wechat_app, wechat_h5, alipay_app, alipay_wap
}

// CreateOrder 统一创建订单接口
func (h *PaymentHandler) CreateOrder(w http.ResponseWriter, r *http.Request) {
    var req CreateOrderRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, "无效的请求参数", http.StatusBadRequest)
        return
    }

    ctx := r.Context()
    var result interface{}
    var err error

    switch req.PayMethod {
    case "wechat_app":
        result, err = h.wechatService.CreateAppPayOrder(
            ctx, req.OrderNo, req.Amount, req.Description)
    
    case "wechat_h5":
        clientIP := getClientIP(r)
        result, err = h.wechatService.CreateH5PayOrder(
            ctx, req.OrderNo, req.Amount, req.Description, clientIP)
    
    case "alipay_app":
        amountStr := fmt.Sprintf("%.2f", float64(req.Amount)/100)
        result, err = h.alipayService.CreateAppPayOrder(
            ctx, req.OrderNo, amountStr, req.Description)
    
    case "alipay_wap":
        amountStr := fmt.Sprintf("%.2f", float64(req.Amount)/100)
        result, err = h.alipayService.CreateWapPayOrder(
            ctx, req.OrderNo, amountStr, req.Description)
    
    default:
        http.Error(w, "不支持的支付方式", http.StatusBadRequest)
        return
    }

    if err != nil {
        log.Printf("创建订单失败: %v", err)
        http.Error(w, "创建订单失败", http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]interface{}{
        "code": 0,
        "data": result,
    })
}

// WechatPayNotify 微信支付回调
func (h *PaymentHandler) WechatPayNotify(w http.ResponseWriter, r *http.Request) {
    body, err := io.ReadAll(r.Body)
    if err != nil {
        log.Printf("读取通知失败: %v", err)
        return
    }

    // 验证签名并解析通知
    notification, err := h.wechatService.ParseNotify(r, body)
    if err != nil {
        log.Printf("验证通知失败: %v", err)
        http.Error(w, "验证失败", http.StatusBadRequest)
        return
    }

    // 处理业务逻辑
    if notification.TradeState == "SUCCESS" {
        // 更新订单状态
        log.Printf("订单支付成功: %s", notification.OutTradeNo)
        // TODO: 更新数据库订单状态
    }

    // 返回成功响应
    w.Header().Set("Content-Type", "application/json")
    w.Write([]byte(`{"code": "SUCCESS", "message": "成功"}`))
}

// AlipayNotify 支付宝回调
func (h *PaymentHandler) AlipayNotify(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseForm(); err != nil {
        log.Printf("解析表单失败: %v", err)
        return
    }

    notification, err := h.alipayService.VerifyNotify(r.Form)
    if err != nil {
        log.Printf("验证通知失败: %v", err)
        w.Write([]byte("fail"))
        return
    }

    // 处理业务逻辑
    if notification.TradeStatus == "TRADE_SUCCESS" || 
       notification.TradeStatus == "TRADE_FINISHED" {
        log.Printf("订单支付成功: %s", notification.OutTradeNo)
        // TODO: 更新数据库订单状态
    }

    // 返回成功响应
    w.Write([]byte("success"))
}

Android Kotlin 客户端实现

添加依赖

// app/build.gradle.kts
dependencies {
    // 微信支付 SDK
    implementation("com.tencent.mm.opensdk:wechat-sdk-android:6.8.24")
    
    // 支付宝 SDK - 需要手动下载 aar 放入 libs 目录
    implementation(files("libs/alipaysdk.aar"))
    
    // 网络请求
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    
    // 协程
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
}

支付服务封装

// PaymentService.kt
package com.example.payment

import android.app.Activity
import android.content.Context
import com.alipay.sdk.app.PayTask
import com.tencent.mm.opensdk.modelpay.PayReq
import com.tencent.mm.opensdk.openapi.IWXAPI
import com.tencent.mm.opensdk.openapi.WXAPIFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class PaymentService(private val context: Context) {

    private val wxApi: IWXAPI by lazy {
        WXAPIFactory.createWXAPI(context, WX_APP_ID, true).apply {
            registerApp(WX_APP_ID)
        }
    }

    companion object {
        private const val WX_APP_ID = "your_wechat_app_id"
    }

    /**
     * 微信 App 支付
     */
    fun wechatPay(payParams: WechatPayParams): Boolean {
        if (!wxApi.isWXAppInstalled) {
            throw PaymentException("请先安装微信")
        }

        val request = PayReq().apply {
            appId = payParams.appId
            partnerId = payParams.partnerId
            prepayId = payParams.prepayId
            packageValue = payParams.packageValue
            nonceStr = payParams.nonceStr
            timeStamp = payParams.timestamp
            sign = payParams.sign
        }

        return wxApi.sendReq(request)
    }

    /**
     * 支付宝 App 支付
     */
    suspend fun alipay(activity: Activity, orderStr: String): AlipayResult {
        return withContext(Dispatchers.IO) {
            val payTask = PayTask(activity)
            val result = payTask.payV2(orderStr, true)
            parseAlipayResult(result)
        }
    }

    private fun parseAlipayResult(result: Map<String, String>): AlipayResult {
        val resultStatus = result["resultStatus"] ?: ""
        val memo = result["memo"] ?: ""
        
        return when (resultStatus) {
            "9000" -> AlipayResult.Success(memo)
            "8000" -> AlipayResult.Processing(memo)
            "6001" -> AlipayResult.Cancelled(memo)
            "6002" -> AlipayResult.NetworkError(memo)
            else -> AlipayResult.Failed(resultStatus, memo)
        }
    }
}

// 数据类
data class WechatPayParams(
    val appId: String,
    val partnerId: String,
    val prepayId: String,
    val packageValue: String,
    val nonceStr: String,
    val timestamp: String,
    val sign: String
)

sealed class AlipayResult {
    data class Success(val memo: String) : AlipayResult()
    data class Processing(val memo: String) : AlipayResult()
    data class Cancelled(val memo: String) : AlipayResult()
    data class NetworkError(val memo: String) : AlipayResult()
    data class Failed(val code: String, val memo: String) : AlipayResult()
}

class PaymentException(message: String) : Exception(message)

微信支付回调

// wxapi/WXPayEntryActivity.kt
package com.example.payment.wxapi

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.tencent.mm.opensdk.constants.ConstantsAPI
import com.tencent.mm.opensdk.modelbase.BaseReq
import com.tencent.mm.opensdk.modelbase.BaseResp
import com.tencent.mm.opensdk.openapi.IWXAPI
import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler
import com.tencent.mm.opensdk.openapi.WXAPIFactory

/**
 * 微信支付回调 Activity
 * 必须放在 包名.wxapi 目录下,类名必须是 WXPayEntryActivity
 */
class WXPayEntryActivity : Activity(), IWXAPIEventHandler {

    private lateinit var api: IWXAPI

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        api = WXAPIFactory.createWXAPI(this, "your_wechat_app_id")
        api.handleIntent(intent, this)
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        setIntent(intent)
        api.handleIntent(intent, this)
    }

    override fun onReq(req: BaseReq?) {}

    override fun onResp(resp: BaseResp?) {
        if (resp?.type == ConstantsAPI.COMMAND_PAY_BY_WX) {
            val result = when (resp.errCode) {
                BaseResp.ErrCode.ERR_OK -> PaymentResult.SUCCESS
                BaseResp.ErrCode.ERR_USER_CANCEL -> PaymentResult.CANCELLED
                else -> PaymentResult.FAILED
            }
            
            // 发送广播或使用 EventBus 通知支付结果
            sendPaymentResult(result)
        }
        finish()
    }

    private fun sendPaymentResult(result: PaymentResult) {
        val intent = Intent("com.example.payment.WECHAT_PAY_RESULT").apply {
            putExtra("result", result.name)
        }
        sendBroadcast(intent)
    }

    enum class PaymentResult {
        SUCCESS, CANCELLED, FAILED
    }
}

使用示例

// PaymentViewModel.kt
class PaymentViewModel : ViewModel() {

    private val paymentApi: PaymentApi = RetrofitClient.create()
    private var paymentService: PaymentService? = null

    private val _paymentState = MutableStateFlow<PaymentState>(PaymentState.Idle)
    val paymentState: StateFlow<PaymentState> = _paymentState

    fun initPaymentService(context: Context) {
        paymentService = PaymentService(context)
    }

    /**
     * 创建订单并支付
     */
    fun pay(orderNo: String, amount: Long, payMethod: String) {
        viewModelScope.launch {
            _paymentState.value = PaymentState.Loading

            try {
                // 1. 调用后端创建订单
                val response = paymentApi.createOrder(
                    CreateOrderRequest(
                        orderNo = orderNo,
                        amount = amount,
                        description = "商品购买",
                        payMethod = payMethod
                    )
                )

                if (response.code != 0) {
                    _paymentState.value = PaymentState.Error("创建订单失败")
                    return@launch
                }

                // 2. 调起支付
                when (payMethod) {
                    "wechat_app" -> {
                        val params = Gson().fromJson(
                            Gson().toJson(response.data), 
                            WechatPayParams::class.java
                        )
                        paymentService?.wechatPay(params)
                        // 结果通过 WXPayEntryActivity 回调
                    }

                    "alipay_app" -> {
                        val orderStr = response.data as String
                        // 需要在 Activity 中调用
                        _paymentState.value = PaymentState.AlipayReady(orderStr)
                    }
                }

            } catch (e: Exception) {
                _paymentState.value = PaymentState.Error(e.message ?: "支付失败")
            }
        }
    }
}

sealed class PaymentState {
    object Idle : PaymentState()
    object Loading : PaymentState()
    data class AlipayReady(val orderStr: String) : PaymentState()
    data class Success(val orderNo: String) : PaymentState()
    data class Error(val message: String) : PaymentState()
}

Web 前端实现

H5 支付页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>收银台</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }
        
        .payment-card {
            background: white;
            border-radius: 20px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
            padding: 40px;
            width: 100%;
            max-width: 420px;
        }
        
        .header {
            text-align: center;
            margin-bottom: 30px;
        }
        
        .header h1 {
            color: #333;
            font-size: 24px;
            margin-bottom: 8px;
        }
        
        .amount {
            font-size: 48px;
            font-weight: bold;
            color: #667eea;
        }
        
        .amount::before {
            content: '¥';
            font-size: 24px;
            vertical-align: top;
        }
        
        .order-info {
            background: #f8f9fa;
            border-radius: 12px;
            padding: 16px;
            margin-bottom: 24px;
        }
        
        .order-info p {
            display: flex;
            justify-content: space-between;
            color: #666;
            font-size: 14px;
            margin-bottom: 8px;
        }
        
        .order-info p:last-child {
            margin-bottom: 0;
        }
        
        .payment-methods {
            margin-bottom: 24px;
        }
        
        .payment-methods h3 {
            font-size: 14px;
            color: #999;
            margin-bottom: 12px;
        }
        
        .method-btn {
            width: 100%;
            padding: 16px 20px;
            border: 2px solid #eee;
            border-radius: 12px;
            background: white;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 12px;
            margin-bottom: 12px;
            transition: all 0.3s ease;
        }
        
        .method-btn:hover {
            border-color: #667eea;
            transform: translateY(-2px);
        }
        
        .method-btn.selected {
            border-color: #667eea;
            background: linear-gradient(135deg, #667eea10 0%, #764ba210 100%);
        }
        
        .method-btn img {
            width: 32px;
            height: 32px;
        }
        
        .method-btn span {
            flex: 1;
            text-align: left;
            font-size: 16px;
            color: #333;
        }
        
        .pay-btn {
            width: 100%;
            padding: 18px;
            border: none;
            border-radius: 12px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            font-size: 18px;
            font-weight: bold;
            cursor: pointer;
            transition: all 0.3s ease;
        }
        
        .pay-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4);
        }
        
        .pay-btn:disabled {
            opacity: 0.6;
            cursor: not-allowed;
            transform: none;
        }
        
        .loading {
            display: none;
            text-align: center;
            padding: 20px;
        }
        
        .loading.show {
            display: block;
        }
        
        .spinner {
            width: 40px;
            height: 40px;
            border: 3px solid #f3f3f3;
            border-top: 3px solid #667eea;
            border-radius: 50%;
            animation: spin 1s linear infinite;
            margin: 0 auto 12px;
        }
        
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
    <div class="payment-card">
        <div class="header">
            <h1>确认支付</h1>
            <div class="amount" id="amount">0.00</div>
        </div>
        
        <div class="order-info">
            <p><span>订单号</span><span id="orderNo">-</span></p>
            <p><span>商品</span><span id="description">-</span></p>
        </div>
        
        <div class="payment-methods">
            <h3>选择支付方式</h3>
            <button class="method-btn selected" data-method="alipay">
                <img src="https://img.alicdn.com/tfs/TB1uHWSIVzqK1RjSZFoXXbfcXXa-200-200.png" alt="支付宝">
                <span>支付宝</span>
            </button>
            <button class="method-btn" data-method="wechat">
                <img src="https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico" alt="微信支付">
                <span>微信支付</span>
            </button>
        </div>
        
        <button class="pay-btn" id="payBtn">立即支付</button>
        
        <div class="loading" id="loading">
            <div class="spinner"></div>
            <p>正在跳转支付...</p>
        </div>
    </div>

    <script>
        const API_BASE = '/api';
        let selectedMethod = 'alipay';
        let orderInfo = null;

        // 从 URL 获取订单信息
        function getOrderFromURL() {
            const params = new URLSearchParams(window.location.search);
            return {
                orderNo: params.get('orderNo') || 'ORDER' + Date.now(),
                amount: parseInt(params.get('amount')) || 100,
                description: params.get('desc') || '商品购买'
            };
        }

        // 初始化页面
        function initPage() {
            orderInfo = getOrderFromURL();
            document.getElementById('orderNo').textContent = orderInfo.orderNo;
            document.getElementById('amount').textContent = (orderInfo.amount / 100).toFixed(2);
            document.getElementById('description').textContent = orderInfo.description;

            // 支付方式选择
            document.querySelectorAll('.method-btn').forEach(btn => {
                btn.addEventListener('click', () => {
                    document.querySelectorAll('.method-btn').forEach(b => b.classList.remove('selected'));
                    btn.classList.add('selected');
                    selectedMethod = btn.dataset.method;
                });
            });

            // 支付按钮
            document.getElementById('payBtn').addEventListener('click', handlePay);
        }

        // 处理支付
        async function handlePay() {
            const payBtn = document.getElementById('payBtn');
            const loading = document.getElementById('loading');
            
            payBtn.disabled = true;
            loading.classList.add('show');

            try {
                const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
                const payMethod = selectedMethod === 'wechat' 
                    ? (isMobile ? 'wechat_h5' : 'wechat_native')
                    : (isMobile ? 'alipay_wap' : 'alipay_page');

                const response = await fetch(`${API_BASE}/payment/create`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        orderNo: orderInfo.orderNo,
                        amount: orderInfo.amount,
                        description: orderInfo.description,
                        payMethod: payMethod
                    })
                });

                const result = await response.json();
                
                if (result.code !== 0) {
                    throw new Error(result.message || '创建订单失败');
                }

                // 跳转到支付页面
                if (typeof result.data === 'string') {
                    window.location.href = result.data;
                } else if (result.data.qrCode) {
                    // 显示二维码(PC 端扫码支付)
                    showQRCode(result.data.qrCode);
                }

            } catch (error) {
                alert('支付失败: ' + error.message);
                payBtn.disabled = false;
                loading.classList.remove('show');
            }
        }

        // 显示二维码
        function showQRCode(url) {
            // 可以使用 qrcode.js 等库生成二维码
            console.log('请扫码支付:', url);
        }

        initPage();
    </script>
</body>
</html>

安全最佳实践

1. 签名验证

// 始终验证回调通知的签名
func (h *PaymentHandler) verifyWechatSign(r *http.Request, body []byte) error {
    timestamp := r.Header.Get("Wechatpay-Timestamp")
    nonce := r.Header.Get("Wechatpay-Nonce")
    signature := r.Header.Get("Wechatpay-Signature")
    serial := r.Header.Get("Wechatpay-Serial")
    
    // 使用平台证书验证签名
    return h.wechatService.VerifySign(timestamp, nonce, string(body), signature, serial)
}

2. 幂等性处理

// 使用 Redis 防止重复处理
func (h *PaymentHandler) handleNotify(orderNo string) error {
    lockKey := "pay_notify:" + orderNo
    
    // 尝试获取锁
    acquired, err := h.redis.SetNX(ctx, lockKey, "1", 10*time.Minute).Result()
    if err != nil || !acquired {
        return errors.New("订单正在处理中")
    }
    defer h.redis.Del(ctx, lockKey)
    
    // 检查订单状态
    order, err := h.orderRepo.GetByOrderNo(orderNo)
    if err != nil {
        return err
    }
    
    if order.Status == OrderStatusPaid {
        return nil // 已处理过
    }
    
    // 更新订单状态
    return h.orderRepo.UpdateStatus(orderNo, OrderStatusPaid)
}

3. 金额校验

// 验证通知中的金额与订单金额是否一致
func verifyAmount(notification *Notification, order *Order) error {
    if notification.Amount != order.Amount {
        return fmt.Errorf("金额不一致: 通知 %d, 订单 %d", 
            notification.Amount, order.Amount)
    }
    return nil
}

常见问题排查

问题可能原因解决方案
签名错误密钥不正确 / 签名算法错误检查密钥配置,确认算法一致
回调收不到域名未备案 / 防火墙拦截检查域名和服务器配置
支付宝SDK报错未添加混淆配置添加 ProGuard 规则
微信未安装提示未注册 AppID确保正确调用 registerApp

参考资料

  1. 微信支付开发文档
  2. 支付宝开放平台文档
  3. wechatpay-go SDK
  4. smartwalle/alipay
  5. 支付系统设计最佳实践

评论 (0)

请先登录后再发表评论

暂无评论,来发表第一条评论吧!