支付宝支付(六):小程序支付(Go+Gin+内网穿透)

news2024/12/22 9:51:01

一、前置条件

(1)go语言,1.18

(2)Gin、第三方依赖包:gopay【github.com/go-pay/gopay/alipay】https://github.com/go-pay/gopay/blob/main/doc/wechat_v3.md

(3)支付宝支付相关信息:appID ,支付宝用户私钥,支付宝公钥【小程序需要拉起支付宝APP,不能使用沙盒账号】小程序文档 - 支付宝文档中心

(4)外网可访问的域名,用于支付成功回调;本地开发自测的话,搞个内网穿透工具;我用的花生壳,花了6块钱买了个域名【当时搞活动买1年,送98年】

(5)基于域名创建映射

(6)启动本地服务,验证映射是否成功 

 准备工作完成,开始编码!!

二、支付客户端代码:aliPayClient 

(1)保证单列,用go的sync.Once实现,具体代码如下

package lib

import (
	"github.com/go-pay/gopay/alipay"
	"log"
	. "go_online_pay/config"
	"sync"
)

var aliPayClient *alipay.Client

var syOnce sync.Once

//GetAliPayClient aliPayClient
func GetAliPayClient() *alipay.Client {
	var err error
	syOnce.Do(func() {
		cf := Conf{}
		config := cf.GetConf()
		aliPayClient, err = alipay.NewClient(config.AliPayAppId, config.AliPayPrivateKey, config.AliPayIsProd)
		if err != nil {
			log.Panic(err.Error())
		}
		//配置公共参数
		aliPayClient.SetCharset("utf-8").
			SetSignType(alipay.RSA2).
			SetNotifyUrl("")

	})
	return aliPayClient
}

(2)基础配置信息,都放到conf.yaml文件内了,同时配套conf.go文件,mian.go启动加载conf.ymal配置内容,后续通过conf.Xx获取属性值【config.AliPayAppId】,根据自己项目的实际情况,放置支付宝相关配置信息

#--------------支付宝支付测试账号(可真实支付)----------------
#是否生产账号
alipay_is_prod: true
#支付宝-APPID
alipay_app_id: 
#支付宝-用户私钥
alipay_private_key: 
#支付宝公钥
alipay_public_key:
#支付宝支付回调地址
alipay_notify_url: https://XXX/alipayNotify

三、支付服务类代码:AlipayService

(1)代码中含有:面对面(收款码链接),小程序支付,查询支付结果,退款,查询退款结果

package service

import (
	"context"
	"fmt"
	"github.com/go-pay/gopay"
	"github.com/go-pay/gopay/alipay"
	"go_online_pay/config"
	"go_online_pay/lib"
)

type AlipayService struct {
}

//TradePreCreate 当面付 扫码支付,获取二维码
func (AlipayService) TradePreCreate(bm gopay.BodyMap) string {
	client := lib.GetAliPayClient()
	client.SetNotifyUrl(config.GetConfLib().AliPayNotifyUrl)
	//统一收单线下交易预创建
	payParam, err := client.TradePrecreate(context.Background(), bm)
	if err != nil {
		fmt.Println(fmt.Sprintf("-----aliPay TradePreCreate()-------error:%s", err.Error()))
		return ""
	}
	return payParam.Response.QrCode
}

//TradeCreate 统一收单交易创建接口
func (AlipayService) TradeCreate(bm gopay.BodyMap) (*alipay.TradeCreateResponse, error) {
	client := lib.GetAliPayClient()
	//统一收单线下交易预创建
	payParam, err := client.TradeCreate(context.Background(), bm)
	if err != nil {
		fmt.Println(fmt.Sprintf("-----aliPay TradeCreate()-------error:%s", err.Error()))
		return nil, err
	}
	return payParam, nil
}

//Refund 退款接口
func (AlipayService) Refund(bm gopay.BodyMap) *alipay.TradeRefundResponse {
	client := lib.GetAliPayClient()
	//统一收单线下交易预创建
	payParam, err := client.TradeRefund(context.Background(), bm)
	if err != nil {
		fmt.Println(fmt.Sprintf("-----aliPay Refund()-------error:%s", err.Error()))
		return nil
	}
	//接口返回fund_change=Y为退款成功,fund_change=N或无此字段值返回时需通过退款查询接口进一步确认退款状态
	return payParam
}

