Skip to content

Commit 0f3440d

Browse files
committed
Release braintree-web 3.137.0 source
1 parent ebc775d commit 0f3440d

33 files changed

+2717
-1437
lines changed

.github/workflows/security.dependency-review.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Security
22

33
on:
44
pull_request:
5-
branches: [master]
5+
branches: [master, v3.x]
66
workflow_dispatch:
77

88
jobs:

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,8 @@ browserstack.err
2323
local.log
2424
logs/
2525
.storybook/certs/
26+
27+
# Playwright
28+
test-results/
29+
playwright-report/
30+
playwright/.cache/

.storybook/constants.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,32 @@ export const PAYPAL_SELECTORS = {
6161
CANCEL_LINK: "#cancelLink",
6262
ALT_CANCEL_LINK: "a*=Cancel",
6363
};
64+
65+
export const browsers = [
66+
{
67+
browserName: "chrome",
68+
osName: "Windows",
69+
osVersion: "10",
70+
browserVersion: "latest",
71+
},
72+
{
73+
browserName: "Edge",
74+
osName: "Windows",
75+
osVersion: "10",
76+
browserVersion: "latest",
77+
},
78+
{
79+
browserName: "Safari",
80+
playwrightName: "playwright-webkit",
81+
osName: "OS X",
82+
osVersion: "Tahoe",
83+
browserVersion: "latest",
84+
},
85+
{
86+
browserName: "firefox",
87+
playwrightName: "playwright-firefox",
88+
osName: "Windows",
89+
osVersion: "10",
90+
browserVersion: "latest",
91+
},
92+
];
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/* eslint-disable no-console */
2+
import browserstack from "browserstack-local";
3+
import uuid from "@braintree/uuid";
4+
5+
// Generate a consistent local identifier, or use environment variable if set
6+
const localIdentifier =
7+
process.env.BROWSERSTACK_LOCAL_IDENTIFIER || (uuid() as string);
8+
let bsLocal: browserstack.Local | null = null;
9+
let isConnected = false;
10+
11+
export function startBrowserStackLocal(): Promise<void> {
12+
return new Promise((resolve, reject) => {
13+
if (isConnected) {
14+
console.log("BrowserStack Local already connected");
15+
resolve();
16+
return;
17+
}
18+
19+
bsLocal = new browserstack.Local();
20+
21+
const bsLocalArgs = {
22+
key: process.env.BROWSERSTACK_ACCESS_KEY ?? "",
23+
localIdentifier,
24+
verbose: true,
25+
forcelocal: true,
26+
force: true,
27+
};
28+
bsLocal.start(bsLocalArgs, (error) => {
29+
if (error) {
30+
console.error("Error starting BrowserStack Local:", error);
31+
reject(error);
32+
return;
33+
}
34+
35+
// Verify the connection is actually running
36+
if (bsLocal && bsLocal.isRunning()) {
37+
isConnected = true;
38+
console.log(
39+
`BrowserStack Local connected with localIdentifier=${localIdentifier}`
40+
);
41+
resolve();
42+
} else {
43+
console.error("BrowserStack Local started but is not running");
44+
reject(new Error("BrowserStack Local is not running"));
45+
}
46+
});
47+
});
48+
}
49+
50+
export function stopBrowserStackLocal(): Promise<void> {
51+
return new Promise((resolve) => {
52+
if (bsLocal && isConnected) {
53+
console.log("Stopping BrowserStack Local...");
54+
bsLocal.stop(() => {
55+
console.log("BrowserStack Local stopped");
56+
isConnected = false;
57+
resolve();
58+
});
59+
} else {
60+
resolve();
61+
}
62+
});
63+
}
64+
65+
export function getLocalIdentifier(): string {
66+
return localIdentifier;
67+
}
68+
69+
export function isLocalConnected(): boolean {
70+
return isConnected;
71+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/* eslint-disable no-console */
2+
import {
3+
startBrowserStackLocal,
4+
getLocalIdentifier,
5+
isLocalConnected,
6+
} from "./browserstack-local";
7+
8+
module.exports = async () => {
9+
if (
10+
process.env.BROWSERSTACK_USERNAME &&
11+
process.env.BROWSERSTACK_ACCESS_KEY
12+
) {
13+
const localId = getLocalIdentifier();
14+
process.env.BROWSERSTACK_LOCAL_IDENTIFIER = localId;
15+
16+
await startBrowserStackLocal();
17+
18+
if (!isLocalConnected()) {
19+
console.error("✗ BrowserStack Local failed to connect properly");
20+
throw new Error("BrowserStack Local is not connected");
21+
}
22+
} else {
23+
console.log("Skipping BrowserStack Local - credentials not set");
24+
}
25+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { stopBrowserStackLocal } from "./browserstack-local";
2+
3+
module.exports = async () => {
4+
await stopBrowserStackLocal();
5+
};

.storybook/stories/LocalPaymentMethods/LocalPayments.stories.ts

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ export default meta;
2626

2727
const createLocalPaymentForm = (
2828
title: string,
29-
description: string
29+
description: string,
30+
paymentType: string
3031
): HTMLElement => {
3132
const container = document.createElement("div");
3233
container.innerHTML = `
@@ -39,6 +40,9 @@ const createLocalPaymentForm = (
3940
</p>
4041
</div>
4142
43+
${
44+
paymentType !== "crypto"
45+
? `
4246
<div class="local-payment-form-section">
4347
<div class="shared-form-group">
4448
<label class="shared-label">Amount</label>
@@ -61,6 +65,10 @@ const createLocalPaymentForm = (
6165
<option value="IT">Italy (IT)</option>
6266
</select>
6367
</div>
68+
`
69+
: ""
70+
}
71+
6472
</div>
6573
6674
<button type="button" id="payment-button" class="shared-button" disabled>Initializing...</button>
@@ -147,33 +155,40 @@ const initializeLocalPayments = (
147155
| Record<string, string>
148156
| boolean
149157
| ((_data: unknown, _start: () => void) => void)
150-
> = {
151-
paymentType: paymentType.toLowerCase(),
152-
amount: amountInput.value,
153-
currencyCode: currencySelect.value,
154-
address: {
158+
> = {};
159+
160+
if (paymentType !== "crypto") {
161+
paymentOptions.paymentType = paymentType.toLowerCase();
162+
paymentOptions.amount = amountInput.value;
163+
paymentOptions.currencyCode = currencySelect.value;
164+
paymentOptions.address = {
155165
countryCode: countrySelect.value,
156-
},
157-
givenName: "John",
158-
surname: "Doe",
159-
fallback: {
166+
};
167+
paymentOptions.givenName = "John";
168+
paymentOptions.surname = "Doe";
169+
paymentOptions.fallback = {
160170
url: "https://your-domain.com/page-to-complete-checkout",
161171
buttonText: "Complete Payment",
162-
},
163-
onPaymentStart: function (_data, start) {
172+
};
173+
paymentOptions.onPaymentStart = function (_data, start) {
164174
// NOTE: It is critical here to store data.paymentId on your server
165175
// so it can be mapped to a webhook sent by Braintree once the
166176
// buyer completes their payment. See Start the payment
167177
// section for details.
168178

169179
// Call start to initiate the popup
170180
start();
171-
},
172-
};
181+
};
182+
}
173183

174184
// Add payment type specific options
175185
if (paymentType === "iDEAL") {
176186
paymentOptions.paymentType = "ideal";
187+
} else if (paymentType === "crypto") {
188+
paymentOptions.paymentType = "crypto";
189+
paymentOptions.cryptoOptions = {
190+
approvalUrl: "https://example.com/crypto-approval",
191+
};
177192
} else if (paymentType === "Pay Upon Invoice") {
178193
paymentOptions.paymentType = "payuponinvoice";
179194
paymentOptions.shippingAddressRequired = true;
@@ -187,18 +202,24 @@ const initializeLocalPayments = (
187202
paymentOptions.countryCode = "DE";
188203
}
189204

190-
localPaymentInstance
191-
.startPayment(paymentOptions)
192-
.then(function (payload) {
193-
// Submit payload.nonce to your server
194-
console.log("nonce", payload.nonce);
195-
paymentButton.disabled = false;
196-
paymentButton.textContent = `Pay with ${paymentType}`;
197-
showSuccess(resultDiv, payload);
198-
})
199-
.catch((error) => {
205+
if (paymentType !== "crypto") {
206+
localPaymentInstance
207+
.startPayment(paymentOptions)
208+
.then(function (payload) {
209+
// Submit payload.nonce to your server
210+
console.log("nonce", payload.nonce);
211+
paymentButton.disabled = false;
212+
paymentButton.textContent = `Pay with ${paymentType}`;
213+
showSuccess(resultDiv, payload);
214+
})
215+
.catch((error) => {
216+
console.error(error);
217+
});
218+
} else {
219+
localPaymentInstance.startPayment(paymentOptions).catch((error) => {
200220
console.error(error);
201221
});
222+
}
202223
});
203224
};
204225

@@ -207,7 +228,8 @@ export const iDEAL: StoryObj = {
207228
(container) => {
208229
const formContainer = createLocalPaymentForm(
209230
"iDEAL Local Payment",
210-
"iDEAL is a popular payment method in the Netherlands. Select your bank and complete the payment."
231+
"iDEAL is a popular payment method in the Netherlands. Select your bank and complete the payment.",
232+
"ideal"
211233
);
212234
container.appendChild(formContainer);
213235
initializeLocalPayments(formContainer, "iDEAL");
@@ -219,3 +241,22 @@ export const iDEAL: StoryObj = {
219241
debugMode: false,
220242
},
221243
};
244+
245+
export const payWithCrypto: StoryObj = {
246+
render: createSimpleBraintreeStory(
247+
(container) => {
248+
const formContainer = createLocalPaymentForm(
249+
"Pay with Crypto Local Payment",
250+
"Pay with Crypto is a payment solution that allows you to accept cryptocurrency (crypto) payments from global buyers and receive automatic settlement in local currency.",
251+
"crypto"
252+
);
253+
container.appendChild(formContainer);
254+
initializeLocalPayments(formContainer, "crypto");
255+
},
256+
["client.min.js", "local-payment.min.js"]
257+
),
258+
args: {
259+
// Example args that could be used to customize the payment flow
260+
debugMode: false,
261+
},
262+
};

0 commit comments

Comments
 (0)