前言
shopify 平台感兴趣的自己搜一下了
我这儿的业务场景是:
我方作为应用提供方,让商户接入、安装我方应用,我方通过商户返回的code拿到商户授权的 accessToken 进行接口访问
这里对应用方和商户的首次交互做个案例
shopify OAuth accessToken 生成介绍
OAuth首次交互流程
下图在官方的API文档中可以找到,这里贴了出来
scope列表
权限名称 | 描述 |
---|---|
read_analytics | View store metrics |
read_assigned_fulfillment_orders, write_assigned_fulfillment_orders | View or manage fulfillment orders |
read_customers, write_customers | View or manage customers, customer addresses, order history, and customer groups |
read_discounts, write_discounts | View or manage automatic discounts and discount codes |
read_draft_orders, write_draft_orders | View or manage orders created by merchants on behalf of customers |
read_files, write_files | View or manage files |
read_fulfillments, write_fulfillments | View or manage fulfillment services |
read_gdpr_data_request | View GDPR data requests |
read_gift_cards, write_gift_cards | View or manage gift cards (Available to Plus merchants only) |
read_inventory, write_inventory | View or manage inventory across multiple locations |
read_legal_policies, write_legal_policies | View or manage a shop’s legal policies |
read_locations | View the geographic location of stores, headquarters, and warehouses |
read_marketing_events, write_marketing_events | View or manage marketing events and engagement data |
read_merchant_managed_fulfillment_orders, write_merchant_managed_fulfillment_orders | View or manage fulfilment orders assigned to merchant-managed locations |
read_online_store_navigation | View menus for display on the storefront |
read_online_store_pages, write_online_store_pages | View or manage Online Store pages |
read_order_edits, write_order_edits | View or manage edits to orders |
read_orders, write_orders, read_all_orders | View or manage orders, transactions, fulfillments, and abandoned checkouts from the last 60 days, or View all past and future orders |
read_price_rules, write_price_rules | View or manage conditional discounts |
read_products, write_products | View or manage products, variants, and collections |
read_product_listings, write_product_listings | View or manage product or collection listings |
read_reports, write_reports | View or manage reports on the Reports page in the Shopify admin |
read_resource_feedbacks, write_resource_feedbacks | View or manage the status of shops and resources |
read_script_tags, write_script_tags | View or manage the JavaScript code in storefront or orders status pages |
read_shipping, write_shipping | View or manage shipping carriers, countries, and provinces |
read_shopify_payments_accounts | View Shopify Payments accounts |
read_shopify_payments_bank_accounts | View bank accounts that can receive Shopify Payment payouts |
read_shopify_payments_disputes | View Shopify Payment disputes raised by buyers |
read_shopify_payments_payouts | View Shopify Payments payouts and the account’s current balance |
read_content, write_content | View or manage articles, blogs, comments, pages, and redirects |
read_themes, write_themes | View or manage theme templates and assets |
read_third_party_fulfillment_orders, write_third_party_fulfillment_orders | View or manage fulfillment orders assigned to a location managed by any fulfillment service |
read_translations, write_translations | View or manage content that can be translated |
java代码示例
接口
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Properties;/*** shopify 接口* 参考文档:https://www.shopify.com/partners/blog/17056443-how-to-generate-a-shopify-api-token* @author Heng.Wei* @date 2022-10-11**/
@Slf4j
@Controller
@RequestMapping("/shopify")
public class ShopifyController {/** API key of your app. */private final static String CLIENT_KEY = "[注册的应用的API KEY]";/** 加密的密钥 */private final static String CLIENT_SECRET = "[API SECRET]";/** 摘要算法 */private final static HMac HMAC = new HMac(HmacAlgorithm.HmacSHA256, CLIENT_SECRET.getBytes(StandardCharsets.UTF_8));/** 向商户申请授权码 */private final static String AUTHORIZE_URL = "https://%s/admin/oauth/authorize?client_id=%s&grant_options[]=%s&redirect_uri=%s&scope=%s";/** 生成 accessToken 地址, 带三个参数 client_id、client_secret、code */private final static String ACCESS_TOKEN_URL = "https://%s/admin/oauth/access_token";/** 权限范围 */private final static String SCOPE = "write_product_listings,read_orders,write_products";/** 商户安装完成后的重定向地址 */private final static String REDIRECT_URI = "http://localhost:20008/shopify/generate_token";private final static String GRANT_OPTIONS = "per-user";@Resourceprivate RestTemplate restTemplate;/*** <pre>* 商户发起安装请求* </pre>** @param hmac GET参数生成的摘要* @param host 这个参数官网没有描述* @param shop 商店域名* @param timestamp 时间戳* @author weiheng**/@GetMapping("/install")public ModelAndView index(@RequestParam("hmac") String hmac,@RequestParam("host") String host,@RequestParam("shop") String shop,@RequestParam("timestamp") String timestamp) {log.info("shop[{}], hmac[{}], host[{}], timestamp[{}]", shop, hmac, host, timestamp);String params = "host=" + host + "&shop=" + shop + "×tamp=" + timestamp;boolean verify = verify(params, hmac, shop);if (verify) {// 校验通过String path = String.format(AUTHORIZE_URL, shop, CLIENT_KEY, GRANT_OPTIONS, REDIRECT_URI, SCOPE);return new ModelAndView("redirect:" + path);}return new ModelAndView("redirect:404");}/*** <pre>* 通过商户给的 code,生成 token 凭据* </pre>** @param hmac HMAC SHA256 数字签名* @param code 商户给的授权码* @param host 官网未给出该字段定义* @param shop 商店域名* @param timestamp 时间戳* @author weiheng**/@GetMapping("/generate_token")public void generateToken(@RequestParam("hmac") String hmac,@RequestParam("code") String code,@RequestParam("host") String host,@RequestParam("shop") String shop,@RequestParam("timestamp") String timestamp) {log.info("shop[{}], hmac[{}], code[{}], host[{}], timestamp[{}]", shop, hmac, code, host, timestamp);String params = "code=" + code + "&host=" + host + "&shop=" + shop + "×tamp=" + timestamp;boolean verify = verify(params, hmac, shop);if (!verify) {log.error("shop[{}], 校验未通过", shop);return;}// 校验通过 - Exchange access code for the shop tokenProperties request = new Properties();request.put("client_id", CLIENT_KEY);request.put("client_secret", CLIENT_SECRET);request.put("code", code);String path = String.format(ACCESS_TOKEN_URL, shop);JSONObject obj = restTemplate.postForObject(path, request, JSONObject.class);log.info("resultObj[{}]", obj);if (obj == null) {log.error("获取token异常, Properties[{}]", request);return;}ShopifyTokenDTO shopifyTokenDto = JSON.parseObject(obj.toJSONString(), ShopifyTokenDTO.class);log.info("shopifyTokenDto[{}]", shopifyTokenDto);// TODO 缓存 商户 的 token 凭据}/*** <pre>* 请求有效性校验* </pre>** @param params ASCII排序后的参数 URL格式的参数,如 a=111&b=222&c=333* @param hmac 调用方传过来的摘要信息* @param shop 商户域名* @return 校验是否通过* @author weiheng**/private boolean verify(String params, String hmac, String shop) {String digest = HMAC.digestHex(params.getBytes(StandardCharsets.UTF_8));boolean verify = HMAC.verify(digest.getBytes(StandardCharsets.UTF_8), hmac.getBytes(StandardCharsets.UTF_8));log.info("shop[{}], digest[{}], verify[{}]", shop, digest, verify);return verify;}}
自定义 token 实体
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;import java.io.Serializable;/*** <pre>* shopify 商户返回的token信息* </pre>* @author Heng.Wei* @date 2022-10-11**/
@Data
public class ShopifyTokenDTO implements Serializable {@JSONField(name = "access_token")private String accessToken;private String scope;@JSONField(name = "expires_in")private Integer expiresIn;@JSONField(name = "associated_user_scope")private String associatedUserScope;private String session;@JSONField(name = "account_number")private String accountNumber;@JSONField(name = "associated_user")private ShopifyUserDTO associatedUser;
}
自定义实体类 ShopifyUserDTO
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;import java.io.Serializable;/*** <pre>* shopify associated user* </pre>* @author Heng.Wei* @date 2022-10-11**/
@Data
public class ShopifyUserDTO implements Serializable {private Long id;@JSONField(name = "first_name")private String firstName;@JSONField(name = "last_name")private String lastName;private String email;@JSONField(name = "account_owner")private Boolean accountOwner;private String locale;private Boolean collaborator;@JSONField(name = "email_verified")private Boolean emailVerified;
}
亲测OK,上图
这里拿到 token 就算完事儿了