//QueryRefund 查询退款接口
func (AlipayService) QueryRefund(orderNo, refundNo string) (*alipay.TradeFastpayRefundQueryResponse, error) {
	client := lib.GetAliPayClient()
	//统一收单线下交易预创建
	bm := make(gopay.BodyMap)
	//商户订单号
	bm.Set("out_trade_no", orderNo)
	//退款请求号
	bm.Set("out_request_no", refundNo)
	payParam, err := client.TradeFastPayRefundQuery(context.Background(), bm)
	if err != nil {
		fmt.Println(fmt.Sprintf("-----aliPay QueryRefund()-------error:%s", err.Error()))
	}
	//接口返回fund_change=Y为退款成功,fund_change=N或无此字段值返回时需通过退款查询接口进一步确认退款状态
	return payParam, err
}

//TradeQuery 交易查询接口
func (AlipayService) TradeQuery(orderNo string) *alipay.TradeQueryResponse {
	client := lib.GetAliPayClient()
	//请求参数
	bm := make(gopay.BodyMap)
	bm.Set("out_trade_no", orderNo)
	//统一收单线下交易预创建
	payParam, err := client.TradeQuery(context.Background(), bm)
	if err != nil {
		fmt.Println(fmt.Sprintf("-----aliPay TradeQuery(%s)-------error:%s", orderNo, err.Error()))
		return nil
	}
	return payParam
}

四、具体方法使用

(1)小程序支付部分代码

    //第三方30分钟过期
	expire := time.Now().Add(30 * 60 * time.Second).Format(time.RFC3339)
	description := util.ExtractStrByLen(orderInfo.CommodityName, 40)

		//请求参数
		bm := make(gopay.BodyMap)
		bm.Set("notify_url", config.GlobalConf.AliPayNotifyUrl)
		bm.Set("subject", description).
			//买家支付宝用户ID
			Set("buyer_id", openId).
			//商户订单号。由商家自定义
			Set("out_trade_no", payNo).
			//元,小数点两位,去掉千分位
			Set("total_amount", strings.Replace(payAmount, ",", "", -1))
		//统一收单线下交易预创建
		payParam, errAli := AlipayService{}.TradeCreate(bm)
		if errAli != nil {
			fmt.Println(fmt.Sprintf("-----aliPay TradeCreate() 返回值  ---errString -> %s", errAli.Error()))
			errMap := util.JsonToMap(errAli.Error())
			if val, ok := errMap["code"]; ok {
				result["errorMsg"] = "拉起支付宝支付失败:" + val.(string)
				return e.ERROR, result
			}
		}
		if payParam == nil || len(payParam.Response.TradeNo) == 0 {
			result["errorMsg"] = "拉起支付宝支付失败:无AliPrepayId"
			return e.ERROR, result
		}
		result["ali_prepay_Id"] = payParam.Response.TradeNo

前端拿到ali_prepay_Id后,通过sdk拉起支付宝APP

(2) 其他方法,都可以仿照小程序入参方式,根据支付宝接口文档填写相应的key即可

(3)支付回调方法代码:回调后写入队列,异步处理

