跳到主要内容

集成应用收款


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

1.定价详情配置

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

One-time charge

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

Recurring charge

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

Recurring Charge Only

  • free trial config

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

Recurring Charge & Usage Charge

  • 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

One-time Charge

处理时间线

详情

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

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

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

      // 在商家完成 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 中设置的金额一致。

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 判断商家的订阅状态。
    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 上确认后被重定向到的页面)。

Recurring Charge Only

处理时间线

详情

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

  2. APP 调用 OpenAPI 创建 recurring charge。

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

      // 在商家完成 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 installation with Usage Charge

提示

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 的事件来判断商家的订阅状态。

      // 在商家完成 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 的事件来判断商家的订阅状态。

      // 在商家完成 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” 类别中,您可以查看此前已提现并完成的结算账单。您还可以查看具体的付款凭证。