github.com/chanxuehong/wechat@v0.0.0-20230222024006-36f0325263cd/mch/pay/micropay.go (about)

     1  package pay
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"time"
     7  
     8  	"github.com/chanxuehong/wechat/mch/core"
     9  	"github.com/chanxuehong/wechat/util"
    10  )
    11  
    12  // MicroPay 提交刷卡支付.
    13  func MicroPay(clt *core.Client, req map[string]string) (resp map[string]string, err error) {
    14  	return clt.PostXML(core.APIBaseURL()+"/pay/micropay", req)
    15  }
    16  
    17  type MicroPayRequest struct {
    18  	XMLName struct{} `xml:"xml" json:"-"`
    19  
    20  	// 必选参数
    21  	Body           string `xml:"body"`             // 商品或支付单简要描述
    22  	OutTradeNo     string `xml:"out_trade_no"`     // 商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
    23  	TotalFee       int64  `xml:"total_fee"`        // 订单总金额,单位为分,详见支付金额
    24  	SpbillCreateIP string `xml:"spbill_create_ip"` // APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
    25  	AuthCode       string `xml:"auth_code"`        // 扫码支付授权码,设备读取用户微信中的条码或者二维码信息
    26  
    27  	// 可选参数
    28  	DeviceInfo string `xml:"device_info"` // 终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB"
    29  	NonceStr   string `xml:"nonce_str"`   // 随机字符串,不长于32位。NOTE: 如果为空则系统会自动生成一个随机字符串。
    30  	SignType   string `xml:"sign_type"`   // 签名类型,默认为MD5,支持HMAC-SHA256和MD5。
    31  	Detail     string `xml:"detail"`      // 商品名称明细列表
    32  	Attach     string `xml:"attach"`      // 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
    33  	FeeType    string `xml:"fee_type"`    // 符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
    34  	GoodsTag   string `xml:"goods_tag"`   // 商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠
    35  	LimitPay   string `xml:"limit_pay"`   // no_credit--指定不能使用信用卡支付
    36  	SceneInfo  string `xml:"scene_info"`  // 场景信息
    37  }
    38  
    39  type MicroPayResponse struct {
    40  	XMLName struct{} `xml:"xml" json:"-"`
    41  
    42  	// 必选返回
    43  	OpenId        string    `xml:"openid"`         // 用户在商户appid下的唯一标识
    44  	IsSubscribe   bool      `xml:"is_subscribe"`   // 用户是否关注公众账号
    45  	TradeType     string    `xml:"trade_type"`     // 调用接口提交的交易类型,取值如下:JSAPI,NATIVE,APP,MICROPAY,详细说明见参数规定
    46  	BankType      string    `xml:"bank_type"`      // 银行类型,采用字符串类型的银行标识
    47  	TotalFee      int64     `xml:"total_fee"`      // 订单总金额,单位为分
    48  	CashFee       int64     `xml:"cash_fee"`       // 现金支付金额订单现金支付金额,详见支付金额
    49  	TransactionId string    `xml:"transaction_id"` // 微信支付订单号
    50  	OutTradeNo    string    `xml:"out_trade_no"`   // 商户系统的订单号,与请求一致。
    51  	TimeEnd       time.Time `xml:"time_end"`       // 订单支付时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
    52  
    53  	// 下面字段都是可选返回的(详细见微信支付文档), 为空值表示没有返回, 程序逻辑里需要判断
    54  	DeviceInfo         string `xml:"device_info"`          // 微信支付分配的终端设备号
    55  	SubOpenId          string `xml:"sub_openid"`           // 子商户appid下用户唯一标识,如需返回则请求时需要传sub_appid
    56  	SubIsSubscribe     *bool  `xml:"sub_is_subscribe"`     // 用户是否关注子公众账号,仅在公众账号类型支付有效,取值范围:Y或N;Y-关注;N-未关注
    57  	FeeType            string `xml:"fee_type"`             // 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
    58  	SettlementTotalFee *int64 `xml:"settlement_total_fee"` // 应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。
    59  	CouponFee          *int64 `xml:"coupon_fee"`           // 代金券金额
    60  	CashFeeType        string `xml:"cash_fee_type"`        // 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型
    61  	Attach             string `xml:"attach"`               // 附加数据,原样返回
    62  	PromotionDetail    string `xml:"promotion_detail"`     // 营销详情
    63  }
    64  
    65  // MicroPay2 提交刷卡支付.
    66  func MicroPay2(clt *core.Client, req *MicroPayRequest) (resp *MicroPayResponse, err error) {
    67  	m1 := make(map[string]string, 24)
    68  	m1["body"] = req.Body
    69  	m1["out_trade_no"] = req.OutTradeNo
    70  	m1["total_fee"] = strconv.FormatInt(req.TotalFee, 10)
    71  	m1["spbill_create_ip"] = req.SpbillCreateIP
    72  	m1["auth_code"] = req.AuthCode
    73  	if req.DeviceInfo != "" {
    74  		m1["device_info"] = req.DeviceInfo
    75  	}
    76  	if req.NonceStr != "" {
    77  		m1["nonce_str"] = req.NonceStr
    78  	} else {
    79  		m1["nonce_str"] = util.NonceStr()
    80  	}
    81  	if req.SignType != "" {
    82  		m1["sign_type"] = req.SignType
    83  	}
    84  	if req.Detail != "" {
    85  		m1["detail"] = req.Detail
    86  	}
    87  	if req.Attach != "" {
    88  		m1["attach"] = req.Attach
    89  	}
    90  	if req.FeeType != "" {
    91  		m1["fee_type"] = req.FeeType
    92  	}
    93  	if req.GoodsTag != "" {
    94  		m1["goods_tag"] = req.GoodsTag
    95  	}
    96  	if req.LimitPay != "" {
    97  		m1["limit_pay"] = req.LimitPay
    98  	}
    99  	if req.SceneInfo != "" {
   100  		m1["scene_info"] = req.SceneInfo
   101  	}
   102  
   103  	m2, err := MicroPay(clt, m1)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	resp = &MicroPayResponse{
   109  		OpenId:          m2["openid"],
   110  		TradeType:       m2["trade_type"],
   111  		BankType:        m2["bank_type"],
   112  		TransactionId:   m2["transaction_id"],
   113  		OutTradeNo:      m2["out_trade_no"],
   114  		DeviceInfo:      m2["device_info"],
   115  		SubOpenId:       m2["sub_openid"],
   116  		FeeType:         m2["fee_type"],
   117  		CashFeeType:     m2["cash_fee_type"],
   118  		Attach:          m2["attach"],
   119  		PromotionDetail: m2["promotion_detail"],
   120  	}
   121  
   122  	if str := m2["is_subscribe"]; str != "" {
   123  		if str == "Y" || str == "y" {
   124  			resp.IsSubscribe = true
   125  		}
   126  	}
   127  	if str := m2["total_fee"]; str != "" {
   128  		if n, err := strconv.ParseInt(str, 10, 64); err != nil {
   129  			err = fmt.Errorf("parse total_fee:%q to int64 failed: %s", str, err.Error())
   130  			return nil, err
   131  		} else {
   132  			resp.TotalFee = n
   133  		}
   134  	}
   135  	if str := m2["cash_fee"]; str != "" {
   136  		if n, err := strconv.ParseInt(str, 10, 64); err != nil {
   137  			err = fmt.Errorf("parse cash_fee:%q to int64 failed: %s", str, err.Error())
   138  			return nil, err
   139  		} else {
   140  			resp.CashFee = n
   141  		}
   142  	}
   143  	if str := m2["time_end"]; str != "" {
   144  		if t, err := core.ParseTime(str); err != nil {
   145  			err = fmt.Errorf("parse time_end:%q to time.Time failed: %s", str, err.Error())
   146  			return nil, err
   147  		} else {
   148  			resp.TimeEnd = t
   149  		}
   150  	}
   151  
   152  	if str := m2["sub_is_subscribe"]; str != "" {
   153  		if str == "Y" || str == "y" {
   154  			resp.SubIsSubscribe = util.Bool(true)
   155  		} else {
   156  			resp.SubIsSubscribe = util.Bool(false)
   157  		}
   158  	}
   159  	if str := m2["settlement_total_fee"]; str != "" {
   160  		if n, err := strconv.ParseInt(str, 10, 64); err != nil {
   161  			err = fmt.Errorf("parse settlement_total_fee:%q to int64 failed: %s", str, err.Error())
   162  			return nil, err
   163  		} else {
   164  			resp.SettlementTotalFee = util.Int64(n)
   165  		}
   166  	}
   167  	if str := m2["coupon_fee"]; str != "" {
   168  		if n, err := strconv.ParseInt(str, 10, 64); err != nil {
   169  			err = fmt.Errorf("parse coupon_fee:%q to int64 failed: %s", str, err.Error())
   170  			return nil, err
   171  		} else {
   172  			resp.CouponFee = util.Int64(n)
   173  		}
   174  	}
   175  
   176  	// 校验返回参数
   177  	if req.OutTradeNo != resp.OutTradeNo {
   178  		err = fmt.Errorf("out_trade_no mismatch, have: %s, want: %s", resp.OutTradeNo, req.OutTradeNo)
   179  		return nil, err
   180  	}
   181  	if req.TotalFee != resp.TotalFee {
   182  		err = fmt.Errorf("total_fee mismatch, have: %d, want: %d", resp.TotalFee, req.TotalFee)
   183  		return nil, err
   184  	}
   185  	return resp, nil
   186  }