//AlipayNotify 解析支付宝支付异步通知的参数到BodyMap
func AlipayNotify(c *gin.Context) {
	logger.Info("---------------------AlipayNotify START -------------")
	// c.Request 是 gin 框架的写法
	notifyReq, err := alipay.ParseNotifyToBodyMap(c.Request)
	if err != nil {
		logger.Info("------AlipayNotify ERR -------------,err:", err.Error())
		return
	}
	logger.Info("-----AlipayNotify notifyReq: ", notifyReq)

	ok, err := alipay.VerifySign(config.GlobalConf.AliPayPublicKey, notifyReq)
	if ok {
		if notifyReq["receipt_amount"] == nil {
			logger.Info("------ AlipayNotify 支付宝退款回调,忽略 -------------")
		} else {
			//验签成功
			var alReq = make(map[string]interface{})
			//商户订单号:商户系统内部订单号
			alReq["pay_no"] = notifyReq["out_trade_no"]
			alReq["trade_no"] = notifyReq["trade_no"]
			//与支付宝同步
			alReq["trade_status"] = notifyReq["trade_status"]
			alReq["notify_time"] = notifyReq["notify_time"]
			//订单金额。本次交易支付的订单金额,单位为人民币(元)
			totalAmountF, _ := strconv.ParseFloat(notifyReq["total_amount"].(string), 64)
			totalAmount := int(totalAmountF * 100)
			//实收金额。商家在交易中实际收到的款项,单位为人民币(元)。
			receiptAmountF, _ := strconv.ParseFloat(notifyReq["receipt_amount"].(string), 64)
			receiptAmount := int(receiptAmountF * 100)
			alReq["total_amount"] = totalAmount
			alReq["receipt_amount"] = receiptAmount

			var mapData = make(map[string]interface{})
			mapData["data_type"] = "PayNotify"
			mapData["param"] = map[string]interface{}{"payType": "aliPay", "notifyReq": alReq}
			reqJson, _ := json.Marshal(mapData)
			lib.PushMessToPayQueue(reqJson)
		}

	}

	/*	notifyReq["order_no"] = "订单号"
		notifyReq["trade_no"] = "2022061322001402060501532448"
		notifyReq["trade_status"] = "TRADE_SUCCESS"
		notifyReq["notify_time"] = "2022-06-13 10:09:36"
		notifyReq["total_amount"] = 202000
		notifyReq["receipt_amount"] = 200000
		var mapData = make(map[string]interface{})
		mapData["data_type"] = "PayNotify"
		mapData["param"] = map[string]interface{}{"payType": "aliPay", "notifyReq": notifyReq}
		reqJson, _ := json.Marshal(mapData)
		lib.PushMessToPayQueue(reqJson)*/

	// 此写法是 gin 框架返回支付宝的写法
	c.String(http.StatusOK, "%s", "success")
	logger.Info("---------------------AlipayNotify END -------------")
	return
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/726378.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java面向对象程序开发——JDK8新特性

文章目录 网络编程入门知识JDK8新特性Lambda表达式以多线程为例:Lambda结合for循环: Stream流获取一个流的2种常用的方式:常用方法终结方法延迟方法 案例 网络编程入门知识 软件结构、协议分类、网络通信协议、网络编程三要素、TCP通信协议、…

爬虫的分布式思维与实现思路

爬虫的分布式思维与实现思路 基本构架 scrapy-redis实现分布式,其实从原理上来说很简单,这里为描述方便,我们把自己的核心服务器称为master,而把用于跑爬虫程序的机器称为slave 我们知道,采用scrapy框架抓取网页&…

netty学习(4):通过SpringBoot Web发送消息实现netty实现多个客户端与服务器通信

1. 基于netty学习(3):SpringBoot整合netty实现多个客户端与服务器通信的学习,要想通过http在netty客户端之间发送消息,需要实现spring-boot-starter-web,封装消息格式,自动调用netty客户端 2. 封装一个简单…

算法笔记——哈希表篇

一般哈希表都是用来快速判断一个元素是否出现集合里,哈希表并不意味着一定要使用HashMap,有时候使用数组更方便,有时候要使用set,依据具体情况而定,哈希表是典型的空间换时间。 数组作为哈希表 一些应用场景就是为数组…

工具 | 应用程序无法启动,应为应用程序的并行配置不正确

工具 | 应用程序无法启动,应为应用程序的并行配置不正确 “E:\02-Doc\朱老师物联网大讲堂-全部视频\朱有鹏老师嵌入式linux核心课程\开发版光盘资料\X210V3S_A\tools\x210_Fusing_Tool.exe”的激活上下文生成失败。 找不到从属程序集 Microsoft.VC90.MFC,processorA…

DNS-去中心化域名系统,创建您在DeSoc 社会中的YUAN ID

传统域名系统 (DNS) 是一个分层的分散信息存储,用于将用户在网络浏览器中输入可读名称(例如 www.baidu.com)解析为IP地址,来访问互联网上的计算机。传统DNS使用一种分布式数据库,有严格的上下级关系,上级仅…

[Android JNI] --- JNIEnv和JavaVM

1 JVMEnv 1.1 JNIEnv 是什么 JNIEnv 即 Java Native Interface Environment,Java 本地编程接口环境。JNIEnv 内部定义了很多函数用于简化我们的 JNI 编程。 JNIEnv是提供JNI Native函数的基础环境,线程相关,不同线程的JNIEnv相互独立&#…

SpringBoot快速回顾(@value读取配置文件)

目录 1.定义配置文件2. 定义Controller类3. 测试4. 优化4.1 封装实体类4.3 定义controller类4.2 测试 本文将介绍如何使用value读取配置文件的内容。 在实际项目中,往往会在配置文件中写项目部署需要配置的环境信息(数据库驱动,数据库账号密码…

医疗金融法律大模型:从ChatDoctor到FinBERT/FinGPT/BloombergGPT、ChatLaw/LawGPT_zh

第一部分 各种医疗类ChatGPT:或中英文数据微调LLaMA、或中文数据微调ChatGLM 1.1 基于LLaMA微调的中英文版ChatDoctor 1.1.1 ChatDoctor:通过self-instruct技术提示API的数据和医患对话数据集微调LLaMA Github上有一个基于LLaMA模型的医疗微调模型&am…

zabbix (自定义监控内容-配置邮件报警-自动发现与自动注册)

目录 zabbix 客户端主机配置自定义监控内容设置邮件报警zabbix 自动发现与自动注册zabbix 自动发现(对于 agent2 是被动模式)//zabbix 自动发现(对于 agent2 是被动模式)zabbix 自动注册(对于 agent2 是主动模式&#…

IDEA+springboot+jpa+Layui+Mysql销售考评系统源码

IDEAspringbootjpaLayuiMysql销售考评系统源码 一、系统介绍1.环境配置 二、系统展示1. 管理员登录2.评分结果3.评分管理4.添加评分5.用户管理6.添加用户7.角色管理8.添加角色8.销售管理9.添加销售 三、部分代码UserDao.javaUserController.javaUser.java 四、其他获取源码 一、…

计算机组成原理实验二:多位逻辑门构建

目录 一、实验目的 二、实验设备 三、实验原理 四、实验内容 1. 16位非门 2.16位与门 3.16位或门 4. 16位复用器 五、实验习题 1.还可以怎样设计各种芯片的物理结构 2.“block copy”(块复制)和edit菜单中“copy to clipboard”的区别 六、自…

在线OJ项目

1.在线OJ-背景介绍 在线的网页版的编程平台.,打开一个网站,上面就能看到很多的算法题.,在线做题,在线提交.立即就能看到运行结果,是否通过. leetcode 牛客等 一个在线OJ平台,核心功能: 能够管理题目(保存很多的题目信息:题干+测试用例)题…

FPGA软核调试方法

软核工程创建步骤 创建如下工程目录 bin目录:存放SDK工程生成的elf文件(Release编译模式) hdf目录:存放fpga工程师提供的的hdf文件 prj目录:工程目录(包含SDK工程源码) doc目录:文档目录 基于2018.2版本SDK建立工程 打开Xil…

Spring Boot中的CSRF攻击及预防

Spring Boot中的CSRF攻击及预防 什么是CSRF攻击? CSRF(Cross-site Request Forgery)跨站请求伪造,也称为“one-click attack”或“session riding”,是一种网络攻击方式,攻击者通过在受害者浏览器上欺骗或…

【redis】生产级部署

目录 环境部署 redis环境部署 redis多实例配置 构建redis cluster集群 cluster生产集群部署 Cluster集群故障切换 环境部署 1 、关闭防火墙 2 、准备两台虚拟机配置内容如下 redis-master 192.168.108.67 7000 redis-master01 7001 redis-master02 7002 redis-ma…

Vue2.0-3.0 入门到实战 - 初始及插件安装

1 创建view实例,初始化渲染 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body><div id"app">{{ msg }} </div><script type&…

第123天:内网安全-域防火墙入站出站规则不出网隧道上线组策略对象同步

#知识点&#xff1a; 0、防火墙组策略对象 1、OSI七层协议模型 2、正反向监听器说明 3、隧道技术分层协议 4、CS&MSF&控制上线-隧道技术&#xff1a;解决不出网协议上线的问题&#xff08;利用出网协议进行封装出网&#xff09; -代理技术&#xff1a;解决网络通讯不通…

arduino平台控制直流电机PID速度闭环控制编程实现

PID&#xff08;Proportional-Integral-Derivative&#xff0c;比例-积分-微分&#xff09;控制是一种常用的控制算法&#xff0c;可以用于实现直流有刷电机的速度闭环控制。PID控制器根据当前的误差&#xff08;期望速度与实际速度之差&#xff09;来计算输出&#xff0c;以调…

RabbitMQ系列(15)--死信队列的简介与死信队列和死信消费者的实现

1、死信的概念 死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;一般来说producer&#xff08;生产者&#xff09;将消息投递到broker或直接放到queue&#xff08;队列&#xff09;中&#xff0c;consumer&#xff08;消费者&#xff09;从queue&#xff08;队列&am…