24 changed files with 1662 additions and 246 deletions
@ -0,0 +1,74 @@
|
||||
/* |
||||
* Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package harmonyos |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"time" |
||||
) |
||||
|
||||
type IapServer struct { |
||||
} |
||||
|
||||
const ( |
||||
// URL_ROOT IAP Server Root Url
|
||||
URL_ROOT = "https://iap.cloud.huawei.com" |
||||
|
||||
TIME_OUT = time.Second * 5 // TODO: Need to replace it with the actual business logic.
|
||||
|
||||
WHITE_SPACE = " " |
||||
) |
||||
|
||||
func (iap *IapServer) httpPost(url string, bodyMap map[string]interface{}) (string, error) { |
||||
bodyData, err := json.Marshal(bodyMap) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewReader(bodyData)) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
jwtGenerator := &JWTGenerator{} |
||||
jwt, err := jwtGenerator.GenJWT(bodyMap) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
req, err = iap.BuildAuthorization(req, jwt) |
||||
|
||||
var RequestHttpClient = http.Client{Timeout: TIME_OUT} |
||||
response, err := RequestHttpClient.Do(req) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
bodyBytes, err := ioutil.ReadAll(response.Body) |
||||
return string(bodyBytes), err |
||||
} |
||||
|
||||
func (iap *IapServer) BuildAuthorization(req *http.Request, jwt string) (*http.Request, error) { |
||||
var authHeader = fmt.Sprintf("Bearer%s%s", WHITE_SPACE, jwt) |
||||
req.Header.Set("Content-Type", "application/json; charset=UTF-8") |
||||
req.Header.Set("Authorization", authHeader) |
||||
return req, nil |
||||
} |
||||
@ -0,0 +1,271 @@
|
||||
/* |
||||
* Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package harmonyos |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"crypto/x509" |
||||
"encoding/base64" |
||||
"encoding/json" |
||||
"encoding/pem" |
||||
"errors" |
||||
"fmt" |
||||
"github.com/cristalhq/jwt/v3" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"strings" |
||||
) |
||||
|
||||
const ( |
||||
CA_CERT_FILE_PATH = "manifest/config/RootCaG2Ecdsa.cer" // TODO: Need to replace it with the actual value.
|
||||
|
||||
LEAF_CERT_OID = "1.3.6.1.4.1.2011.2.415.1.1" |
||||
|
||||
// HEADER_PARAM_ALG Algorithm type. The value is always ES256.
|
||||
HEADER_PARAM_ALG = "ES256" |
||||
|
||||
X5C_CHAIN_LENGTH = 3 |
||||
) |
||||
|
||||
type JWSChecker struct { |
||||
} |
||||
|
||||
type Header struct { |
||||
// Alg Algorithm type. The value is always ES256.
|
||||
Alg string `json:"alg"` |
||||
// X5c Indicates the X.509 certificate chain. The sequence is leaf certificate, intermediate certificate, and root certificate.
|
||||
X5c []string `json:"x5c"` |
||||
} |
||||
|
||||
func (jwsChecker *JWSChecker) CheckAndDecodeJWS(jwsStr string) (string, error) { |
||||
jwsStrArr := strings.Split(jwsStr, ".") |
||||
if len(jwsStrArr) != X5C_CHAIN_LENGTH { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", errors.New("invalid jwsStr length") |
||||
} |
||||
|
||||
certChain, err := jwsChecker.extractCertChain(jwsStrArr) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", err |
||||
} |
||||
|
||||
rootCert, err := jwsChecker.loadRootCert() |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", err |
||||
} |
||||
|
||||
if err = jwsChecker.verifyCertChain(certChain, rootCert); err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", err |
||||
} |
||||
|
||||
if err = jwsChecker.verifyOID(certChain[0]); err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", err |
||||
} |
||||
|
||||
if err = jwsChecker.verifyCRLFromCerts(certChain[:2]); err != nil { |
||||
// TODO: Determine whether to ignore CRL certificate download exception based on application Security Policy.
|
||||
return "", err |
||||
} |
||||
|
||||
if err = jwsChecker.verifySignature(jwsStr, certChain[0], string(jwt.ES256)); err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", err |
||||
} |
||||
|
||||
payLoadBytes, err := base64.RawStdEncoding.DecodeString(jwsStrArr[1]) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", err |
||||
} |
||||
|
||||
return string(payLoadBytes), err |
||||
} |
||||
|
||||
func (jwsChecker *JWSChecker) loadRootCert() (*x509.Certificate, error) { |
||||
rootCABytes, err := ioutil.ReadFile(CA_CERT_FILE_PATH) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return nil, err |
||||
} |
||||
|
||||
rootCerBlock, _ := pem.Decode(rootCABytes) |
||||
if rootCerBlock == nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return nil, err |
||||
} |
||||
|
||||
rootCer, err := x509.ParseCertificate(rootCerBlock.Bytes) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return nil, err |
||||
} |
||||
|
||||
return rootCer, err |
||||
} |
||||
|
||||
func (jwsChecker *JWSChecker) extractCertChain(jwsStrArr []string) ([]*x509.Certificate, error) { |
||||
headerByte, err := base64.RawStdEncoding.DecodeString(jwsStrArr[0]) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return nil, err |
||||
} |
||||
|
||||
var header Header |
||||
err = json.Unmarshal(headerByte, &header) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return nil, err |
||||
} |
||||
|
||||
certChain := make([]*x509.Certificate, len(header.X5c)) |
||||
|
||||
for i := range header.X5c { |
||||
certByte, err := base64.StdEncoding.DecodeString(header.X5c[i]) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return nil, err |
||||
} |
||||
cert, err := x509.ParseCertificate(certByte) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return nil, err |
||||
} |
||||
certChain[i] = cert |
||||
} |
||||
|
||||
return certChain, err |
||||
} |
||||
|
||||
func (jwsChecker *JWSChecker) verifyCertChain(certChain []*x509.Certificate, rootCert *x509.Certificate) error { |
||||
roots := x509.NewCertPool() |
||||
roots.AddCert(rootCert) |
||||
|
||||
intermedia := x509.NewCertPool() |
||||
intermedia.AddCert(certChain[1]) |
||||
|
||||
opts := x509.VerifyOptions{ |
||||
Roots: roots, |
||||
Intermediates: intermedia, |
||||
} |
||||
_, err := rootCert.Verify(opts) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return err |
||||
} |
||||
|
||||
_, err = certChain[0].Verify(opts) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return err |
||||
} |
||||
|
||||
return err |
||||
} |
||||
|
||||
func (jwsChecker *JWSChecker) verifyCRLFromCerts(certChain []*x509.Certificate) error { |
||||
for _, cert := range certChain { |
||||
err := checkCrl(cert) |
||||
if err != nil { |
||||
// TODO: Determine whether to ignore CRL certificate download exception based on application Security Policy.
|
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func checkCrl(cert *x509.Certificate) error { |
||||
if len(cert.CRLDistributionPoints) == 0 { |
||||
return errors.New("No CRL distribution points found in certificate") |
||||
} |
||||
|
||||
resp, err := http.Get(cert.CRLDistributionPoints[0]) |
||||
if err != nil { |
||||
// TODO: Determine whether to ignore CRL certificate download exception based on application Security Policy.
|
||||
return err |
||||
} |
||||
defer resp.Body.Close() |
||||
|
||||
crlData, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return err |
||||
} |
||||
|
||||
crl, err := x509.ParseCRL(crlData) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return err |
||||
} |
||||
|
||||
if crl.TBSCertList.RevokedCertificates == nil { |
||||
return nil |
||||
} |
||||
|
||||
for _, revokedCert := range crl.TBSCertList.RevokedCertificates { |
||||
if revokedCert.SerialNumber.Cmp(cert.SerialNumber) == 0 { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return fmt.Errorf("certificate %s has been revoked", cert.Subject.CommonName) |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (jwsChecker *JWSChecker) verifyOID(leafCert *x509.Certificate) error { |
||||
var isExist = false |
||||
for i := range leafCert.Extensions { |
||||
if LEAF_CERT_OID == leafCert.Extensions[i].Id.String() && leafCert.Extensions[i].Critical == false { |
||||
isExist = true |
||||
} |
||||
} |
||||
|
||||
if !isExist { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return errors.New("oid verification failed") |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (jwsChecker *JWSChecker) verifySignature(jwsStr string, leafCert *x509.Certificate, algParam string) error { |
||||
if algParam != jwt.ES256.String() { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return errors.New("invalid algorithm") |
||||
} |
||||
switch pubicKey := leafCert.PublicKey.(type) { |
||||
case *ecdsa.PublicKey: |
||||
verifier, err := jwt.NewVerifierES(jwt.ES256, pubicKey) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
token := []byte(jwsStr) |
||||
_, err = jwt.ParseAndVerify(token, verifier) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
default: |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return errors.New("public key not supported") |
||||
} |
||||
return nil |
||||
} |
||||
@ -0,0 +1,126 @@
|
||||
/* |
||||
* Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package harmonyos |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"crypto/sha256" |
||||
"crypto/x509" |
||||
"encoding/hex" |
||||
"encoding/json" |
||||
"encoding/pem" |
||||
"errors" |
||||
"github.com/cristalhq/jwt/v3" |
||||
"io/ioutil" |
||||
"time" |
||||
) |
||||
|
||||
type JWTGenerator struct { |
||||
} |
||||
|
||||
type IAPJWTClaims struct { |
||||
// Iss Key issuer ID.
|
||||
Iss string `json:"iss"` |
||||
// Aud Expected receiver of the JWT. The value is fixed at iap-v1.
|
||||
Aud string `json:"aud"` |
||||
// Iat Time when the JWT is issued. The value is a UTC timestamp, in seconds.
|
||||
Iat int64 `json:"iat"` |
||||
// Exp Time when the JWT expires. The value is a UTC timestamp, in seconds. exp-iat indicates the validity period of the JWT, which cannot exceed one hour.
|
||||
Exp int64 `json:"exp"` |
||||
// Aid App ID.
|
||||
Aid string `json:"aid"` |
||||
// Digest Hash value of the request body (JSON character string), which is used to verify the integrity of the body. The algorithm is SHA-256.
|
||||
Digest string `json:"digest"` |
||||
} |
||||
|
||||
const ( |
||||
// JWT_PRI_KEY_PATH Private key file path.
|
||||
JWT_PRI_KEY_PATH = "manifest/config/IAPKey.p8" // TODO: Need to replace it with the actual value.
|
||||
|
||||
// ACTIVE_TIME JWT validity period, which is a UTC timestamp in seconds. The validity period cannot exceed 1 hour.
|
||||
ACTIVE_TIME_SECOND = 3600 // TODO: Need to replace it with the actual value.
|
||||
|
||||
KID = "f35b90cf-48be-4bb9-bb66-f7e814d5521c" // TODO: Need to replace it with the actual value.
|
||||
|
||||
ISSUER_ID = "002124f1-f9a9-468e-92ea-26561ea9c605" // TODO: Need to replace it with the actual business logic.
|
||||
|
||||
APP_ID = "6917589392953468039" // TODO: Need to replace it with the actual business logic.
|
||||
) |
||||
|
||||
func (jwtGenerator *JWTGenerator) GenJWT(bodyMap map[string]interface{}) (string, error) { |
||||
privateKeyPEM, err := ioutil.ReadFile(JWT_PRI_KEY_PATH) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", err |
||||
} |
||||
|
||||
block, _ := pem.Decode(privateKeyPEM) |
||||
if block == nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", errors.New("the key content is empty") |
||||
} |
||||
|
||||
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", err |
||||
} |
||||
|
||||
ecdsaPrivateKey, ok := privateKey.(*ecdsa.PrivateKey) |
||||
if !ok { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", errors.New("failed to convert private key to *ecdsa.PrivateKey") |
||||
} |
||||
|
||||
signer, err := jwt.NewSignerES(jwt.ES256, ecdsaPrivateKey) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", err |
||||
} |
||||
|
||||
payloadJson, err := json.Marshal(bodyMap) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
return "", err |
||||
} |
||||
|
||||
hash := sha256.New() |
||||
hash.Write(payloadJson) |
||||
sha256Sum := hash.Sum(nil) |
||||
sha256Hex := hex.EncodeToString(sha256Sum) |
||||
|
||||
builder := jwt.NewBuilder(signer, jwt.WithKeyID(KID)) // TODO: Need to replace it with the actual business logic.
|
||||
|
||||
signTime := time.Now().UTC().Unix() |
||||
claims := IAPJWTClaims{ |
||||
Iss: ISSUER_ID, // TODO: Need to replace it with the actual business logic.
|
||||
Aud: "iap-v1", |
||||
Iat: signTime, |
||||
Exp: signTime + ACTIVE_TIME_SECOND, |
||||
Aid: APP_ID, // TODO: Need to replace it with the actual business logic.
|
||||
Digest: sha256Hex, |
||||
} |
||||
|
||||
iapJwt, err := builder.Build(claims) |
||||
if err != nil { |
||||
// TODO: Need replace it with your business logic.
|
||||
return "", err |
||||
} |
||||
|
||||
return iapJwt.String(), nil |
||||
} |
||||
@ -0,0 +1,79 @@
|
||||
/* |
||||
* Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package notification |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
) |
||||
|
||||
type AppServer struct{} |
||||
|
||||
// DealNotificationV3 Deal notification information V3.
|
||||
// Params:
|
||||
//
|
||||
// notificationPayloadStr: Notification content, which is the payload decoded by jwsNotification.
|
||||
//
|
||||
// Return:
|
||||
//
|
||||
// nil
|
||||
//
|
||||
// Exception:
|
||||
//
|
||||
// error
|
||||
func (a *AppServer) DealNotificationV3(notificationPayloadStr string) error { |
||||
var notificationPayload = NotificationPayload{} |
||||
if err := json.Unmarshal([]byte(notificationPayloadStr), ¬ificationPayload); err != nil { |
||||
return err |
||||
} |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
notificationSubtype := notificationPayload.NotificationSubtype |
||||
switch notificationSubtype { |
||||
case INITIAL_BUY: |
||||
break |
||||
case DID_RENEW: |
||||
break |
||||
case RESTORE: |
||||
break |
||||
case AUTO_RENEW_ENABLED: |
||||
break |
||||
case AUTO_RENEW_DISABLED: |
||||
break |
||||
case DOWNGRADE: |
||||
break |
||||
case UPGRADE: |
||||
break |
||||
case REFUND_TRANSACTION: |
||||
break |
||||
case BILLING_RETRY: |
||||
break |
||||
case PRICE_INCREASE: |
||||
break |
||||
case BILLING_RECOVERY: |
||||
break |
||||
case PRODUCT_NOT_FOR_SALE: |
||||
break |
||||
case APPLICATION_DELETE_SUBSCRIPTION_HOSTING: |
||||
break |
||||
case RENEWAL_EXTENDED: |
||||
break |
||||
default: |
||||
break |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
@ -0,0 +1,46 @@
|
||||
/* |
||||
* Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package notification |
||||
|
||||
type NotificationType = string |
||||
|
||||
type SubNotificationType = string |
||||
|
||||
const ( |
||||
DID_NEW_TRANSACTION NotificationType = "DID_NEW_TRANSACTION" |
||||
DID_CHANGE_RENEWAL_STATUS NotificationType = "DID_CHANGE_RENEWAL_STATUS" |
||||
REVOKE NotificationType = "REVOKE" |
||||
RENEWAL_TIME_MODIFIED NotificationType = "RENEWAL_TIME_MODIFIED" |
||||
EXPIRE NotificationType = "EXPIRE" |
||||
SYNC NotificationType = "SYNC" |
||||
|
||||
INITIAL_BUY SubNotificationType = "INITIAL_BUY" |
||||
DID_RENEW SubNotificationType = "DID_RENEW" |
||||
RESTORE SubNotificationType = "RESTORE" |
||||
AUTO_RENEW_ENABLED SubNotificationType = "AUTO_RENEW_ENABLED" |
||||
AUTO_RENEW_DISABLED SubNotificationType = "AUTO_RENEW_DISABLED" |
||||
DOWNGRADE SubNotificationType = "DOWNGRADE" |
||||
UPGRADE SubNotificationType = "UPGRADE" |
||||
REFUND_TRANSACTION SubNotificationType = "REFUND_TRANSACTION" |
||||
BILLING_RETRY SubNotificationType = "BILLING_RETRY" |
||||
PRICE_INCREASE SubNotificationType = "PRICE_INCREASE" |
||||
BILLING_RECOVERY SubNotificationType = "BILLING_RECOVERY" |
||||
PRODUCT_NOT_FOR_SALE SubNotificationType = "PRODUCT_NOT_FOR_SALE" |
||||
APPLICATION_DELETE_SUBSCRIPTION_HOSTING SubNotificationType = "APPLICATION_DELETE_SUBSCRIPTION_HOSTING" |
||||
RENEWAL_EXTENDED SubNotificationType = "RENEWAL_EXTENDED" |
||||
) |
||||
@ -0,0 +1,31 @@
|
||||
/* |
||||
* Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package notification |
||||
|
||||
type NotificationMetaData struct { |
||||
Environment string `json:"environment"` |
||||
ApplicationId string `json:"applicationId"` |
||||
PackageName string `json:"packageName"` |
||||
Type int `json:"type"` |
||||
CurrentProductId string `json:"currentProductId"` |
||||
SubGroupId string `json:"subGroupId"` |
||||
SubGroupGenerationId string `json:"subGroupGenerationId"` |
||||
SubscriptionId string `json:"subscriptionId"` |
||||
PurchaseToken string `json:"purchaseToken"` |
||||
PurchaseOrderId string `json:"purchaseOrderId"` |
||||
} |
||||
@ -0,0 +1,27 @@
|
||||
/* |
||||
* Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package notification |
||||
|
||||
type NotificationPayload struct { |
||||
NotificationType NotificationType `json:"notificationType"` |
||||
NotificationSubtype SubNotificationType `json:"notificationSubtype"` |
||||
NotificationRequestId string `json:"notificationRequestId"` |
||||
NotificationMetaData NotificationMetaData `json:"notificationMetaData"` |
||||
NotificationVersion string `json:"notificationVersion"` |
||||
SignedTime int64 `json:"signedTime"` |
||||
} |
||||
@ -0,0 +1,107 @@
|
||||
/* |
||||
* Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package harmonyos |
||||
|
||||
import ( |
||||
"log" |
||||
) |
||||
|
||||
const ( |
||||
URL_ORDER_LOOKUP = "/harmony/v1/application/order/lookup" |
||||
URL_ORDER_LIST_QUERY = "/order/harmony/v1/application/trade/orders/query" |
||||
URL_USER_ORDER_LIST_QUERY = "/harmony/v1/application/user/orders/query" |
||||
) |
||||
|
||||
type OrderLookup struct { |
||||
IapServer |
||||
} |
||||
|
||||
// OrderQuery This API is used to query the latest status of a consumable or non-consumable order.
|
||||
// Params:
|
||||
//
|
||||
// orderNo: Order ID of a purchase.
|
||||
//
|
||||
// Return:
|
||||
//
|
||||
// nil
|
||||
//
|
||||
// Exception:
|
||||
//
|
||||
// nil
|
||||
func (orderService *OrderLookup) OrderQuery(bodyMap map[string]interface{}) (resp string, err error) { |
||||
|
||||
resp, err = orderService.httpPost(URL_ROOT+URL_ORDER_LOOKUP, bodyMap) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
log.Print("Error:", err) |
||||
} else { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
log.Print("order query response is: ", resp) |
||||
} |
||||
return resp, err |
||||
} |
||||
|
||||
// UserOrdersQuery This API is used to query the latest status of a consumable or non-consumable order.
|
||||
// Params:
|
||||
//
|
||||
// purchaseOrderId: Order ID of a purchase.
|
||||
// productType: product Type.
|
||||
// continuationToken: continuationToken.
|
||||
//
|
||||
// Return:
|
||||
//
|
||||
// nil
|
||||
//
|
||||
// Exception:
|
||||
//
|
||||
// nil
|
||||
func (orderService *OrderLookup) UserOrdersQuery(bodyMap map[string]interface{}) (resp string, err error) { |
||||
resp, err = orderService.httpPost(URL_ROOT+URL_USER_ORDER_LIST_QUERY, bodyMap) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
log.Print("Error:", err) |
||||
} else { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
log.Print("order status query response is: ", resp) |
||||
} |
||||
return resp, err |
||||
} |
||||
|
||||
// OrdersQuery This API is used to query the latest status of a consumable or non-consumable order.
|
||||
// Params:
|
||||
//
|
||||
// orderNo: Order ID of a purchase.
|
||||
//
|
||||
// Return:
|
||||
//
|
||||
// nil
|
||||
//
|
||||
// Exception:
|
||||
//
|
||||
// nil
|
||||
func (orderService *OrderLookup) OrdersQuery(bodyMap map[string]interface{}) (resp string, err error) { |
||||
resp, err = orderService.httpPost(URL_ROOT+URL_ORDER_LIST_QUERY, bodyMap) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
log.Print("Error:", err) |
||||
} else { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
log.Print("order status query response is: ", resp) |
||||
} |
||||
return resp, err |
||||
} |
||||
@ -0,0 +1,87 @@
|
||||
/* |
||||
* Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package harmonyos |
||||
|
||||
import ( |
||||
"log" |
||||
) |
||||
|
||||
const ( |
||||
URL_ORDER_STATUS_QUERY = "/order/harmony/v1/application/order/status/query" |
||||
|
||||
URL_ORDER_SHIPPED_CONFIRM = "/order/harmony/v1/application/purchase/shipped/confirm" |
||||
) |
||||
|
||||
type OrderService struct { |
||||
IapServer |
||||
} |
||||
|
||||
// OrderStatusQuery This API is used to query the latest status of a consumable or non-consumable order.
|
||||
// Params:
|
||||
//
|
||||
// purchaseOrderId: Order ID of a purchase.
|
||||
// purchaseToken: Purchase token of a product, which you can obtain from the information returned after a purchase or an order query.
|
||||
//
|
||||
// Return:
|
||||
//
|
||||
// nil
|
||||
//
|
||||
// Exception:
|
||||
//
|
||||
// nil
|
||||
func (orderService *OrderService) OrderStatusQuery(purchaseOrderId string, purchaseToken string) (resp string, err error) { |
||||
bodyMap := make(map[string]interface{}) |
||||
bodyMap["purchaseOrderId"] = purchaseOrderId |
||||
bodyMap["purchaseToken"] = purchaseToken |
||||
|
||||
resp, err = orderService.httpPost(URL_ROOT+URL_ORDER_STATUS_QUERY, bodyMap) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
log.Print("Error:", err) |
||||
} else { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
log.Print("order status query response is: ", resp) |
||||
} |
||||
return resp, err |
||||
} |
||||
|
||||
// OrderShippedConfirm This API is used to acknowledge that a purchased consumable or non-consumable has been delivered.
|
||||
// Params:
|
||||
//
|
||||
// purchaseOrderId: Order ID of a purchase.
|
||||
// purchaseToken: Purchase token of a product, which you can obtain from the information returned after a purchase or an order query.
|
||||
//
|
||||
// Return:
|
||||
//
|
||||
// nil
|
||||
//
|
||||
// Exception:
|
||||
//
|
||||
// nil
|
||||
func (orderService *OrderService) OrderShippedConfirm(purchaseOrderId string, purchaseToken string) { |
||||
bodyMap := make(map[string]interface{}) |
||||
bodyMap["purchaseOrderId"] = purchaseOrderId |
||||
bodyMap["purchaseToken"] = purchaseToken |
||||
resp, err := orderService.httpPost(URL_ROOT+URL_ORDER_SHIPPED_CONFIRM, bodyMap) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
log.Print("Error:", err) |
||||
} |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
log.Print("order shipped confirm response is: ", resp) |
||||
} |
||||
@ -0,0 +1,84 @@
|
||||
/* |
||||
* Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
package harmonyos |
||||
|
||||
import ( |
||||
"fmt" |
||||
) |
||||
|
||||
const ( |
||||
URL_SUB_STATUS_QUERY = "/subscription/harmony/v1/application/subscription/status/query" |
||||
|
||||
URL_SUB_SHIPPED_CONFIRM = "/subscription/harmony/v1/application/purchase/shipped/confirm" |
||||
) |
||||
|
||||
type SubscriptionService struct { |
||||
IapServer |
||||
} |
||||
|
||||
// SubStatusQuery This API is used to query the latest status of an auto-renewable subscription.
|
||||
// Params:
|
||||
//
|
||||
// purchaseOrderId: Order ID of a purchase.
|
||||
// purchaseToken: Purchase token of a product, which you can obtain from the information returned after a purchase or an order query.
|
||||
//
|
||||
// Return:
|
||||
//
|
||||
// nil
|
||||
//
|
||||
// Exception:
|
||||
//
|
||||
// nil
|
||||
func (subService *SubscriptionService) SubStatusQuery(purchaseOrderId string, purchaseToken string) { |
||||
bodyMap := make(map[string]interface{}) |
||||
bodyMap["purchaseOrderId"] = purchaseOrderId |
||||
bodyMap["purchaseToken"] = purchaseToken |
||||
resp, err := subService.httpPost(URL_ROOT+URL_SUB_STATUS_QUERY, bodyMap) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
fmt.Println("Error:", err) |
||||
} |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
fmt.Println("sub status query response is:", resp) |
||||
} |
||||
|
||||
// SubShippedConfirm This API is used to acknowledge that a purchased subscription has been delivered to the user.
|
||||
// Params:
|
||||
//
|
||||
// purchaseOrderId: Order ID of a purchase.
|
||||
// purchaseToken: Purchase token of a product, which you can obtain from the information returned after a purchase or an order query.
|
||||
//
|
||||
// Return:
|
||||
//
|
||||
// nil
|
||||
//
|
||||
// Exception:
|
||||
//
|
||||
// nil
|
||||
func (subService *SubscriptionService) SubShippedConfirm(purchaseOrderId string, purchaseToken string) { |
||||
// pack the request body
|
||||
bodyMap := make(map[string]interface{}) |
||||
bodyMap["purchaseOrderId"] = purchaseOrderId |
||||
bodyMap["purchaseToken"] = purchaseToken |
||||
resp, err := subService.httpPost(URL_ROOT+URL_SUB_SHIPPED_CONFIRM, bodyMap) |
||||
if err != nil { |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
} |
||||
// TODO: Need to replace it with the actual business logic.
|
||||
fmt.Println("sub shipped confirm response is:", resp) |
||||
} |
||||
@ -0,0 +1,123 @@
|
||||
package service |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"errors" |
||||
"log" |
||||
"strconv" |
||||
"tyj_admin/api/v1/game" |
||||
"tyj_admin/internal/harmonyos" |
||||
) |
||||
|
||||
func HarmonyOrderLookup(req *game.GetHarmonyOrderReq) (res *game.GetHarmonyOrderRes, err error) { |
||||
res = new(game.GetHarmonyOrderRes) |
||||
bodyMap := make(map[string]interface{}) |
||||
bodyMap["orderNo"] = req.Order |
||||
orderService := &harmonyos.OrderLookup{} |
||||
orderInfoStr, err := orderService.OrderQuery(bodyMap) |
||||
if err != nil { |
||||
log.Printf("HarmonyOrderLookup======>.OrderStatusQuery : %v - err:%s", req.Order, err.Error()) |
||||
return |
||||
} |
||||
orderInfo := &game.HarmonyLookup{} |
||||
if err = json.Unmarshal([]byte(orderInfoStr), &orderInfo); err != nil { |
||||
log.Printf("HarmonyOrderLookup======>.Unmarshal : %v", orderInfoStr) |
||||
return |
||||
} |
||||
res.OrderStatus = orderInfo.OrderStatus |
||||
if orderInfo.OrderStatus != 1 { |
||||
err = errors.New("交易号无效") |
||||
return |
||||
} |
||||
|
||||
jwsChecker := &harmonyos.JWSChecker{} |
||||
purchaseOrderStr, err := jwsChecker.CheckAndDecodeJWS(orderInfo.JwsPurchaseOrder) |
||||
if err != nil { |
||||
log.Printf("HarmonyOrderLookup======>.jwsChecker.CheckAndDecodeJWS : err: %s, %s", err.Error(), purchaseOrderStr) |
||||
return |
||||
} |
||||
purchaseOrder := game.PurchaseOrderPayload{} |
||||
if err = json.Unmarshal([]byte(purchaseOrderStr), &purchaseOrder); err != nil { |
||||
log.Printf("HarmonyOrderLookup======>.CheckAndDecodeJWS Unmarshal: %v, Err: %s", purchaseOrderStr, err) |
||||
return |
||||
} |
||||
res.PurchaseOrderInfo = purchaseOrder |
||||
return |
||||
} |
||||
|
||||
func HarmonyOrders(req *game.GetHarmonyOrdersReq) (res *game.GetHarmonyOrdersRes, err error) { |
||||
res = new(game.GetHarmonyOrdersRes) |
||||
bodyMap := map[string]interface{}{ |
||||
"startTime": req.StartTime, |
||||
"endTime": req.EndTime, |
||||
} |
||||
if len(req.ContinuationToken) > 0 { |
||||
bodyMap["continuationToken"] = req.ContinuationToken |
||||
} |
||||
orderService := &harmonyos.OrderLookup{} |
||||
orderInfoStr, err := orderService.OrdersQuery(bodyMap) |
||||
if err != nil { |
||||
log.Printf("HarmonyOrders======>.StartTime: %d, EndTime : %d - err:%s", req.StartTime, req.EndTime, err.Error()) |
||||
return |
||||
} |
||||
|
||||
if err = json.Unmarshal([]byte(orderInfoStr), &res); err != nil { |
||||
log.Printf("HarmonyOrders======>.Unmarshal : %v", orderInfoStr) |
||||
return |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
func HarmonyUserOrders(req *game.GetHarmonyUserOrdersReq) (res *game.GetHarmonyUserOrdersRes, err error) { |
||||
res = new(game.GetHarmonyUserOrdersRes) |
||||
bodyMap := make(map[string]interface{}) |
||||
bodyMap["purchaseOrderId"] = req.PurchaseOrderId |
||||
if req.ProductType != "" { |
||||
bodyMap["productType"], err = strconv.Atoi(req.ProductType) |
||||
} |
||||
if req.StartTime != 0 { |
||||
bodyMap["startTime"] = req.StartTime |
||||
} |
||||
if req.EndTime != 0 { |
||||
bodyMap["endTime"] = req.EndTime |
||||
} |
||||
if len(req.ProductIdList) > 0 { |
||||
bodyMap["productIdList"] = req.ProductIdList |
||||
} |
||||
if len(req.ContinuationToken) > 0 { |
||||
bodyMap["continuationToken"] = req.ContinuationToken |
||||
} |
||||
orderService := &harmonyos.OrderLookup{} |
||||
orderInfoStr, err := orderService.UserOrdersQuery(bodyMap) |
||||
if err != nil { |
||||
log.Printf("HarmonyUserOrders======>.purchaseOrderId: %s, - err:%s", req.PurchaseOrderId, err.Error()) |
||||
return |
||||
} |
||||
|
||||
orderInfo := game.HarmonyUserOrders{} |
||||
if err = json.Unmarshal([]byte(orderInfoStr), &orderInfo); err != nil { |
||||
log.Printf("HarmonyUserOrders======>.Unmarshal : %v", orderInfoStr) |
||||
return |
||||
} |
||||
res.ResponseCode = orderInfo.ResponseCode |
||||
res.ResponseMessage = orderInfo.ResponseMessage |
||||
res.ContinuationToken = orderInfo.ContinuationToken |
||||
for _, v := range orderInfo.JwsPurchaseOrderList { |
||||
jwsChecker := &harmonyos.JWSChecker{} |
||||
purchaseOrderStr, err1 := jwsChecker.CheckAndDecodeJWS(v) |
||||
if err1 != nil { |
||||
err = err1 |
||||
log.Printf("HarmonyUserOrders======>.jwsChecker.CheckAndDecodeJWS : err: %s, %s", err.Error(), purchaseOrderStr) |
||||
continue |
||||
} |
||||
purchaseOrder := game.PurchaseOrderPayload{} |
||||
if err = json.Unmarshal([]byte(purchaseOrderStr), &purchaseOrder); err != nil { |
||||
log.Printf("HarmonyUserOrders======>.CheckAndDecodeJWS Unmarshal: %v, Err: %s", purchaseOrderStr, err) |
||||
continue |
||||
} |
||||
res.OrderInfoList = append(res.OrderInfoList, purchaseOrder) |
||||
} |
||||
|
||||
return |
||||
} |
||||
@ -0,0 +1,6 @@
|
||||
-----BEGIN PRIVATE KEY----- |
||||
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg+oOEZ3evRCWbbgUj |
||||
BDfyrt+OinOGwgW7jcWQ27lcAWWgCgYIKoZIzj0DAQehRANCAAQy3I+h5+egmhf9 |
||||
NEOdXwACHa7Brz5atHtCAaTG4OTekPW9kbAGVYR+qAmWGmI7kDQYbO+5jG30YRdJ |
||||
Ed4992DO |
||||
-----END PRIVATE KEY----- |
||||
@ -0,0 +1,14 @@
|
||||
-----BEGIN CERTIFICATE----- |
||||
MIICGjCCAaGgAwIBAgIIShhpn519jNAwCgYIKoZIzj0EAwMwUzELMAkGA1UEBhMC |
||||
Q04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UECwwKSHVhd2VpIENCRzEeMBwGA1UE |
||||
AwwVSHVhd2VpIENCRyBSb290IENBIEcyMB4XDTIwMDMxNjAzMDQzOVoXDTQ5MDMx |
||||
NjAzMDQzOVowUzELMAkGA1UEBhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UE |
||||
CwwKSHVhd2VpIENCRzEeMBwGA1UEAwwVSHVhd2VpIENCRyBSb290IENBIEcyMHYw |
||||
EAYHKoZIzj0CAQYFK4EEACIDYgAEWidkGnDSOw3/HE2y2GHl+fpWBIa5S+IlnNrs |
||||
GUvwC1I2QWvtqCHWmwFlFK95zKXiM8s9yV3VVXh7ivN8ZJO3SC5N1TCrvB2lpHMB |
||||
wcz4DA0kgHCMm/wDec6kOHx1xvCRo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T |
||||
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUo45a9Vq8cYwqaiVyfkiS4pLcIAAwCgYIKoZI |
||||
zj0EAwMDZwAwZAIwMypeB7P0IbY7c6gpWcClhRznOJFj8uavrNu2PIoz9KIqr3jn |
||||
BlBHJs0myI7ntYpEAjBbm8eDMZY5zq5iMZUC6H7UzYSix4Uy1YlsLVV738PtKP9h |
||||
FTjgDHctXJlC5L7+ZDY= |
||||
-----END CERTIFICATE----- |
||||
@ -0,0 +1,5 @@
|
||||
$env:GOOS="windows" |
||||
go build -o ./test/copyAccount.exe ./test/test.go |
||||
go build -o ./test/rechargeAccount.exe ./test/recharge.go |
||||
|
||||
go build -o ./test/rechargeAccount100.exe ./test/recharge.go |
||||
@ -0,0 +1,126 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"crypto/md5" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"log" |
||||
"net/http" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
const TIME_FORMAT = "2006-01-02 15:04:05" |
||||
|
||||
func main() { |
||||
var unitId string |
||||
|
||||
fmt.Print("unitId账号数据输入 : ") |
||||
_, err := fmt.Scan(&unitId) // 读取输入并赋值给变量
|
||||
if err != nil { |
||||
fmt.Println("输入错误:", err) |
||||
return |
||||
} |
||||
var config string |
||||
|
||||
fmt.Print("输入充值配置Id(101-106) : ") |
||||
_, err = fmt.Scan(&config) // 读取输入并赋值给变量
|
||||
if err != nil { |
||||
fmt.Println("输入错误:", err) |
||||
return |
||||
} |
||||
configs := map[string]string{ |
||||
"101": "6", |
||||
"102": "30", |
||||
"103": "68", |
||||
"104": "198", |
||||
"105": "328", |
||||
"106": "648", |
||||
} |
||||
if configs[config] == "" { |
||||
fmt.Println("输入错误, 请输入(101-106)的id:") |
||||
_, err = fmt.Scan(&config) // 读取输入并赋值给变量
|
||||
if err != nil { |
||||
fmt.Println("输入错误:", err) |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
tradeNo := fmt.Sprintf("GM%v%v", config, time.Now().UnixMilli()) |
||||
body := map[string]string{"unitId": unitId, "server": "1", "channel": "000000000000", "rechargeId": config, |
||||
"tradeStatus": "SUCCESS", "externalTradeNo": tradeNo, "totalAmount": configs[config], "tradeNo": tradeNo, |
||||
"buyerId": "", "createTime": time.Now().Format(TIME_FORMAT), "payedTime": time.Now().Format(TIME_FORMAT)} |
||||
sendToServer(body) |
||||
|
||||
ticker := time.NewTicker(1 * time.Second) |
||||
defer ticker.Stop() |
||||
|
||||
fmt.Println("操作完成... (输入 'ctrl-c' 退出)") |
||||
for { |
||||
select { |
||||
case <-ticker.C: |
||||
// 这里可以添加条件检查(如读取文件、网络请求等)
|
||||
default: |
||||
// 非阻塞检查用户输入(需配合 goroutine)
|
||||
} |
||||
} |
||||
} |
||||
|
||||
func addServerMd5(body map[string]string) string { |
||||
data := body["unitId"] + body["server"] + body["channel"] + body["rechargeId"] + |
||||
body["tradeStatus"] + body["externalTradeNo"] + body["totalAmount"] + body["tradeNo"] + body["buyerId"] + body["createTime"] + |
||||
body["payedTime"] + "b93ac9c70a071c537542f6fe95cf4d67" |
||||
sign := fmt.Sprintf("%x", md5.Sum([]byte(data))) |
||||
log.Printf("data: %s, md5: %s", data, sign) |
||||
return fmt.Sprintf("%x", md5.Sum([]byte(data))) |
||||
} |
||||
|
||||
type TradeRes struct { |
||||
T string `json:"_t"` |
||||
Error int `json:"Error"` |
||||
Message string `json:"Message"` |
||||
} |
||||
|
||||
func sendToServer(body map[string]string) string { |
||||
//url := "http://192.168.2.100:30300/Recharge"
|
||||
url := "http://127.0.0.1:30300/Recharge" |
||||
|
||||
log.Printf("notify========== url: %s", url) |
||||
body["sign"] = addServerMd5(body) |
||||
buf, err := json.Marshal(body) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
return err.Error() |
||||
} |
||||
reqBody := strings.NewReader(string(buf)) |
||||
log.Printf("notify========== url: %s, reqBody: %v", url, reqBody) |
||||
reqt, _ := http.NewRequest("GET", url, reqBody) |
||||
|
||||
response, err := http.DefaultClient.Do(reqt) |
||||
if err != nil { |
||||
log.Printf("notify========== server callback Error %s", err.Error()) |
||||
return err.Error() |
||||
} |
||||
// 这里要格式化再输出,因为 ReadAll 返回的是字节切片
|
||||
log.Printf("notify========== server callback StatusCode %d", response.StatusCode) |
||||
defer response.Body.Close() |
||||
bytes, err := io.ReadAll(response.Body) |
||||
if err != nil { |
||||
return err.Error() |
||||
} |
||||
// 这里要格式化再输出,因为 ReadAll 返回的是字节切片
|
||||
log.Printf("notify========== server callback Body %s", bytes) |
||||
if response.StatusCode == 200 { |
||||
var tradeRes TradeRes |
||||
err = json.Unmarshal(bytes, &tradeRes) |
||||
if tradeRes.Error == 400000 { |
||||
return "SUCCESS" |
||||
} else { |
||||
return tradeRes.Message |
||||
} |
||||
} |
||||
|
||||
return fmt.Sprint(response.StatusCode) |
||||
} |
||||
Loading…
Reference in new issue