跳到主要内容

集成应用收款

前置需求​

你已经有一个可运行的应用(参见开发独立应用或开发嵌入式应用),并且想向商家收费。这一步是可选的——如果应用是免费的可以跳过。


备注

测试店铺或开发者店铺不支持 Billing 支付。请在开发和测试阶段将其设置为 test billing。

1.定价详情配置​

在 Developer Platform 中,您可以编辑 APP Listing 来添加套餐并提交审核。目前支持 free、free to install、one-time 和 recurring charging 模式。每个 APP 只能有一种收费模式;一旦设置了某种收费类型的套餐,之后就不能再设置其他类型的套餐。

一次性收费​

操作路径:Developer Platform --> Apps --> App Display List [Management List] --> Edit listing。

周期性收费​

操作路径:Developer Platform --> Apps --> App Display List [Management List] --> Edit listing。

仅周期性收费​

  • free trial config

“trial period” 表示在此期间不会收费。

周期性收费与按量收费​

  • free trial config

“trial period” 表示在此期间不会收费。

  • Add plan and Usage charge Config
备注

Usage charges 只能应用于 recurring charging 模式下。附加费用是指增加用量所产生的费用。

  1. Subscription Only
  1. Subscription + Usage

2.App 调用 Billing API​

一次性收费​

处理时间线​

详情​

  1. 商家在 APP 内选择 “one-time” 订阅。

  2. APP 调用 OpenAPI 创建 one-time charge。

    1. 创建 webhook,用于跟踪一次性账单的状态变化。 对于每个安装 APP 的商家,都会创建一个 webhook。随后,可以通过处理该 webhook 的事件来判断商家的订阅状态。

      go
      // 在商家完成 app 安装授权后调用该函数创建一次性收费 webhook
      CreateWebhook(form.ShopDomain, "app/purchases_one_time_update", "https://APP-Host/webhook/one_time", token)
      // CreateWebhook
      // @Description:
      // @param shopDomain 商家域名 eg:test.myshoplaza.com
      // @param event webhook 名称
      // @param token access_token
      func CreateWebhook(shopDomain, event, address, token string) error {
      url := fmt.Sprintf("https://%s/openapi/2022-07/webhooks", shopDomain)
      method := "POST"
      param := map[string]interface{}{
      "address": address,
      "topic": event,
      }

      payload, err := json.Marshal(param)
      if err != nil {
      return err
      }

      client := &http.Client{}
      req, err := http.NewRequest(method, url, bytes.NewReader(payload))

      if err != nil {
      return err
      }
      req.Header.Add("Access-Token", token)
      req.Header.Add("Content-Type", "application/json")

      res, err := client.Do(req)
      if err != nil {
      return err
      }

      defer res.Body.Close()

      body, err := ioutil.ReadAll(res.Body)
      if err != nil {
      return err
      }
      fmt.Println(string(body))
      return nil
      }
    2. 创建 one-time charge

备注

金额必须与 APP SetUp 中设置的金额一致。

