Merge branch 'dev'
This commit is contained in:
commit
d36b9217fd
@ -117,7 +117,7 @@ func (r *OrderInquiryService) CancelOrderInquiry(req requests.CancelOrderInquiry
|
||||
NotifyUrl: config.C.Wechat.RefundNotifyDomain + config.C.Wechat.PatientInquiryRefundNotifyUrl,
|
||||
}
|
||||
|
||||
refund, err := refundRequest.Refund()
|
||||
refund, err := refundRequest.Refund(1)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return false, errors.New(err.Error())
|
||||
|
||||
@ -171,7 +171,7 @@ func (r *OrderProductService) CancelOrderProduct(req requests.CancelOrderProduct
|
||||
NotifyUrl: config.C.Wechat.RefundNotifyDomain + config.C.Wechat.PatientProductRefundNotifyUrl,
|
||||
}
|
||||
|
||||
refund, err := refundRequest.Refund()
|
||||
refund, err := refundRequest.Refund(2)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return false, errors.New(err.Error())
|
||||
|
||||
17
config.yaml
17
config.yaml
@ -70,9 +70,20 @@ wechat:
|
||||
refund-notify-domain: https://dev.hospital.applets.igandanyiyuan.com/
|
||||
doctor-app-id: wxc83296720404aa7b
|
||||
doctor-app-secret: 817665d3763637fe66d56548f8484622
|
||||
mch-id: 1636644248
|
||||
v3-api-secret: gdxz292sjSOadN3m2pCda03NfCsmNadY
|
||||
mch-certificate-serial-number: 7DEC0E6C57E0DC71F077F02F52406566AF39BEBB
|
||||
pay-1636644248:
|
||||
mch-id: 1636644248
|
||||
v3-api-secret: gdxz292sjSOadN3m2pCda03NfCsmNadY
|
||||
mch-certificate-serial-number: 7DEC0E6C57E0DC71F077F02F52406566AF39BEBB
|
||||
platform-certs: extend/weChat/certs/1659662936/wechatpay_112FCCD1B9ECC8292703AB7363C73D74B6AFDC1A.pem
|
||||
private-key: extend/weChat/certs/1636644248/apiclient_key.pem
|
||||
certificate: extend/weChat/certs/1636644248/apiclient_cert.pem
|
||||
pay-1659662936:
|
||||
mch-id: 1659662936
|
||||
v3-api-secret: gdxz292sjSOadNNad2pCda03NfC2msmY
|
||||
mch-certificate-serial-number: 12FAA5F61708B795BB5337AE915494E2DC2CA87B
|
||||
platform-certs: extend/weChat/certs/1659662936/wechatpay_5B5C8A69CC86D1127F6B6AA06AAAF10531EEFE90.pem
|
||||
private-key: extend/weChat/certs/1659662936/apiclient_key.pem
|
||||
certificate: extend/weChat/certs/1659662936/apiclient_cert.pem
|
||||
|
||||
# [处方平台]
|
||||
pre:
|
||||
|
||||
@ -1,18 +1,37 @@
|
||||
package config
|
||||
|
||||
type Wechat struct {
|
||||
PatientAppId string `mapstructure:"patient-app-id" json:"patient-app-id" yaml:"patient-app-id"`
|
||||
PatientAppSecret string `mapstructure:"patient-app-secret" json:"patient-app-secret" yaml:"patient-app-secret"`
|
||||
PatientInquiryPayNotifyUrl string `mapstructure:"patient-inquiry-pay-notify-url" json:"patient-inquiry-pay-notify-url" yaml:"patient-inquiry-pay-notify-url"`
|
||||
PatientInquiryRefundNotifyUrl string `mapstructure:"patient-inquiry-refund-notify-url" json:"patient-inquiry-refund-notify-url" yaml:"patient-inquiry-refund-notify-url"`
|
||||
PatientProductPayNotifyUrl string `mapstructure:"patient-product-pay-notify-url" json:"patient-product-pay-notify-url" yaml:"patient-product-pay-notify-url"`
|
||||
PatientProductRefundNotifyUrl string `mapstructure:"patient-product-refund-notify-url" json:"patient-product-refund-notify-url" yaml:"patient-product-refund-notify-url"`
|
||||
PatientDetectionPayNotifyUrl string `mapstructure:"patient-detection-pay-notify-url" json:"patient-detection-pay-notify-url" yaml:"patient-detection-pay-notify-url"`
|
||||
PatientDetectionRefundNotifyUrl string `mapstructure:"patient-detection-refund-notify-url" json:"patient-detection-refund-notify-url" yaml:"patient-detection-refund-notify-url"`
|
||||
RefundNotifyDomain string `mapstructure:"refund-notify-domain" json:"refund-notify-domain" yaml:"refund-notify-domain"` // 回调域名
|
||||
DoctorAppId string `mapstructure:"doctor-app-id" json:"doctor-app-id" yaml:"doctor-app-id"`
|
||||
DoctorAppSecret string `mapstructure:"doctor-app-secret" json:"doctor-app-secret" yaml:"doctor-app-secret"`
|
||||
MchId string `mapstructure:"mch-id" json:"mch-id" yaml:"mch-id"` // 商户号
|
||||
V3ApiSecret string `mapstructure:"v3-api-secret" json:"v3-api-secret" yaml:"v3-api-secret"` // 商户APIv3密钥
|
||||
MchCertificateSerialNumber string `mapstructure:"mch-certificate-serial-number" json:"mch-certificate-serial-number" yaml:"mch-certificate-serial-number"` // 商户证书序列号
|
||||
PatientAppId string `mapstructure:"patient-app-id" json:"patient-app-id" yaml:"patient-app-id"`
|
||||
PatientAppSecret string `mapstructure:"patient-app-secret" json:"patient-app-secret" yaml:"patient-app-secret"`
|
||||
PatientInquiryPayNotifyUrl string `mapstructure:"patient-inquiry-pay-notify-url" json:"patient-inquiry-pay-notify-url" yaml:"patient-inquiry-pay-notify-url"`
|
||||
PatientInquiryRefundNotifyUrl string `mapstructure:"patient-inquiry-refund-notify-url" json:"patient-inquiry-refund-notify-url" yaml:"patient-inquiry-refund-notify-url"`
|
||||
PatientProductPayNotifyUrl string `mapstructure:"patient-product-pay-notify-url" json:"patient-product-pay-notify-url" yaml:"patient-product-pay-notify-url"`
|
||||
PatientProductRefundNotifyUrl string `mapstructure:"patient-product-refund-notify-url" json:"patient-product-refund-notify-url" yaml:"patient-product-refund-notify-url"`
|
||||
PatientDetectionPayNotifyUrl string `mapstructure:"patient-detection-pay-notify-url" json:"patient-detection-pay-notify-url" yaml:"patient-detection-pay-notify-url"`
|
||||
PatientDetectionRefundNotifyUrl string `mapstructure:"patient-detection-refund-notify-url" json:"patient-detection-refund-notify-url" yaml:"patient-detection-refund-notify-url"`
|
||||
RefundNotifyDomain string `mapstructure:"refund-notify-domain" json:"refund-notify-domain" yaml:"refund-notify-domain"` // 回调域名
|
||||
DoctorAppId string `mapstructure:"doctor-app-id" json:"doctor-app-id" yaml:"doctor-app-id"`
|
||||
DoctorAppSecret string `mapstructure:"doctor-app-secret" json:"doctor-app-secret" yaml:"doctor-app-secret"`
|
||||
Pay1636644248 Pay1636644248 `mapstructure:"pay-1636644248" json:"pay-1636644248" yaml:"pay-1636644248"`
|
||||
Pay1659662936 Pay1659662936 `mapstructure:"pay-1659662936" json:"pay-1659662936" yaml:"pay-1659662936"`
|
||||
}
|
||||
|
||||
// Pay1636644248 北京欣欣相照
|
||||
type Pay1636644248 struct {
|
||||
MchId string `mapstructure:"mch-id" json:"mch-id" yaml:"mch-id"` // 商户号
|
||||
V3ApiSecret string `mapstructure:"v3-api-secret" json:"v3-api-secret" yaml:"v3-api-secret"` // 商户APIv3密钥
|
||||
MchCertificateSerialNumber string `mapstructure:"mch-certificate-serial-number" json:"mch-certificate-serial-number" yaml:"mch-certificate-serial-number"` // 商户证书序列号
|
||||
PlatformCerts string `mapstructure:"platform-certs" json:"platform-certs" yaml:"platform-certs"` // 平台证书
|
||||
PrivateKey string `mapstructure:"private-key" json:"private-key" yaml:"private-key"`
|
||||
Certificate string `mapstructure:"certificate" json:"certificate" yaml:"certificate"`
|
||||
}
|
||||
|
||||
// Pay1659662936 成都欣欣相照
|
||||
type Pay1659662936 struct {
|
||||
MchId string `mapstructure:"mch-id" json:"mch-id" yaml:"mch-id"` // 商户号
|
||||
V3ApiSecret string `mapstructure:"v3-api-secret" json:"v3-api-secret" yaml:"v3-api-secret"` // 商户APIv3密钥
|
||||
MchCertificateSerialNumber string `mapstructure:"mch-certificate-serial-number" json:"mch-certificate-serial-number" yaml:"mch-certificate-serial-number"` // 商户证书序列号
|
||||
PlatformCerts string `mapstructure:"platform-certs" json:"platform-certs" yaml:"platform-certs"` // 平台证书
|
||||
PrivateKey string `mapstructure:"private-key" json:"private-key" yaml:"private-key"`
|
||||
Certificate string `mapstructure:"certificate" json:"certificate" yaml:"certificate"`
|
||||
}
|
||||
|
||||
BIN
extend/weChat/certs/1659662936/apiclient_cert.p12
Normal file
BIN
extend/weChat/certs/1659662936/apiclient_cert.p12
Normal file
Binary file not shown.
25
extend/weChat/certs/1659662936/apiclient_cert.pem
Normal file
25
extend/weChat/certs/1659662936/apiclient_cert.pem
Normal file
@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIENzCCAx+gAwIBAgIUEvql9hcIt5W7UzeukVSU4twsqHswDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
|
||||
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
|
||||
Q0EwHhcNMjMxMjE5MDUxNTA4WhcNMjgxMjE3MDUxNTA4WjCBkDETMBEGA1UEAwwK
|
||||
MTY1OTY2MjkzNjEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTwwOgYDVQQL
|
||||
DDPmiJDpg73ph5HniZvmrKPmrKPnm7jnhafkupLogZTnvZHljLvpmaLmnInpmZDl
|
||||
hazlj7gxCzAJBgNVBAYTAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZI
|
||||
hvcNAQEBBQADggEPADCCAQoCggEBAPCINl3HwUEM2CzerR5u4GbUnvRJDjVmaEbm
|
||||
qHlaYRUrrGDIF/Q63QEbcUh+PP+zZ0Grln7hGT5dwPj6e8jwmVvtEqO8mhBQzynH
|
||||
6MZUONVfxj7L5Pf/+vZtB/PPGKdupZ9tUuodqOrbLycE8xWmSUDPrKEZ9iOEYamz
|
||||
p0s9fPWwcZM8Vf4Gqx8d9ItnFJjVmF8aIJfsvcGNvKWD44JUzZItcsWz/srzxRAH
|
||||
2moGIM8eNf1UNm3k6q43dfoRbZryWmVIyw49xybAKzm5e4Q4z8jCwGoQWgs2tSF4
|
||||
uM0/SxK2j1XRGnVmwRzcdDq/rCPlbqynHXZyUh8Te1540HJKZK8CAwEAAaOBuTCB
|
||||
tjAJBgNVHRMEAjAAMAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGH
|
||||
hoGEaHR0cDovL2V2Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0x
|
||||
QkQ0MjIwRTUwREJDMDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0
|
||||
NzFCNjU0MjJFMTJCMjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQAe4CfX/m0Xz6aI/Studd5pbdb/M7cyTjTwYSoBUNKtEmBwQFCu
|
||||
CsFOEGuqmhq4ciMkooDWY/6mccgluZtdiYB8PQAIk1+IKBgpmhSuJP6yONecoit0
|
||||
JBWzFeSLkBxKkq6CfHhb9wW4EK0oPtgE6LjqLMj13fLy+Mxf4Eu4fgSlsdgGLpBt
|
||||
bXIPbL12bCcP5hJLGRoLbECyx9LaOe1D9RAxwcTx/i+8C1s3IajkkyZnoPasLMs6
|
||||
cdSIp20QSct0klMgpEobmV+Z59NMKAQC/pbaSRrT/PDHUTDAuwA7W+COALg6H26c
|
||||
MAGyeSTnwhc9WccmohKPgi3UcXh/Sn84QqSs
|
||||
-----END CERTIFICATE-----
|
||||
28
extend/weChat/certs/1659662936/apiclient_key.pem
Normal file
28
extend/weChat/certs/1659662936/apiclient_key.pem
Normal file
@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDwiDZdx8FBDNgs
|
||||
3q0ebuBm1J70SQ41ZmhG5qh5WmEVK6xgyBf0Ot0BG3FIfjz/s2dBq5Z+4Rk+XcD4
|
||||
+nvI8Jlb7RKjvJoQUM8px+jGVDjVX8Y+y+T3//r2bQfzzxinbqWfbVLqHajq2y8n
|
||||
BPMVpklAz6yhGfYjhGGps6dLPXz1sHGTPFX+BqsfHfSLZxSY1ZhfGiCX7L3Bjbyl
|
||||
g+OCVM2SLXLFs/7K88UQB9pqBiDPHjX9VDZt5OquN3X6EW2a8lplSMsOPccmwCs5
|
||||
uXuEOM/IwsBqEFoLNrUheLjNP0sSto9V0Rp1ZsEc3HQ6v6wj5W6spx12clIfE3te
|
||||
eNBySmSvAgMBAAECggEBAOVndDqjLozFHFRHGGOjKgDJHsUr/BIwFpowmVQMP/V6
|
||||
DtDLZhU4ItpQew3B4JmbWIrIhSODupjBwC92bqLp3cpP5Gwnj+SpvbtCf57QatgO
|
||||
nTv9KObizE9FE8WTqhbeL7ZLBT5mhVlhLKqRTOpECy92IlYQNbIQKzk4MAFRpqGH
|
||||
CqSMtD0ng6DiMDxdQYLgIDCZIKamv0lF3rQymINQ6VZHpdlB0f3Gp/T/yWzn0nmJ
|
||||
5eOrBhzFKGpevsp00FmvFE7HSqnogC7a3ieLluhfRPyB4QZQjfMorEGKwS8i7I8U
|
||||
ZLA9FhtsaZpvOeNSZoVh3z0sblKUOvhXvL+jgRQK4PkCgYEA+M0QxM3VXst+deDr
|
||||
ZkpmhWdPUQykVdSbfOdR8thYDu30QdjKe/T0X75wcqKh+s2GPK2GZsgeU02+EWqv
|
||||
gVZMgFwGKFCBzDiCs9QwOTO+32OWP62yapFWNba1g4K7Un7MJRsJIj0u0nwjik68
|
||||
hec5KFYTOYttzk9oOLgXLFM6zm0CgYEA933lgvPITFykd2kGKg1v4oA52PPmq9LT
|
||||
2dvVkDpIxioaUUHPALwFLhHLT+w/RPwCfP9gqZAQQFAfpMiTezm+ULA54rLHF2Tj
|
||||
Lm0q3ko98sFW3Ltk1ehw8CdU0p5bii7a7AbuS6V36iTIrC53rhl7N+WL82uMw2w/
|
||||
a8Gz3fIl3gsCgYA+XsHeZC8iBWddS5YXXX1X4e8bRU0JCzQzWpXLh/qDO5mozBzu
|
||||
eBiuy8HKqwRqKA2HtoRjzbT0cx+7o//9L1IcN3V/s7bmKCBzzjSMknE99OwcaIG6
|
||||
f1aaPoRARIyLAKhSgPWINMhBEcejC1vtQWqttu441cAgIP3ighulC/RI/QKBgQDE
|
||||
lxKqdK8USTqzR4+H8+h+CNDqjsMalXuGwGLiEAoirur8xMODl9adg7D2KXkQeQYY
|
||||
+Fp2FmNyUrLwGAtehL2yJmm0s8IFyTPUew7kSCDxJbaz2377k4mymet86iFYoGNx
|
||||
vQeouyWHrfRhIQAcIU2JVyNWFoZX7TJrjBAuKtz9hQKBgGUFIyhQiPYRWDocvuSi
|
||||
McyzYov2AnNehn24vsXC3SCXT804KPOX8RY4he63zWMGaLfCmSq1fhsJnf2pvffK
|
||||
W+9ZXlIH6dwgQ9QyDNjKRVCGwTASsWOyqgn1Be0VDuuoXlREyGNQSn2RpvL1SMFH
|
||||
PW/ePgdfoFkzU5WFJFPnvlU2
|
||||
-----END PRIVATE KEY-----
|
||||
@ -0,0 +1,24 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEFDCCAvygAwIBAgIUW1yKacyG0RJ/a2qgaqrxBTHu/pAwDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
|
||||
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
|
||||
Q0EwHhcNMjMxMjE5MDUxNTA3WhcNMjgxMjE3MDUxNTA3WjBuMRgwFgYDVQQDDA9U
|
||||
ZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl
|
||||
bnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGEwJDTjERMA8GA1UEBwwIU2hlblpo
|
||||
ZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQClp0R6MR6NwEkqRJNF
|
||||
aD1INE0uHoRg1of0D8mTVwoWvADgt9gutpeJ1uTweAtrXkvu9NALxTNaG51tsZa8
|
||||
2fsJ00/Ry1IYUb6xG7kAuh0h4KLgWUUhoXDOKW0B1K/7g3AhCAylUzhD/ghD1Y8F
|
||||
hlMtw87oNzfTE51I/2sGZgnPX2IqIEOjxx1F8Ebzfh5shJGN40guMpBItsCWYe7s
|
||||
zj1zdyERL6zxQN2A7o2QvDeX1EsNIdwoEECv06tWjUEGJioFwb3OKBu5n9jJT+Og
|
||||
EcJFmRasLRQWsooutsDCO7y5wIKrCY9n52eZgk9QlHkYWyiwUqpnrrJ1DC+ueJei
|
||||
QptFAgMBAAGjgbkwgbYwCQYDVR0TBAIwADALBgNVHQ8EBAMCA/gwgZsGA1UdHwSB
|
||||
kzCBkDCBjaCBiqCBh4aBhGh0dHA6Ly9ldmNhLml0cnVzLmNvbS5jbi9wdWJsaWMv
|
||||
aXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1NDk4NDZDMDFDM0U4
|
||||
RUJEMiZzZz1IQUNDNDcxQjY1NDIyRTEyQjI3QTlEMzNBODdBRDFDREY1OTI2RTE0
|
||||
MDM3MTANBgkqhkiG9w0BAQsFAAOCAQEAhWwscSAE1OBR3AGBOuFHs3vFJ+y37z5/
|
||||
EoB9FwVu4zXP1dih101zl83iwIdPEHIR2skXcTjHRI2qdOvu7X5JmWOJP+51RGtX
|
||||
Y+aWXKfhRzRQOUlpNyltlgsGKzbIXLLBzQjMzBNv+n/HX4q9F0TV3SW4zTiMlhD8
|
||||
+bGGGwuIhziWpK9qvr3RPU1j+0bggHhIre+cNolnh1FepS4Gt964zhx6THtrS/jI
|
||||
I64UBBh6moBq7zB5QYloBhW464c7GCEEv5/AdcxGhAe+vuL/mkVRNsSRxVPIxPE6
|
||||
+qoIiNBmQvL/mL+4UKfX6b9h4wrUQUdQP3ljRdpL3a5YTMTUJuoJSQ==
|
||||
-----END CERTIFICATE-----
|
||||
@ -21,9 +21,33 @@ type RefundRequest struct {
|
||||
}
|
||||
|
||||
// Refund 退款
|
||||
func (r RefundRequest) Refund() (*refunddomestic.Refund, error) {
|
||||
func (r RefundRequest) Refund(orderType int) (*refunddomestic.Refund, error) {
|
||||
mchId := ""
|
||||
mchCertificateSerialNumber := ""
|
||||
v3ApiSecret := ""
|
||||
|
||||
if orderType == 1 {
|
||||
// 问诊
|
||||
mchId = config.C.Wechat.Pay1659662936.MchId
|
||||
mchCertificateSerialNumber = config.C.Wechat.Pay1659662936.MchCertificateSerialNumber
|
||||
v3ApiSecret = config.C.Wechat.Pay1659662936.V3ApiSecret
|
||||
|
||||
} else if orderType == 2 || orderType == 3 {
|
||||
// 药品/检测
|
||||
mchId = config.C.Wechat.Pay1636644248.MchId
|
||||
mchCertificateSerialNumber = config.C.Wechat.Pay1636644248.MchCertificateSerialNumber
|
||||
v3ApiSecret = config.C.Wechat.Pay1636644248.V3ApiSecret
|
||||
}
|
||||
|
||||
if mchId == "" {
|
||||
return nil, errors.New("商户号错误")
|
||||
}
|
||||
|
||||
// certsDir := "extend/weChat/certs/" + mchId + "/apiclient_key.pem"
|
||||
|
||||
// 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
|
||||
certsDir := filepath.Join("extend/weChat/certs", "/1636644248/apiclient_key.pem")
|
||||
certsDir := filepath.Join("extend/weChat/certs", mchId, "/apiclient_key.pem")
|
||||
// certsDir := filepath.Join("extend/weChat/certs", "/1636644248/apiclient_key.pem")
|
||||
mchPrivateKey, err := utils.LoadPrivateKeyWithPath(certsDir)
|
||||
if err != nil {
|
||||
return nil, errors.New("微信签名生成失败")
|
||||
@ -32,7 +56,7 @@ func (r RefundRequest) Refund() (*refunddomestic.Refund, error) {
|
||||
ctx := context.Background()
|
||||
// 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力
|
||||
opts := []core.ClientOption{
|
||||
option.WithWechatPayAutoAuthCipher(config.C.Wechat.MchId, config.C.Wechat.MchCertificateSerialNumber, mchPrivateKey, config.C.Wechat.V3ApiSecret),
|
||||
option.WithWechatPayAutoAuthCipher(mchId, mchCertificateSerialNumber, mchPrivateKey, v3ApiSecret),
|
||||
}
|
||||
|
||||
client, err := core.NewClient(ctx, opts...)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user