yuleduiPay/service/payservice.go

442 lines
13 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"yuleduiPay/common"
"yuleduiPay/repo"
"yuleduiPay/service/po"
"yuleduiPay/service/vo"
"context"
"math/rand"
"github.com/gogf/gf/os/gctx"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/guid"
)
type Pay struct {
payOrderRepo repo.PayOrder
shopRepo repo.Shop
userRepo repo.User
}
// 微信和支付宝小程序,扫码支付
func (t *Pay) GetReduction(ctx context.Context, req *vo.GetReductionReq) (resp *vo.GetReductionResp, err error) {
resp = &vo.GetReductionResp{}
shopAdmin, err := t.shopRepo.GetShopAdminByShopId(req.ShopId)
if err != nil {
return
}
rand_reduction := shopAdmin.RandReduction
r := rand.New(rand.NewSource(time.Now().UnixNano())) //以当前时间纳秒为种子
fmt.Println(rand_reduction)
resp.Reduction = r.Int63n(rand_reduction + 1)
return
}
// 微信和支付宝小程序,扫码支付
func (t *Pay) PayQrCode(ctx context.Context, req *vo.PayQrCodeReq) (resp *vo.PayQrCodeResp, err error) {
g.Log().Line().Printf(nil, "%+v", req)
//计算金豆和随机立减,算出最终打给银盛的钱
payOrder, err := t.getAmount(req.Price, req.Reduction, req.Bean)
if err != nil {
return
}
//获取商铺信息
shop, err := t.shopRepo.GetShopById(req.ShopId)
if err != nil {
return
}
//创建订单记录
payOrder.OrderId = guid.S()
payOrder.OpenId = req.OpenId
payOrder.Mobile = req.Mobile
payOrder.ShopId = req.ShopId
payOrder.Channel = req.Channel
payOrder.Created = gtime.Now()
payOrder.Updated = payOrder.Created
err = t.payOrderRepo.CreatePayOrder(payOrder)
if err != nil {
return
}
yseRespBytes, err := t.ysePayRequestJson(shop, payOrder) //访问java项目获取银盛返回的结果
if err != nil {
return
}
fmt.Println(string(yseRespBytes))
g.Log().Line().Printf(nil, string(yseRespBytes))
yseResp, err := t.yseParseResp(payOrder.Channel, yseRespBytes) //解析报文返回的报文
if err != nil {
return
}
fmt.Println(yseResp)
if yseResp.OutTradeNo != payOrder.OrderId {
errStr := fmt.Sprintf("银盛返回订单号错误,银盛订单号:%s,余乐兑订单号:%s", yseResp.OutTradeNo, payOrder.OpenId)
err = errors.New(errStr)
return
}
//更新支付订单表字段
updates := g.Map{"ysePayStatus": yseResp.TradeStatus, "updated": time.Now()}
err = t.payOrderRepo.UpdatePayOrderByOrderId(updates, payOrder.OpenId)
if err != nil {
return
}
resp = &vo.PayQrCodeResp{}
resp.OrderId = payOrder.OrderId
resp.Bean = payOrder.Bean
resp.Reduction = payOrder.RandReduce
resp.JsapiPayInfo = yseResp.JsapiPayInfo
return
}
func (t *Pay) getAmount(orderPrice string, reduction int, bean float64) (payOrder *po.PayOrder, err error) {
payOrder = &po.PayOrder{}
/*
shopAdmin, err := t.shopRepo.GetShopAdminByShopId(shopId)
if err != nil {
return payOrder, err
}
*/
price, err := strconv.ParseFloat(orderPrice, 64)
if err != nil {
return
}
payOrder.Price = price
payOrder.Bean = bean * 0.15
price = price - payOrder.Bean //去除金豆后的价格
payOrder.RandReduce = float64(reduction) * 0.01
payOrder.Amount = price - payOrder.RandReduce //去除随机立减和金豆后的价格
if payOrder.Amount < 0.01 {
err = errors.New("支付金额小于1分,失败")
return
}
return
}
// 微信用户登录
func (t *Pay) Login(ctx context.Context, req *vo.WxLoginReq) (res *vo.WxLoginRes, err error) {
fmt.Println("11-----", req)
res, err = t.wxAuthorize(req.JsCode)
if err != nil {
return
}
//根据openId查询手机号
userItem, _ := t.userRepo.GetUserByWxId(res.OpenId)
res.Mobile = userItem.Mobile
res.Bean = userItem.Bean
return
}
// 用户注册
func (t *Pay) WxRegister(ctx context.Context, req *vo.WxMobileReq) (res *vo.WxMobileRes, err error) {
fmt.Println("WxRegister----", req)
value, err := g.Redis().Get(ctx, "WxAccessToken")
if err != nil {
return
}
fmt.Println("1111111", err)
accessToken := value.String()
fmt.Println("-------", accessToken)
if accessToken == "" { //从微信小程序获取token并更新redis
fmt.Println("2222222")
accessToken, err = t.getWeiXinAccessToken()
if err != nil {
fmt.Println("444--", err)
return
}
}
fmt.Println("----> ", accessToken)
//获取手机号
mobile, err := t.getUserPhoneNumber(req.Code, accessToken)
if err != nil {
return
}
res = &vo.WxMobileRes{}
res.Mobile = mobile
//查询此手机号在数据库中是否存在
ok, err := t.userRepo.MobileExist(mobile)
if err != nil {
return
}
if ok { //如果存在更新openId
updates := g.Map{"wx_id": req.OpenId, "update_time": gtime.Now()}
err = t.userRepo.UpdatesUser(updates, mobile)
return
}
//不存在的,创建
user := po.User{}
user.UserId = guid.S()
user.WxId = req.OpenId
user.Mobile = mobile
user.CreateTime = gtime.Now()
user.UpdateTime = user.CreateTime
err = t.userRepo.CreateUser(&user)
if err != nil {
return
}
res = &vo.WxMobileRes{}
res.Mobile = mobile
return
}
func (t *Pay) yseParseResp(channel int, respBytes []byte) (*vo.BusPayResp, error) {
yepayOnlineWeixinPayResponse := &vo.BusPayResp{}
if channel == 1 { //微信
ysePayResp := &vo.WxPayResp{}
fmt.Println("999-------> ", string(respBytes))
err := json.Unmarshal(respBytes, ysePayResp)
if err != nil {
return nil, err
}
fmt.Println("银盛返回的应答:", ysePayResp)
yepayOnlineWeixinPayResponse = &ysePayResp.YsepayOnlineWeixinPayResponse
} else { //支付宝
ysePayResp := &vo.ZfbPayResp{}
err := json.Unmarshal(respBytes, ysePayResp)
if err != nil {
return nil, err
}
fmt.Println("银盛返回的应答:", ysePayResp)
yepayOnlineWeixinPayResponse = &ysePayResp.YsepayOnlineAlijsapiPayResponse
}
if yepayOnlineWeixinPayResponse.Code != "10000" {
return nil, errors.New("银盛应答返回错误:" + yepayOnlineWeixinPayResponse.Msg)
}
return yepayOnlineWeixinPayResponse, nil
}
func (t *Pay) ysePayRequestJson(shop *po.Shop, payOrder *po.PayOrder) ([]byte, error) {
//业务请求参数
busReq := vo.PayBusReq{}
busReq.OutTradeNo = payOrder.OrderId //订单编号
busReq.Shopdate = payOrder.Created.Format("20060102") //商户系统的交易发生日期格式
busReq.Subject = "余乐兑小程序订单" //订单备注
busReq.TotalAmount = fmt.Sprintf("%.2f", payOrder.Amount) //该笔订单的资金总额,保留两位小数
busReq.Currency = "CNY" //默认人民币
busReq.SellerId = shop.ChildMerchNumber //银盛商户ID
busReq.TimeoutExpress = "1h" //设置未付款交易的超时时间,一个小时
busReq.BusinessCode = g.Cfg().MustGet(nil, "ysepay.busCode").String() //业务代码
var bjson []byte
var err error
if payOrder.Channel == 1 { //微信渠道
wxBusReq := vo.PayWxBusReq{PayBusReq: busReq}
wxBusReq.SubOpenid = payOrder.OpenId //微信用户ID
wxBusReq.IsMinipg = "1" //微信小程序支付:1
wxBusReq.Appid = g.Cfg().MustGet(nil, "weixin.appId").String() //微信小程序APPID
bjson, err = json.Marshal(wxBusReq)
if err != nil {
return nil, err
}
} else { //支付宝渠道
zfBusReq := vo.PayZfBusReq{PayBusReq: busReq}
zfBusReq.BuyerId = payOrder.OpenId //支付宝用户ID
bjson, err = json.Marshal(zfBusReq)
if err != nil {
return nil, err
}
}
commonReq := vo.PayCommonReq{}
if payOrder.Channel == 1 { //微信渠道
commonReq.Method = "ysepay.online.weixin.pay"
} else { //支付宝渠道
commonReq.Method = "ysepay.online.alijsapi.pay"
}
commonReq.PartnerId = g.Cfg().MustGet(nil, "ysepay.CERTID").String() //在银盛支付开设的服务商商户号
commonReq.Timestamp = time.Now().Format("2006-01-02 15:04:05") //发送请求的时间
commonReq.Charset = "UTF-8" //商户网站使用的编码格式
commonReq.SignType = "SM" //报文签名算法
commonReq.NotifyUrl = g.Cfg().MustGet(nil, "ysepay.notifyUrl").String() //交易成功异步通知到商户的后台地址
commonReq.Version = "3.0" //接口版本
commonReq.BizContent = string(bjson)
bjson, err = json.Marshal(&commonReq)
if err != nil {
return nil, err
}
url := g.Cfg().MustGet(nil, "ysepay.url").String()
xxx := string(bjson)
fmt.Println(xxx)
//发送给java项目让其访问银盛接口
bjson, err = common.HttpPost(url, "application/json", bjson)
if err != nil {
return nil, err
}
return bjson, err
}
// 微信授权获取openId
func (t *Pay) wxAuthorize(jsCode string) (*vo.WxLoginRes, error) {
var urlResp *http.Response
var err error
appid := g.Cfg().MustGet(nil, "weixin.appId").String()
wxSecret := g.Cfg().MustGet(nil, "weixin.secret").String()
wx_url := "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid +
"&secret=" + wxSecret + "&js_code=" + jsCode + "&grant_type=authorization_code"
fmt.Println("jsCode:", jsCode)
for i := 0; i < 2; i++ { // 失败一次,再次调用
urlResp, err = http.Get(wx_url)
if err == nil { // 成功的话,跳出
break
}
}
if err != nil {
return nil, err
}
body, err := io.ReadAll(urlResp.Body)
if err != nil {
return nil, err
}
urlResp.Body.Close()
sessionResp := vo.WxLoginRes{} // 用户标志信息
err = json.Unmarshal(body, &sessionResp)
if err != nil {
return nil, err
}
if sessionResp.ErrCode != 0 {
err = errors.New("errCode:" + strconv.Itoa(sessionResp.ErrCode) + " errMsg:" + sessionResp.ErrMsg)
return nil, err
}
return &sessionResp, err
}
// 获取accessToken
func (t *Pay) getWeiXinAccessToken() (string, error) {
type WeiXinAccessResp struct {
AccessToken string `json:"access_token"` // 获取到的凭证
ExpiresIn int `json:"expires_in"` // 凭证有效时间 单位秒。目前是7200秒之内的值。
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
}
type WxSessionResp struct {
OpenId string `json:"openid"` // 用户唯一标识
SessionKey string `json:"session_key"` // 会话密钥
UnionId string `json:"unionid"` // 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回
ErrCode int `json:"errcode"` // 错误码
ErrMsg string `json:"errmsg"` // 错误信息
}
resp := WeiXinAccessResp{}
appid := g.Cfg().MustGet(nil, "weixin.appId").String()
wxSecret := g.Cfg().MustGet(nil, "weixin.secret").String()
url := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" +
appid + "&secret=" + wxSecret
fmt.Println("获取token:", url)
var urlResp *http.Response
var err error
for i := 0; i < 2; i++ { // 失败一次,再次调用
urlResp, err = http.Get(url)
if err == nil { // 成功的话,跳出
break
}
}
if err != nil {
return "", err
}
body, err := io.ReadAll(urlResp.Body)
if err != nil {
return "", err
}
urlResp.Body.Close()
fmt.Println(string(body))
sessionResp := WxSessionResp{}
err = json.Unmarshal(body, &resp)
if err != nil {
return "", err
}
if resp.Errcode != 0 {
err = errors.New(sessionResp.ErrMsg)
return "", err
}
// AccessToken 存入Redis中
live := resp.ExpiresIn - 60*10 // 提前10分钟过期
ctx := gctx.New()
err = g.Redis().SetEX(ctx, "WxAccessToken", resp.AccessToken, int64(live))
if err != nil {
return "", err
}
return resp.AccessToken, nil
}
// 获取手机号
func (s *Pay) getUserPhoneNumber(code, accessToken string) (string, error) {
type WeiXinWatermark struct {
Appid string `json:"appid"` // 小程序appid
Timestamp int `json:"timestamp"` // 用户获取手机号操作的时间戳
}
type WeixinPhoneInfo struct {
PhoneNumber string `json:"phoneNumber"` // 用户绑定的手机号(国外手机号会有区号)
PurePhoneNumber string `json:"purePhoneNumber"` // 没有区号的手机号
CountryCode string `json:"countryCode"` // 区号
Watermark WeiXinWatermark `json:"watermark"` // 数据水印
}
type WeiXinMobileResp struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
PhoneInfo WeixinPhoneInfo `json:"phone_info"` // 用户手机号信息
}
type WeiXinMobileReq struct {
Code string `json:"code"` // 前端传的Code
}
req := WeiXinMobileReq{Code: code}
url := "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken
fmt.Println("Code:", req.Code)
reqParam, err := json.Marshal(&req)
if err != nil {
return "", err
}
reqBody := strings.NewReader(string(reqParam))
httpReq, err := http.NewRequest("POST", url, reqBody)
if err != nil {
return "", err
}
httpReq.Header.Add("Content-Type", "application/json")
var urlResp *http.Response
for i := 0; i < 2; i++ { // 失败一次,再次调用
urlResp, err = http.DefaultClient.Do(httpReq)
if err == nil { // 成功的话,跳出
break
}
}
if err != nil {
return "", err
}
body, err := io.ReadAll(urlResp.Body)
urlResp.Body.Close()
resp := WeiXinMobileResp{}
fmt.Println(string(body))
err = json.Unmarshal(body, &resp)
if err != nil {
return "", err
}
if resp.Errcode != 0 {
fmt.Println(resp)
err = errors.New(resp.Errmsg)
return "", err
}
return resp.PhoneInfo.PhoneNumber, nil
}