Files
2026-09-11 16:08:17 +08:00

145 lines
3.6 KiB
Go

package pay
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
)
const jsapiPrepayPath = "/v3/pay/transactions/jsapi"
type jsapiPrepayBody struct {
AppID string `json:"appid"`
MchID string `json:"mchid"`
Description string `json:"description"`
OutTradeNo string `json:"out_trade_no"`
TimeExpire interface{} `json:"time_expire,omitempty"`
Attach string `json:"attach,omitempty"`
NotifyURL string `json:"notify_url"`
GoodsTag string `json:"goods_tag,omitempty"`
SupportFapiao *bool `json:"support_fapiao,omitempty"`
Amount Amount `json:"amount"`
Payer Payer `json:"payer"`
Detail *Detail `json:"detail,omitempty"`
SceneInfo *SceneInfo `json:"scene_info,omitempty"`
SettleInfo *SettleInfo `json:"settle_info,omitempty"`
}
func (c *Client) JsapiPrepay(ctx context.Context, req *JsapiPrepayRequest) (*JsapiPrepayResponse, error) {
if req == nil {
return nil, fmt.Errorf("request is required")
}
if req.OutTradeNo == "" || req.Description == "" || req.Payer.OpenID == "" {
return nil, fmt.Errorf("out_trade_no, description and payer.openid are required")
}
if req.Amount.Total <= 0 {
return nil, fmt.Errorf("amount.total must be greater than 0")
}
notifyURL := req.NotifyURL
if notifyURL == "" {
notifyURL = c.NotifyURL
}
if notifyURL == "" {
return nil, fmt.Errorf("notify_url is required")
}
currency := req.Amount.Currency
if currency == "" {
currency = "CNY"
}
body := jsapiPrepayBody{
AppID: c.AppID,
MchID: c.MchID,
Description: req.Description,
OutTradeNo: req.OutTradeNo,
Attach: req.Attach,
NotifyURL: notifyURL,
GoodsTag: req.GoodsTag,
SupportFapiao: req.SupportFapiao,
Amount: Amount{
Total: req.Amount.Total,
Currency: currency,
},
Payer: req.Payer,
Detail: req.Detail,
SceneInfo: req.SceneInfo,
SettleInfo: req.SettleInfo,
}
if req.TimeExpire != nil {
body.TimeExpire = req.TimeExpire
}
reqBody, err := json.Marshal(body)
if err != nil {
return nil, err
}
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.Host+jsapiPrepayPath, bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpReq.Header.Set("Accept", "application/json")
httpReq.Header.Set("Content-Type", "application/json")
if c.platformSerial != "" {
httpReq.Header.Set("Wechatpay-Serial", c.platformSerial)
}
authorization, err := c.buildAuthorization(http.MethodPost, jsapiPrepayPath, reqBody)
if err != nil {
return nil, err
}
httpReq.Header.Set("Authorization", authorization)
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if !isSuccessStatus(resp.StatusCode) {
return nil, newAPIError(resp.StatusCode, respBody)
}
if err := c.validateResponse(resp.Header, respBody); err != nil {
return nil, err
}
var result JsapiPrepayResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, err
}
if result.PrepayID == "" {
return nil, fmt.Errorf("empty prepay_id in response")
}
return &result, nil
}
func (c *Client) MiniProgramPay(ctx context.Context, req *JsapiPrepayRequest) (*MiniProgramPayResult, error) {
prepay, err := c.JsapiPrepay(ctx, req)
if err != nil {
return nil, err
}
payParams, err := c.BuildMiniProgramPayParams(prepay.PrepayID)
if err != nil {
return nil, err
}
return &MiniProgramPayResult{
PrepayID: prepay.PrepayID,
Pay: payParams,
}, nil
}