Skip to content

Commit 941c599

Browse files
Admin SPA: Migrate merchant account show page (#1871)
Part of #1057 Migrates `/admin/merchant_accounts/:id` to Inertia. Component code is based on #1586 but added a proper presenter for data serialization. ### Before https://github.com/user-attachments/assets/0190972b-35eb-4f6a-b270-92023eae4e52 ### After https://github.com/user-attachments/assets/e6e4face-9756-4259-9efe-80d92ecc36dd ## AI Disclosure Cursor (Sonnet 4.5) used for refactoring, writing specs, etc. --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent d720630 commit 941c599

File tree

8 files changed

+400
-92
lines changed

8 files changed

+400
-92
lines changed

app/controllers/admin/merchant_accounts_controller.rb

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,6 @@ class Admin::MerchantAccountsController < Admin::BaseController
44
def show
55
@merchant_account = MerchantAccount.find_by(id: params[:id]) || MerchantAccount.find_by(charge_processor_merchant_id: params[:id]) || e404
66
@title = "Merchant Account #{@merchant_account.id}"
7-
load_live_attributes
7+
render inertia: "Admin/MerchantAccounts/Show", props: { merchant_account: Admin::MerchantAccountPresenter.new(merchant_account: @merchant_account).props }
88
end
9-
10-
private
11-
def load_live_attributes
12-
return unless @merchant_account.charge_processor_merchant_id.present?
13-
14-
if @merchant_account.stripe_charge_processor?
15-
stripe_account = Stripe::Account.retrieve(@merchant_account.charge_processor_merchant_id)
16-
@live_attributes = {
17-
"Charges enabled" => stripe_account.charges_enabled,
18-
"Payout enabled" => stripe_account.payouts_enabled,
19-
"Disabled reason" => stripe_account.requirements.disabled_reason,
20-
"Fields needed" => stripe_account.requirements.as_json
21-
}
22-
elsif @merchant_account.paypal_charge_processor?
23-
paypal_account_details = @merchant_account.paypal_account_details
24-
if paypal_account_details.present?
25-
@live_attributes = {
26-
"Email" => paypal_account_details["primary_email"]
27-
}
28-
end
29-
end
30-
end
319
end

app/javascript/packs/admin_inertia.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const urlsMigratedtoInertia = [
6969
/\/admin\/payouts\/\w+/u, // Routes.admin_payout_url
7070
/\/admin\/users\/\w+\/payouts/u, // Routes.admin_user_payouts_url
7171
Routes.admin_affiliates_url(),
72+
/\/admin\/merchant_accounts\/\w+/u, // Routes.admin_merchant_account_url
7273
// Add other urls here when they are migrated to inertia
7374
];
7475

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { Link, usePage } from "@inertiajs/react";
2+
import { capitalize } from "lodash";
3+
import React from "react";
4+
import { cast } from "ts-safe-cast";
5+
6+
import DateTimeWithRelativeTooltip from "$app/components/Admin/DateTimeWithRelativeTooltip";
7+
import { BooleanIcon, NoIcon } from "$app/components/Admin/Icons";
8+
9+
export type AdminMerchantAccountProps = {
10+
id: number;
11+
charge_processor_id: string;
12+
charge_processor_merchant_id: string | null;
13+
created_at: string;
14+
external_id: string;
15+
user_id: number | null;
16+
country: string;
17+
country_name: string | null;
18+
currency: string;
19+
holder_of_funds: string;
20+
stripe_account_url: string | null;
21+
charge_processor_alive_at: string | null;
22+
charge_processor_verified_at: string | null;
23+
charge_processor_deleted_at: string | null;
24+
updated_at: string;
25+
deleted_at: string | null;
26+
live_attributes: { label: string; value: unknown }[];
27+
};
28+
29+
const AdminMerchantAccountsShow = () => {
30+
const { merchant_account } = cast<{ merchant_account: AdminMerchantAccountProps }>(usePage().props);
31+
32+
return (
33+
<div className="override grid gap-4 rounded border border-border bg-background p-4">
34+
<div>
35+
<h2>Merchant Account {merchant_account.id}</h2>
36+
<DateTimeWithRelativeTooltip date={merchant_account.created_at} utc />
37+
</div>
38+
39+
<hr />
40+
<div>
41+
<dl>
42+
<dt>ID</dt>
43+
<dd>{merchant_account.id}</dd>
44+
45+
<dt>External ID</dt>
46+
<dd>{merchant_account.external_id}</dd>
47+
48+
<dt>User</dt>
49+
<dd>
50+
{merchant_account.user_id ? (
51+
<Link href={Routes.admin_user_path(merchant_account.user_id)}>{merchant_account.user_id}</Link>
52+
) : (
53+
"none"
54+
)}
55+
</dd>
56+
57+
<dt>Country</dt>
58+
<dd>
59+
{merchant_account.country_name} ({merchant_account.country})
60+
</dd>
61+
62+
<dt>Currency</dt>
63+
<dd>{merchant_account.currency.toUpperCase()}</dd>
64+
65+
<dt>Active</dt>
66+
<dd>
67+
<BooleanIcon value={!!merchant_account.deleted_at} />
68+
</dd>
69+
70+
<dt>Funds are held by</dt>
71+
<dd>{capitalize(merchant_account.holder_of_funds)}</dd>
72+
73+
<dt>Charge Processor</dt>
74+
<dd>
75+
{capitalize(merchant_account.charge_processor_id)}{" "}
76+
{merchant_account.charge_processor_merchant_id && merchant_account.stripe_account_url ? (
77+
<a href={merchant_account.stripe_account_url} target="_blank" rel="noopener noreferrer">
78+
{merchant_account.charge_processor_merchant_id}
79+
</a>
80+
) : null}
81+
</dd>
82+
83+
<dt>{capitalize(merchant_account.charge_processor_id)} Alive</dt>
84+
<dd>
85+
<BooleanIcon value={!!merchant_account.charge_processor_alive_at} />{" "}
86+
<DateTimeWithRelativeTooltip date={merchant_account.charge_processor_alive_at} utc />
87+
</dd>
88+
89+
<dt>{capitalize(merchant_account.charge_processor_id)} Verified</dt>
90+
<dd>
91+
<BooleanIcon value={!!merchant_account.charge_processor_verified_at} />{" "}
92+
<DateTimeWithRelativeTooltip date={merchant_account.charge_processor_verified_at} utc />
93+
</dd>
94+
95+
<dt>{capitalize(merchant_account.charge_processor_id)} Deleted</dt>
96+
<dd>
97+
<BooleanIcon value={!!merchant_account.charge_processor_deleted_at} />{" "}
98+
<DateTimeWithRelativeTooltip date={merchant_account.charge_processor_deleted_at} utc />
99+
</dd>
100+
</dl>
101+
</div>
102+
103+
<hr />
104+
<div className="paragraphs">
105+
<h3>Charge Processor live attributes</h3>
106+
{merchant_account.live_attributes.length > 0 ? (
107+
<dl>
108+
{merchant_account.live_attributes.map(({ label, value }) => (
109+
<React.Fragment key={label}>
110+
<dt>{label}</dt>
111+
<dd>
112+
<code>{JSON.stringify(value)}</code>
113+
</dd>
114+
</React.Fragment>
115+
))}
116+
</dl>
117+
) : (
118+
<div role="alert" className="info">
119+
Charge Processor Merchant information is missing.
120+
</div>
121+
)}
122+
</div>
123+
124+
<hr />
125+
<div>
126+
<dl>
127+
<dt>Updated</dt>
128+
<dd>
129+
<DateTimeWithRelativeTooltip date={merchant_account.updated_at} utc />
130+
</dd>
131+
</dl>
132+
133+
<dl>
134+
<dt>Deleted</dt>
135+
<dd>
136+
<DateTimeWithRelativeTooltip date={merchant_account.deleted_at} utc placeholder={<NoIcon />} />
137+
</dd>
138+
</dl>
139+
</div>
140+
</div>
141+
);
142+
};
143+
144+
export default AdminMerchantAccountsShow;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# frozen_string_literal: true
2+
3+
class Admin::MerchantAccountPresenter
4+
attr_reader :merchant_account
5+
6+
def initialize(merchant_account:)
7+
@merchant_account = merchant_account
8+
end
9+
10+
def props
11+
{
12+
id: merchant_account.id,
13+
charge_processor_id: merchant_account.charge_processor_id,
14+
charge_processor_merchant_id: merchant_account.charge_processor_merchant_id,
15+
created_at: merchant_account.created_at,
16+
external_id: merchant_account.external_id,
17+
user_id: merchant_account.user_id,
18+
country: merchant_account.country,
19+
country_name:,
20+
currency: merchant_account.currency,
21+
holder_of_funds: merchant_account.holder_of_funds,
22+
stripe_account_url:,
23+
charge_processor_alive_at: merchant_account.charge_processor_alive_at,
24+
charge_processor_verified_at: merchant_account.charge_processor_verified_at,
25+
charge_processor_deleted_at: merchant_account.charge_processor_deleted_at,
26+
updated_at: merchant_account.updated_at,
27+
deleted_at: merchant_account.deleted_at,
28+
live_attributes: live_attributes || [],
29+
}
30+
end
31+
32+
private
33+
def country_name
34+
merchant_account.country.presence && ISO3166::Country[merchant_account.country]&.common_name
35+
end
36+
37+
def stripe_account_url
38+
return unless merchant_account.charge_processor_merchant_id?
39+
return unless merchant_account.stripe_charge_processor?
40+
41+
StripeUrl.connected_account_url(merchant_account.charge_processor_merchant_id)
42+
end
43+
44+
def live_attributes
45+
return unless merchant_account.charge_processor_merchant_id.present?
46+
47+
if merchant_account.stripe_charge_processor?
48+
stripe_account = Stripe::Account.retrieve(merchant_account.charge_processor_merchant_id)
49+
[
50+
{ label: "Charges enabled", value: stripe_account.charges_enabled },
51+
{ label: "Payout enabled", value: stripe_account.payouts_enabled },
52+
{ label: "Disabled reason", value: stripe_account.requirements.disabled_reason },
53+
{ label: "Fields needed", value: stripe_account.requirements.as_json }
54+
]
55+
elsif merchant_account.paypal_charge_processor?
56+
paypal_account_details = merchant_account.paypal_account_details
57+
if paypal_account_details.present?
58+
[
59+
{ label: "Email", value: paypal_account_details["primary_email"] }
60+
]
61+
end
62+
end
63+
end
64+
end

spec/controllers/admin/merchant_accounts_controller_spec.rb

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
require "spec_helper"
44
require "shared_examples/admin_base_controller_concern"
5+
require "inertia_rails/rspec"
56

6-
describe Admin::MerchantAccountsController do
7+
describe Admin::MerchantAccountsController, type: :controller, inertia: true do
78
render_views
89

910
it_behaves_like "inherits from Admin::BaseController"
@@ -21,30 +22,7 @@
2122
get :show, params: { id: merchant_account }
2223

2324
expect(response).to be_successful
24-
expect(response).to render_template(:show)
25-
end
26-
27-
context "for merchant accounts of type paypal", :vcr do
28-
it "returns the email address associated with the paypal account" do
29-
paypal_merchant_account = create(:merchant_account_paypal, charge_processor_merchant_id: "B66YJBBNCRW6L")
30-
31-
get :show, params: { id: paypal_merchant_account.id }
32-
33-
expect(response.body).to have_content("Email \"[email protected]\"", normalize_ws: true)
34-
end
35-
end
36-
37-
context "for merchant accounts of type stripe", :vcr do
38-
it "returns the charges and payouts related flags" do
39-
stripe_merchant_account = create(:merchant_account, charge_processor_merchant_id: "acct_19paZxAQqMpdRp2I")
40-
41-
get :show, params: { id: stripe_merchant_account.id }
42-
43-
expect(response.body).to have_content("Charges enabled false", normalize_ws: true)
44-
expect(response.body).to have_content("Payout enabled false", normalize_ws: true)
45-
expect(response.body).to have_content("Disabled reason \"rejected.fraud\"", normalize_ws: true)
46-
expect(response.body).to have_content("Fields needed", normalize_ws: true)
47-
end
25+
expect(inertia.component).to eq("Admin/MerchantAccounts/Show")
4826
end
4927
end
5028
end

0 commit comments

Comments
 (0)