go
type CreateOneTimeChargeResp struct {
ID string `json:"id"`
AppID string `json:"application_id"`
Name string `json:"name"`
Price string `json:"price"`
ConfirmUrl string `json:"confirm_url"` //订阅确认链接
ReturnUrl string `json:"return_url"`
Status string `json:"status"`
Test bool `json:"test"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// 在订阅套餐的接口中调用,创建一次性账单
func CreateOneTimeCharge(shopDomain, appName, appUrl, token string, price float64) (*CreateOneTimeChargeResp, error) {
url := fmt.Sprintf("https://%s/openapi/2022-01/application_charges", shopDomain)
body, err := json.Marshal(map[string]interface{}{
"application_charge": map[string]interface{}{
"name": appName, //app name
"price": price, //一次性收费的价格,需与套餐设置一致
"return_url": appUrl, //完成订阅后,重定向的页面
"test": false, //是否为测试链接
},
})
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
req.Header.Add("accept", "application/json")
req.Header.Add("content-type", "application/json")
req.Header.Add("access-token", token)

resp, err := http.DefaultClient.Do(req)

if err != nil {
return nil, err
}

content, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
chargeResp := &CreateOneTimeChargeResp{}

err = json.Unmarshal(content, chargeResp)
if err != nil {
return nil, err
}

fmt.Println(chargeResp)

return chargeResp, nil
}
  1. Shoplazza 会返回 confirmation_url。

  2. APP 展示 confirmation_url 页面。

  3. 商家确认订阅并发起支付。

  4. Shoplazza 从商家账户中扣款,并将款项收取到 Shoplazza 账户。

  5. webhook 会通知 APP 此实例的订阅信息。

    1. 处理 Webhook 通知。 当 Billing 账单状态发生变化时,APP 会通过 webhook 收到通知。APP 可以根据收到的 webhook event 判断商家的订阅状态。
    go
    type OneTimeChargeEvent struct {
    ID string `json:"id"`
    StoreID int `json:"store_id"`
    ApplicationID string `json:"application_id"`
    Name string `json:"name"`
    Price string `json:"price"`
    Status string `json:"status"`
    Test bool `json:"test"`
    UpdatedAt *time.Time `json:"updated_at"`
    }
    // gin 框架 handle 示例
    func OneTimeChargeWebhook(c *gin.Context) {
    body, err := ioutil.ReadAll(c.Request.Body)
    if err != nil {
    c.JSON(http.StatusInternalServerError, err.Error())
    return
    }
    fmt.Printf("Header: %+v\n", c.Request.Header)
    headerHmac := c.GetHeader("X-Shoplazza-Hmac-Sha256")
    if !hmacCheck(headerHmac, clientSecret, body) {
    c.JSON(http.StatusUnauthorized, "hmac verification fails.")
    return
    }

    event := &OneTimeChargeEvent{}
    err = json.Unmarshal(body, event)
    if err != nil {
    fmt.Println(err.Error())
    c.JSON(http.StatusInternalServerError, err.Error())
    return
    }
    fmt.Printf("Body: %+v\n", event)

    // todo 根据账单状态进行处理

    c.JSON(http.StatusOK, event)
    }
  6. 重定向到 return_url(用户在 confirmation_url 上确认后被重定向到的页面)。

仅周期性收费​

处理时间线​

详情​

  1. 商家在 APP 内选择 “recurring” 订阅套餐。

  2. APP 调用 OpenAPI 创建 recurring charge。

    1. 创建 webhook,用于跟踪 recurring charge 的状态变化。
      对于每个安装 APP 的商家,都会创建一个 webhook。随后,可以通过处理该 webhook 的事件来判断商家的订阅状态。

      go
      // 在商家完成 app 安装授权后调用该函数创建周期性账单更新 webhook
      CreateWebhook(form.ShopDomain, "app/subscriptions_update", "https://APP-Host/webhook/recurring", token)
      // CreateWebhook
      // @Description:
      // @param shopDomain 商家域名 eg:test.myshoplaza.com
      // @param event webhook 名称
      // @param token access_token
      func CreateWebhook(shopDomain, event,address, token string) error {
      url := fmt.Sprintf("https://%s/openapi/2022-07/webhooks", shopDomain)
      method := "POST"
      param := map[string]interface{}{
      "address": address,
      "topic": event,
      }

      payload, err := json.Marshal(param)
      if err != nil {
      return err
      }

      client := &http.Client{}
      req, err := http.NewRequest(method, url, bytes.NewReader(payload))

      if err != nil {
      return err
      }
      req.Header.Add("Access-Token", token)
      req.Header.Add("Content-Type", "application/json")

      res, err := client.Do(req)
      if err != nil {
      return err
      }

      defer res.Body.Close()

      body, err := ioutil.ReadAll(res.Body)
      if err != nil {
      return err
      }
      fmt.Println(string(body))
      return nil
      }

    2. 创建 recurring charge

      备注

      套餐价格必须与 APP listing 中设置的套餐价格一致。

type CreateRecurringChargeResp struct {
ID string `json:"id"`
ApplicationID string `json:"application_id"`
Name string `json:"name"`
Price string `json:"price"`
CappedAmount string `json:"capped_amount"`
Terms string `json:"terms"`
ReturnURL string `json:"return_url"`
ConfirmationURL string `json:"confirmation_url"` //确认链接
Status string `json:"status"`
TrialDays int `json:"trial_days"`
ActivatedOn string `json:"activated_on"`
TrialEndsOn string `json:"trial_ends_on"`
BillingOn string `json:"billing_on"`
CancelledOn string `json:"cancelled_on"`
CancelSubOn string `json:"cancel_sub_on"`
Test bool `json:"test"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// 在订阅套餐的接口中调用,创建周期性账单
func createRecurringCharge(shopDomain, appName, appUrl, token string, price, cappedAmount float64) (*CreateRecurringChargeResp, error) {
url := fmt.Sprintf("https://%s/openapi/2022-01/recurring_application_charges", shopDomain)
body, err := json.Marshal(map[string]interface{}{
"recurring_application_charge": map[string]interface{}{
"name": appName, //app name
"price": price, //套餐价格,需要与listing中设置的套餐价格一致
"return_url": appUrl, //商家完成订阅之后跳转的页面
"trial_days": 0, //试用期天数
"capped_amount": cappedAmount, //每个周期使用费上限
"terms": "terms", //使用费收费说明
"test": false, //是否为测试账单
},
})
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
req.Header.Add("accept", "application/json")
req.Header.Add("content-type", "application/json")
req.Header.Add("access-token", token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}

respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

chargeResp := &CreateRecurringChargeResp{}

err = json.Unmarshal(respBody, chargeResp)
if err != nil {
return nil, err
}
fmt.Println(chargeResp)

return chargeResp, nil
}
  1. Shoplazza 会返回 confirmation_url。
  2. APP 展示 confirmation_url 页面。

  1. 商家确认订阅并发起支付。
  2. Shoplazza 从商家账户中扣款,并将款项收取到 Shoplazza 账户。
  3. webhook 会通知 APP 此实例的订阅信息。
    1. 处理 Webhook Notifications。当 Billing 账单状态发生变化时,APP 会通过 webhook 收到通知。APP 可以根据收到的 webhook event 判断商家的订阅状态。
    type RecurringChargeEvent struct {
    ActivatedOn *time.Time `json:"activated_on"`
    ApplicationID string `json:"application_id"`
    BillingOn *time.Time `json:"billing_on"`
    CancelledOn *time.Time `json:"cancelled_on"`
    CappedAmount string `json:"capped_amount"`
    ID string `json:"id"`
    Name string `json:"name"`
    Price string `json:"price"`
    Status string `json:"status"`
    StoreID int `json:"store_id"`
    Test bool `json:"test"`
    TrialDays int `json:"trial_days"`
    TrialEndsOn *time.Time `json:"trial_ends_on"`
    UpdatedAt *time.Time `json:"updated_at"`
    }
    // gin 框架 handle 示例
    func RecurringChargeWebhook(c *gin.Context) {
    body, err := ioutil.ReadAll(c.Request.Body)
    if err != nil {
    c.JSON(http.StatusInternalServerError, err.Error())
    return
    }
    fmt.Printf("Header: %+v\n", c.Request.Header)
    headerHmac := c.GetHeader("X-Shoplazza-Hmac-Sha256")
    if !hmacCheck(headerHmac, clientSecret, body) {
    c.JSON(http.StatusUnauthorized, "hmac verification fails.")
    return
    }
    event := &RecurringChargeEvent{}
    err = json.Unmarshal(body, event)
    if err != nil {
    fmt.Println(err.Error())
    c.JSON(http.StatusInternalServerError, err.Error())
    return
    }
    fmt.Printf("body: %+v\n", event)

    // todo 根据账单状态进行处理

    c.JSON(http.StatusOK, event)
    }

  4. 重定向到 return_url(用户在 confirmation_url 上确认后被重定向到的页面)。
  5. Shoplazza 会自动计算并扣除下一周期的款项。
  6. webhook 会向 APP 推送订阅状态新变化的信息。

周期性收费叠加按量收费与免费安装叠加按量收费​

提示

Recurring Charge with Usage Charge 与 Free to Install with Usage Charge 对比​

这两种模式本质上相似,主要区别如下:

  1. Free to Install 没有基础月费,月度基础成本为 $0。
  2. Free to Install 允许通过 charge_interval_days 自定义计费周期。

Recurring charge with Usage Charge 处理时间线​

详情​

  1. 商家在 APP 内选择 “recurring” 订阅套餐。

  2. APP 调用 OpenAPI 创建 recurring charge。

    1. 创建 webhook,用于跟踪 recurring charge 的状态变化。
      对于每个安装 APP 的商家,都会创建一个 webhook。随后,可以通过处理该 webhook 的事件来判断商家的订阅状态。

      go
      // 在商家完成 app 安装授权后调用该函数创建周期性账单更新 webhook
      CreateWebhook(form.ShopDomain, "app/subscriptions_update", "https://APP-Host/webhook/recurring", token)
      // CreateWebhook
      // @Description:
      // @param shopDomain 商家域名 eg:test.myshoplaza.com
      // @param event webhook 名称
      // @param token access_token
      func CreateWebhook(shopDomain, event,address, token string) error {
      url := fmt.Sprintf("https://%s/openapi/2022-07/webhooks", shopDomain)
      method := "POST"
      param := map[string]interface{}{
      "address": address,
      "topic": event,
      }

      payload, err := json.Marshal(param)
      if err != nil {
      return err
      }

      client := &http.Client{}
      req, err := http.NewRequest(method, url, bytes.NewReader(payload))

      if err != nil {
      return err
      }
      req.Header.Add("Access-Token", token)
      req.Header.Add("Content-Type", "application/json")

      res, err := client.Do(req)
      if err != nil {
      return err
      }

      defer res.Body.Close()

      body, err := ioutil.ReadAll(res.Body)
      if err != nil {
      return err
      }
      fmt.Println(string(body))
      return nil
      }

    2. 创建 recurring charge

      备注

      套餐价格必须与 APP listing 中设置的套餐价格一致。

type CreateRecurringChargeResp struct {
ID string `json:"id"`
ApplicationID string `json:"application_id"`
Name string `json:"name"`
Price string `json:"price"`
CappedAmount string `json:"capped_amount"`
Terms string `json:"terms"`
ReturnURL string `json:"return_url"`
ConfirmationURL string `json:"confirmation_url"` //确认链接
Status string `json:"status"`
TrialDays int `json:"trial_days"`
ActivatedOn string `json:"activated_on"`
TrialEndsOn string `json:"trial_ends_on"`
BillingOn string `json:"billing_on"`
CancelledOn string `json:"cancelled_on"`
CancelSubOn string `json:"cancel_sub_on"`
Test bool `json:"test"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// 在订阅套餐的接口中调用,创建周期性账单
func createRecurringCharge(shopDomain, appName, appUrl, token string, price, cappedAmount float64) (*CreateRecurringChargeResp, error) {
url := fmt.Sprintf("https://%s/openapi/2022-01/recurring_application_charges", shopDomain)
body, err := json.Marshal(map[string]interface{}{
"recurring_application_charge": map[string]interface{}{
"name": appName, //app name
"price": price, //套餐价格,需要与listing中设置的套餐价格一致
"return_url": appUrl, //商家完成订阅之后跳转的页面
"trial_days": 0, //试用期天数
"capped_amount": cappedAmount, //每个周期使用费上限
"terms": "terms", //使用费收费说明
"test": false, //是否为测试账单
},
})
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
req.Header.Add("accept", "application/json")
req.Header.Add("content-type", "application/json")
req.Header.Add("access-token", token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}

respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

chargeResp := &CreateRecurringChargeResp{}

err = json.Unmarshal(respBody, chargeResp)
if err != nil {
return nil, err
}
fmt.Println(chargeResp)

return chargeResp, nil
}
  1. Shoplazza 会返回 confirmation_url。
  2. APP 展示 confirmation_url 页面。

  1. 商家确认订阅并发起支付。

  2. Shoplazza 从商家账户中扣款,并将款项收取到 Shoplazza 账户。

  3. webhook 会通知 APP 此实例的订阅信息。

    1. 处理 Webhook Notifications。当 Billing 账单状态发生变化时,APP 会通过 webhook 收到通知。APP 可以根据收到的 webhook event 判断商家的订阅状态。
    type RecurringChargeEvent struct {
    ActivatedOn *time.Time `json:"activated_on"`
    ApplicationID string `json:"application_id"`
    BillingOn *time.Time `json:"billing_on"`
    CancelledOn *time.Time `json:"cancelled_on"`
    CappedAmount string `json:"capped_amount"`
    ID string `json:"id"`
    Name string `json:"name"`
    Price string `json:"price"`
    Status string `json:"status"`
    StoreID int `json:"store_id"`
    Test bool `json:"test"`
    TrialDays int `json:"trial_days"`
    TrialEndsOn *time.Time `json:"trial_ends_on"`
    UpdatedAt *time.Time `json:"updated_at"`
    }
    // gin 框架 handle 示例
    func RecurringChargeWebhook(c *gin.Context) {
    body, err := ioutil.ReadAll(c.Request.Body)
    if err != nil {
    c.JSON(http.StatusInternalServerError, err.Error())
    return
    }
    fmt.Printf("Header: %+v\n", c.Request.Header)
    headerHmac := c.GetHeader("X-Shoplazza-Hmac-Sha256")
    if !hmacCheck(headerHmac, clientSecret, body) {
    c.JSON(http.StatusUnauthorized, "hmac verification fails.")
    return
    }
    event := &RecurringChargeEvent{}
    err = json.Unmarshal(body, event)
    if err != nil {
    fmt.Println(err.Error())
    c.JSON(http.StatusInternalServerError, err.Error())
    return
    }
    fmt.Printf("body: %+v\n", event)

    // todo 根据账单状态进行处理

    c.JSON(http.StatusOK, event)
    }
  4. 重定向到 return_url(用户在 confirmation_url 上确认后被重定向到的页面)。

  5. 在一个订阅周期内,如果产生 usage fees,则创建 Usage charge(可以创建多笔)。

    1. 创建 usage charge

      type CreateUsageChargeResp struct {
      ID string `json:"id"`
      Price string `json:"price"`
      BalanceRemaining string `json:"balance_remaining"`
      BalanceUsed string `json:"balance_used"`
      CreatedAt *time.Time `json:"created_at"`
      Description string `json:"description"`
      }

      func createUsageCharge(shopDomain, description, chargeID, token string, price float64) (*CreateUsageChargeResp, error) {
      url := fmt.Sprintf("https://%s/openapi/2022-01/recurring_application_charges/%s/usage_charge", shopDomain, chargeID)

      body, err := json.Marshal(map[string]interface{}{
      "usage_charge": map[string]interface{}{
      "description": description,
      "price": price,
      },
      })
      req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
      req.Header.Add("accept", "application/json")
      req.Header.Add("content-type", "application/json")
      req.Header.Add("access-token", token)
      resp, err := http.DefaultClient.Do(req)
      if err != nil {
      return nil, err
      }

      respBody, err := ioutil.ReadAll(resp.Body)
      if err != nil {
      return nil, err
      }

      chargeResp := &CreateUsageChargeResp{}

      err = json.Unmarshal(respBody, chargeResp)
      if err != nil {
      return nil, err
      }
      fmt.Printf("%+v\n", chargeResp)
      return chargeResp, err
      }
  6. Shoplazza 会自动汇总当前周期的总 usage fees 以及下一周期的订阅费,并向商家扣款。

  7. webhook 会向 APP 推送订阅状态新变化的信息。

Free installation with Usage Charge 处理时间线​

详情​

  1. 商家在 APP 内选择 extra charge(one-time services)。

  2. APP 调用 OpenAPI 创建 free recurring charge。

    1. 创建 webhook,用于跟踪 recurring charge 的状态变化。
      对于每个安装 APP 的商家,都会创建一个 webhook。随后,可以通过处理该 webhook 的事件来判断商家的订阅状态。

      go
      // 在商家完成 app 安装授权后调用该函数创建周期性账单更新 webhook
      CreateWebhook(form.ShopDomain, "app/subscriptions_update", "https://APP-Host/webhook/recurring", token)
      // CreateWebhook
      // @Description:
      // @param shopDomain 商家域名 eg:test.myshoplaza.com
      // @param event webhook 名称
      // @param token access_token
      func CreateWebhook(shopDomain, event,address, token string) error {
      url := fmt.Sprintf("https://%s/openapi/2022-07/webhooks", shopDomain)
      method := "POST"
      param := map[string]interface{}{
      "address": address,
      "topic": event,
      }

      payload, err := json.Marshal(param)
      if err != nil {
      return err
      }

      client := &http.Client{}
      req, err := http.NewRequest(method, url, bytes.NewReader(payload))

      if err != nil {
      return err
      }
      req.Header.Add("Access-Token", token)
      req.Header.Add("Content-Type", "application/json")

      res, err := client.Do(req)
      if err != nil {
      return err
      }

      defer res.Body.Close()

      body, err := ioutil.ReadAll(res.Body)
      if err != nil {
      return err
      }
      fmt.Println(string(body))
      return nil
      }

    2. 创建 free recurring charge

      备注

      套餐价格必须与 APP listing 中设置的套餐价格一致。

type CreateRecurringChargeResp struct {
ID string `json:"id"`
ApplicationID string `json:"application_id"`
Name string `json:"name"`
Price string `json:"price"`
CappedAmount string `json:"capped_amount"`
Terms string `json:"terms"`
ReturnURL string `json:"return_url"`
ConfirmationURL string `json:"confirmation_url"` //确认链接
Status string `json:"status"`
TrialDays int `json:"trial_days"`
ActivatedOn string `json:"activated_on"`
TrialEndsOn string `json:"trial_ends_on"`
BillingOn string `json:"billing_on"`
CancelledOn string `json:"cancelled_on"`
CancelSubOn string `json:"cancel_sub_on"`
Test bool `json:"test"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// 在订阅套餐的接口中调用,创建周期性账单
func createRecurringCharge(shopDomain, appName, appUrl, token string, price, cappedAmount float64) (*CreateRecurringChargeResp, error) {
url := fmt.Sprintf("https://%s/openapi/2022-01/recurring_application_charges", shopDomain)
body, err := json.Marshal(map[string]interface{}{
"recurring_application_charge": map[string]interface{}{
"name": appName, //app name
"price": price, //套餐价格,当模式为free to intall时,价格设置为“0”
"return_url": appUrl, //商家完成订阅之后跳转的页面
"trial_days": 0, //试用期天数
"capped_amount": cappedAmount, //每个周期使用费上限
"terms": "terms", //使用费收费说明
"test": false, //是否为测试账单
},
})
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
req.Header.Add("accept", "application/json")
req.Header.Add("content-type", "application/json")
req.Header.Add("access-token", token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}

respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

chargeResp := &CreateRecurringChargeResp{}

err = json.Unmarshal(respBody, chargeResp)
if err != nil {
return nil, err
}
fmt.Println(chargeResp)

return chargeResp, nil
}
  1. Shoplazza 会返回 confirmation_url。

  2. APP 展示 confirmation_url 页面。

  3. 商家确认订阅并发起支付。

  4. Shoplazza 从商家账户中扣款,并将款项收取到 Shoplazza 账户。

  5. webhook 会通知 APP 此实例的订阅信息。

    1. 处理 Webhook Notifications。当 Billing 账单状态发生变化时,APP 会通过 webhook 收到通知。APP 可以根据收到的 webhook event 判断商家的订阅状态。
    type RecurringChargeEvent struct {
    ActivatedOn *time.Time `json:"activated_on"`
    ApplicationID string `json:"application_id"`
    BillingOn *time.Time `json:"billing_on"`
    CancelledOn *time.Time `json:"cancelled_on"`
    CappedAmount string `json:"capped_amount"`
    ID string `json:"id"`
    Name string `json:"name"`
    Price string `json:"price"`
    Status string `json:"status"`
    StoreID int `json:"store_id"`
    Test bool `json:"test"`
    TrialDays int `json:"trial_days"`
    TrialEndsOn *time.Time `json:"trial_ends_on"`
    UpdatedAt *time.Time `json:"updated_at"`
    }
    // gin 框架 handle 示例
    func RecurringChargeWebhook(c *gin.Context) {
    body, err := ioutil.ReadAll(c.Request.Body)
    if err != nil {
    c.JSON(http.StatusInternalServerError, err.Error())
    return
    }
    fmt.Printf("Header: %+v\n", c.Request.Header)
    headerHmac := c.GetHeader("X-Shoplazza-Hmac-Sha256")
    if !hmacCheck(headerHmac, clientSecret, body) {
    c.JSON(http.StatusUnauthorized, "hmac verification fails.")
    return
    }
    event := &RecurringChargeEvent{}
    err = json.Unmarshal(body, event)
    if err != nil {
    fmt.Println(err.Error())
    c.JSON(http.StatusInternalServerError, err.Error())
    return
    }
    fmt.Printf("body: %+v\n", event)

    // todo 根据账单状态进行处理

    c.JSON(http.StatusOK, event)
    }
  6. 重定向到 return_url(用户在 confirmation_url 上确认后被重定向到的页面)。

  7. 在一个订阅周期内,如果产生 usage fees,则创建 Usage charge(可以创建多笔)。

    1. 创建 usage charge

      type CreateUsageChargeResp struct {
      ID string `json:"id"`
      Price string `json:"price"`
      BalanceRemaining string `json:"balance_remaining"`
      BalanceUsed string `json:"balance_used"`
      CreatedAt *time.Time `json:"created_at"`
      Description string `json:"description"`
      }

      func createUsageCharge(shopDomain, description, chargeID, token string, price float64) (*CreateUsageChargeResp, error) {
      url := fmt.Sprintf("https://%s/openapi/2022-01/recurring_application_charges/%s/usage_charge", shopDomain, chargeID)

      body, err := json.Marshal(map[string]interface{}{
      "usage_charge": map[string]interface{}{
      "description": description,
      "price": price,
      },
      })
      req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
      req.Header.Add("accept", "application/json")
      req.Header.Add("content-type", "application/json")
      req.Header.Add("access-token", token)
      resp, err := http.DefaultClient.Do(req)
      if err != nil {
      return nil, err
      }

      respBody, err := ioutil.ReadAll(resp.Body)
      if err != nil {
      return nil, err
      }

      chargeResp := &CreateUsageChargeResp{}

      err = json.Unmarshal(respBody, chargeResp)
      if err != nil {
      return nil, err
      }
      fmt.Printf("%+v\n", chargeResp)
      return chargeResp, err
      }
  8. Shoplazza 会自动汇总当前周期的总 usage fees 以及下一周期的订阅费,并向商家扣款。

  9. webhook 会向 APP 推送订阅状态新变化的信息。

补充:通过 BillingAPI 事件触发 Webhook​

在以下状态变化时,会触发 webhook 通知 app。

3.开发者结算指南​

已集成 Billing API 的 app 可以在 Developer Platform 查看收入明细,开发者可以在此结算 app 收益。

收入审核​

当 app 通过 billing 产生收入时,您可以在如下图所示页面查看 app 的收入明细。

点击 “Details” 查看结算账单详情后,您可以查看该账单周期内的所有交易明细:

点击交易上的 “Details”,查看该笔交易的具体收费明细。

提现收益​

选择可提现类别,并确认账单明细无误后,点击 “Withdraw”。这会向 Shoplazza 发送审核请求。审核通过后,收益将按约定方式到账。

在可提现页面,您可以查看提现进度。

查看历史收益​

在 “Withdrawn” 类别中,您可以查看此前已提现并完成的结算账单。您还可以查看具体的付款凭证。

下一步​

完成计费接入后,继续阅读测试应用,在开发店铺里验证完整流程,再提交审核。