NUTRINTG Loyalty integrations - AIMIA
Business background:
Some of the brand sides use external CLP systems like AIMIA (AA). Middleware Loyality Platform Service is responsible for passing customers and orders data from Shopify do AIMIA platform.
Below is very simplified and high level diagram:
Orders:
Only fulfilled orders are send to AIMIA.
Products in AIMIA are identified by BARCODE/UPC code (12 digit code). AIMIA is responsible for updating products list on their side.
Products not present in AIMIA during Order are inserted there by MLPS
Orders only from registered Users are send to AIMIA (Guest’s orders are not being send)
Product Variant in Shopify = Product Item in AIMIA
Customers:
Customer is being enrolled with Email
Customer state has to be enabled (customers created only by making an order are not send)
Customer needs to get his AccountIDs added (CustomerID as one and Email as second AccountID)
Opt-Out settings are skipped for AIMIA, it is stored in SFMC.
Each customer has to accept marketing during account creation
Third party materials:
AIMIA
Documentation delivered by AIMIA (Clicking on methods names redirects you to the documentation):
On each method page you can find:
It’s detailed view with example requests
Possible response codes
Description
Whole documentation is available to everyone
Shopify
Shopify Webhooks Documentation: https://shopify.dev/docs/admin-api/rest/reference/events/webhook
Shopify Variants API: https://shopify.dev/docs/admin-api/rest/reference/products/product-variant
Each Shopify Shop will have Private App which provides a Client ID and Client Secret to authenticate calls to API. To be done together with Shopify shop.
MongoDB:
Collection name for storing documents with data used in integration is middlewareLoyalityPlatformService_shopifyDomains
Document json which will be stored in this collection(Each field explained):
{
"_id" : "<SHOPIFY_DOMAIN>",
"wsToken" : "<TOKEN_FROM_AIMA>(AES ENC)", - provided by AIMIA
"location" : "<SHOP LOCATION IN AIMIA>", - provided by AIMIA
"clientId" : "<CLIENT ID FOR SHOPIFY API>(AES ENC)", - provided by Shopify
"clientSecret" : "<CLIENT SECRET FOR SHOPIFY API>(AES ENC)", - provided by Shopify
"hmacKey" : "<HMAC SECRET FROM SHOPIFY>(AES ENC)", - provided by Shopify
"transactionType" : "<TRANSACTION TYPE FOR ORDERS FROM AIMIA>", - provided by AIMIA
"countryId" : "1(US COUNTRY CODE FROM AIMIA>", - provided by AIMIA
"regionId" : "-1(UNKNOWN REGION ID REPRESENTATION)", - Constant value
"emailIdType" : "<EMAIL ID TYPE FROM AIMIA>", - provided by AIMIA
"customerIdType" : "<CUSTOMER ID TYPE FROM AIMIA>", - provided by AIMIA
"shopifyVariantApiUri" : "<VARIANT ENDPOINT FROM SHOPIFY API>”
}
Sensitive data is encrypted with AES
Collection for storing errors is called middlewareLoyalityPlatformService_errors
Application has 2 flows from business perspective, following descriptions contains their flow diagrams, technical overview and mappings.
Order Flow:
Solution:
# | Step | Description |
---|---|---|
1 | Shopify sends order requests | Shopify sends order request to an endpoint { "id": 820982911946154508, "email": "jon@doe.ca", "closed_at": null, "created_at": "2020-10-29T19:26:35-04:00", "updated_at": "2020-10-29T19:26:35-04:00", "number": 234, "note": null, "token": "123456abcd", "gateway": null, "test": true, "total_price": "403.00", "subtotal_price": "393.00", "total_weight": 0, "total_tax": "0.00", "taxes_included": false, "currency": "USD", "financial_status": "voided", "confirmed": false, "total_discounts": "5.00", "total_line_items_price": "398.00", "cart_token": null, "buyer_accepts_marketing": true, "name": "#9999", "referring_site": null, "landing_site": null, "cancelled_at": "2020-10-29T19:26:35-04:00", "cancel_reason": "customer", "total_price_usd": null, "checkout_token": null, "reference": null, "user_id": null, "location_id": null, "source_identifier": null, "source_url": null, "processed_at": null, "device_id": null, "phone": null, "customer_locale": "en", "app_id": null, "browser_ip": null, "landing_site_ref": null, "order_number": 1234, "discount_applications": [ { "type": "manual", "value": "5.0", "value_type": "fixed_amount", "allocation_method": "across", "target_selection": "explicit", "target_type": "line_item", "description": "Discount", "title": "Discount" } ], "discount_codes": [ ], "note_attributes": [ ], "payment_gateway_names": [ "visa", "bogus" ], "processing_method": "", "checkout_id": null, "source_name": "web", "fulfillment_status": "pending", "tax_lines": [ ], "tags": "", "contact_email": "jon@doe.ca", "order_status_url": "https:\/\/apple.myshopify.com\/690933842\/orders\/123456abcd\/authenticate?key=abcdefg", "presentment_currency": "USD", "total_line_items_price_set": { "shop_money": { "amount": "398.00", "currency_code": "USD" }, "presentment_money": { "amount": "398.00", "currency_code": "USD" } }, "total_discounts_set": { "shop_money": { "amount": "5.00", "currency_code": "USD" }, "presentment_money": { "amount": "5.00", "currency_code": "USD" } }, "total_shipping_price_set": { "shop_money": { "amount": "10.00", "currency_code": "USD" }, "presentment_money": { "amount": "10.00", "currency_code": "USD" } }, "subtotal_price_set": { "shop_money": { "amount": "393.00", "currency_code": "USD" }, "presentment_money": { "amount": "393.00", "currency_code": "USD" } }, "total_price_set": { "shop_money": { "amount": "403.00", "currency_code": "USD" }, "presentment_money": { "amount": "403.00", "currency_code": "USD" } }, "total_tax_set": { "shop_money": { "amount": "0.00", "currency_code": "USD" }, "presentment_money": { "amount": "0.00", "currency_code": "USD" } }, "line_items": [ { "id": 866550311766439020, "variant_id": 808950810, "title": "IPod Nano - 8GB", "quantity": 1, "sku": "IPOD2008PINK", "variant_title": null, "vendor": null, "fulfillment_service": "manual", "product_id": 632910392, "requires_shipping": true, "taxable": true, "gift_card": false, "name": "IPod Nano - 8GB", "variant_inventory_management": "shopify", "properties": [ ], "product_exists": true, "fulfillable_quantity": 1, "grams": 567, "price": "199.00", "total_discount": "0.00", "fulfillment_status": null, "price_set": { "shop_money": { "amount": "199.00", "currency_code": "USD" }, "presentment_money": { "amount": "199.00", "currency_code": "USD" } }, "total_discount_set": { "shop_money": { "amount": "0.00", "currency_code": "USD" }, "presentment_money": { "amount": "0.00", "currency_code": "USD" } }, "discount_allocations": [ ], "duties": [ ], "admin_graphql_api_id": "gid:\/\/shopify\/LineItem\/866550311766439020", "tax_lines": [ ] }, { "id": 141249953214522974, "variant_id": 808950810, "title": "IPod Nano - 8GB", "quantity": 1, "sku": "IPOD2008PINK", "variant_title": null, "vendor": null, "fulfillment_service": "manual", "product_id": 632910392, "requires_shipping": true, "taxable": true, "gift_card": false, "name": "IPod Nano - 8GB", "variant_inventory_management": "shopify", "properties": [ ], "product_exists": true, "fulfillable_quantity": 1, "grams": 567, "price": "199.00", "total_discount": "5.00", "fulfillment_status": null, "price_set": { "shop_money": { "amount": "199.00", "currency_code": "USD" }, "presentment_money": { "amount": "199.00", "currency_code": "USD" } }, "total_discount_set": { "shop_money": { "amount": "5.00", "currency_code": "USD" }, "presentment_money": { "amount": "5.00", "currency_code": "USD" } }, "discount_allocations": [ { "amount": "5.00", "discount_application_index": 0, "amount_set": { "shop_money": { "amount": "5.00", "currency_code": "USD" }, "presentment_money": { "amount": "5.00", "currency_code": "USD" } } } ], "duties": [ ], "admin_graphql_api_id": "gid:\/\/shopify\/LineItem\/141249953214522974", "tax_lines": [ ] } ], "fulfillments": [ ], "refunds": [ ], "total_tip_received": "0.0", "original_total_duties_set": null, "current_total_duties_set": null, "admin_graphql_api_id": "gid:\/\/shopify\/Order\/820982911946154508", "shipping_lines": [ { "id": 271878346596884015, "title": "Generic Shipping", "price": "10.00", "code": null, "source": "shopify", "phone": null, "requested_fulfillment_service_id": null, "delivery_category": null, "carrier_identifier": null, "discounted_price": "10.00", "price_set": { "shop_money": { "amount": "10.00", "currency_code": "USD" }, "presentment_money": { "amount": "10.00", "currency_code": "USD" } }, "discounted_price_set": { "shop_money": { "amount": "10.00", "currency_code": "USD" }, "presentment_money": { "amount": "10.00", "currency_code": "USD" } }, "discount_allocations": [ ], "tax_lines": [ ] } ], "billing_address": { "first_name": "Bob", "address1": "123 Billing Street", "phone": "555-555-BILL", "city": "Billtown", "zip": "K2P0B0", "province": "Kentucky", "country": "United States", "last_name": "Biller", "address2": null, "company": "My Company", "latitude": null, "longitude": null, "name": "Bob Biller", "country_code": "US", "province_code": "KY" }, "shipping_address": { "first_name": "Steve", "address1": "123 Shipping Street", "phone": "555-555-SHIP", "city": "Shippington", "zip": "40003", "province": "Kentucky", "country": "United States", "last_name": "Shipper", "address2": null, "company": "Shipping Company", "latitude": null, "longitude": null, "name": "Steve Shipper", "country_code": "US", "province_code": "KY" }, "customer": { "id": 115310627314723954, "email": "john@test.com", "accepts_marketing": false, "created_at": null, "updated_at": null, "first_name": "John", "last_name": "Smith", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": null, "marketing_opt_in_level": null, "admin_graphql_api_id": "gid:\/\/shopify\/Customer\/115310627314723954", "default_address": { "id": 715243470612851245, "customer_id": 115310627314723954, "first_name": null, "last_name": null, "company": null, "address1": "123 Elm St.", "address2": null, "city": "Ottawa", "province": "Ontario", "country": "Canada", "zip": "K2H7A8", "phone": "123-123-1234", "name": "", "province_code": "ON", "country_code": "CA", "country_name": "Canada", "default": true } } }
|
2 | Middleware Loyality Platform Service validates if Shopify Domain is enrolled for AIMIA | MLPS validates if Shopify Domain is enrolled for AIMIA by looking it up in mongo database. Collection can be named “loyalityService_AimiaShopifyDomains”. (In the future collection may be expanded by a field called “default_location” In case when Shopify Domain is not enrolled, Request is dropped. Message about it should be logged only on DEBUG level. |
3 | MLPS perform further validations | MLPS performs:
If either validation fails, error should be logged. |
4 | MLPS fetches barcodes(UPC codes) for products in order | MLPS fetches products barcodes by Shopify API or from collection in mongo (to be decieded) |
5 | MLPS transform message accordingly to the prepared mapping |
|
6 | MLPS sends SOAP request to AIMIA | Used request: https://info.aimialoyalty.com/default.asp?W792 WS Security Token is stored in properties and is encrypted. |
AIMIA point of view for the Order Flow(Screenshot from the documentation):
Mapping for Order flow:
Customer Flow:
# | Step | Description |
---|---|---|
1 | Shopify sends customer request | Shopify sends customer request to an endpoint { "id": 706405506930370084, "email": "bob@biller.com", "accepts_marketing": true, "created_at": null, "updated_at": null, "first_name": "Bob", "last_name": "Biller", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": "This customer loves ice cream", "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "addresses": [ ], "accepts_marketing_updated_at": null, "marketing_opt_in_level": null, "admin_graphql_api_id": "gid:\/\/shopify\/Customer\/706405506930370084" }
|
2 | MLPS performs Validation on Request | MLPS validates if Shopify Domain is enrolled for AIMIA by looking it up in mongo database. Collection can be named “loyalityService_AimiaShopifyDomains”. (In the future collection may be expanded by a field called “default_location”) In case when Shopify Domain is not enrolled, Request is dropped. Message about it should be logged only on DEBUG level. |
3 | MLPS perform further validations | MLPS performs:
If either validation fails, error should be logged. |
4 | MLPS checks if member is existing in AIMIA | MLPS sends SOAP request FetchMemberCore https://info.aimialoyalty.com/default.asp?W580. Customer ID from Shopify Webhook is passed as Account ID |
5 | (Optional) MLPS sends EnrollMemberWithEmail | MLPS sends SOAP request EnrollMemberWithEmail https://info.aimialoyalty.com/default.asp?W608 (Has to store Internal Account ID returned by AIMIA) |
6 | (Optional) MLPS sends | MLPS sends SOAP request AddMemberAccountID https://info.aimialoyalty.com/default.asp?W662 Customer IDs from Shopify Webhook is passed as added Account ID |
7 | (Optional) MLPS sends Terms and Conditions | MLPS sends SOAP request IssueInteractionTermsAcceptance if Customer accepts marketing https://info.aimialoyalty.com/default.asp?W598 |
8 | MLPS sends further data | MLPS sends SOAP requests as shown on following diagram from AIMIA. In case of updating customer data, MLPS has to find differences between existing customer and the one which came from Shopify and update it accordingly. Unnecessary requests can be skipped. Mapping is also attached in a file below. |
AIMIA point of view for the Customer Flow(Screenshot from Documentation):
Mapping for Customer flow: