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