Update the Profile | Invoices section to finish migrating the invoices UX over to the React component.

Findings

The “My Profile” interface for users logged into the SaaS platform comes from the MySLP Dashboard plugin.

The Invoices section is created by a React component defined within wp-content/plugins/myslp-dashboard/src/profile_subscription/profile_subscription.tsx

The invoices are pulled via a REST request to this endpoint:

Request…

GET /icecream_at_slp_dot_guru/wp-json/myslp/v2/invoices HTTP/1.1
Accept: application/json, text/plain, */*
Sec-Fetch-Site: same-origin

Response…


{
  "id": "string",
  "object": "invoice",
  "account_country": "string",
  "account_name": "string",
  "account_tax_ids": null | array | object,
  "amount_due": "number",
  "amount_overpaid": "number",
  "amount_paid": "number",
  "amount_remaining": "number",
  "amount_shipping": "number",
  "application": null,
  "application_fee_amount": null,
  "attempt_count": "number",
  "attempted": "boolean",
  "auto_advance": "boolean",

  "automatic_tax": {
    "disabled_reason": null | string,
    "enabled": "boolean",
    "liability": null | object,
    "provider": null | string,
    "status": null | string
  },

  "automatically_finalizes_at": null,
  "billing_reason": "string",
  "charge": null,
  "collection_method": "string",
  "created": "number",
  "currency": "string",
  "custom_fields": null,

  "customer": "string",
  "customer_account": null,
  "customer_address": null,
  "customer_email": "string",
  "customer_name": null | string,
  "customer_phone": null | string,
  "customer_shipping": null,

  "customer_tax_exempt": "string",
  "customer_tax_ids": [],

  "default_payment_method": null,
  "default_source": null,
  "default_tax_rates": [],

  "description": null,

  "discount": {
    "id": "string",
    "object": "discount",
    "checkout_session": null,
    "coupon": {
      "id": "string",
      "object": "coupon",
      "amount_off": null | number,
      "created": "number",
      "currency": null | string,
      "duration": "string",
      "duration_in_months": null | number,
      "livemode": "boolean",
      "max_redemptions": "number",
      "metadata": [],
      "name": null | string,
      "percent_off": "number",
      "redeem_by": null | number,
      "times_redeemed": "number",
      "valid": "boolean"
    },
    "customer": "string",
    "customer_account": null,
    "end": null,
    "invoice": null,
    "invoice_item": null,
    "promotion_code": null,
    "start": "number",
    "subscription": null,
    "subscription_item": null
  },

  "discounts": ["string"],

  "due_date": null,
  "effective_at": "number",
  "ending_balance": "number",
  "footer": null,

  "from_invoice": null,

  "hosted_invoice_url": "string",
  "invoice_pdf": "string",

  "issuer": {
    "type": "string"
  },

  "last_finalization_error": null,
  "latest_revision": null,

  "lines": {
    "object": "list",
    "data": [
      {
        "id": "string",
        "object": "line_item",
        "amount": "number",
        "amount_excluding_tax": "number",
        "currency": "string",
        "description": "string",

        "discount_amounts": [
          {
            "amount": "number",
            "discount": "string"
          }
        ],

        "discountable": "boolean",
        "discounts": [],

        "invoice": "string",
        "livemode": "boolean",
        "metadata": [],

        "parent": {
          "invoice_item_details": null,
          "subscription_item_details": {
            "invoice_item": null,
            "proration": "boolean",
            "proration_details": {
              "credited_items": null
            },
            "subscription": "string",
            "subscription_item": "string"
          },
          "type": "string"
        },

        "period": {
          "start": "number",
          "end": "number"
        },

        "plan": {
          "id": "string",
          "object": "plan",
          "active": "boolean",
          "aggregate_usage": null,
          "amount": "number",
          "amount_decimal": "string",
          "billing_scheme": "string",
          "created": "number",
          "currency": "string",
          "interval": "string",
          "interval_count": "number",
          "livemode": "boolean",
          "metadata": [],
          "meter": null,
          "nickname": null,
          "product": "string",
          "tiers_mode": null,
          "transform_usage": null,
          "trial_period_days": null,
          "usage_type": "string"
        },

        "pretax_credit_amounts": [
          {
            "amount": "number",
            "discount": "string",
            "type": "string"
          }
        ],

        "price": {
          "id": "string",
          "object": "price",
          "active": "boolean",
          "billing_scheme": "string",
          "created": "number",
          "currency": "string",
          "custom_unit_amount": null,
          "livemode": "boolean",
          "lookup_key": null,
          "metadata": [],
          "nickname": null,
          "product": "string",
          "recurring": {
            "aggregate_usage": null,
            "interval": "string",
            "interval_count": "number",
            "meter": null,
            "trial_period_days": null,
            "usage_type": "string"
          },
          "tax_behavior": "string",
          "tiers_mode": null,
          "transform_quantity": null,
          "type": "string",
          "unit_amount": "number",
          "unit_amount_decimal": "string"
        },

        "pricing": {
          "price_details": {
            "price": "string",
            "product": "string"
          },
          "type": "string",
          "unit_amount_decimal": "string"
        },

        "proration": "boolean",
        "proration_details": {
          "credited_items": null
        },

        "quantity": "number",
        "subscription": "string",
        "subscription_item": "string",

        "tax_amounts": [],
        "tax_rates": [],
        "taxes": [],

        "type": "string",
        "unit_amount_excluding_tax": "string"
      }
    ],

    "has_more": "boolean",
    "total_count": "number",
    "url": "string"
  },

  "livemode": "boolean",
  "metadata": [],

  "next_payment_attempt": null,

  "number": "string",
  "on_behalf_of": null,

  "paid": "boolean",
  "paid_out_of_band": "boolean",

  "parent": {
    "quote_details": null,
    "subscription_details": {
      "metadata": [],
      "subscription": "string"
    },
    "type": "string"
  },

  "payment_intent": null,

  "payment_settings": {
    "default_mandate": null,
    "payment_method_options": null,
    "payment_method_types": null
  },

  "period_end": "number",
  "period_start": "number",

  "post_payment_credit_notes_amount": "number",
  "pre_payment_credit_notes_amount": "number",

  "quote": null,

  "receipt_number": null,

  "rendering": null,

  "shipping_cost": null,
  "shipping_details": null,

  "starting_balance": "number",

  "statement_descriptor": null,

  "status": "string",

  "status_transitions": {
    "finalized_at": "number",
    "marked_uncollectible_at": null,
    "paid_at": "number",
    "voided_at": null
  },

  "subscription": "string",

  "subscription_details": {
    "metadata": []
  },

  "subtotal": "number",
  "subtotal_excluding_tax": "number",

  "tax": null,
  "test_clock": null,

  "total": "number",

  "total_discount_amounts": [
    {
      "amount": "number",
      "discount": "string"
    }
  ],

  "total_excluding_tax": "number",

  "total_pretax_credit_amounts": [
    {
      "amount": "number",
      "discount": "string",
      "type": "string"
    }
  ],

  "total_tax_amounts": [],
  "total_taxes": [],

  "transfer_data": null,

  "webhooks_delivered_at": "number"
}

Add a link to the Stripe hosted invoice

The JSON object returned has all the elements needed to create a hyperlink to the live invoice.

Adding this to the React component should render the hyperlink to the Stripe invoice:

                                            <Typography variant="body2" color="text.secondary">
                                                <a href={invoice.hosted_invoice_url} target="_blank" rel="noopener noreferrer">
                                                    Invoice number {invoice.number}
                                                </a>
                                            </Typography>
Live invoice link added to the React invoices panel.

Move The List of Referring Sites Into the Site Info Panel

Convert this from PHP output to a React component.

Add site list into the site info panel.

Simplify The History Block

Update the MySLP History Logger plugin.

Stop pushing this stupid header to every single blog message… we already record the blog ID in the history table, no need for all this crap…

Blog ID: 310
Name: icecream_at_slp_dot_guru
URL: https://local.storelocatorplus.com/icecream_at_slp_dot_guru
Customer Name: Lance Cleveland

That means dropping this method:

	/**
	 * Set a standard blog header, makes the search feature more useful.
	 *
	 * @param string $user_ID
	 * @param string $blog_ID
	 *
	 * @return string
	 */
	private function get_standard_blog_header( $user_ID, $blog_ID ) {
		$blog_details = get_blog_details( $blog_ID );

		return sprintf( "<strong>Blog ID:</strong> %s <br/><strong>Name:</strong> %s <br/><strong>URL:</strong> <a href='%s'>%s</a> <br/><strong>Customer Name:</strong> <a href='%s'>%s</a><br/>",
			$this->myslp->User->blog_id,
			$blog_details->blogname, $blog_details->siteurl, $blog_details->siteurl,
			sprintf( admin_url( 'network/users.php?page=customer-maintenance&action=view&customer=%d' ), $user_ID ),
			$this->myslp->User->first_name . " " . $this->myslp->User->last_name
		);
	}

SLP Development Notes

React components are built using the WordPress block component processor in the NPM package @wordpress/scripts via the wp-scripts build NPM command.

These components are built by running the OS command within each plugin’s root directory:

 yarn build

When updating packages:

rm -rf node_modules yarn.lock
yarn install
yarn build

Leave a Reply